ic-mops 2.12.0 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## Next
4
4
 
5
+ ## 2.12.1
6
+ - `mops check`/`build`/`check-stable` skip migration staging when only the pending `next` migration is needed, so `moc` diagnostics reference the real `next-migration/<file>` path.
7
+
5
8
  ## 2.12.0
6
9
  - Migration staging directory moved from `.mops/.migrations/<canister>/` to `<parent-of-chain>/.migrations-<canister>/`, so migration files can import shared modules from sibling folders (e.g. a `types/` folder next to `migrations/`) — relative imports now resolve to the same target whether moc reads the original chain dir or the staged one. The staged dir self-stamps a `.gitignore` so it doesn't pollute `git status`; `mops init` now also adds `.migrations-*/` to the project `.gitignore`
7
10
  - `[canisters.<name>.migrations]` now requires `chain` and `next` to share the same parent directory (any layout where the parents differed is rejected with a clear error). The default layout `chain = "migrations"` + `next = "next-migration"` already satisfies this. For per-canister setups, use sibling subdirectories, e.g. `chain = "src/backend/migrations"` + `next = "src/backend/next-migration"`
package/bundle/cli.tgz CHANGED
Binary file
@@ -98,6 +98,15 @@ export async function prepareMigrationArgs(migrations, canisterName, mode, verbo
98
98
  cleanup: async () => { },
99
99
  };
100
100
  }
101
+ // Shortcut: when only the pending next migration is needed (empty chain or
102
+ // trimmed to 1), point moc at next-migration/ so diagnostics use the real path.
103
+ if (nextFile && nextDir && (chainFiles.length === 0 || limit === 1)) {
104
+ const migrationArgs = [`--enhanced-migration=${nextDir}`];
105
+ if (isTrimming) {
106
+ migrationArgs.push("-A=M0254");
107
+ }
108
+ return { migrationArgs, cleanup: async () => { } };
109
+ }
101
110
  const tempDir = stagedMigrationsDir(chainDir, canisterName);
102
111
  await rm(tempDir, { recursive: true, force: true });
103
112
  mkdirSync(tempDir, { recursive: true });
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "2.12.0",
3
+ "version": "2.12.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "bin/mops.js",
@@ -112,6 +112,41 @@ describe("migrate", () => {
112
112
  await patchMigrations(cwd, "check-limit = 2");
113
113
  await cliSnapshot(["check", "--verbose"], { cwd }, 0);
114
114
  });
115
+ test("error inside a chain migration reports its file location", async () => {
116
+ const cwd = await makeTempFixture("with-next");
117
+ await writeFile(path.join(cwd, "migrations", "20250301_000000_AddEmail.mo"), 'import State "../types/State";\n' +
118
+ "module {\n" +
119
+ " public func migration(old : State.V2) : State.V3 {\n" +
120
+ " { old with email = 42 };\n" +
121
+ " };\n" +
122
+ "};\n");
123
+ await cliSnapshot(["check"], { cwd }, 1);
124
+ });
125
+ async function corruptNextMigration(cwd) {
126
+ const nextDir = path.join(cwd, "next-migration");
127
+ const nextFile = readdirSync(nextDir).find((f) => f.endsWith(".mo"));
128
+ await writeFile(path.join(nextDir, nextFile), 'import State "../types/State";\n' +
129
+ "module {\n" +
130
+ " public func migration(old : State.V3) : State.V4 {\n" +
131
+ ' { id = "wrong"; name = old.name; email = old.email };\n' +
132
+ " };\n" +
133
+ "};\n");
134
+ }
135
+ test("check-limit=1 with pending next reports real next-migration path on error", async () => {
136
+ const cwd = await makeTempFixture("with-next");
137
+ await patchMigrations(cwd, "check-limit = 1");
138
+ await corruptNextMigration(cwd);
139
+ await cliSnapshot(["check"], { cwd }, 1);
140
+ });
141
+ test("empty chain with pending next reports real next-migration path on error", async () => {
142
+ const cwd = await makeTempFixture("with-next");
143
+ const chainDir = path.join(cwd, "migrations");
144
+ for (const f of readdirSync(chainDir).filter((f) => f.endsWith(".mo"))) {
145
+ await rm(path.join(chainDir, f));
146
+ }
147
+ await corruptNextMigration(cwd);
148
+ await cliSnapshot(["check"], { cwd }, 1);
149
+ });
115
150
  });
