Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ target/
dist/
.dfx/
.mops/
mops.lock

.DS_Store
20 changes: 10 additions & 10 deletions cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,9 @@ program

// check
program
.command("check [files...]")
.command("check [args...]")
.description(
"Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured, and runs linting if lintoko is configured in [toolchain] and rule directories are present",
"Check Motoko canisters or files for syntax errors and type issues. Arguments can be canister names or file paths. If no arguments are given, checks all canisters from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured, and runs linting if lintoko is configured in [toolchain]",
)
.option("--verbose", "Verbose console output")
.addOption(
Expand All @@ -342,15 +342,15 @@ program
),
)
.allowUnknownOption(true)
.action(async (files, options) => {
.action(async (args, options) => {
checkConfigFile(true);
const { extraArgs, args: fileList } = parseExtraArgs(files);
const { extraArgs, args: argList } = parseExtraArgs(args);
await installAll({
silent: true,
lock: "ignore",
installFromLockFile: true,
});
await check(fileList, {
await check(argList, {
...options,
extraArgs,
});
Expand All @@ -372,21 +372,21 @@ program

// check-stable
program
.command("check-stable <old-file> [canister]")
.command("check-stable [args...]")
.description(
"Check stable variable compatibility between an old version (.mo or .most file) and the current canister entrypoint",
"Check stable variable compatibility. With no arguments, checks all canisters with [check-stable] configured. Arguments can be canister names or an old file path followed by an optional canister name",
)
.option("--verbose", "Verbose console output")
.allowUnknownOption(true)
.action(async (oldFile, canister, options) => {
.action(async (args, options) => {
checkConfigFile(true);
const { extraArgs } = parseExtraArgs();
const { extraArgs, args: argList } = parseExtraArgs(args);
await installAll({
silent: true,
lock: "ignore",
installFromLockFile: true,
});
await checkStable(oldFile, canister, {
await checkStable(argList, {
...options,
extraArgs,
});
Expand Down
29 changes: 7 additions & 22 deletions cli/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { join } from "node:path";
import { lock, unlockSync } from "proper-lockfile";
import { cliError } from "../error.js";
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
import { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
import {
filterCanisters,
resolveCanisterConfigs,
validateCanisterArgs,
} from "../helpers/resolve-canisters.js";
import { CanisterConfig, Config } from "../types.js";
import { CustomSection, getWasmBindings } from "../wasm.js";
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
Expand Down Expand Up @@ -41,26 +45,11 @@ export async function build(
cliError(`No Motoko canisters found in mops.toml configuration`);
}

if (canisterNames) {
let invalidNames = canisterNames.filter((name) => !(name in canisters));
if (invalidNames.length) {
cliError(
`Motoko canister(s) not found in mops.toml configuration: ${invalidNames.join(", ")}`,
);
}
}

if (!(await exists(outputDir))) {
await mkdir(outputDir, { recursive: true });
}

const filteredCanisters = canisterNames
? Object.fromEntries(
Object.entries(canisters).filter(([name]) =>
canisterNames.includes(name),
),
)
: canisters;
const filteredCanisters = filterCanisters(canisters, canisterNames);

for (let [canisterName, canister] of Object.entries(filteredCanisters)) {
console.log(chalk.blue("build canister"), chalk.bold(canisterName));
Expand Down Expand Up @@ -249,11 +238,7 @@ function collectExtraArgs(
args.push(...config.build.args);
}
if (canister.args) {
if (typeof canister.args === "string") {
cliError(
`Canister config 'args' should be an array of strings for canister ${canisterName}`,
);
}
validateCanisterArgs(canister, canisterName);
args.push(...canister.args);
}
if (extraArgs) {
Expand Down
127 changes: 110 additions & 17 deletions cli/commands/check-stable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import chalk from "chalk";
import { execa } from "execa";
import { cliError } from "../error.js";
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
import { resolveSingleCanister } from "../helpers/resolve-canisters.js";
import { CanisterConfig } from "../types.js";
import {
filterCanisters,
looksLikeFile,
resolveCanisterConfigs,
resolveSingleCanister,
validateCanisterArgs,
} from "../helpers/resolve-canisters.js";
import { sourcesArgs } from "./sources.js";
import { toolchain } from "./toolchain/index.js";

Expand All @@ -16,29 +23,108 @@ export interface CheckStableOptions {
extraArgs: string[];
}

export function resolveStablePath(
canister: CanisterConfig,
canisterName: string,
options?: { required?: boolean },
): string | null {
const stableConfig = canister["check-stable"];
if (!stableConfig) {
if (options?.required) {
cliError(
`Canister '${canisterName}' has no [canisters.${canisterName}.check-stable] configuration in mops.toml`,
);
}
return null;
}
const stablePath = resolveConfigPath(stableConfig.path);
if (!existsSync(stablePath)) {
if (stableConfig.skipIfMissing) {
return null;
}
cliError(
`Deployed file not found: ${stablePath} (canister '${canisterName}')\n` +
"Set skipIfMissing = true in [canisters." +
canisterName +
".check-stable] to skip this check when the file is missing.",
);
}
return stablePath;
}

export async function checkStable(
oldFile: string,
canisterName: string | undefined,
args: string[],
options: Partial<CheckStableOptions> = {},
): Promise<void> {
const config = readConfig();
const { name, canister } = resolveSingleCanister(config, canisterName);
const mocPath = await toolchain.bin("moc", { fallback: true });
const globalMocArgs = getGlobalMocArgs(config);

const firstArg = args[0];
if (firstArg && looksLikeFile(firstArg)) {
const oldFile = firstArg;
const canisterName = args[1];
const { name, canister } = resolveSingleCanister(config, canisterName);

if (!canister.main) {
cliError(`No main file specified for canister '${name}' in mops.toml`);
}

validateCanisterArgs(canister, name);

if (!canister.main) {
cliError(`No main file specified for canister '${name}' in mops.toml`);
await runStableCheck({
oldFile,
canisterMain: resolveConfigPath(canister.main),
canisterName: name,
mocPath,
globalMocArgs,
canisterArgs: canister.args ?? [],
options,
});
return;
}

const mocPath = await toolchain.bin("moc", { fallback: true });
const globalMocArgs = getGlobalMocArgs(config);
const canisters = resolveCanisterConfigs(config);
const canisterNames = args.length > 0 ? args : undefined;
const filteredCanisters = filterCanisters(canisters, canisterNames);
const sources = (await sourcesArgs()).flat();

await runStableCheck({
oldFile,
canisterMain: resolveConfigPath(canister.main),
canisterName: name,
mocPath,
globalMocArgs,
options,
});
let checked = 0;
for (const [name, canister] of Object.entries(filteredCanisters)) {
if (!canister.main) {
cliError(`No main file specified for canister '${name}' in mops.toml`);
}

validateCanisterArgs(canister, name);
const stablePath = resolveStablePath(canister, name, {
required: !!canisterNames,
});
if (!stablePath) {
continue;
}

await runStableCheck({
oldFile: stablePath,
canisterMain: resolveConfigPath(canister.main),
canisterName: name,
mocPath,
globalMocArgs,
canisterArgs: canister.args ?? [],
sources,
options,
});
checked++;
}

if (checked === 0 && !canisterNames) {
cliError(
"No canisters with [check-stable] configuration found in mops.toml.\n" +
"Either pass an old file: mops check-stable <old-file> [canister]\n" +
"Or configure check-stable for a canister:\n\n" +
" [canisters.backend.check-stable]\n" +
' path = "deployed.mo"',
);
}
}

export interface RunStableCheckParams {
Expand All @@ -47,6 +133,8 @@ export interface RunStableCheckParams {
canisterName: string;
mocPath: string;
globalMocArgs: string[];
canisterArgs: string[];
sources?: string[];
options?: Partial<CheckStableOptions>;
}

Expand All @@ -59,10 +147,11 @@ export async function runStableCheck(
canisterName,
mocPath,
globalMocArgs,
canisterArgs,
options = {},
} = params;

const sources = (await sourcesArgs()).flat();
const sources = params.sources ?? (await sourcesArgs()).flat();
const isOldMostFile = oldFile.endsWith(".most");

if (!existsSync(oldFile)) {
Expand All @@ -80,6 +169,7 @@ export async function runStableCheck(
join(CHECK_STABLE_DIR, "old.most"),
sources,
globalMocArgs,
canisterArgs,
options,
);

Expand All @@ -89,6 +179,7 @@ export async function runStableCheck(
join(CHECK_STABLE_DIR, "new.most"),
sources,
globalMocArgs,
canisterArgs,
options,
);

Expand Down Expand Up @@ -134,6 +225,7 @@ async function generateStableTypes(
outputPath: string,
sources: string[],
globalMocArgs: string[],
canisterArgs: string[],
options: Partial<CheckStableOptions>,
): Promise<string> {
const base = basename(outputPath, ".most");
Expand All @@ -145,6 +237,7 @@ async function generateStableTypes(
moFile,
...sources,
...globalMocArgs,
...canisterArgs,
...(options.extraArgs ?? []),
];

Expand Down
Loading
Loading