ic-mops 2.10.0 → 2.12.0

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/bundle/cli.tgz +0 -0
  3. package/cli.ts +24 -0
  4. package/commands/build.ts +10 -1
  5. package/commands/check-stable.ts +52 -21
  6. package/commands/check.ts +65 -52
  7. package/commands/init.ts +17 -4
  8. package/commands/migrate.ts +165 -0
  9. package/declarations/main/main.did +38 -0
  10. package/declarations/main/main.did.d.ts +36 -0
  11. package/declarations/main/main.did.js +36 -0
  12. package/dist/cli.js +18 -0
  13. package/dist/commands/build.js +5 -1
  14. package/dist/commands/check-stable.d.ts +1 -0
  15. package/dist/commands/check-stable.js +39 -21
  16. package/dist/commands/check.js +51 -42
  17. package/dist/commands/init.js +11 -4
  18. package/dist/commands/migrate.d.ts +2 -0
  19. package/dist/commands/migrate.js +104 -0
  20. package/dist/declarations/main/main.did +38 -0
  21. package/dist/declarations/main/main.did.d.ts +36 -0
  22. package/dist/declarations/main/main.did.js +36 -0
  23. package/dist/helpers/migrations.d.ts +10 -0
  24. package/dist/helpers/migrations.js +125 -0
  25. package/dist/helpers/resolve-canisters.d.ts +1 -1
  26. package/dist/helpers/resolve-canisters.js +15 -1
  27. package/dist/package.json +1 -1
  28. package/dist/tests/migrate.test.d.ts +1 -0
  29. package/dist/tests/migrate.test.js +181 -0
  30. package/dist/types.d.ts +7 -0
  31. package/helpers/migrations.ts +190 -0
  32. package/helpers/resolve-canisters.ts +17 -0
  33. package/package.json +1 -1
  34. package/tests/__snapshots__/migrate.test.ts.snap +119 -0
  35. package/tests/migrate/basic/deployed.most +12 -0
  36. package/tests/migrate/basic/migrations/20250101_000000_Init.mo +5 -0
  37. package/tests/migrate/basic/migrations/20250201_000000_AddName.mo +5 -0
  38. package/tests/migrate/basic/migrations/20250301_000000_AddEmail.mo +9 -0
  39. package/tests/migrate/basic/mops.toml +15 -0
  40. package/tests/migrate/basic/next-migration/.gitkeep +0 -0
  41. package/tests/migrate/basic/src/main.mo +11 -0
  42. package/tests/migrate/with-next/deployed.most +12 -0
  43. package/tests/migrate/with-next/migrations/20250101_000000_Init.mo +7 -0
  44. package/tests/migrate/with-next/migrations/20250201_000000_AddName.mo +7 -0
  45. package/tests/migrate/with-next/migrations/20250301_000000_AddEmail.mo +7 -0
  46. package/tests/migrate/with-next/mops.toml +15 -0
  47. package/tests/migrate/with-next/next-migration/20250401_000000_RenameId.mo +7 -0
  48. package/tests/migrate/with-next/src/main.mo +11 -0
  49. package/tests/migrate/with-next/types/State.mo +7 -0
  50. package/tests/migrate.test.ts +255 -0
  51. package/types.ts +8 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Next
4
4
 
5
+ ## 2.12.0
6
+ - 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
+ - `[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"`
8
+
9
+ ## 2.11.0
10
+ - Add `mops migrate new <Name>` and `mops migrate freeze` commands for managing enhanced migration chains
11
+ - Add `[canisters.<name>.migrations]` config section with `chain`, `next`, `check-limit`, and `build-limit` fields
12
+ - `mops check`, `mops build`, and `mops check-stable` now auto-inject `--enhanced-migration` when `[migrations]` is configured
13
+ - `mops check` and `mops check-stable` emit a hint to create a migration when a stable compatibility check fails and `[migrations]` is configured
14
+ - Migration chain trimming: only the last N migrations are passed to `moc` based on `check-limit`/`build-limit` settings
15
+
5
16
  ## 2.10.0
6
17
  - `mops check` and `mops check-stable` now apply per-canister `[canisters.<name>].args` (previously only `mops build` applied them)
7
18
  - `mops check` now accepts canister names as arguments (e.g. `mops check backend`) to check a specific canister
