ic-mops 2.8.0 → 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 +8 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +11 -1
- package/commands/build.ts +127 -92
- package/commands/check-stable.ts +9 -10
- 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/check-stable.js +9 -10
- 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/check-stable.test.js +11 -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/check-stable/actor-idl/mops.toml +8 -0
- package/tests/check-stable/actor-idl/new.mo +6 -0
- package/tests/check-stable/actor-idl/old.mo +5 -0
- package/tests/check-stable/actor-idl/system-idl/aaaaa-aa.did +3 -0
- package/tests/check-stable.test.ts +12 -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
|
@@ -8,7 +8,7 @@ import * as toolchainUtils from "./toolchain-utils.js";
|
|
|
8
8
|
|
|
9
9
|
let cacheDir = path.join(globalCacheDir, "moc");
|
|
10
10
|
|
|
11
|
-
export let repo = "
|
|
11
|
+
export let repo = "caffeinelabs/motoko";
|
|
12
12
|
|
|
13
13
|
export let getLatestReleaseTag = async () => {
|
|
14
14
|
return toolchainUtils.getLatestReleaseTag(repo);
|
|
@@ -44,7 +44,7 @@ export let download = async (
|
|
|
44
44
|
}
|
|
45
45
|
} else {
|
|
46
46
|
// Download the .js artifact
|
|
47
|
-
const jsUrl = `https://github.com/
|
|
47
|
+
const jsUrl = `https://github.com/caffeinelabs/motoko/releases/download/${version}/moc-${version}.js`;
|
|
48
48
|
const jsDestPath = path.join(destDir, "moc.js");
|
|
49
49
|
|
|
50
50
|
if (verbose && !silent) {
|
|
@@ -75,14 +75,14 @@ export let download = async (
|
|
|
75
75
|
? "arm64"
|
|
76
76
|
: "aarch64"
|
|
77
77
|
: "x86_64";
|
|
78
|
-
url = `https://github.com/
|
|
78
|
+
url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
|
|
79
79
|
} else if (new SemVer(version).compare(new SemVer("0.9.5")) >= 0) {
|
|
80
80
|
let platfrom = process.platform == "darwin" ? "Darwin" : "Linux";
|
|
81
81
|
let arch = "x86_64";
|
|
82
|
-
url = `https://github.com/
|
|
82
|
+
url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
|
|
83
83
|
} else {
|
|
84
84
|
let platfrom = process.platform == "darwin" ? "macos" : "linux64";
|
|
85
|
-
url = `https://github.com/
|
|
85
|
+
url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${version}.tar.gz`;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
if (verbose && !silent) {
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { checkStable } from "./commands/check-stable.js";
|
|
|
15
15
|
import { docsCoverage } from "./commands/docs-coverage.js";
|
|
16
16
|
import { docs } from "./commands/docs.js";
|
|
17
17
|
import { format } from "./commands/format.js";
|
|
18
|
+
import { info } from "./commands/info.js";
|
|
18
19
|
import { init } from "./commands/init.js";
|
|
19
20
|
import { lint } from "./commands/lint.js";
|
|
20
21
|
import { installAll } from "./commands/install/install-all.js";
|
|
@@ -213,6 +214,14 @@ program
|
|
|
213
214
|
.action(async (text) => {
|
|
214
215
|
await search(text);
|
|
215
216
|
});
|
|
217
|
+
// info
|
|
218
|
+
program
|
|
219
|
+
.command("info <pkg>")
|
|
220
|
+
.description("Show detailed information about a package from the registry")
|
|
221
|
+
.option("--versions", "List all published versions, one per line")
|
|
222
|
+
.action(async (pkg, options) => {
|
|
223
|
+
await info(pkg, options);
|
|
224
|
+
});
|
|
216
225
|
// cache
|
|
217
226
|
program
|
|
218
227
|
.command("cache")
|
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`));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { basename, join
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
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";
|
|
@@ -28,8 +28,7 @@ export async function checkStable(oldFile, canisterName, options = {}) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function runStableCheck(params) {
|
|
30
30
|
const { oldFile, canisterMain, canisterName, mocPath, globalMocArgs, options = {}, } = params;
|
|
31
|
-
const
|
|
32
|
-
const sources = (await sourcesArgs({ cwd: checkStableDir })).flat();
|
|
31
|
+
const sources = (await sourcesArgs()).flat();
|
|
33
32
|
const isOldMostFile = oldFile.endsWith(".most");
|
|
34
33
|
if (!existsSync(oldFile)) {
|
|
35
34
|
cliError(`File not found: ${oldFile}`);
|
|
@@ -65,10 +64,13 @@ export async function runStableCheck(params) {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
async function generateStableTypes(mocPath, moFile, outputPath, sources, globalMocArgs, options) {
|
|
68
|
-
const
|
|
67
|
+
const base = basename(outputPath, ".most");
|
|
68
|
+
const wasmPath = join(CHECK_STABLE_DIR, base + ".wasm");
|
|
69
69
|
const args = [
|
|
70
70
|
"--stable-types",
|
|
71
|
-
|
|
71
|
+
"-o",
|
|
72
|
+
wasmPath,
|
|
73
|
+
moFile,
|
|
72
74
|
...sources,
|
|
73
75
|
...globalMocArgs,
|
|
74
76
|
...(options.extraArgs ?? []),
|
|
@@ -78,7 +80,6 @@ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalM
|
|
|
78
80
|
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
79
81
|
}
|
|
80
82
|
const result = await execa(mocPath, args, {
|
|
81
|
-
cwd: CHECK_STABLE_DIR,
|
|
82
83
|
stdio: "pipe",
|
|
83
84
|
reject: false,
|
|
84
85
|
});
|
|
@@ -88,8 +89,6 @@ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalM
|
|
|
88
89
|
}
|
|
89
90
|
cliError(`Failed to generate stable types for ${moFile} (exit code: ${result.exitCode})`);
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
-
await rename(join(CHECK_STABLE_DIR, base + ".most"), outputPath);
|
|
93
|
-
await rm(join(CHECK_STABLE_DIR, base + ".wasm"), { force: true });
|
|
92
|
+
await rm(wasmPath, { force: true });
|
|
94
93
|
return outputPath;
|
|
95
94
|
}
|
|
@@ -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;
|