Draft
Conversation
Test that the language server correctly handles `--enhanced-migration` from mops.toml, verifying that relative migration paths resolve against the source file's directory in moc.js. Requires caffeinelabs/motoko#6002 to be released in a new motoko npm package. Made-with: Cursor
…ons] Parse the mops 2.11.0 per-canister migrations config (chain/next dirs) and apply --enhanced-migration as a per-file flag before type-checking the canister's main actor, restoring default flags afterwards. The moc.js compiler (js_of_ocaml) stores Flags state in globalThis, so separate instances cannot be created via require.cache clearing. Instead, flags are swapped around each check call. When next-migration has .mo files, it is preferred over the frozen chain dir (moc accepts a suffix of the migration chain). Made-with: Cursor
The --enhanced-migration flag was added in motoko@4.3.0. The previous lockfile pinned 4.2.0 which doesn't support this flag, causing CI to fail with M0001 syntax errors on uninitialized let declarations. Made-with: Cursor
Made-with: Cursor
Adds types/State.mo with a Counter type used by both the last migration and main.mo, verifying cross-module imports resolve correctly during type-checking. Made-with: Cursor
moc.js's setExtraFlags does not reset the --enhanced-migration flag between calls, causing the flag to leak across canisters that share a single moc.js instance. Each canister with a [canisters.<name>.migrations] section now gets its own moc.js compiler instance with the flag baked into its mopsArgs. Compiler isolation is achieved by re-executing moc.min.js via Module._compile to obtain an independent module.exports (and therefore independent Flags state). The bundled moc.min.js is shipped separately as src/server/compiler/moc-bundled.js (gitignored, copied via prepare:moc-bundled from postinstall, pretest, and compile:motoko[:dev]) so a single code path works in both jest and the bundled extension. Per-canister setup failures no longer abort workspace setup. Made-with: Cursor
The bundled fallback in `addIsolatedContext` (`?? bundledMocJsPath`) only existed because the bundled `MocJsInfo` left `path` undefined. Setting `path: bundledMocJsPath` directly when constructing the bundled `MocJsInfo` makes the field required and removes the conditional. Existing mocJsPath tests that asserted `path === undefined` for the bundled fallback now check `source === 'bundled'`, which is the actual invariant they meant to verify. Made-with: Cursor
Drop the parallel addIsolatedContext + manual wiring in handlers in favor
of an `isolated` flag threaded through addContext → requestMotokoInstance
→ createMotokoInstance. The same moc.js resolution (custom path → workspace
toolchain version → bundled fallback) now serves both shared workspace
contexts and per-canister isolated ones; only the loader differs (require
vs Module._compile via loadFreshCompiler).
Cache key includes the isolated flag so an isolated and a shared instance
for the same URI don't collide.
Replace `require('motoko/lib').default(compiler)` calls with the already-
imported `wrapMotoko` for consistency. Shorten loadFreshCompiler docstring.
Made-with: Cursor
Each unique cache key already gets a fresh moc.js instance via loadFreshCompiler; the canister `uri` is the disambiguator. The flag was dead weight. Also drop the require.cache cleanup (no longer using require for moc.js) and the unused `motokoPath` indirection. The default context keeps the npm `motoko` singleton so tests that import it directly stay in sync. Made-with: Cursor
Restore wrapMotoko/defaultMotoko/path-required refactors that were not needed for the canister isolation feature. Keep only: - loadFreshCompiler + Module._compile to spawn isolated moc.js instances - bundled fallback uses loadFreshCompiler so the canister context cache produces independent Flags state per canister - prepare:moc-bundled script wired into postinstall, pretest, compile Made-with: Cursor
Made-with: Cursor
The require.cache cleanup in requestMotokoInstance is dead now that all compiler loads go through loadFreshCompiler (which uses Module._compile and never touches the require cache). Also drop the existsSync / missing-export checks: getCompiler() already wraps in try/catch and returns Result, so readFileSync's ENOENT and a downstream wrap failure surface the same way. Made-with: Cursor
- handlers: skip + warn when canister mainUri collides with workspace context - handlers: extract applyPackages helper, demote per-canister log to debug - context: matchesContext gates getContext/hasContext on path-segment boundary (prevents main.mo accidentally matching main.module.mo) - context: clear motokoInstances in resetContexts to bound memory - utils: distinguish missing mops.toml (silent) from parse error (warn) - test: add behavioral probe verifying --enhanced-migration set on backend ctx does not leak into the workspace ctx; restore via applyMocFlags - fixture: drop misleading [toolchain] override (suite uses bundled moc.js) Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The VS Code extension didn't support Motoko's enhanced migration feature. Projects using
[canisters.<name>.migrations]inmops.toml(mops 2.11.0+) saw spuriousM0257errors on uninitialized stable variables, because the extension never passed--enhanced-migrationto the type checker.The naive fix — set
--enhanced-migrationglobally viasetExtraFlags— corrupts every other file in the workspace: stable variables without initializers become legal everywhere, hiding real errors in non-canister code (e.g. anIntegrationTest.moactor that should still require initializers).Root cause — why "just swap flags before each check" doesn't work
--enhanced-migrationis per-canister: it should apply when checking the canister's main actor, but not when checking other files. Two sub-problems made the obvious approaches unworkable:moc.js(Flags) state is global within an instance.setExtraFlagsmutates OCaml refs; there is no API to swap flags atomically per check, and LSP request handling can interleave.require()returns singletons — so does jest's module registry. A freshrequire('motoko/.../moc.min.js')returns the same compiler object every time, even afterdelete require.cache[...]. You can't "just" spin up a secondmoc.jsinstance the obvious way.Fix
For each canister with a
[canisters.<name>.migrations]chain, register a dedicatedContextkeyed by the canister's main file URI. Each such context owns its ownmoc.jsinstance loaded viaModule._compile, which bypasses both Node'srequire.cacheand jest's module registry, giving genuinely isolatedFlagsstate per canister.Routing in
getContext/hasContextis now path-segment aware (matchesContext) so canister-file URIs don't accidentally match siblings whose names extend them as a prefix (e.g.main.movs.main.module.mo).Build pipeline:
npm run prepare:moc-bundledcopiesnode_modules/motoko/versions/latest/moc.min.jstosrc/server/compiler/moc-bundled.jssoloadFreshCompilerhas a stable on-disk path in dev, jest, and the bundled extension.Bumps the
motokodependency to^4.4.0(--enhanced-migrationwas added in 4.3.0).Caveats
chaindirectory is supported.next-migrationandcheck-limitare ignored — users staging the next migration innext-migrationwon't get type-checked feedback for it from the canister's context.moc.jsinstance adds a few MB of memory; cleared onresetContexts.Module._compileis a private Node API. It works in both Node and jest today; if it breaks in a future Node version we can fall back tovm.Script.Test plan
enhancedMigration.spec.ts: distinct compiler instances per canister, behavioral probe that flags set on one context don't leak to the other, end-to-end compile of a canister with a 3-stage migration chain (stable variable type evolution + sharedtypes/State.moimport), and a sibling test canister that compiles correctly without the flag