package/bundle/cli.tgz CHANGED
Binary file
package/cli.ts CHANGED
@@ -43,6 +43,7 @@ import {
43
43
  importPem,
44
44
  setUserProp,
45
45
  } from "./commands/user.js";
46
+ import { migrateNew, migrateFreeze } from "./commands/migrate.js";
46
47
  import { watch } from "./commands/watch/watch.js";
47
48
  import {
48
49
  apiVersion,
@@ -707,6 +708,29 @@ toolchainCommand
707
708
 
708
709
  program.addCommand(toolchainCommand);
709
710
 
711
+ // migrate
712
+ const migrateCommand = new Command("migrate").description(
713
+ "Manage enhanced migration chains",
714
+ );
715
+
716
+ migrateCommand
717
+ .command("new <name> [canister]")
718
+ .description("Create a new migration file in the next-migration directory")
719
+ .action(async (name, canister) => {
720
+ checkConfigFile(true);
721
+ await migrateNew(name, canister);
722
+ });
723
+
724
+ migrateCommand
725
+ .command("freeze [canister]")
726
+ .description("Move the next migration into the frozen chain")
727
+ .action(async (canister) => {
728
+ checkConfigFile(true);
729
+ await migrateFreeze(canister);
730
+ });
731
+
732
+ program.addCommand(migrateCommand);
733
+
710
734
  // self
711
735
  const selfCommand = new Command("self").description("Mops CLI management");
712
736
 
package/commands/build.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  resolveCanisterConfigs,
12
12
  validateCanisterArgs,
13
13
  } from "../helpers/resolve-canisters.js";
14
+ import { prepareMigrationArgs } from "../helpers/migrations.js";
14
15
  import { CanisterConfig, Config } from "../types.js";
15
16
  import { CustomSection, getWasmBindings } from "../wasm.js";
16
17
  import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
@@ -87,6 +88,12 @@ export async function build(
87
88
  };
88
89
  process.on("exit", exitCleanup);
89
90
 
