ic-mops 2.9.0 → 2.11.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 +13 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +34 -10
- package/commands/build.ts +16 -22
- package/commands/check-stable.ts +141 -17
- package/commands/check.ts +195 -101
- 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 +28 -10
- package/dist/commands/build.js +7 -13
- package/dist/commands/check-stable.d.ts +8 -1
- package/dist/commands/check-stable.js +101 -19
- package/dist/commands/check.d.ts +1 -1
- package/dist/commands/check.js +136 -78
- 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 +109 -0
- package/dist/helpers/resolve-canisters.d.ts +3 -1
- package/dist/helpers/resolve-canisters.js +34 -5
- package/dist/package.json +1 -1
- package/dist/tests/check-stable.test.js +18 -0
- package/dist/tests/check.test.js +23 -5
- package/dist/tests/migrate.test.d.ts +1 -0
- package/dist/tests/migrate.test.js +160 -0
- package/dist/types.d.ts +7 -0
- package/helpers/migrations.ts +166 -0
- package/helpers/resolve-canisters.ts +53 -5
- package/package.json +1 -1
- package/tests/__snapshots__/check.test.ts.snap +2 -2
- package/tests/__snapshots__/migrate.test.ts.snap +119 -0
- package/tests/check/canisters-canister-args/Warning.mo +5 -0
- package/tests/check/canisters-canister-args/mops.toml +9 -0
- package/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo +8 -0
- package/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo +9 -0
- package/tests/check-stable/canister-args/mops.toml +9 -0
- package/tests/check-stable/canister-args/old.most +8 -0
- package/tests/check-stable/canister-args/src/main.mo +11 -0
- package/tests/check-stable.test.ts +21 -0
- package/tests/check.test.ts +26 -5
- 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 +5 -0
- package/tests/migrate/with-next/migrations/20250201_000000_AddName.mo +5 -0
- package/tests/migrate/with-next/migrations/20250301_000000_AddEmail.mo +9 -0
- package/tests/migrate/with-next/mops.toml +15 -0
- package/tests/migrate/with-next/next-migration/20250401_000000_RenameId.mo +9 -0
- package/tests/migrate/with-next/src/main.mo +11 -0
- package/tests/migrate.test.ts +228 -0
- package/types.ts +8 -0
package/commands/check.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
2
|
import chalk from "chalk";
|
|
4
3
|
import { execa } from "execa";
|
|
5
4
|
import { cliError } from "../error.js";
|
|
@@ -9,13 +8,17 @@ import {
|
|
|
9
8
|
readConfig,
|
|
10
9
|
resolveConfigPath,
|
|
11
10
|
} from "../mops.js";
|
|
12
|
-
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
11
|
+
import { AutofixResult, autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
13
12
|
import { getMocSemVer } from "../helpers/get-moc-version.js";
|
|
14
13
|
import {
|
|
14
|
+
filterCanisters,
|
|
15
|
+
looksLikeFile,
|
|
15
16
|
resolveCanisterConfigs,
|
|
16
|
-
|
|
17
|
+
validateCanisterArgs,
|
|
17
18
|
} from "../helpers/resolve-canisters.js";
|
|
18
|
-
import {
|
|
19
|
+
import { prepareMigrationArgs } from "../helpers/migrations.js";
|
|
20
|
+
import { CanisterConfig, Config } from "../types.js";
|
|
21
|
+
import { resolveStablePath, runStableCheck } from "./check-stable.js";
|
|
19
22
|
import { sourcesArgs } from "./sources.js";
|
|
20
23
|
import { toolchain } from "./toolchain/index.js";
|
|
21
24
|
import { collectLintRules, lint } from "./lint.js";
|
|
@@ -33,52 +36,209 @@ export interface CheckOptions {
|
|
|
33
36
|
extraArgs: string[];
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
function checkAllLibsSupport(verbose?: boolean): boolean {
|
|
40
|
+
const allLibs = supportsAllLibsFlag();
|
|
41
|
+
if (!allLibs) {
|
|
42
|
+
console.log(
|
|
43
|
+
chalk.yellow(
|
|
44
|
+
`moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`,
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
} else if (verbose) {
|
|
48
|
+
console.log(
|
|
49
|
+
chalk.blue("check"),
|
|
50
|
+
chalk.gray("Using --all-libs for richer diagnostics"),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return allLibs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logAutofixResult(
|
|
57
|
+
fixResult: AutofixResult | null,
|
|
58
|
+
verbose?: boolean,
|
|
59
|
+
): void {
|
|
60
|
+
if (fixResult) {
|
|
61
|
+
for (const [file, codes] of fixResult.fixedFiles) {
|
|
62
|
+
const unique = [...new Set(codes)].sort();
|
|
63
|
+
const n = codes.length;
|
|
64
|
+
const rel = path.relative(process.cwd(), file);
|
|
65
|
+
console.log(
|
|
66
|
+
chalk.green(
|
|
67
|
+
`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`,
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const fileCount = fixResult.fixedFiles.size;
|
|
72
|
+
console.log(
|
|
73
|
+
chalk.green(
|
|
74
|
+
`\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
} else if (verbose) {
|
|
78
|
+
console.log(chalk.yellow("No fixes were needed"));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
36
82
|
export async function check(
|
|
37
|
-
|
|
83
|
+
args: string[],
|
|
38
84
|
options: Partial<CheckOptions> = {},
|
|
39
85
|
): Promise<void> {
|
|
40
|
-
const explicitFiles = Array.isArray(files) ? files : files ? [files] : [];
|
|
41
|
-
let fileList = [...explicitFiles];
|
|
42
|
-
|
|
43
86
|
const config = readConfig();
|
|
87
|
+
const canisters = resolveCanisterConfigs(config);
|
|
88
|
+
const hasCanisters = Object.keys(canisters).length > 0;
|
|
89
|
+
const fileArgs = args.filter(looksLikeFile);
|
|
90
|
+
const nonFileArgs = args.filter((a) => !looksLikeFile(a));
|
|
91
|
+
const isFileMode = fileArgs.length > 0;
|
|
44
92
|
|
|
45
|
-
if (
|
|
46
|
-
fileList = resolveCanisterEntrypoints(config).map(resolveConfigPath);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (fileList.length === 0) {
|
|
93
|
+
if (isFileMode && nonFileArgs.length > 0) {
|
|
50
94
|
cliError(
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"Or define canisters in mops.toml:\n\n" +
|
|
54
|
-
" [canisters.backend]\n" +
|
|
55
|
-
' main = "src/main.mo"',
|
|
95
|
+
`Cannot mix file paths and canister names: ${args.join(", ")}\n` +
|
|
96
|
+
"Pass either file paths (e.g. mops check src/main.mo) or canister names (e.g. mops check backend)",
|
|
56
97
|
);
|
|
57
98
|
}
|
|
99
|
+
|
|
100
|
+
if (isFileMode) {
|
|
101
|
+
await checkFiles(config, fileArgs, options);
|
|
102
|
+
} else {
|
|
103
|
+
if (!hasCanisters) {
|
|
104
|
+
cliError(
|
|
105
|
+
"No canisters defined in mops.toml.\n" +
|
|
106
|
+
"Either pass files: mops check <files...>\n" +
|
|
107
|
+
"Or define canisters in mops.toml:\n\n" +
|
|
108
|
+
" [canisters.backend]\n" +
|
|
109
|
+
' main = "src/main.mo"',
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const canisterNames = args.length > 0 ? args : undefined;
|
|
114
|
+
const filtered = filterCanisters(canisters, canisterNames);
|
|
115
|
+
await checkCanisters(config, filtered, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (config.toolchain?.lintoko) {
|
|
119
|
+
const rootDir = getRootDir();
|
|
120
|
+
const lintRules = await collectLintRules(config, rootDir);
|
|
121
|
+
const lintFiles = isFileMode ? fileArgs : undefined;
|
|
122
|
+
await lint(undefined, {
|
|
123
|
+
verbose: options.verbose,
|
|
124
|
+
fix: options.fix,
|
|
125
|
+
rules: lintRules,
|
|
126
|
+
files: lintFiles,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function checkCanisters(
|
|
132
|
+
config: Config,
|
|
133
|
+
canisters: Record<string, CanisterConfig>,
|
|
134
|
+
options: Partial<CheckOptions>,
|
|
135
|
+
): Promise<void> {
|
|
58
136
|
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
59
|
-
const sources = await sourcesArgs();
|
|
137
|
+
const sources = (await sourcesArgs()).flat();
|
|
60
138
|
const globalMocArgs = getGlobalMocArgs(config);
|
|
139
|
+
const allLibs = checkAllLibsSupport(options.verbose);
|
|
61
140
|
|
|
62
|
-
|
|
63
|
-
|
|
141
|
+
for (const [canisterName, canister] of Object.entries(canisters)) {
|
|
142
|
+
if (!canister.main) {
|
|
143
|
+
cliError(
|
|
144
|
+
`No main file specified for canister '${canisterName}' in mops.toml`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
64
147
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
chalk.blue("check"),
|
|
74
|
-
chalk.gray("Using --all-libs for richer diagnostics"),
|
|
148
|
+
validateCanisterArgs(canister, canisterName, config);
|
|
149
|
+
const motokoPath = resolveConfigPath(canister.main);
|
|
150
|
+
|
|
151
|
+
const migration = await prepareMigrationArgs(
|
|
152
|
+
canister.migrations,
|
|
153
|
+
canisterName,
|
|
154
|
+
"check",
|
|
155
|
+
options.verbose,
|
|
75
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
|
+
];
|
|
167
|
+
|
|
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);
|
|
178
|
+
}
|
|
179
|
+
|
|
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
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = await execa(mocPath, args, {
|
|
191
|
+
stdio: "inherit",
|
|
192
|
+
reject: false,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (result.exitCode !== 0) {
|
|
196
|
+
cliError(
|
|
197
|
+
`✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(chalk.green(`✓ ${canisterName}`));
|
|
202
|
+
} catch (err: any) {
|
|
203
|
+
cliError(
|
|
204
|
+
`Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
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();
|
|
224
|
+
}
|
|
76
225
|
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function checkFiles(
|
|
229
|
+
config: Config,
|
|
230
|
+
files: string[],
|
|
231
|
+
options: Partial<CheckOptions>,
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
234
|
+
const sources = (await sourcesArgs()).flat();
|
|
235
|
+
const globalMocArgs = getGlobalMocArgs(config);
|
|
236
|
+
const allLibs = checkAllLibsSupport(options.verbose);
|
|
77
237
|
|
|
78
238
|
const mocArgs = [
|
|
79
239
|
"--check",
|
|
80
240
|
...(allLibs ? ["--all-libs"] : []),
|
|
81
|
-
...sources
|
|
241
|
+
...sources,
|
|
82
242
|
...globalMocArgs,
|
|
83
243
|
...(options.extraArgs ?? []),
|
|
84
244
|
];
|
|
@@ -88,32 +248,11 @@ export async function check(
|
|
|
88
248
|
console.log(chalk.blue("check"), chalk.gray("Attempting to fix files"));
|
|
89
249
|
}
|
|
90
250
|
|
|
91
|
-
const fixResult = await autofixMotoko(mocPath,
|
|
92
|
-
|
|
93
|
-
for (const [file, codes] of fixResult.fixedFiles) {
|
|
94
|
-
const unique = [...new Set(codes)].sort();
|
|
95
|
-
const n = codes.length;
|
|
96
|
-
const rel = path.relative(process.cwd(), file);
|
|
97
|
-
console.log(
|
|
98
|
-
chalk.green(
|
|
99
|
-
`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`,
|
|
100
|
-
),
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
const fileCount = fixResult.fixedFiles.size;
|
|
104
|
-
console.log(
|
|
105
|
-
chalk.green(
|
|
106
|
-
`\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`,
|
|
107
|
-
),
|
|
108
|
-
);
|
|
109
|
-
} else {
|
|
110
|
-
if (options.verbose) {
|
|
111
|
-
console.log(chalk.yellow("No fixes were needed"));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
251
|
+
const fixResult = await autofixMotoko(mocPath, files, mocArgs);
|
|
252
|
+
logAutofixResult(fixResult, options.verbose);
|
|
114
253
|
}
|
|
115
254
|
|
|
116
|
-
for (const file of
|
|
255
|
+
for (const file of files) {
|
|
117
256
|
try {
|
|
118
257
|
const args = [file, ...mocArgs];
|
|
119
258
|
if (options.verbose) {
|
|
@@ -139,49 +278,4 @@ export async function check(
|
|
|
139
278
|
);
|
|
140
279
|
}
|
|
141
280
|
}
|
|
142
|
-
|
|
143
|
-
const canisters = resolveCanisterConfigs(config);
|
|
144
|
-
for (const [name, canister] of Object.entries(canisters)) {
|
|
145
|
-
const stableConfig = canister["check-stable"];
|
|
146
|
-
if (!stableConfig) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!canister.main) {
|
|
151
|
-
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const stablePath = resolveConfigPath(stableConfig.path);
|
|
155
|
-
if (!existsSync(stablePath)) {
|
|
156
|
-
if (stableConfig.skipIfMissing) {
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
cliError(
|
|
160
|
-
`Deployed file not found: ${stablePath} (canister '${name}')\n` +
|
|
161
|
-
"Set skipIfMissing = true in [canisters." +
|
|
162
|
-
name +
|
|
163
|
-
".check-stable] to skip this check when the file is missing.",
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
await runStableCheck({
|
|
168
|
-
oldFile: stablePath,
|
|
169
|
-
canisterMain: resolveConfigPath(canister.main),
|
|
170
|
-
canisterName: name,
|
|
171
|
-
mocPath,
|
|
172
|
-
globalMocArgs,
|
|
173
|
-
options: { verbose: options.verbose, extraArgs: options.extraArgs },
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (config.toolchain?.lintoko) {
|
|
178
|
-
const rootDir = getRootDir();
|
|
179
|
-
const lintRules = await collectLintRules(config, rootDir);
|
|
180
|
-
await lint(undefined, {
|
|
181
|
-
verbose: options.verbose,
|
|
182
|
-
fix: options.fix,
|
|
183
|
-
rules: lintRules,
|
|
184
|
-
files: explicitFiles.length > 0 ? explicitFiles : undefined,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
281
|
}
|
|
@@ -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>>,
|
|
@@ -196,6 +196,41 @@ export const idlFactory = ({ IDL }) => {
|
|
|
196
196
|
'cyclesBalance' : IDL.Nat,
|
|
197
197
|
'memorySize' : IDL.Nat,
|
|
198
198
|
});
|
|
199
|
+
const StructureStats = IDL.Record({
|
|
200
|
+
'count' : IDL.Nat,
|
|
201
|
+
'bytes' : IDL.Nat,
|
|
202
|
+
});
|
|
203
|
+
const MemoryStats = IDL.Record({
|
|
204
|
+
'rtsHeapSize' : IDL.Nat,
|
|
205
|
+
'rtsMemorySize' : IDL.Nat,
|
|
206
|
+
'packageVersions' : StructureStats,
|
|
207
|
+
'packageConfigs' : StructureStats,
|
|
208
|
+
'highestConfigs' : StructureStats,
|
|
209
|
+
'packagePublications' : StructureStats,
|
|
210
|
+
'ownersByPackage' : StructureStats,
|
|
211
|
+
'maintainersByPackage' : StructureStats,
|
|
212
|
+
'fileIdsByPackage' : StructureStats,
|
|
213
|
+
'hashByFileId' : StructureStats,
|
|
214
|
+
'packageFileStats' : StructureStats,
|
|
215
|
+
'packageTestStats' : StructureStats,
|
|
216
|
+
'packageBenchmarks' : StructureStats,
|
|
217
|
+
'packageNotes' : StructureStats,
|
|
218
|
+
'packageDocsCoverage' : StructureStats,
|
|
219
|
+
'downloadsByPackageName' : StructureStats,
|
|
220
|
+
'downloadsByPackageId' : StructureStats,
|
|
221
|
+
'dailySnapshots' : StructureStats,
|
|
222
|
+
'weeklySnapshots' : StructureStats,
|
|
223
|
+
'dailySnapshotsByPackageName' : StructureStats,
|
|
224
|
+
'dailySnapshotsByPackageId' : StructureStats,
|
|
225
|
+
'weeklySnapshotsByPackageName' : StructureStats,
|
|
226
|
+
'weeklySnapshotsByPackageId' : StructureStats,
|
|
227
|
+
'dailyTempRecords' : StructureStats,
|
|
228
|
+
'weeklyTempRecords' : StructureStats,
|
|
229
|
+
'storages' : StructureStats,
|
|
230
|
+
'storageByFileId' : StructureStats,
|
|
231
|
+
'users' : StructureStats,
|
|
232
|
+
'names' : StructureStats,
|
|
233
|
+
});
|
|
199
234
|
const Header = IDL.Tuple(IDL.Text, IDL.Text);
|
|
200
235
|
const Request = IDL.Record({
|
|
201
236
|
'url' : IDL.Text,
|
|
@@ -308,6 +343,7 @@ export const idlFactory = ({ IDL }) => {
|
|
|
308
343
|
['query'],
|
|
309
344
|
),
|
|
310
345
|
'getHighestVersion' : IDL.Func([PackageName], [Result_5], ['query']),
|
|
346
|
+
'getMemoryStats' : IDL.Func([], [MemoryStats], ['query']),
|
|
311
347
|
'getMostDownloadedPackages' : IDL.Func(
|
|
312
348
|
[],
|
|
313
349
|
[IDL.Vec(PackageSummary)],
|