ic-mops 2.8.1 → 2.9.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 +4 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +11 -1
- package/commands/build.ts +127 -92
- package/commands/info.ts +103 -0
- package/commands/lint.ts +132 -42
- package/commands/toolchain/moc.ts +5 -5
- package/dist/cli.js +9 -0
- package/dist/commands/build.js +102 -69
- package/dist/commands/info.d.ts +4 -0
- package/dist/commands/info.js +75 -0
- package/dist/commands/lint.js +84 -37
- package/dist/commands/toolchain/moc.js +5 -5
- package/dist/package.json +3 -1
- package/dist/templates/mops-publish.yml +1 -1
- package/dist/templates/mops-test.yml +1 -1
- package/dist/tests/build.test.js +17 -0
- package/dist/tests/lint.test.js +33 -0
- package/dist/types.d.ts +1 -0
- package/dist/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/dist/wasm/pkg/web/wasm_bg.wasm +0 -0
- package/package.json +3 -1
- package/templates/mops-publish.yml +1 -1
- package/templates/mops-test.yml +1 -1
- package/tests/__snapshots__/lint.test.ts.snap +163 -5
- package/tests/build.test.ts +17 -0
- package/tests/lint-extra/mops.toml +5 -0
- package/tests/lint-extra/src/Ok.mo +5 -0
- package/tests/lint-extra/src/restricted/B.mo +8 -0
- package/tests/lint-extra/src/restricted/Restricted.mo +8 -0
- package/tests/lint-extra-edge-cases/mops.toml +8 -0
- package/tests/lint-extra-edge-cases/src/Clean.mo +5 -0
- package/tests/lint-extra-example-rules/lint/migration-only/migration-only.toml +9 -0
- package/tests/lint-extra-example-rules/lint/no-types/no-types.toml +5 -0
- package/tests/lint-extra-example-rules/lint/types-only/types-only.toml +6 -0
- package/tests/lint-extra-example-rules/mops.toml +7 -0
- package/tests/lint-extra-example-rules/src/Main.mo +10 -0
- package/tests/lint-extra-example-rules/src/Migration.mo +9 -0
- package/tests/lint-extra-example-rules/src/Types.mo +10 -0
- package/tests/lint-extra-with-base/mops.toml +8 -0
- package/tests/lint-extra-with-base/src/BadBase.mo +8 -0
- package/tests/lint-extra-with-base/src/Ok.mo +5 -0
- package/tests/lint-extra-with-base/src/Restricted.mo +5 -0
- package/tests/lint-extra-with-cli-rules/empty-rules/.gitkeep +0 -0
- package/tests/lint-extra-with-cli-rules/mops.toml +5 -0
- package/tests/lint-extra-with-cli-rules/rules-b/no-bool-switch-2.toml +9 -0
- package/tests/lint-extra-with-cli-rules/src/Ok.mo +5 -0
- package/tests/lint-extra-with-cli-rules/src/Restricted.mo +8 -0
- package/tests/lint.test.ts +42 -0
- package/types.ts +1 -0
- package/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/wasm/pkg/web/wasm_bg.wasm +0 -0
package/dist/commands/build.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execa } from "execa";
|
|
|
3
3
|
import { exists } from "fs-extra";
|
|
4
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { lock, unlockSync } from "proper-lockfile";
|
|
6
7
|
import { cliError } from "../error.js";
|
|
7
8
|
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
|
|
8
9
|
import { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
|
|
@@ -46,88 +47,120 @@ export async function build(canisterNames, options) {
|
|
|
46
47
|
motokoPath = resolveConfigPath(motokoPath);
|
|
47
48
|
const wasmPath = join(outputDir, `${canisterName}.wasm`);
|
|
48
49
|
const mostPath = join(outputDir, `${canisterName}.most`);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
];
|
|
59
|
-
args.push(...collectExtraArgs(config, canister, canisterName, options.extraArgs));
|
|
60
|
-
const isPublicCandid = true; // always true for now to reduce corner cases
|
|
61
|
-
const candidVisibility = isPublicCandid ? "icp:public" : "icp:private";
|
|
62
|
-
if (isPublicCandid) {
|
|
63
|
-
args.push("--public-metadata", "candid:service");
|
|
64
|
-
args.push("--public-metadata", "candid:args");
|
|
50
|
+
// per-canister lock to prevent parallel builds of the same canister from clobbering output files
|
|
51
|
+
const lockTarget = join(outputDir, `.${canisterName}.buildlock`);
|
|
52
|
+
await writeFile(lockTarget, "", { flag: "a" });
|
|
53
|
+
let release;
|
|
54
|
+
try {
|
|
55
|
+
release = await lock(lockTarget, {
|
|
56
|
+
stale: 300_000,
|
|
57
|
+
retries: { retries: 60, minTimeout: 500, maxTimeout: 5_000 },
|
|
58
|
+
});
|
|
65
59
|
}
|
|
60
|
+
catch {
|
|
61
|
+
cliError(`Failed to acquire build lock for canister ${canisterName} — another build may be stuck`);
|
|
62
|
+
}
|
|
63
|
+
// proper-lockfile registers its own signal-exit handler, but it doesn't reliably
|
|
64
|
+
// fire on process.exit(). This manual handler covers that gap. Double-unlock is
|
|
65
|
+
// harmless (the second call throws and is caught).
|
|
66
|
+
const exitCleanup = () => {
|
|
67
|
+
try {
|
|
68
|
+
unlockSync(lockTarget);
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
};
|
|
72
|
+
process.on("exit", exitCleanup);
|
|
66
73
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
let args = [
|
|
75
|
+
"-c",
|
|
76
|
+
"--idl",
|
|
77
|
+
"--stable-types",
|
|
78
|
+
"-o",
|
|
79
|
+
wasmPath,
|
|
80
|
+
motokoPath,
|
|
81
|
+
...(await sourcesArgs()).flat(),
|
|
82
|
+
...getGlobalMocArgs(config),
|
|
83
|
+
];
|
|
84
|
+
args.push(...collectExtraArgs(config, canister, canisterName, options.extraArgs));
|
|
85
|
+
const isPublicCandid = true; // always true for now to reduce corner cases
|
|
86
|
+
const candidVisibility = isPublicCandid ? "icp:public" : "icp:private";
|
|
87
|
+
if (isPublicCandid) {
|
|
88
|
+
args.push("--public-metadata", "candid:service");
|
|
89
|
+
args.push("--public-metadata", "candid:args");
|
|
69
90
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
try {
|
|
92
|
+
if (options.verbose) {
|
|
93
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
94
|
+
}
|
|
95
|
+
const result = await execa(mocPath, args, {
|
|
96
|
+
stdio: options.verbose ? "inherit" : "pipe",
|
|
97
|
+
reject: false,
|
|
98
|
+
});
|
|
99
|
+
if (result.exitCode !== 0) {
|
|
100
|
+
if (!options.verbose) {
|
|
101
|
+
if (result.stderr) {
|
|
102
|
+
console.error(chalk.red(result.stderr));
|
|
103
|
+
}
|
|
104
|
+
if (result.stdout?.trim()) {
|
|
105
|
+
console.error(chalk.yellow("Build output:"));
|
|
106
|
+
console.error(result.stdout);
|
|
107
|
+
}
|
|
82
108
|
}
|
|
109
|
+
cliError(`Build failed for canister ${canisterName} (exit code: ${result.exitCode})`);
|
|
83
110
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
if (options.verbose && result.stdout && result.stdout.trim()) {
|
|
112
|
+
console.log(result.stdout);
|
|
113
|
+
}
|
|
114
|
+
options.verbose &&
|
|
115
|
+
console.log(chalk.gray(`Stable types written to ${mostPath}`));
|
|
116
|
+
const generatedDidPath = join(outputDir, `${canisterName}.did`);
|
|
117
|
+
const resolvedCandidPath = canister.candid
|
|
118
|
+
? resolveConfigPath(canister.candid)
|
|
119
|
+
: null;
|
|
120
|
+
if (resolvedCandidPath) {
|
|
121
|
+
try {
|
|
122
|
+
const compatible = await isCandidCompatible(generatedDidPath, resolvedCandidPath);
|
|
123
|
+
if (!compatible) {
|
|
124
|
+
cliError(`Candid compatibility check failed for canister ${canisterName}`);
|
|
125
|
+
}
|
|
126
|
+
if (options.verbose) {
|
|
127
|
+
console.log(chalk.gray(`Candid compatibility check passed for canister ${canisterName}`));
|
|
128
|
+
}
|
|
100
129
|
}
|
|
101
|
-
|
|
102
|
-
|
|
130
|
+
catch (err) {
|
|
131
|
+
cliError(`Error during Candid compatibility check for canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`);
|
|
103
132
|
}
|
|
104
133
|
}
|
|
105
|
-
|
|
106
|
-
|
|
134
|
+
options.verbose &&
|
|
135
|
+
console.log(chalk.gray(`Adding metadata to ${wasmPath}`));
|
|
136
|
+
const candidPath = resolvedCandidPath ?? generatedDidPath;
|
|
137
|
+
const candidText = await readFile(candidPath, "utf-8");
|
|
138
|
+
const customSections = [
|
|
139
|
+
{ name: `${candidVisibility} candid:service`, data: candidText },
|
|
140
|
+
];
|
|
141
|
+
if (canister.initArg) {
|
|
142
|
+
customSections.push({
|
|
143
|
+
name: `${candidVisibility} candid:args`,
|
|
144
|
+
data: canister.initArg,
|
|
145
|
+
});
|
|
107
146
|
}
|
|
147
|
+
const wasmBytes = await readFile(wasmPath);
|
|
148
|
+
const newWasm = getWasmBindings().add_custom_sections(wasmBytes, customSections);
|
|
149
|
+
await writeFile(wasmPath, newWasm);
|
|
108
150
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{ name: `${candidVisibility} candid:service`, data: candidText },
|
|
115
|
-
];
|
|
116
|
-
if (canister.initArg) {
|
|
117
|
-
customSections.push({
|
|
118
|
-
name: `${candidVisibility} candid:args`,
|
|
119
|
-
data: canister.initArg,
|
|
120
|
-
});
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.message?.includes("Build failed for canister")) {
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
cliError(`Error while compiling canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`);
|
|
121
156
|
}
|
|
122
|
-
const wasmBytes = await readFile(wasmPath);
|
|
123
|
-
const newWasm = getWasmBindings().add_custom_sections(wasmBytes, customSections);
|
|
124
|
-
await writeFile(wasmPath, newWasm);
|
|
125
157
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
finally {
|
|
159
|
+
process.removeListener("exit", exitCleanup);
|
|
160
|
+
try {
|
|
161
|
+
await release?.();
|
|
129
162
|
}
|
|
130
|
-
|
|
163
|
+
catch { }
|
|
131
164
|
}
|
|
132
165
|
}
|
|
133
166
|
console.log(chalk.green(`\n✓ Built ${Object.keys(filteredCanisters).length} canister${Object.keys(filteredCanisters).length === 1 ? "" : "s"} successfully`));
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { mainActor } from "../api/actors.js";
|
|
4
|
+
import { resolveVersion } from "../api/resolveVersion.js";
|
|
5
|
+
function label(text) {
|
|
6
|
+
return chalk.bold(text.padEnd(16));
|
|
7
|
+
}
|
|
8
|
+
export async function info(pkgArg, options = {}) {
|
|
9
|
+
let [name, versionArg] = pkgArg.split("@");
|
|
10
|
+
let actor = await mainActor();
|
|
11
|
+
let version;
|
|
12
|
+
try {
|
|
13
|
+
version = await resolveVersion(name, versionArg ?? "");
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
17
|
+
console.error(chalk.red("Error: ") + message);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
let res = await actor.getPackageDetails(name, version);
|
|
21
|
+
if ("err" in res) {
|
|
22
|
+
console.error(chalk.red("Error: ") + res.err);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
let d = res.ok;
|
|
26
|
+
let c = d.config;
|
|
27
|
+
// d.versions is in ascending order (oldest first)
|
|
28
|
+
if (options.versions) {
|
|
29
|
+
for (let ver of d.versions) {
|
|
30
|
+
console.log(ver);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log(`${chalk.green.bold(c.name)}${chalk.gray("@")}${chalk.yellow(c.version)}`);
|
|
36
|
+
if (c.description) {
|
|
37
|
+
console.log(chalk.dim(c.description));
|
|
38
|
+
}
|
|
39
|
+
if (c.version !== d.highestVersion) {
|
|
40
|
+
console.log(chalk.yellow(`latest: ${d.highestVersion}`));
|
|
41
|
+
}
|
|
42
|
+
console.log("");
|
|
43
|
+
if (c.license) {
|
|
44
|
+
console.log(`${label("license")}${c.license}`);
|
|
45
|
+
}
|
|
46
|
+
if (c.repository) {
|
|
47
|
+
console.log(`${label("repository")}${chalk.cyan(c.repository)}`);
|
|
48
|
+
}
|
|
49
|
+
if (c.homepage) {
|
|
50
|
+
console.log(`${label("homepage")}${chalk.cyan(c.homepage)}`);
|
|
51
|
+
}
|
|
52
|
+
if (c.documentation) {
|
|
53
|
+
console.log(`${label("documentation")}${chalk.cyan(c.documentation)}`);
|
|
54
|
+
}
|
|
55
|
+
if (c.dependencies.length > 0) {
|
|
56
|
+
console.log("");
|
|
57
|
+
console.log(`${label("dependencies")}${c.dependencies.map((dep) => `${dep.name}${chalk.gray("@")}${dep.version || dep.repo}`).join(", ")}`);
|
|
58
|
+
}
|
|
59
|
+
if (c.devDependencies.length > 0) {
|
|
60
|
+
console.log(`${label("dev-deps")}${c.devDependencies.map((dep) => `${dep.name}${chalk.gray("@")}${dep.version || dep.repo}`).join(", ")}`);
|
|
61
|
+
}
|
|
62
|
+
if (c.keywords.length > 0) {
|
|
63
|
+
console.log("");
|
|
64
|
+
console.log(`${label("keywords")}${c.keywords.map((k) => chalk.yellow(k)).join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
if (d.versions.length > 0) {
|
|
67
|
+
let versionsDisplay = d.versions.slice(-10).reverse().join(", ");
|
|
68
|
+
let extra = d.versions.length > 10
|
|
69
|
+
? ` ${chalk.gray(`(+${d.versions.length - 10} more)`)}`
|
|
70
|
+
: "";
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log(`${label("versions")}${versionsDisplay}${extra}`);
|
|
73
|
+
}
|
|
74
|
+
console.log("");
|
|
75
|
+
}
|
package/dist/commands/lint.js
CHANGED
|
@@ -64,6 +64,43 @@ export async function collectLintRules(config, rootDir) {
|
|
|
64
64
|
const depRules = await resolveDepRules(config, rootDir);
|
|
65
65
|
return [...localRules, ...depRules];
|
|
66
66
|
}
|
|
67
|
+
function buildCommonArgs(options, config) {
|
|
68
|
+
const args = [];
|
|
69
|
+
if (options.verbose) {
|
|
70
|
+
args.push("--verbose");
|
|
71
|
+
}
|
|
72
|
+
if (options.fix) {
|
|
73
|
+
args.push("--fix");
|
|
74
|
+
}
|
|
75
|
+
if (config.lint?.args) {
|
|
76
|
+
if (typeof config.lint.args === "string") {
|
|
77
|
+
cliError(`[lint] config 'args' should be an array of strings in mops.toml config file`);
|
|
78
|
+
}
|
|
79
|
+
args.push(...config.lint.args);
|
|
80
|
+
}
|
|
81
|
+
if (options.extraArgs && options.extraArgs.length > 0) {
|
|
82
|
+
args.push(...options.extraArgs);
|
|
83
|
+
}
|
|
84
|
+
return args;
|
|
85
|
+
}
|
|
86
|
+
async function runLintoko(lintokoBinPath, rootDir, args, options, label) {
|
|
87
|
+
try {
|
|
88
|
+
if (options.verbose) {
|
|
89
|
+
console.log(chalk.blue("lint"), chalk.gray(`Running lintoko (${label}):`));
|
|
90
|
+
console.log(chalk.gray(lintokoBinPath));
|
|
91
|
+
console.log(chalk.gray(JSON.stringify(args)));
|
|
92
|
+
}
|
|
93
|
+
const result = await execa(lintokoBinPath, args, {
|
|
94
|
+
cwd: rootDir,
|
|
95
|
+
stdio: "inherit",
|
|
96
|
+
reject: false,
|
|
97
|
+
});
|
|
98
|
+
return result.exitCode === 0;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
cliError(`Error while running lintoko${err?.message ? `\n${err.message}` : ""}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
67
104
|
export async function lint(filter, options) {
|
|
68
105
|
let config = readConfig();
|
|
69
106
|
let rootDir = getRootDir();
|
|
@@ -86,49 +123,59 @@ export async function lint(filter, options) {
|
|
|
86
123
|
: "No .mo files found in the project");
|
|
87
124
|
}
|
|
88
125
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
if (options.fix) {
|
|
94
|
-
args.push("--fix");
|
|
95
|
-
}
|
|
126
|
+
const commonArgs = buildCommonArgs(options, config);
|
|
127
|
+
// --- base run ---
|
|
128
|
+
const baseArgs = [...commonArgs];
|
|
96
129
|
const rules = options.rules !== undefined
|
|
97
130
|
? options.rules
|
|
98
131
|
: await collectLintRules(config, rootDir);
|
|
99
|
-
rules.forEach((rule) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
132
|
+
rules.forEach((rule) => baseArgs.push("--rules", rule));
|
|
133
|
+
baseArgs.push(...filesToLint);
|
|
134
|
+
let failed = !(await runLintoko(lintokoBinPath, rootDir, baseArgs, options, "base"));
|
|
135
|
+
// --- extra runs ---
|
|
136
|
+
const extraEntries = config.lint?.extra;
|
|
137
|
+
if (extraEntries) {
|
|
138
|
+
const isFiltered = filter || (options.files && options.files.length > 0);
|
|
139
|
+
const baseFileSet = isFiltered
|
|
140
|
+
? new Set(filesToLint.map((f) => path.resolve(rootDir, f)))
|
|
141
|
+
: undefined;
|
|
142
|
+
for (const [globPattern, ruleDirs] of Object.entries(extraEntries)) {
|
|
143
|
+
if (!Array.isArray(ruleDirs) || ruleDirs.length === 0) {
|
|
144
|
+
console.warn(chalk.yellow(`[lint.extra] skipping '${globPattern}': value must be a non-empty array of rule directories`));
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
for (const dir of ruleDirs) {
|
|
148
|
+
if (!existsSync(path.join(rootDir, dir))) {
|
|
149
|
+
cliError(`[lint.extra] rule directory '${dir}' not found (referenced by glob '${globPattern}')`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
let matchedFiles = globSync(path.join(rootDir, globPattern), {
|
|
153
|
+
...MOTOKO_GLOB_CONFIG,
|
|
154
|
+
cwd: rootDir,
|
|
155
|
+
});
|
|
156
|
+
if (baseFileSet) {
|
|
157
|
+
matchedFiles = matchedFiles.filter((f) => baseFileSet.has(path.resolve(rootDir, f)));
|
|
158
|
+
}
|
|
159
|
+
if (matchedFiles.length === 0) {
|
|
160
|
+
console.warn(chalk.yellow(`[lint.extra] no files matched glob '${globPattern}', skipping`));
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const extraArgs = [...commonArgs];
|
|
164
|
+
for (const dir of ruleDirs) {
|
|
165
|
+
extraArgs.push("--rules", dir);
|
|
166
|
+
}
|
|
167
|
+
extraArgs.push(...matchedFiles);
|
|
168
|
+
const passed = await runLintoko(lintokoBinPath, rootDir, extraArgs, options, `extra: ${globPattern}`);
|
|
169
|
+
failed ||= !passed;
|
|
103
170
|
}
|
|
104
|
-
args.push(...config.lint.args);
|
|
105
171
|
}
|
|
106
|
-
if (
|
|
107
|
-
|
|
172
|
+
if (failed) {
|
|
173
|
+
cliError("Lint failed");
|
|
108
174
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (options.verbose) {
|
|
112
|
-
console.log(chalk.blue("lint"), chalk.gray("Running lintoko:"));
|
|
113
|
-
console.log(chalk.gray(lintokoBinPath));
|
|
114
|
-
console.log(chalk.gray(JSON.stringify(args)));
|
|
115
|
-
}
|
|
116
|
-
const result = await execa(lintokoBinPath, args, {
|
|
117
|
-
cwd: rootDir,
|
|
118
|
-
stdio: "inherit",
|
|
119
|
-
reject: false,
|
|
120
|
-
});
|
|
121
|
-
if (result.exitCode !== 0) {
|
|
122
|
-
cliError(`Lint failed with exit code ${result.exitCode}`);
|
|
123
|
-
}
|
|
124
|
-
if (options.fix) {
|
|
125
|
-
console.log(chalk.green("✓ Lint fixes applied"));
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
console.log(chalk.green("✓ Lint succeeded"));
|
|
129
|
-
}
|
|
175
|
+
else if (options.fix) {
|
|
176
|
+
console.log(chalk.green("✓ Lint fixes applied"));
|
|
130
177
|
}
|
|
131
|
-
|
|
132
|
-
|
|
178
|
+
else {
|
|
179
|
+
console.log(chalk.green("✓ Lint succeeded"));
|
|
133
180
|
}
|
|
134
181
|
}
|
|
@@ -5,7 +5,7 @@ import { SemVer } from "semver";
|
|
|
5
5
|
import { globalCacheDir } from "../../mops.js";
|
|
6
6
|
import * as toolchainUtils from "./toolchain-utils.js";
|
|
7
7
|
let cacheDir = path.join(globalCacheDir, "moc");
|
|
8
|
-
export let repo = "
|
|
8
|
+
export let repo = "caffeinelabs/motoko";
|
|
9
9
|
export let getLatestReleaseTag = async () => {
|
|
10
10
|
return toolchainUtils.getLatestReleaseTag(repo);
|
|
11
11
|
};
|
|
@@ -33,7 +33,7 @@ export let download = async (version, { silent = false, verbose = false } = {})
|
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
35
|
// Download the .js artifact
|
|
36
|
-
const jsUrl = `https://github.com/
|
|
36
|
+
const jsUrl = `https://github.com/caffeinelabs/motoko/releases/download/${version}/moc-${version}.js`;
|
|
37
37
|
const jsDestPath = path.join(destDir, "moc.js");
|
|
38
38
|
if (verbose && !silent) {
|
|
39
39
|
console.log(`Downloading ${jsUrl}`);
|
|
@@ -61,16 +61,16 @@ export let download = async (version, { silent = false, verbose = false } = {})
|
|
|
61
61
|
? "arm64"
|
|
62
62
|
: "aarch64"
|
|
63
63
|
: "x86_64";
|
|
64
|
-
url = `https://github.com/
|
|
64
|
+
url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
|
|
65
65
|
}
|
|
66
66
|
else if (new SemVer(version).compare(new SemVer("0.9.5")) >= 0) {
|
|
67
67
|
let platfrom = process.platform == "darwin" ? "Darwin" : "Linux";
|
|
68
68
|
let arch = "x86_64";
|
|
69
|
-
url = `https://github.com/
|
|
69
|
+
url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
72
|
let platfrom = process.platform == "darwin" ? "macos" : "linux64";
|
|
73
|
-
url = `https://github.com/
|
|
73
|
+
url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${version}.tar.gz`;
|
|
74
74
|
}
|
|
75
75
|
if (verbose && !silent) {
|
|
76
76
|
console.log(`Downloading ${url}`);
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ic-mops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mops": "bin/mops.js",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"prettier-plugin-motoko": "0.13.0",
|
|
66
66
|
"promisify-child-process": "4.1.2",
|
|
67
67
|
"prompts": "2.4.2",
|
|
68
|
+
"proper-lockfile": "4.1.2",
|
|
68
69
|
"semver": "7.7.1",
|
|
69
70
|
"stream-to-promise": "3.0.0",
|
|
70
71
|
"string-width": "7.2.0",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"@types/ncp": "2.0.8",
|
|
82
83
|
"@types/node": "24.0.3",
|
|
83
84
|
"@types/prompts": "2.4.9",
|
|
85
|
+
"@types/proper-lockfile": "4.1.4",
|
|
84
86
|
"@types/semver": "7.5.8",
|
|
85
87
|
"@types/stream-to-promise": "2.2.4",
|
|
86
88
|
"@types/tar": "6.1.13",
|
|
@@ -10,7 +10,7 @@ jobs:
|
|
|
10
10
|
|
|
11
11
|
steps:
|
|
12
12
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
13
|
-
- uses:
|
|
13
|
+
- uses: caffeinelabs/setup-mops@v1
|
|
14
14
|
with:
|
|
15
15
|
# Make sure you set the MOPS_IDENTITY_PEM secret in your repository settings https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository
|
|
16
16
|
identity-pem: ${{ secrets.MOPS_IDENTITY_PEM }}
|
package/dist/tests/build.test.js
CHANGED
|
@@ -85,6 +85,23 @@ describe("build", () => {
|
|
|
85
85
|
cleanFixture(cwd, artifact, artifactDid);
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
|
+
test("parallel builds of the same canister both succeed", async () => {
|
|
89
|
+
const cwd = path.join(import.meta.dirname, "build/success");
|
|
90
|
+
try {
|
|
91
|
+
const [a, b] = await Promise.all([
|
|
92
|
+
cli(["build", "foo"], { cwd }),
|
|
93
|
+
cli(["build", "foo"], { cwd }),
|
|
94
|
+
]);
|
|
95
|
+
expect(a.exitCode).toBe(0);
|
|
96
|
+
expect(b.exitCode).toBe(0);
|
|
97
|
+
expect(existsSync(path.join(cwd, ".mops/.build/foo.wasm"))).toBe(true);
|
|
98
|
+
expect(existsSync(path.join(cwd, ".mops/.build/foo.did"))).toBe(true);
|
|
99
|
+
expect(existsSync(path.join(cwd, ".mops/.build/foo.most"))).toBe(true);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
cleanFixture(cwd);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
88
105
|
// Regression: bin/mops.js must route through environments/nodejs/cli.js
|
|
89
106
|
// so that setWasmBindings() is called before any command runs.
|
|
90
107
|
// The dev entry point (npm run mops) uses tsx and always worked;
|
package/dist/tests/lint.test.js
CHANGED
|
@@ -38,4 +38,37 @@ describe("lint", () => {
|
|
|
38
38
|
expect(result.exitCode).toBe(0);
|
|
39
39
|
expect(result.stderr).toMatch(/not found in dependencies/);
|
|
40
40
|
});
|
|
41
|
+
describe("[lint.extra]", () => {
|
|
42
|
+
test("extra rules on glob-matched files", async () => {
|
|
43
|
+
// src/restricted/*.mo has violations, Ok.mo does not.
|
|
44
|
+
// Extra rules apply only to the glob match → fails on restricted/ files.
|
|
45
|
+
// Filter "Ok" narrows scope so extra is skipped → passes.
|
|
46
|
+
const cwd = path.join(import.meta.dirname, "lint-extra");
|
|
47
|
+
await cliSnapshot(["lint"], { cwd }, 1);
|
|
48
|
+
await cliSnapshot(["lint", "Ok"], { cwd }, 0);
|
|
49
|
+
});
|
|
50
|
+
test("edge cases: pass, empty value, no-match, missing dir", async () => {
|
|
51
|
+
// Single fixture with 4 entries processed in order:
|
|
52
|
+
// 1. Clean.mo + valid rules → passes
|
|
53
|
+
// 2. empty array → warns and skips
|
|
54
|
+
// 3. non-matching glob → warns and skips
|
|
55
|
+
// 4. missing rule dir → errors
|
|
56
|
+
const cwd = path.join(import.meta.dirname, "lint-extra-edge-cases");
|
|
57
|
+
await cliSnapshot(["lint"], { cwd }, 1);
|
|
58
|
+
});
|
|
59
|
+
test("base rules still run alongside extra rules", async () => {
|
|
60
|
+
const cwd = path.join(import.meta.dirname, "lint-extra-with-base");
|
|
61
|
+
await cliSnapshot(["lint"], { cwd }, 1);
|
|
62
|
+
});
|
|
63
|
+
test("--rules CLI flag does not affect extra runs, multi-rules", async () => {
|
|
64
|
+
// --rules overrides base with an empty dir (no base violations).
|
|
65
|
+
// Extra runs independently with two rule dirs → Restricted.mo fails.
|
|
66
|
+
const cwd = path.join(import.meta.dirname, "lint-extra-with-cli-rules");
|
|
67
|
+
await cliSnapshot(["lint", "--rules", "empty-rules", "--verbose"], { cwd }, 1);
|
|
68
|
+
});
|
|
69
|
+
test("example rules: no-types, types-only, migration-only", async () => {
|
|
70
|
+
const cwd = path.join(import.meta.dirname, "lint-extra-example-rules");
|
|
71
|
+
await cliSnapshot(["lint"], { cwd }, 1);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
41
74
|
});
|
package/dist/types.d.ts
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ic-mops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mops": "dist/bin/mops.js",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"prettier-plugin-motoko": "0.13.0",
|
|
87
87
|
"promisify-child-process": "4.1.2",
|
|
88
88
|
"prompts": "2.4.2",
|
|
89
|
+
"proper-lockfile": "4.1.2",
|
|
89
90
|
"semver": "7.7.1",
|
|
90
91
|
"stream-to-promise": "3.0.0",
|
|
91
92
|
"string-width": "7.2.0",
|
|
@@ -102,6 +103,7 @@
|
|
|
102
103
|
"@types/ncp": "2.0.8",
|
|
103
104
|
"@types/node": "24.0.3",
|
|
104
105
|
"@types/prompts": "2.4.9",
|
|
106
|
+
"@types/proper-lockfile": "4.1.4",
|
|
105
107
|
"@types/semver": "7.5.8",
|
|
106
108
|
"@types/stream-to-promise": "2.2.4",
|
|
107
109
|
"@types/tar": "6.1.13",
|
|
@@ -10,7 +10,7 @@ jobs:
|
|
|
10
10
|
|
|
11
11
|
steps:
|
|
12
12
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
13
|
-
- uses:
|
|
13
|
+
- uses: caffeinelabs/setup-mops@v1
|
|
14
14
|
with:
|
|
15
15
|
# Make sure you set the MOPS_IDENTITY_PEM secret in your repository settings https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository
|
|
16
16
|
identity-pem: ${{ secrets.MOPS_IDENTITY_PEM }}
|