91
+ const migration = await prepareMigrationArgs(
92
+ canister.migrations,
93
+ canisterName,
94
+ "build",
95
+ options.verbose,
96
+ );
90
97
  try {
91
98
  let args = [
92
99
  "-c",
@@ -97,6 +104,7 @@ export async function build(
97
104
  motokoPath,
98
105
  ...(await sourcesArgs()).flat(),
99
106
  ...getGlobalMocArgs(config),
107
+ ...migration.migrationArgs,
100
108
  ];
101
109
  args.push(
102
110
  ...collectExtraArgs(config, canister, canisterName, options.extraArgs),
@@ -199,6 +207,7 @@ export async function build(
199
207
  );
200
208
  }
201
209
  } finally {
210
+ await migration.cleanup();
202
211
  process.removeListener("exit", exitCleanup);
203
212
  try {
204
213
  await release?.();
@@ -238,7 +247,7 @@ function collectExtraArgs(
238
247
  args.push(...config.build.args);
239
248
  }
240
249
  if (canister.args) {
241
- validateCanisterArgs(canister, canisterName);
250
+ validateCanisterArgs(canister, canisterName, config);
242
251
  args.push(...canister.args);
243
252
  }
244
253
  if (extraArgs) {
@@ -4,6 +4,7 @@ import { rm } from "node:fs/promises";
4
4
  import chalk from "chalk";
5
5
  import { execa } from "execa";
6
6
  import { cliError } from "../error.js";
7
+ import { prepareMigrationArgs } from "../helpers/migrations.js";
7
8
  import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
8
9
  import { CanisterConfig } from "../types.js";
9
10
  import {
@@ -70,17 +71,28 @@ export async function checkStable(
70
71
  cliError(`No main file specified for canister '${name}' in mops.toml`);
71
72
  }
72
73
 
73
- validateCanisterArgs(canister, name);
74
+ validateCanisterArgs(canister, name, config);
74
75
 
75
- await runStableCheck({
76
- oldFile,
77
- canisterMain: resolveConfigPath(canister.main),
78
- canisterName: name,
79
- mocPath,
80
- globalMocArgs,
81
- canisterArgs: canister.args ?? [],
82
- options,
83
- });
76
+ const migration = await prepareMigrationArgs(
77
+ canister.migrations,
78
+ name,
79
+ "check",
80
+ options.verbose,
81
+ );
82
+ try {
83
+ await runStableCheck({
84
+ oldFile,
85
+ canisterMain: resolveConfigPath(canister.main),
86
+ canisterName: name,
87
+ mocPath,
88
+ globalMocArgs,
89
+ canisterArgs: [...migration.migrationArgs, ...(canister.args ?? [])],
90
+ options,
91
+ hasMigrations: !!canister.migrations,
92
+ });
93
+ } finally {
94
+ await migration.cleanup();
95
+ }
84
96
  return;
85
97
  }
86
98
 
@@ -95,7 +107,7 @@ export async function checkStable(
95
107
  cliError(`No main file specified for canister '${name}' in mops.toml`);
96
108
  }
97
109
 
98
- validateCanisterArgs(canister, name);
110
+ validateCanisterArgs(canister, name, config);
99
111
  const stablePath = resolveStablePath(canister, name, {
100
112
  required: !!canisterNames,
101
113
  });
@@ -103,16 +115,27 @@ export async function checkStable(
103
115
  continue;
104
116
  }
105
117
 
106
- await runStableCheck({
107
- oldFile: stablePath,
108
- canisterMain: resolveConfigPath(canister.main),
109
- canisterName: name,
110
- mocPath,
111
- globalMocArgs,
112
- canisterArgs: canister.args ?? [],
113
- sources,
114
- options,
115
- });
118
+ const migration = await prepareMigrationArgs(
119
+ canister.migrations,
120
+ name,
121
+ "check",
122
+ options.verbose,
123
+ );
124
+ try {
125
+ await runStableCheck({
126
+ oldFile: stablePath,
127
+ canisterMain: resolveConfigPath(canister.main),
128
+ canisterName: name,
129
+ mocPath,
130
+ globalMocArgs,
131
+ canisterArgs: [...migration.migrationArgs, ...(canister.args ?? [])],
132
+ sources,
133
+ options,
134
+ hasMigrations: !!canister.migrations,
135
+ });
136
+ } finally {
137
+ await migration.cleanup();
138
+ }
116
139
  checked++;
117
140
  }
118
141
 
@@ -136,6 +159,7 @@ export interface RunStableCheckParams {
136
159
  canisterArgs: string[];
137
160
  sources?: string[];
138
161
  options?: Partial<CheckStableOptions>;
162
+ hasMigrations?: boolean;
139
163
  }
140
164
 
141
165
  export async function runStableCheck(
@@ -204,6 +228,13 @@ export async function runStableCheck(
204
228
  if (result.stderr) {
205
229
  console.error(result.stderr);
206
230
  }
231
+ if (params.hasMigrations) {
232
+ console.error(
233
+ chalk.yellow(
234
+ "Hint: You may need a migration. Run `mops migrate new <Name>` to create one.",
235
+ ),
236
+ );
237
+ }
207
238
  cliError(
208
239
  `✗ Stable compatibility check failed for canister '${canisterName}'`,
209
240
  );
package/commands/check.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  resolveCanisterConfigs,
17
17
  validateCanisterArgs,
18
18
  } from "../helpers/resolve-canisters.js";
19
+ import { prepareMigrationArgs } from "../helpers/migrations.js";
19
20
  import { CanisterConfig, Config } from "../types.js";
20
21
  import { resolveStablePath, runStableCheck } from "./check-stable.js";
21
22
  import { sourcesArgs } from "./sources.js";
@@ -144,70 +145,82 @@ async function checkCanisters(
144
145
  );
145
146
  }
146
147
 
147
- validateCanisterArgs(canister, canisterName);
148
+ validateCanisterArgs(canister, canisterName, config);
148
149
  const motokoPath = resolveConfigPath(canister.main);
149
150
 
150
- const mocArgs = [
151
- "--check",
152
- ...(allLibs ? ["--all-libs"] : []),
153
- ...sources,
154
- ...globalMocArgs,
155
- ...(canister.args ?? []),
156
- ...(options.extraArgs ?? []),
157
- ];
151
+ const migration = await prepareMigrationArgs(
152
+ canister.migrations,
153
+ canisterName,
154
+ "check",
155
+ options.verbose,
156
+ );
157
+ try {
158
+ const mocArgs = [
159
+ "--check",
160
+ ...(allLibs ? ["--all-libs"] : []),
161
+ ...sources,
162
+ ...globalMocArgs,
163
+ ...migration.migrationArgs,
164
+ ...(canister.args ?? []),
165
+ ...(options.extraArgs ?? []),
166
+ ];
158
167
 
159
- if (options.fix) {
160
- if (options.verbose) {
161
- console.log(
162
- chalk.blue("check"),
163
- chalk.gray(`Attempting to fix ${canisterName}`),
164
- );
168
+ if (options.fix) {
169
+ if (options.verbose) {
170
+ console.log(
171
+ chalk.blue("check"),
172
+ chalk.gray(`Attempting to fix ${canisterName}`),
173
+ );
174
+ }
175
+
176
+ const fixResult = await autofixMotoko(mocPath, [motokoPath], mocArgs);
177
+ logAutofixResult(fixResult, options.verbose);
165
178
  }
166
179
 
167
- const fixResult = await autofixMotoko(mocPath, [motokoPath], mocArgs);
168
- logAutofixResult(fixResult, options.verbose);
169
- }
180
+ try {
181
+ const args = [motokoPath, ...mocArgs];
182
+ if (options.verbose) {
183
+ console.log(
184
+ chalk.blue("check"),
185
+ chalk.gray(`Checking canister ${canisterName}:`),
186
+ );
187
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
188
+ }
170
189
 
171
- try {
172
- const args = [motokoPath, ...mocArgs];
173
- if (options.verbose) {
174
- console.log(
175
- chalk.blue("check"),
176
- chalk.gray(`Checking canister ${canisterName}:`),
177
- );
178
- console.log(chalk.gray(mocPath, JSON.stringify(args)));
179
- }
190
+ const result = await execa(mocPath, args, {
191
+ stdio: "inherit",
192
+ reject: false,
193
+ });
180
194
 
181
- const result = await execa(mocPath, args, {
182
- stdio: "inherit",
183
- reject: false,
184
- });
195
+ if (result.exitCode !== 0) {
196
+ cliError(
197
+ `✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`,
198
+ );
199
+ }
185
200
 
186
- if (result.exitCode !== 0) {
201
+ console.log(chalk.green(`✓ ${canisterName}`));
202
+ } catch (err: any) {
187
203
  cliError(
188
- `✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`,
204
+ `Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`,
189
205
  );
190
206
  }
191
207
 
192
- console.log(chalk.green(`✓ ${canisterName}`));
193
- } catch (err: any) {
194
- cliError(
195
- `Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`,
196
- );
197
- }
198
-
199
- const stablePath = resolveStablePath(canister, canisterName);
200
- if (stablePath) {
201
- await runStableCheck({
202
- oldFile: stablePath,
203
- canisterMain: motokoPath,
204
- canisterName,
205
- mocPath,
206
- globalMocArgs,
207
- canisterArgs: canister.args ?? [],
208
- sources,
209
- options: { verbose: options.verbose, extraArgs: options.extraArgs },
210
- });
208
+ const stablePath = resolveStablePath(canister, canisterName);
209
+ if (stablePath) {
210
+ await runStableCheck({
211
+ oldFile: stablePath,
212
+ canisterMain: motokoPath,
213
+ canisterName,
214
+ mocPath,
215
+ globalMocArgs,
216
+ canisterArgs: [...migration.migrationArgs, ...(canister.args ?? [])],
217
+ sources,
218
+ options: { verbose: options.verbose, extraArgs: options.extraArgs },
219
+ hasMigrations: !!canister.migrations,
220
+ });
221
+ }
222
+ } finally {
223
+ await migration.cleanup();
211
224
  }
212
225
  }
213
226
  }
package/commands/init.ts CHANGED
@@ -282,16 +282,29 @@ async function applyInit({
282
282
  await template("github-workflow:mops-test");
283
283
  }
284
284
 
285
- // add .mops to .gitignore
285
+ // add mops-managed paths to .gitignore
286
286
  {
287
287
  let gitignore = path.join(process.cwd(), ".gitignore");
288
288
  let gitignoreData = existsSync(gitignore)
289
289
  ? readFileSync(gitignore).toString()
290
290
  : "";
291
- let lf = gitignoreData.endsWith("\n") ? "\n" : "";
291
+ const additions: string[] = [];
292
292
  if (!gitignoreData.includes(".mops")) {
293
- writeFileSync(gitignore, `${gitignoreData}\n.mops${lf}`.trimStart());
294
- console.log(chalk.green("Added"), ".mops to .gitignore");
293
+ additions.push(".mops");
294
+ }
295
+ if (!gitignoreData.includes(".migrations-")) {
296
+ additions.push(".migrations-*/");
297
+ }
298
+ if (additions.length > 0) {
299
+ let lf = gitignoreData.endsWith("\n") ? "\n" : "";
300
+ writeFileSync(
301
+ gitignore,
302
+ `${gitignoreData}\n${additions.join("\n")}${lf}`.trimStart(),
303
+ );
304
+ console.log(
305
+ chalk.green("Added"),
306
+ `${additions.join(", ")} to .gitignore`,
307
+ );
295
308
  }
296
309
  }
297
310
 
@@ -0,0 +1,165 @@
1
+ import { existsSync, mkdirSync, renameSync } from "node:fs";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import chalk from "chalk";
5
+ import { cliError } from "../error.js";
6
+ import {
7
+ getNextMigrationFile,
8
+ validateMigrationsConfig,
9
+ validateNextMigrationOrder,
10
+ } from "../helpers/migrations.js";
11
+ import { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
12
+ import { readConfig, resolveConfigPath } from "../mops.js";
13
+ import { CanisterConfig } from "../types.js";
14
+
15
+ function resolveMigrationCanister(canisterName?: string): {
16
+ name: string;
17
+ canister: CanisterConfig;
18
+ } {
19
+ const config = readConfig();
20
+ const canisters = resolveCanisterConfigs(config);
21
+ const withMigrations = Object.entries(canisters).filter(
22
+ ([, c]) => c.migrations,
23
+ );
24
+
25
+ if (withMigrations.length === 0) {
26
+ cliError(
27
+ "No canisters with [migrations] config found in mops.toml.\n" +
28
+ "Add a [canisters.<name>.migrations] section first:\n\n" +
29
+ " [canisters.backend.migrations]\n" +
30
+ ' chain = "migrations"\n' +
31
+ ' next = "next-migration" # required for migrate new/freeze',
32
+ );
33
+ }
34
+
35
+ if (canisterName) {
36
+ const canister = canisters[canisterName];
37
+ if (!canister) {
38
+ cliError(
39
+ `Canister '${canisterName}' not found in mops.toml. Available: ${Object.keys(canisters).join(", ")}`,
40
+ );
41
+ }
42
+ if (!canister.migrations) {
43
+ cliError(
44
+ `Canister '${canisterName}' has no [canisters.${canisterName}.migrations] config in mops.toml`,
45
+ );
46
+ }
47
+ return { name: canisterName, canister };
48
+ }
49
+
50
+ if (withMigrations.length > 1) {
51
+ cliError(
52
+ `Multiple canisters with [migrations] config. Please specify one: ${withMigrations.map(([n]) => n).join(", ")}`,
53
+ );
54
+ }
55
+
56
+ return { name: withMigrations[0]![0], canister: withMigrations[0]![1] };
57
+ }
58
+
59
+ const VALID_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
60
+
61
+ function generateTimestamp(): string {
62
+ const now = new Date();
63
+ const pad = (n: number) => String(n).padStart(2, "0");
64
+ return (
65
+ `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}` +
66
+ `_${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`
67
+ );
68
+ }
69
+
70
+ const MIGRATION_TEMPLATE = `module {
71
+ public func migration(old : {}) : {} {
72
+ {}
73
+ }
74
+ }
75
+ `;
76
+
77
+ export async function migrateNew(
78
+ name: string,
79
+ canisterName?: string,
80
+ ): Promise<void> {
81
+ if (!VALID_NAME_RE.test(name)) {
82
+ cliError(
83
+ `Invalid migration name: "${name}"\n` +
84
+ "Name must start with a letter and contain only letters, digits, and underscores.",
85
+ );
86
+ }
87
+
88
+ const { name: resolvedName, canister } =
89
+ resolveMigrationCanister(canisterName);
90
+ const migrations = canister.migrations!;
91
+ validateMigrationsConfig(migrations, resolvedName);
92
+
93
+ if (!migrations.next) {
94
+ cliError(
95
+ `[canisters.${resolvedName}.migrations] is missing the "next" field.\n` +
96
+ 'Add next = "next-migration" to use `mops migrate new/freeze`.',
97
+ );
98
+ }
99
+
100
+ const chainDir = resolveConfigPath(migrations.chain);
101
+ const nextDir = resolveConfigPath(migrations.next);
102
+
103
+ const existingNext = existsSync(nextDir)
104
+ ? getNextMigrationFile(nextDir)
105
+ : null;
106
+ if (existingNext) {
107
+ cliError(
108
+ `A next migration already exists: ${existingNext}\n` +
109
+ "Freeze it first with `mops migrate freeze`.",
110
+ );
111
+ }
112
+
113
+ const timestamp = generateTimestamp();
114
+ const fileName = `${timestamp}_${name}.mo`;
115
+
116
+ validateNextMigrationOrder(chainDir, fileName);
117
+
118
+ if (!existsSync(chainDir)) {
119
+ mkdirSync(chainDir, { recursive: true });
120
+ }
121
+ if (!existsSync(nextDir)) {
122
+ mkdirSync(nextDir, { recursive: true });
123
+ }
124
+
125
+ const filePath = join(nextDir, fileName);
126
+ await writeFile(filePath, MIGRATION_TEMPLATE);
127
+
128
+ console.log(chalk.green(`✓ Created migration: ${filePath}`));
129
+ }
130
+
131
+ export async function migrateFreeze(canisterName?: string): Promise<void> {
132
+ const { name: resolvedName, canister } =
133
+ resolveMigrationCanister(canisterName);
134
+ const migrations = canister.migrations!;
135
+ validateMigrationsConfig(migrations, resolvedName);
136
+
137
+ if (!migrations.next) {
138
+ cliError(
139
+ `[canisters.${resolvedName}.migrations] is missing the "next" field.\n` +
140
+ 'Add next = "next-migration" to use `mops migrate new/freeze`.',
141
+ );
142
+ }
143
+
144
+ const chainDir = resolveConfigPath(migrations.chain);
145
+ const nextDir = resolveConfigPath(migrations.next);
146
+
147
+ const nextFile = existsSync(nextDir) ? getNextMigrationFile(nextDir) : null;
148
+ if (!nextFile) {
149
+ cliError(
150
+ "No next migration to freeze. Create one with `mops migrate new <Name>`.",
151
+ );
152
+ }
153
+
154
+ validateNextMigrationOrder(chainDir, nextFile);
155
+
156
+ if (!existsSync(chainDir)) {
157
+ mkdirSync(chainDir, { recursive: true });
158
+ }
159
+
160
+ const src = join(nextDir, nextFile);
161
+ const dest = join(chainDir, nextFile);
162
+ renameSync(src, dest);
163
+
164
+ console.log(chalk.green(`✓ Frozen migration: ${nextFile} → ${chainDir}/`));
165
+ }
@@ -42,6 +42,11 @@ type StreamingCallbackResponse =
42
42
  };
43
43
  type StreamingCallback = func (StreamingToken) ->
44
44
  (opt StreamingCallbackResponse) query;
45
+ type StructureStats =
46
+ record {
47
+ bytes: nat;
48
+ count: nat;
49
+ };
45
50
  type StorageStats =
46
51
  record {
47
52
  cyclesBalance: nat;
@@ -272,6 +277,38 @@ type PackageChanges =
272
277
  prevDocsCoverage: float64;
273
278
  tests: TestsChanges;
274
279
  };
280
+ type MemoryStats =
281
+ record {
282
+ dailySnapshots: StructureStats;
283
+ dailySnapshotsByPackageId: StructureStats;
284
+ dailySnapshotsByPackageName: StructureStats;
285
+ dailyTempRecords: StructureStats;
286
+ downloadsByPackageId: StructureStats;
287
+ downloadsByPackageName: StructureStats;
288
+ fileIdsByPackage: StructureStats;
289
+ hashByFileId: StructureStats;
290
+ highestConfigs: StructureStats;
291
+ maintainersByPackage: StructureStats;
292
+ ownersByPackage: StructureStats;
293
+ packageBenchmarks: StructureStats;
294
+ packageConfigs: StructureStats;
295
+ packageDocsCoverage: StructureStats;
296
+ packageFileStats: StructureStats;
297
+ packageNotes: StructureStats;
298
+ packagePublications: StructureStats;
299
+ packageTestStats: StructureStats;
300
+ packageVersions: StructureStats;
301
+ rtsHeapSize: nat;
302
+ rtsMemorySize: nat;
303
+ names: StructureStats;
304
+ storageByFileId: StructureStats;
305
+ storages: StructureStats;
306
+ users: StructureStats;
307
+ weeklySnapshots: StructureStats;
308
+ weeklySnapshotsByPackageId: StructureStats;
309
+ weeklySnapshotsByPackageName: StructureStats;
310
+ weeklyTempRecords: StructureStats;
311
+ };
275
312
  type Main =
276
313
  service {
277
314
  addMaintainer: (packageName: PackageName, newMaintainer: principal) ->
@@ -311,6 +348,7 @@ type Main =
311
348
  SemverPart;
312
349
  }) -> (Result_6) query;
313
350
  getHighestVersion: (name: PackageName) -> (Result_5) query;
351
+ getMemoryStats: () -> (MemoryStats) query;
314
352
  getMostDownloadedPackages: () -> (vec PackageSummary) query;
315
353
  getMostDownloadedPackagesIn7Days: () -> (vec PackageSummary) query;
316
354
  getNewPackages: () -> (vec PackageSummary) query;
@@ -45,6 +45,41 @@ export interface HttpRequestResult {
45
45
  'body' : Uint8Array | number[],
46
46
  'headers' : Array<HttpHeader>,
47
47
  }
48
+ export interface StructureStats {
49
+ 'count' : bigint,
50
+ 'bytes' : bigint,
51
+ }
52
+ export interface MemoryStats {
53
+ 'rtsHeapSize' : bigint,
54
+ 'rtsMemorySize' : bigint,
55
+ 'packageVersions' : StructureStats,
56
+ 'packageConfigs' : StructureStats,
57
+ 'highestConfigs' : StructureStats,
58
+ 'packagePublications' : StructureStats,
59
+ 'ownersByPackage' : StructureStats,
60
+ 'maintainersByPackage' : StructureStats,
61
+ 'fileIdsByPackage' : StructureStats,
62
+ 'hashByFileId' : StructureStats,
63
+ 'packageFileStats' : StructureStats,
64
+ 'packageTestStats' : StructureStats,
65
+ 'packageBenchmarks' : StructureStats,
66
+ 'packageNotes' : StructureStats,
67
+ 'packageDocsCoverage' : StructureStats,
68
+ 'downloadsByPackageName' : StructureStats,
69
+ 'downloadsByPackageId' : StructureStats,
70
+ 'dailySnapshots' : StructureStats,
71
+ 'weeklySnapshots' : StructureStats,
72
+ 'dailySnapshotsByPackageName' : StructureStats,
73
+ 'dailySnapshotsByPackageId' : StructureStats,
74
+ 'weeklySnapshotsByPackageName' : StructureStats,
75
+ 'weeklySnapshotsByPackageId' : StructureStats,
76
+ 'dailyTempRecords' : StructureStats,
77
+ 'weeklyTempRecords' : StructureStats,
78
+ 'storages' : StructureStats,
79
+ 'storageByFileId' : StructureStats,
80
+ 'users' : StructureStats,
81
+ 'names' : StructureStats,
82
+ }
48
83
  export interface Main {
49
84
  'addMaintainer' : ActorMethod<[PackageName, Principal], Result_3>,
50
85
  'addOwner' : ActorMethod<[PackageName, Principal], Result_3>,
@@ -77,6 +112,7 @@ export interface Main {
77
112
  Result_6
78
113
  >,
79
114
  'getHighestVersion' : ActorMethod<[PackageName], Result_5>,
115
+ 'getMemoryStats' : ActorMethod<[], MemoryStats, 'query'>,
80
116
  'getMostDownloadedPackages' : ActorMethod<[], Array<PackageSummary>>,
81
117
  'getMostDownloadedPackagesIn7Days' : ActorMethod<[], Array<PackageSummary>>,
82
118
  'getNewPackages' : ActorMethod<[], Array<PackageSummary>>,