From 613c39e99f33c301367cdf799d8004f1d3411bfe Mon Sep 17 00:00:00 2001 From: Kamil Listopad Date: Tue, 14 Apr 2026 13:06:42 +0200 Subject: [PATCH 1/5] fix: apply per-canister moc args in check and check-stable commands [canisters.].args from mops.toml were only passed to moc during build but silently ignored by check and check-stable. This caused flags like --enhanced-migration= to work in build but fail in check, forcing users to set global [moc].args that don't make sense for multi-canister projects where each canister needs different paths. - check and check-stable now accept canister names as arguments (e.g. mops check backend, mops check-stable backend) - check-stable supports a no-args mode that checks all canisters with [check-stable] configured - per-canister args are applied alongside global [moc].args in all three commands (build, check, check-stable) - shared helpers extracted: filterCanisters, validateCanisterArgs, looksLikeFile, resolveStablePath Made-with: Cursor --- cli/cli.ts | 20 +- cli/commands/build.ts | 29 +- cli/commands/check-stable.ts | 122 ++++++-- cli/commands/check.ts | 288 +++++++++++------- cli/helpers/resolve-canisters.ts | 41 ++- cli/tests/__snapshots__/check.test.ts.snap | 4 +- cli/tests/check-stable.test.ts | 21 ++ .../migrations/20250101_000000_Init.mo | 8 + .../migrations/20250201_000000_AddField.mo | 5 + .../check-stable/canister-args/mops.toml | 9 + cli/tests/check-stable/canister-args/old.most | 8 + .../check-stable/canister-args/src/main.mo | 11 + cli/tests/check.test.ts | 34 ++- .../check/canisters-canister-args/Warning.mo | 5 + .../check/canisters-canister-args/mops.toml | 9 + docs/docs/09-mops.toml.md | 12 +- docs/docs/cli/4-dev/04-mops-check.md | 27 +- docs/docs/cli/4-dev/05-mops-check-stable.md | 59 ++-- 18 files changed, 522 insertions(+), 190 deletions(-) create mode 100644 cli/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo create mode 100644 cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo create mode 100644 cli/tests/check-stable/canister-args/mops.toml create mode 100644 cli/tests/check-stable/canister-args/old.most create mode 100644 cli/tests/check-stable/canister-args/src/main.mo create mode 100644 cli/tests/check/canisters-canister-args/Warning.mo create mode 100644 cli/tests/check/canisters-canister-args/mops.toml diff --git a/cli/cli.ts b/cli/cli.ts index bf67bb53..3ab0231c 100755 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -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 .mo 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] and rule directories are present", ) .option("--verbose", "Verbose console output") .addOption( @@ -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, }); @@ -372,21 +372,21 @@ program // check-stable program - .command("check-stable [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, }); diff --git a/cli/commands/build.ts b/cli/commands/build.ts index 2d552aea..a5ca3b70 100644 --- a/cli/commands/build.ts +++ b/cli/commands/build.ts @@ -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"; @@ -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)); @@ -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) { diff --git a/cli/commands/check-stable.ts b/cli/commands/check-stable.ts index 70efe2b8..bc9b5f46 100644 --- a/cli/commands/check-stable.ts +++ b/cli/commands/check-stable.ts @@ -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"; @@ -16,29 +23,103 @@ 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 = {}, ): Promise { const config = readConfig(); - const { name, canister } = resolveSingleCanister(config, canisterName); + const mocPath = await toolchain.bin("moc", { fallback: true }); + const globalMocArgs = getGlobalMocArgs(config); + + if (args.length > 0 && looksLikeFile(args[0]!)) { + const oldFile = args[0]!; + const canisterName = args[1]; + const { name, canister } = resolveSingleCanister(config, canisterName); + + if (!canister.main) { + cliError(`No main file specified for canister '${name}' in mops.toml`); + } - if (!canister.main) { - cliError(`No main file specified for canister '${name}' in mops.toml`); + validateCanisterArgs(canister, name); + + 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); - 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 ?? [], + 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 [canister]\n" + + "Or configure check-stable for a canister:\n\n" + + " [canisters.backend.check-stable]\n" + + ' path = "deployed.mo"', + ); + } } export interface RunStableCheckParams { @@ -47,6 +128,8 @@ export interface RunStableCheckParams { canisterName: string; mocPath: string; globalMocArgs: string[]; + canisterArgs: string[]; + sources?: string[]; options?: Partial; } @@ -59,10 +142,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)) { @@ -80,6 +164,7 @@ export async function runStableCheck( join(CHECK_STABLE_DIR, "old.most"), sources, globalMocArgs, + canisterArgs, options, ); @@ -89,6 +174,7 @@ export async function runStableCheck( join(CHECK_STABLE_DIR, "new.most"), sources, globalMocArgs, + canisterArgs, options, ); @@ -134,6 +220,7 @@ async function generateStableTypes( outputPath: string, sources: string[], globalMocArgs: string[], + canisterArgs: string[], options: Partial, ): Promise { const base = basename(outputPath, ".most"); @@ -145,6 +232,7 @@ async function generateStableTypes( moFile, ...sources, ...globalMocArgs, + ...canisterArgs, ...(options.extraArgs ?? []), ]; diff --git a/cli/commands/check.ts b/cli/commands/check.ts index aac2f5a6..8a803448 100644 --- a/cli/commands/check.ts +++ b/cli/commands/check.ts @@ -1,5 +1,4 @@ import path from "node:path"; -import { existsSync } from "node:fs"; import chalk from "chalk"; import { execa } from "execa"; import { cliError } from "../error.js"; @@ -9,13 +8,19 @@ import { readConfig, resolveConfigPath, } from "../mops.js"; -import { autofixMotoko } from "../helpers/autofix-motoko.js"; +import { + AutofixResult, + autofixMotoko, +} from "../helpers/autofix-motoko.js"; import { getMocSemVer } from "../helpers/get-moc-version.js"; import { + filterCanisters, + looksLikeFile, resolveCanisterConfigs, - resolveCanisterEntrypoints, + validateCanisterArgs, } from "../helpers/resolve-canisters.js"; -import { runStableCheck } from "./check-stable.js"; +import { CanisterConfig, Config } from "../types.js"; +import { resolveStablePath, runStableCheck } from "./check-stable.js"; import { sourcesArgs } from "./sources.js"; import { toolchain } from "./toolchain/index.js"; import { collectLintRules, lint } from "./lint.js"; @@ -33,52 +38,197 @@ export interface CheckOptions { extraArgs: string[]; } +function logAllLibsSupport(verbose?: boolean): boolean { + const allLibs = supportsAllLibsFlag(); + if (!allLibs) { + console.log( + chalk.yellow( + `moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`, + ), + ); + } else if (verbose) { + console.log( + chalk.blue("check"), + chalk.gray("Using --all-libs for richer diagnostics"), + ); + } + return allLibs; +} + +function logAutofixResult( + fixResult: AutofixResult | null, + verbose?: boolean, +): void { + if (fixResult) { + for (const [file, codes] of fixResult.fixedFiles) { + const unique = [...new Set(codes)].sort(); + const n = codes.length; + const rel = path.relative(process.cwd(), file); + console.log( + chalk.green( + `Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`, + ), + ); + } + const fileCount = fixResult.fixedFiles.size; + console.log( + chalk.green( + `\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`, + ), + ); + } else if (verbose) { + console.log(chalk.yellow("No fixes were needed")); + } +} + export async function check( - files: string | string[], + args: string[], options: Partial = {}, ): Promise { - const explicitFiles = Array.isArray(files) ? files : files ? [files] : []; - let fileList = [...explicitFiles]; - const config = readConfig(); + const canisters = resolveCanisterConfigs(config); + const hasCanisters = Object.keys(canisters).length > 0; + const fileArgs = args.filter(looksLikeFile); + const nonFileArgs = args.filter((a) => !looksLikeFile(a)); + const isFileMode = fileArgs.length > 0; - if (fileList.length === 0) { - fileList = resolveCanisterEntrypoints(config).map(resolveConfigPath); - } - - if (fileList.length === 0) { + if (isFileMode && nonFileArgs.length > 0) { cliError( - "No Motoko files specified and no canisters defined in mops.toml.\n" + - "Either pass files: mops check \n" + - "Or define canisters in mops.toml:\n\n" + - " [canisters.backend]\n" + - ' main = "src/main.mo"', + `Cannot mix file paths and canister names: ${args.join(", ")}\n` + + "Pass either file paths (e.g. mops check src/main.mo) or canister names (e.g. mops check backend)", ); } + + if (isFileMode) { + await checkFiles(config, args, options); + } else { + if (!hasCanisters) { + cliError( + "No canisters defined in mops.toml.\n" + + "Either pass files: mops check \n" + + "Or define canisters in mops.toml:\n\n" + + " [canisters.backend]\n" + + ' main = "src/main.mo"', + ); + } + + const canisterNames = args.length > 0 ? args : undefined; + const filtered = filterCanisters(canisters, canisterNames); + await checkCanisters(config, filtered, options); + } + + if (config.toolchain?.lintoko) { + const rootDir = getRootDir(); + const lintRules = await collectLintRules(config, rootDir); + const lintFiles = isFileMode ? fileArgs : undefined; + await lint(undefined, { + verbose: options.verbose, + fix: options.fix, + rules: lintRules, + files: lintFiles, + }); + } +} + +async function checkCanisters( + config: Config, + canisters: Record, + options: Partial, +): Promise { const mocPath = await toolchain.bin("moc", { fallback: true }); - const sources = await sourcesArgs(); + const sources = (await sourcesArgs()).flat(); const globalMocArgs = getGlobalMocArgs(config); + const allLibs = logAllLibsSupport(options.verbose); - // --all-libs enables richer diagnostics with edit suggestions from moc (requires moc >= 1.3.0) - const allLibs = supportsAllLibsFlag(); + for (const [canisterName, canister] of Object.entries(canisters)) { + if (!canister.main) { + cliError( + `No main file specified for canister '${canisterName}' in mops.toml`, + ); + } - if (!allLibs) { - console.log( - chalk.yellow( - `moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`, - ), - ); - } else if (options.verbose) { - console.log( - chalk.blue("check"), - chalk.gray("Using --all-libs for richer diagnostics"), - ); + validateCanisterArgs(canister, canisterName); + const motokoPath = resolveConfigPath(canister.main); + + const mocArgs = [ + "--check", + ...(allLibs ? ["--all-libs"] : []), + ...sources, + ...globalMocArgs, + ...(canister.args ?? []), + ...(options.extraArgs ?? []), + ]; + + if (options.fix) { + if (options.verbose) { + console.log( + chalk.blue("check"), + chalk.gray(`Attempting to fix ${canisterName}`), + ); + } + + const fixResult = await autofixMotoko(mocPath, [motokoPath], mocArgs); + logAutofixResult(fixResult, options.verbose); + } + + try { + const args = [motokoPath, ...mocArgs]; + if (options.verbose) { + console.log( + chalk.blue("check"), + chalk.gray(`Checking canister ${canisterName}:`), + ); + console.log(chalk.gray(mocPath, JSON.stringify(args))); + } + + const result = await execa(mocPath, args, { + stdio: "inherit", + reject: false, + }); + + if (result.exitCode !== 0) { + cliError( + `✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`, + ); + } + + console.log(chalk.green(`✓ ${canisterName}`)); + } catch (err: any) { + cliError( + `Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`, + ); + } + + const stablePath = resolveStablePath(canister, canisterName); + if (stablePath) { + await runStableCheck({ + oldFile: stablePath, + canisterMain: motokoPath, + canisterName, + mocPath, + globalMocArgs, + canisterArgs: canister.args ?? [], + sources, + options: { verbose: options.verbose, extraArgs: options.extraArgs }, + }); + } } +} + +async function checkFiles( + config: Config, + files: string[], + options: Partial, +): Promise { + const mocPath = await toolchain.bin("moc", { fallback: true }); + const sources = (await sourcesArgs()).flat(); + const globalMocArgs = getGlobalMocArgs(config); + const allLibs = logAllLibsSupport(options.verbose); const mocArgs = [ "--check", ...(allLibs ? ["--all-libs"] : []), - ...sources.flat(), + ...sources, ...globalMocArgs, ...(options.extraArgs ?? []), ]; @@ -88,32 +238,11 @@ export async function check( console.log(chalk.blue("check"), chalk.gray("Attempting to fix files")); } - const fixResult = await autofixMotoko(mocPath, fileList, mocArgs); - if (fixResult) { - for (const [file, codes] of fixResult.fixedFiles) { - const unique = [...new Set(codes)].sort(); - const n = codes.length; - const rel = path.relative(process.cwd(), file); - console.log( - chalk.green( - `Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`, - ), - ); - } - const fileCount = fixResult.fixedFiles.size; - console.log( - chalk.green( - `\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`, - ), - ); - } else { - if (options.verbose) { - console.log(chalk.yellow("No fixes were needed")); - } - } + const fixResult = await autofixMotoko(mocPath, files, mocArgs); + logAutofixResult(fixResult, options.verbose); } - for (const file of fileList) { + for (const file of files) { try { const args = [file, ...mocArgs]; if (options.verbose) { @@ -139,49 +268,4 @@ export async function check( ); } } - - const canisters = resolveCanisterConfigs(config); - for (const [name, canister] of Object.entries(canisters)) { - const stableConfig = canister["check-stable"]; - if (!stableConfig) { - continue; - } - - if (!canister.main) { - cliError(`No main file specified for canister '${name}' in mops.toml`); - } - - const stablePath = resolveConfigPath(stableConfig.path); - if (!existsSync(stablePath)) { - if (stableConfig.skipIfMissing) { - continue; - } - cliError( - `Deployed file not found: ${stablePath} (canister '${name}')\n` + - "Set skipIfMissing = true in [canisters." + - name + - ".check-stable] to skip this check when the file is missing.", - ); - } - - await runStableCheck({ - oldFile: stablePath, - canisterMain: resolveConfigPath(canister.main), - canisterName: name, - mocPath, - globalMocArgs, - options: { verbose: options.verbose, extraArgs: options.extraArgs }, - }); - } - - if (config.toolchain?.lintoko) { - const rootDir = getRootDir(); - const lintRules = await collectLintRules(config, rootDir); - await lint(undefined, { - verbose: options.verbose, - fix: options.fix, - rules: lintRules, - files: explicitFiles.length > 0 ? explicitFiles : undefined, - }); - } } diff --git a/cli/helpers/resolve-canisters.ts b/cli/helpers/resolve-canisters.ts index dd2764b6..3cdabfd8 100644 --- a/cli/helpers/resolve-canisters.ts +++ b/cli/helpers/resolve-canisters.ts @@ -14,11 +14,22 @@ export function resolveCanisterConfigs( ); } -export function resolveCanisterEntrypoints(config: Config): string[] { - const canisters = resolveCanisterConfigs(config); - return Object.values(canisters) - .map((c) => c.main) - .filter((main): main is string => Boolean(main)); +export function filterCanisters( + canisters: Record, + names?: string[], +): Record { + if (!names) { + return canisters; + } + const invalidNames = names.filter((name) => !(name in canisters)); + if (invalidNames.length) { + cliError( + `Canister(s) not found in mops.toml: ${invalidNames.join(", ")}. Available: ${Object.keys(canisters).join(", ")}`, + ); + } + return Object.fromEntries( + Object.entries(canisters).filter(([name]) => names.includes(name)), + ); } export function resolveSingleCanister( @@ -50,3 +61,23 @@ export function resolveSingleCanister( return { name: names[0]!, canister: canisters[names[0]!]! }; } + +export function looksLikeFile(arg: string): boolean { + return ( + arg.endsWith(".mo") || + arg.endsWith(".most") || + arg.includes("/") || + arg.includes("\\") + ); +} + +export function validateCanisterArgs( + canister: CanisterConfig, + canisterName: string, +): void { + if (canister.args && typeof canister.args === "string") { + cliError( + `Canister config 'args' should be an array of strings for canister ${canisterName}`, + ); + } +} diff --git a/cli/tests/__snapshots__/check.test.ts.snap b/cli/tests/__snapshots__/check.test.ts.snap index cbfee6d0..f4eb7990 100644 --- a/cli/tests/__snapshots__/check.test.ts.snap +++ b/cli/tests/__snapshots__/check.test.ts.snap @@ -32,11 +32,11 @@ exports[`check error 2`] = ` } `; -exports[`check no args falls back to [canisters] entrypoints 1`] = ` +exports[`check no args checks all canisters 1`] = ` { "exitCode": 0, "stderr": "", - "stdout": "✓ Ok.mo", + "stdout": "✓ backend", } `; diff --git a/cli/tests/check-stable.test.ts b/cli/tests/check-stable.test.ts index c2561357..9d11f453 100644 --- a/cli/tests/check-stable.test.ts +++ b/cli/tests/check-stable.test.ts @@ -59,6 +59,27 @@ describe("check-stable", () => { expect(existsSync(path.join(cwd, "new.wasm"))).toBe(false); }); + test("[canisters.X].args are passed to moc (enhanced migration)", async () => { + const cwd = path.join(import.meta.dirname, "check-stable/canister-args"); + const result = await cli(["check-stable", "old.most"], { cwd }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/Stable compatibility check passed/); + }); + + test("no args checks all canisters with [check-stable] config", async () => { + const cwd = path.join(import.meta.dirname, "check/deployed-compatible"); + const result = await cli(["check-stable"], { cwd }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/Stable compatibility check passed/); + }); + + test("canister name filters to specific canister", async () => { + const cwd = path.join(import.meta.dirname, "check/deployed-compatible"); + const result = await cli(["check-stable", "backend"], { cwd }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/Stable compatibility check passed/); + }); + test("errors when old file does not exist", async () => { const cwd = path.join(import.meta.dirname, "check-stable/compatible"); const result = await cli(["check-stable", "nonexistent.mo"], { cwd }); diff --git a/cli/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo b/cli/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo new file mode 100644 index 00000000..d706b262 --- /dev/null +++ b/cli/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo @@ -0,0 +1,8 @@ +module { + public func migration(_ : {}) : { a : Nat; b : Text } { + { + a = 42; + b = "hello"; + }; + }; +}; diff --git a/cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo b/cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo new file mode 100644 index 00000000..14cd9eee --- /dev/null +++ b/cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo @@ -0,0 +1,5 @@ +module { + public func migration(old : { a : Nat; b : Text }) : { a : Nat; b : Text; c : Bool } { + { old with c = true }; + }; +}; diff --git a/cli/tests/check-stable/canister-args/mops.toml b/cli/tests/check-stable/canister-args/mops.toml new file mode 100644 index 00000000..7fc9852a --- /dev/null +++ b/cli/tests/check-stable/canister-args/mops.toml @@ -0,0 +1,9 @@ +[toolchain] +moc = "1.5.0" + +[moc] +args = ["--default-persistent-actors"] + +[canisters.backend] +main = "src/main.mo" +args = ["--enhanced-migration=migrations"] diff --git a/cli/tests/check-stable/canister-args/old.most b/cli/tests/check-stable/canister-args/old.most new file mode 100644 index 00000000..08af92b1 --- /dev/null +++ b/cli/tests/check-stable/canister-args/old.most @@ -0,0 +1,8 @@ +// Version: 4.0.0 +{ + "20250101_000000_Init" : {} -> {a : Nat; b : Text} +} +actor { + stable a : Nat; + stable b : Text +}; diff --git a/cli/tests/check-stable/canister-args/src/main.mo b/cli/tests/check-stable/canister-args/src/main.mo new file mode 100644 index 00000000..0012596c --- /dev/null +++ b/cli/tests/check-stable/canister-args/src/main.mo @@ -0,0 +1,11 @@ +import Prim "mo:prim"; + +actor { + let a : Nat; + let b : Text; + let c : Bool; + + public func check() : async () { + Prim.debugPrint(debug_show { a; b; c }); + }; +}; diff --git a/cli/tests/check.test.ts b/cli/tests/check.test.ts index 8509a281..47970a05 100644 --- a/cli/tests/check.test.ts +++ b/cli/tests/check.test.ts @@ -49,12 +49,19 @@ describe("check", () => { await cliSnapshot(["check", "Warning.mo"], { cwd }, 1); }); - test("no args falls back to [canisters] entrypoints", async () => { + test("no args checks all canisters", async () => { const cwd = path.join(import.meta.dirname, "check/canisters"); await cliSnapshot(["check"], { cwd }, 0); }); - test("canister entrypoint resolved relative to config root when run from subdirectory", async () => { + test("canister name filters to specific canister", async () => { + const cwd = path.join(import.meta.dirname, "check/canisters"); + const result = await cli(["check", "backend"], { cwd }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/✓ backend/); + }); + + test("canister resolved relative to config root when run from subdirectory", async () => { const fixtureRoot = path.join( import.meta.dirname, "check/canisters-subdir", @@ -65,21 +72,38 @@ describe("check", () => { expect(result.stdout).toMatch(/✓/); }); - test("[moc] args applied when using canister fallback", async () => { + test("[moc] args applied to canister check", async () => { const cwd = path.join(import.meta.dirname, "check/canisters-moc-args"); const result = await cli(["check"], { cwd }); expect(result.exitCode).toBe(1); expect(result.stderr).toMatch(/warning \[M0194\]/); }); - test("canister entrypoint with errors", async () => { + test("[canisters.X].args applied to canister check", async () => { + const cwd = path.join( + import.meta.dirname, + "check/canisters-canister-args", + ); + const result = await cli(["check"], { cwd }); + expect(result.exitCode).toBe(1); + expect(result.stderr).toMatch(/warning \[M0194\]/); + }); + + test("canister with errors", async () => { const cwd = path.join(import.meta.dirname, "check/canisters-error"); const result = await cli(["check"], { cwd }); expect(result.exitCode).toBe(1); expect(result.stderr).toMatch(/error/i); }); - test("--fix with canister fallback", async () => { + test("invalid canister name errors", async () => { + const cwd = path.join(import.meta.dirname, "check/canisters"); + const result = await cli(["check", "nonexistent"], { cwd }); + expect(result.exitCode).toBe(1); + expect(result.stderr).toMatch(/not found in mops\.toml/); + }); + + test("--fix with canister", async () => { const cwd = path.join(import.meta.dirname, "check/canisters"); const result = await cli(["check", "--fix"], { cwd }); expect(result.exitCode).toBe(0); diff --git a/cli/tests/check/canisters-canister-args/Warning.mo b/cli/tests/check/canisters-canister-args/Warning.mo new file mode 100644 index 00000000..b508b058 --- /dev/null +++ b/cli/tests/check/canisters-canister-args/Warning.mo @@ -0,0 +1,5 @@ +actor { + public func example() : async () { + let unused = 123; + }; +}; diff --git a/cli/tests/check/canisters-canister-args/mops.toml b/cli/tests/check/canisters-canister-args/mops.toml new file mode 100644 index 00000000..07ba85a9 --- /dev/null +++ b/cli/tests/check/canisters-canister-args/mops.toml @@ -0,0 +1,9 @@ +[toolchain] +moc = "1.3.0" + +[moc] +args = ["--default-persistent-actors"] + +[canisters.backend] +main = "Warning.mo" +args = ["-Werror"] diff --git a/docs/docs/09-mops.toml.md b/docs/docs/09-mops.toml.md index b1d7d2ef..105fa729 100644 --- a/docs/docs/09-mops.toml.md +++ b/docs/docs/09-mops.toml.md @@ -97,7 +97,7 @@ Each canister entry specifies the entrypoint file and optional compiler settings | Field | Description | | -------- | --------------------------------------------------------------- | | main | Path to the main Motoko file (required) | -| args | Array of additional `moc` arguments for this canister (optional)| +| args | Array of additional `moc` arguments for this canister (optional). Applied after `[moc].args` in `check`, `check-stable`, and `build`. | | candid | Path to a Candid interface file for compatibility checking (optional) | | initArg | Candid-encoded initialization arguments (optional) | @@ -110,6 +110,16 @@ candid = "candid/backend.did" initArg = "(\"Hello\")" ``` +Multi-canister example with per-canister flags: +```toml +[canisters.backend] +main = "src/backend/main.mo" +args = ["--enhanced-migration=migrations/backend"] + +[canisters.frontend] +main = "src/frontend/main.mo" +``` + ### `[canisters..check-stable]` Configure automatic stable variable compatibility checking for a canister. When set, [`mops check`](/cli/mops-check) will verify that the current canister is compatible with the deployed version. diff --git a/docs/docs/cli/4-dev/04-mops-check.md b/docs/docs/cli/4-dev/04-mops-check.md index 66e6fcd8..c3b42330 100644 --- a/docs/docs/cli/4-dev/04-mops-check.md +++ b/docs/docs/cli/4-dev/04-mops-check.md @@ -5,25 +5,32 @@ sidebar_label: mops check # `mops check` -Check Motoko files for syntax errors and type issues +Check Motoko canisters or files for syntax errors and type issues ``` -mops check [files...] +mops check [args...] ``` Runs the Motoko compiler in check-only mode (`moc --check`). All package sources from the project are automatically included. -When no files are specified, checks all canister entrypoints defined in the `[canisters]` section of `mops.toml`. +Arguments can be **canister names** (as defined in `[canisters]`) or **file paths** (`.mo` files). When no arguments are given, checks all canisters defined in the `[canisters]` section of `mops.toml`. + +When checking canisters, per-canister `[canisters.].args` from `mops.toml` are applied alongside global `[moc].args`. Exits with a non-zero code if any file has errors, making it suitable for CI pipelines. Warnings do not cause a failure by default. ### Examples -Check all canister entrypoints defined in `mops.toml` +Check all canisters defined in `mops.toml` ``` mops check ``` +Check a specific canister +``` +mops check backend +``` + Check a single file ``` mops check src/main.mo @@ -46,9 +53,15 @@ mops check -- -Werror ## Arguments -### `[files...]` +### `[args...]` + +Canister names or file paths to check. + +- **Canister names** — resolved from `[canisters.]` in `mops.toml`. Per-canister `args` are applied. +- **File paths** — `.mo` files to check directly. Only global `[moc].args` and CLI `-- flags` are applied. +- **No arguments** — checks all canisters defined in `mops.toml`. -One or more paths to Motoko files to check. When omitted, all canister entrypoints from `mops.toml` are checked. +You cannot mix canister names and file paths in the same invocation. ## Options @@ -113,7 +126,7 @@ mops check --fix `--fix` is forwarded to both the Motoko compiler and lintoko, so both type-level and lint fixes are applied in a single invocation. :::note -When files are passed explicitly (e.g. `mops check src/Main.mo`), linting is scoped to those same files. When no files are specified and `mops check` resolves entrypoints from `[canisters]`, linting covers all `.mo` files in the project. +When file paths are passed explicitly (e.g. `mops check src/Main.mo`), linting is scoped to those files. When checking canisters (by name or with no arguments), linting covers all `.mo` files in the project. ::: :::info diff --git a/docs/docs/cli/4-dev/05-mops-check-stable.md b/docs/docs/cli/4-dev/05-mops-check-stable.md index e4c6e7c0..ab205282 100644 --- a/docs/docs/cli/4-dev/05-mops-check-stable.md +++ b/docs/docs/cli/4-dev/05-mops-check-stable.md @@ -8,16 +8,28 @@ sidebar_label: mops check-stable Check stable variable compatibility between a previously deployed version and the current canister entrypoint ``` -mops check-stable [canister] +mops check-stable [args...] ``` Verifies that an upgrade from an old actor to the current canister entrypoint is safe — i.e., that stable variable signatures are compatible. This prevents `Memory-incompatible program upgrade` traps at deploy time. The command handles the full workflow internally: generating `.most` stable type signatures, comparing them, and cleaning up intermediate files. +When checking canisters, per-canister `[canisters.].args` from `mops.toml` are applied alongside global `[moc].args`. + ### Examples -Check upgrade compatibility using the old source file +Check all canisters that have `[check-stable]` configured in `mops.toml` +``` +mops check-stable +``` + +Check a specific canister by name +``` +mops check-stable backend +``` + +Check upgrade compatibility using an old source file ``` mops check-stable .old/src/backend/main.mo ``` @@ -27,36 +39,45 @@ Check using a pre-generated `.most` file mops check-stable /path/to/deployed.most ``` -Check a specific canister in a multi-canister project +Check a specific canister using an old file ``` mops check-stable .old/src/backend/main.mo backend ``` Check with verbose output ``` -mops check-stable .old/src/backend/main.mo --verbose +mops check-stable backend --verbose ``` -## Arguments +## Usage modes + +### Canister mode (recommended) + +When no arguments are given, or when arguments are canister names: -### `` +``` +mops check-stable +mops check-stable backend +``` -Path to the old (deployed) version of the actor. Accepts two formats: +Resolves the old (deployed) file from `[canisters..check-stable].path` in `mops.toml`. Per-canister `[canisters.].args` are applied to `moc`. -- **`.mo` file** — the old Motoko source file. The command generates the `.most` stable type signature automatically. -- **`.most` file** — a pre-generated stable type signature. Used directly without compilation. +With no arguments, all canisters that have `[check-stable]` configured are checked. Canisters without `[check-stable]` are silently skipped. When a canister name is given explicitly but has no `[check-stable]` config, an error is shown. -:::tip -`mops build` generates a `.most` file for each canister alongside `.wasm` and `.did`. Save it before deploying an upgrade, then configure `[canisters..check-stable]` in `mops.toml` so `mops check` verifies upgrade safety automatically on every run. -::: +### File mode + +When the first argument looks like a file path (`.mo` or `.most`): -### `[canister]` +``` +mops check-stable [canister] +``` -Name of the canister to check against (as defined in `mops.toml`). The current entrypoint is resolved from `[canisters.].main`. +- **``** — Path to the old (deployed) version. A `.mo` file is compiled to extract stable types; a `.most` file is used directly. +- **`[canister]`** — Name of the canister to check against. When omitted, auto-detected if exactly one canister is defined; errors if multiple canisters exist. -When omitted: -- If there is exactly one canister defined, it is used automatically -- If there are multiple canisters, an error is shown listing the available names +:::tip +`mops build` generates a `.most` file for each canister alongside `.wasm` and `.did`. Save it before deploying an upgrade, then configure `[canisters..check-stable]` in `mops.toml` so `mops check-stable` (and `mops check`) verify upgrade safety automatically on every run. +::: ## Options @@ -69,9 +90,9 @@ Show detailed output including the `moc` commands being run and the intermediate Any arguments after `--` are forwarded to `moc` when generating stable type signatures. ``` -mops check-stable .old/src/main.mo -- --experimental-stable-memory=1 +mops check-stable -- --experimental-stable-memory=1 ``` :::tip -Global `moc` flags configured in `[moc].args` are automatically applied. See [`mops.toml` reference](/mops.toml#moc). +Global `moc` flags configured in `[moc].args` and per-canister flags in `[canisters.].args` are automatically applied. See [`mops.toml` reference](/mops.toml#moc). ::: From b52c9fa569a0a63c24c7d12e3932914a7b23a891 Mon Sep 17 00:00:00 2001 From: Kamil Listopad Date: Tue, 14 Apr 2026 13:19:11 +0200 Subject: [PATCH 2/5] fix lint errors and add mops.lock to .gitignore Made-with: Cursor --- .gitignore | 1 + cli/commands/check-stable.ts | 4 +++- cli/commands/check.ts | 5 +---- cli/tests/check.test.ts | 5 +---- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 9e66c6c0..4c9b67fc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ target/ dist/ .dfx/ .mops/ +mops.lock .DS_Store diff --git a/cli/commands/check-stable.ts b/cli/commands/check-stable.ts index bc9b5f46..c35a4d1e 100644 --- a/cli/commands/check-stable.ts +++ b/cli/commands/check-stable.ts @@ -97,7 +97,9 @@ export async function checkStable( const stablePath = resolveStablePath(canister, name, { required: !!canisterNames, }); - if (!stablePath) continue; + if (!stablePath) { + continue; + } await runStableCheck({ oldFile: stablePath, diff --git a/cli/commands/check.ts b/cli/commands/check.ts index 8a803448..1373732e 100644 --- a/cli/commands/check.ts +++ b/cli/commands/check.ts @@ -8,10 +8,7 @@ import { readConfig, resolveConfigPath, } from "../mops.js"; -import { - AutofixResult, - autofixMotoko, -} from "../helpers/autofix-motoko.js"; +import { AutofixResult, autofixMotoko } from "../helpers/autofix-motoko.js"; import { getMocSemVer } from "../helpers/get-moc-version.js"; import { filterCanisters, diff --git a/cli/tests/check.test.ts b/cli/tests/check.test.ts index 47970a05..aa549742 100644 --- a/cli/tests/check.test.ts +++ b/cli/tests/check.test.ts @@ -80,10 +80,7 @@ describe("check", () => { }); test("[canisters.X].args applied to canister check", async () => { - const cwd = path.join( - import.meta.dirname, - "check/canisters-canister-args", - ); + const cwd = path.join(import.meta.dirname, "check/canisters-canister-args"); const result = await cli(["check"], { cwd }); expect(result.exitCode).toBe(1); expect(result.stderr).toMatch(/warning \[M0194\]/); From ff0d6762ed2fda3977b7caf2965e23545761ab70 Mon Sep 17 00:00:00 2001 From: Kamil Listopad Date: Tue, 14 Apr 2026 13:26:03 +0200 Subject: [PATCH 3/5] cleanup: address review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hoist sources in checkStable canister loop (avoid repeated sourcesArgs) - rename logAllLibsSupport → checkAllLibsSupport (name matches behavior) - pass fileArgs to checkFiles (clearer intent) - bind args[0] once in check-stable file mode (remove ! assertions) - docs: fix "file paths" wording, clarify [build].args scope in [moc] section Made-with: Cursor --- cli/cli.ts | 2 +- cli/commands/check-stable.ts | 7 +++++-- cli/commands/check.ts | 8 ++++---- docs/docs/09-mops.toml.md | 2 +- docs/docs/cli/4-dev/04-mops-check.md | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cli/cli.ts b/cli/cli.ts index 3ab0231c..38b1f537 100755 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -332,7 +332,7 @@ program program .command("check [args...]") .description( - "Check Motoko canisters or files for syntax errors and type issues. Arguments can be canister names or .mo 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] 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( diff --git a/cli/commands/check-stable.ts b/cli/commands/check-stable.ts index c35a4d1e..8056be32 100644 --- a/cli/commands/check-stable.ts +++ b/cli/commands/check-stable.ts @@ -60,8 +60,9 @@ export async function checkStable( const mocPath = await toolchain.bin("moc", { fallback: true }); const globalMocArgs = getGlobalMocArgs(config); - if (args.length > 0 && looksLikeFile(args[0]!)) { - const oldFile = args[0]!; + const firstArg = args[0]; + if (firstArg && looksLikeFile(firstArg)) { + const oldFile = firstArg; const canisterName = args[1]; const { name, canister } = resolveSingleCanister(config, canisterName); @@ -86,6 +87,7 @@ export async function checkStable( const canisters = resolveCanisterConfigs(config); const canisterNames = args.length > 0 ? args : undefined; const filteredCanisters = filterCanisters(canisters, canisterNames); + const sources = (await sourcesArgs()).flat(); let checked = 0; for (const [name, canister] of Object.entries(filteredCanisters)) { @@ -108,6 +110,7 @@ export async function checkStable( mocPath, globalMocArgs, canisterArgs: canister.args ?? [], + sources, options, }); checked++; diff --git a/cli/commands/check.ts b/cli/commands/check.ts index 1373732e..ad77876f 100644 --- a/cli/commands/check.ts +++ b/cli/commands/check.ts @@ -35,7 +35,7 @@ export interface CheckOptions { extraArgs: string[]; } -function logAllLibsSupport(verbose?: boolean): boolean { +function checkAllLibsSupport(verbose?: boolean): boolean { const allLibs = supportsAllLibsFlag(); if (!allLibs) { console.log( @@ -97,7 +97,7 @@ export async function check( } if (isFileMode) { - await checkFiles(config, args, options); + await checkFiles(config, fileArgs, options); } else { if (!hasCanisters) { cliError( @@ -135,7 +135,7 @@ async function checkCanisters( const mocPath = await toolchain.bin("moc", { fallback: true }); const sources = (await sourcesArgs()).flat(); const globalMocArgs = getGlobalMocArgs(config); - const allLibs = logAllLibsSupport(options.verbose); + const allLibs = checkAllLibsSupport(options.verbose); for (const [canisterName, canister] of Object.entries(canisters)) { if (!canister.main) { @@ -220,7 +220,7 @@ async function checkFiles( const mocPath = await toolchain.bin("moc", { fallback: true }); const sources = (await sourcesArgs()).flat(); const globalMocArgs = getGlobalMocArgs(config); - const allLibs = logAllLibsSupport(options.verbose); + const allLibs = checkAllLibsSupport(options.verbose); const mocArgs = [ "--check", diff --git a/docs/docs/09-mops.toml.md b/docs/docs/09-mops.toml.md index 105fa729..257f8408 100644 --- a/docs/docs/09-mops.toml.md +++ b/docs/docs/09-mops.toml.md @@ -83,7 +83,7 @@ Example: args = ["--default-persistent-actors", "-W=M0223,M0236,M0237"] ``` -These flags are applied before command-specific flags (`[build].args`, `[canisters.].args`) and CLI `-- flags`. +These flags are applied before per-canister `[canisters.].args` and CLI `-- flags`. For `mops build`, `[build].args` are also applied (after `[moc].args`, before per-canister args). Use `mops moc-args` to print the moc flags defined in `mops.toml` (useful when invoking `moc` directly). diff --git a/docs/docs/cli/4-dev/04-mops-check.md b/docs/docs/cli/4-dev/04-mops-check.md index c3b42330..3e4bc414 100644 --- a/docs/docs/cli/4-dev/04-mops-check.md +++ b/docs/docs/cli/4-dev/04-mops-check.md @@ -13,7 +13,7 @@ mops check [args...] Runs the Motoko compiler in check-only mode (`moc --check`). All package sources from the project are automatically included. -Arguments can be **canister names** (as defined in `[canisters]`) or **file paths** (`.mo` files). When no arguments are given, checks all canisters defined in the `[canisters]` section of `mops.toml`. +Arguments can be **canister names** (as defined in `[canisters]`) or **file paths**. When no arguments are given, checks all canisters defined in the `[canisters]` section of `mops.toml`. When checking canisters, per-canister `[canisters.].args` from `mops.toml` are applied alongside global `[moc].args`. From 0637cb81f259dbef969deddd21bd4633235133dc Mon Sep 17 00:00:00 2001 From: Kamil Listopad Date: Tue, 14 Apr 2026 13:31:24 +0200 Subject: [PATCH 4/5] docs: add check-stable to [moc] command list Made-with: Cursor --- docs/docs/09-mops.toml.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/09-mops.toml.md b/docs/docs/09-mops.toml.md index 257f8408..045d1d10 100644 --- a/docs/docs/09-mops.toml.md +++ b/docs/docs/09-mops.toml.md @@ -71,7 +71,7 @@ File paths must start with `/`, `./`, or `../`. ## [moc] -Global Motoko compiler flags applied to all `moc` invocations (`check`, `build`, `test`, `bench`, `watch`). +Global Motoko compiler flags applied to all `moc` invocations (`check`, `check-stable`, `build`, `test`, `bench`, `watch`). | Field | Description | | ----- | ----------- | From 13e39403f4f240e118d567744ad4cb0b7370ad29 Mon Sep 17 00:00:00 2001 From: Kamil Listopad Date: Tue, 14 Apr 2026 13:52:34 +0200 Subject: [PATCH 5/5] fix: format test fixture Motoko files Made-with: Cursor --- .../migrations/20250201_000000_AddField.mo | 6 +++++- cli/tests/check-stable/canister-args/src/main.mo | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo b/cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo index 14cd9eee..432414c9 100644 --- a/cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo +++ b/cli/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo @@ -1,5 +1,9 @@ module { - public func migration(old : { a : Nat; b : Text }) : { a : Nat; b : Text; c : Bool } { + public func migration(old : { a : Nat; b : Text }) : { + a : Nat; + b : Text; + c : Bool; + } { { old with c = true }; }; }; diff --git a/cli/tests/check-stable/canister-args/src/main.mo b/cli/tests/check-stable/canister-args/src/main.mo index 0012596c..6efaf107 100644 --- a/cli/tests/check-stable/canister-args/src/main.mo +++ b/cli/tests/check-stable/canister-args/src/main.mo @@ -1,11 +1,11 @@ import Prim "mo:prim"; actor { - let a : Nat; - let b : Text; - let c : Bool; + let a : Nat; + let b : Text; + let c : Bool; - public func check() : async () { - Prim.debugPrint(debug_show { a; b; c }); - }; + public func check() : async () { + Prim.debugPrint(debug_show { a; b; c }); + }; };