116
151
  describe("build", () => {
117
152
  test("build produces .most with full migration chain", async () => {
@@ -153,6 +153,16 @@ export async function prepareMigrationArgs(
153
153
  };
154
154
  }
155
155
 
156
+ // Shortcut: when only the pending next migration is needed (empty chain or
157
+ // trimmed to 1), point moc at next-migration/ so diagnostics use the real path.
158
+ if (nextFile && nextDir && (chainFiles.length === 0 || limit === 1)) {
159
+ const migrationArgs = [`--enhanced-migration=${nextDir}`];
160
+ if (isTrimming) {
161
+ migrationArgs.push("-A=M0254");
162
+ }
163
+ return { migrationArgs, cleanup: async () => {} };
164
+ }
165
+
156
166
  const tempDir = stagedMigrationsDir(chainDir, canisterName);
157
167
  await rm(tempDir, { recursive: true, force: true });
158
168
  mkdirSync(tempDir, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "2.12.0",
3
+ "version": "2.12.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/bin/mops.js",
@@ -85,6 +85,42 @@ check-stable Comparing deployed.most ↔ .mops/.check-stable/new.most
85
85
  }
86
86
  `;
87
87
 
88
+ exports[`migrate check check-limit=1 with pending next reports real next-migration path on error 1`] = `
89
+ {
90
+ "exitCode": 1,
91
+ "stderr": "next-migration/20250401_000000_RenameId.mo:4.12-4.19: type error [M0050], literal of type
92
+ Text
93
+ does not have expected type
94
+ Nat
95
+ ✗ Check failed for canister backend (exit code: 1)",
96
+ "stdout": "",
97
+ }
98
+ `;
99
+
100
+ exports[`migrate check empty chain with pending next reports real next-migration path on error 1`] = `
101
+ {
102
+ "exitCode": 1,
103
+ "stderr": "next-migration/20250401_000000_RenameId.mo:4.12-4.19: type error [M0050], literal of type
104
+ Text
105
+ does not have expected type
106
+ Nat
107
+ ✗ Check failed for canister backend (exit code: 1)",
108
+ "stdout": "",
109
+ }
110
+ `;
111
+
112
+ exports[`migrate check error inside a chain migration reports its file location 1`] = `
113
+ {
114
+ "exitCode": 1,
115
+ "stderr": ".migrations-backend/20250301_000000_AddEmail.mo:4.24-4.26: type error [M0050], literal of type
116
+ Nat
117
+ does not have expected type
118
+ Text
119
+ ✗ Check failed for canister backend (exit code: 1)",
120
+ "stdout": "",
121
+ }
122
+ `;
123
+
88
124
  exports[`migrate migrate freeze moves the file from next to chain 1`] = `
89
125
  {
90
126
  "exitCode": 0,
@@ -154,6 +154,51 @@ describe("migrate", () => {
154
154
  await patchMigrations(cwd, "check-limit = 2");
155
155
  await cliSnapshot(["check", "--verbose"], { cwd }, 0);
156
156
  });
157
+
158
+ test("error inside a chain migration reports its file location", async () => {
159
+ const cwd = await makeTempFixture("with-next");
160
+ await writeFile(
161
+ path.join(cwd, "migrations", "20250301_000000_AddEmail.mo"),
162
+ 'import State "../types/State";\n' +
163
+ "module {\n" +
164
+ " public func migration(old : State.V2) : State.V3 {\n" +
165
+ " { old with email = 42 };\n" +
166
+ " };\n" +
167
+ "};\n",
168
+ );
169
+ await cliSnapshot(["check"], { cwd }, 1);
170
+ });
171
+
172
+ async function corruptNextMigration(cwd: string): Promise<void> {
173
+ const nextDir = path.join(cwd, "next-migration");
174
+ const nextFile = readdirSync(nextDir).find((f) => f.endsWith(".mo"))!;
175
+ await writeFile(
176
+ path.join(nextDir, nextFile),
177
+ 'import State "../types/State";\n' +
178
+ "module {\n" +
179
+ " public func migration(old : State.V3) : State.V4 {\n" +
180
+ ' { id = "wrong"; name = old.name; email = old.email };\n' +
181
+ " };\n" +
182
+ "};\n",
183
+ );
184
+ }
185
+
186
+ test("check-limit=1 with pending next reports real next-migration path on error", async () => {
187
+ const cwd = await makeTempFixture("with-next");
188
+ await patchMigrations(cwd, "check-limit = 1");
189
+ await corruptNextMigration(cwd);
190
+ await cliSnapshot(["check"], { cwd }, 1);
191
+ });
192
+
193
+ test("empty chain with pending next reports real next-migration path on error", async () => {
194
+ const cwd = await makeTempFixture("with-next");
195
+ const chainDir = path.join(cwd, "migrations");
196
+ for (const f of readdirSync(chainDir).filter((f) => f.endsWith(".mo"))) {
197
+ await rm(path.join(chainDir, f));
198
+ }
199
+ await corruptNextMigration(cwd);
200
+ await cliSnapshot(["check"], { cwd }, 1);
201
+ });
157
202
  });
158
203
 
159
204
  describe("build", () => {