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.
- package/CHANGELOG.md +11 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +24 -0
- package/commands/build.ts +10 -1
- package/commands/check-stable.ts +52 -21
- package/commands/check.ts +65 -52
- package/commands/init.ts +17 -4
- package/commands/migrate.ts +165 -0
- package/declarations/main/main.did +38 -0
- package/declarations/main/main.did.d.ts +36 -0
- package/declarations/main/main.did.js +36 -0
- package/dist/cli.js +18 -0
- package/dist/commands/build.js +5 -1
- package/dist/commands/check-stable.d.ts +1 -0
- package/dist/commands/check-stable.js +39 -21
- package/dist/commands/check.js +51 -42
- package/dist/commands/init.js +11 -4
- package/dist/commands/migrate.d.ts +2 -0
- package/dist/commands/migrate.js +104 -0
- package/dist/declarations/main/main.did +38 -0
- package/dist/declarations/main/main.did.d.ts +36 -0
- package/dist/declarations/main/main.did.js +36 -0
- package/dist/helpers/migrations.d.ts +10 -0
- package/dist/helpers/migrations.js +125 -0
- package/dist/helpers/resolve-canisters.d.ts +1 -1
- package/dist/helpers/resolve-canisters.js +15 -1
- package/dist/package.json +1 -1
- package/dist/tests/migrate.test.d.ts +1 -0
- package/dist/tests/migrate.test.js +181 -0
- package/dist/types.d.ts +7 -0
- package/helpers/migrations.ts +190 -0
- package/helpers/resolve-canisters.ts +17 -0
- package/package.json +1 -1
- package/tests/__snapshots__/migrate.test.ts.snap +119 -0
- package/tests/migrate/basic/deployed.most +12 -0
- package/tests/migrate/basic/migrations/20250101_000000_Init.mo +5 -0
- package/tests/migrate/basic/migrations/20250201_000000_AddName.mo +5 -0
- package/tests/migrate/basic/migrations/20250301_000000_AddEmail.mo +9 -0
- package/tests/migrate/basic/mops.toml +15 -0
- package/tests/migrate/basic/next-migration/.gitkeep +0 -0
- package/tests/migrate/basic/src/main.mo +11 -0
- package/tests/migrate/with-next/deployed.most +12 -0
- package/tests/migrate/with-next/migrations/20250101_000000_Init.mo +7 -0
- package/tests/migrate/with-next/migrations/20250201_000000_AddName.mo +7 -0
- package/tests/migrate/with-next/migrations/20250301_000000_AddEmail.mo +7 -0
- package/tests/migrate/with-next/mops.toml +15 -0
- package/tests/migrate/with-next/next-migration/20250401_000000_RenameId.mo +7 -0
- package/tests/migrate/with-next/src/main.mo +11 -0
- package/tests/migrate/with-next/types/State.mo +7 -0
- package/tests/migrate.test.ts +255 -0
- 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) {
|
package/commands/check-stable.ts
CHANGED
|
@@ -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
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
195
|
+
if (result.exitCode !== 0) {
|
|
196
|
+
cliError(
|
|
197
|
+
`✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
185
200
|
|
|
186
|
-
|
|
201
|
+
console.log(chalk.green(`✓ ${canisterName}`));
|
|
202
|
+
} catch (err: any) {
|
|
187
203
|
cliError(
|
|
188
|
-
|
|
204
|
+
`Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`,
|
|
189
205
|
);
|
|
190
206
|
}
|
|
191
207
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
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
|
-
|
|
291
|
+
const additions: string[] = [];
|
|
292
292
|
if (!gitignoreData.includes(".mops")) {
|
|
293
|
-
|
|
294
|
-
|
|
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>>,
|