ic-mops 2.12.1 → 2.12.3
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 +7 -0
- package/bundle/cli.tgz +0 -0
- package/commands/publish.ts +0 -48
- package/dist/commands/publish.js +0 -43
- package/dist/integrity.d.ts +3 -1
- package/dist/integrity.js +10 -4
- package/dist/package.json +1 -1
- package/dist/tests/cli.test.js +50 -1
- package/integrity.ts +14 -4
- package/package.json +1 -1
- package/tests/cli.test.ts +58 -1
- package/tests/install/aliases/mops.toml +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Next
|
|
4
4
|
|
|
5
|
+
## 2.12.3
|
|
6
|
+
- Fix `mops install --lock update` silently no-op'ing on a corrupt lockfile (#515)
|
|
7
|
+
- `mops publish` no longer rejects unknown `mops.toml` sections, `package.*` keys, or `requirements.*` entries — these typo guards were the only place in the CLI that complained about unknown keys, drifted from the docs/types, and blocked publish on harmless local-only config like `[moc]`, `[canisters]`, `[build]`, and `[lint]` (#512)
|
|
8
|
+
|
|
9
|
+
## 2.12.2
|
|
10
|
+
- Fix `mops install` (and any `--lock check` flow) failing with "Mismatched number of resolved packages" when a project's resolved dependencies include multiple aliases (e.g. `base`, `base@0`, `base@0.16`) that pin to the same `name@version`
|
|
11
|
+
|
|
5
12
|
## 2.12.1
|
|
6
13
|
- `mops check`/`build`/`check-stable` skip migration staging when only the pending `next` migration is needed, so `moc` diagnostics reference the real `next-migration/<file>` path.
|
|
7
14
|
|
package/bundle/cli.tgz
CHANGED
|
Binary file
|
package/commands/publish.ts
CHANGED
|
@@ -47,22 +47,6 @@ export async function publish(
|
|
|
47
47
|
|
|
48
48
|
console.log(`Publishing ${config.package?.name}@${config.package?.version}`);
|
|
49
49
|
|
|
50
|
-
// validate
|
|
51
|
-
for (let key of Object.keys(config)) {
|
|
52
|
-
if (
|
|
53
|
-
![
|
|
54
|
-
"package",
|
|
55
|
-
"dependencies",
|
|
56
|
-
"dev-dependencies",
|
|
57
|
-
"toolchain",
|
|
58
|
-
"requirements",
|
|
59
|
-
].includes(key)
|
|
60
|
-
) {
|
|
61
|
-
console.log(chalk.red("Error: ") + `Unknown config section [${key}]`);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
50
|
// required fields
|
|
67
51
|
if (!config.package) {
|
|
68
52
|
console.log(
|
|
@@ -97,29 +81,6 @@ export async function publish(
|
|
|
97
81
|
}
|
|
98
82
|
}
|
|
99
83
|
|
|
100
|
-
let packageKeys = [
|
|
101
|
-
"name",
|
|
102
|
-
"version",
|
|
103
|
-
"keywords",
|
|
104
|
-
"description",
|
|
105
|
-
"repository",
|
|
106
|
-
"documentation",
|
|
107
|
-
"homepage",
|
|
108
|
-
"baseDir",
|
|
109
|
-
"readme",
|
|
110
|
-
"license",
|
|
111
|
-
"files",
|
|
112
|
-
"dfx",
|
|
113
|
-
"moc",
|
|
114
|
-
"donation",
|
|
115
|
-
];
|
|
116
|
-
for (let key of Object.keys(config.package)) {
|
|
117
|
-
if (!packageKeys.includes(key)) {
|
|
118
|
-
console.log(chalk.red("Error: ") + `Unknown config key 'package.${key}'`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
84
|
// disabled fields
|
|
124
85
|
for (let key of ["dfx", "moc", "homepage", "documentation", "donation"]) {
|
|
125
86
|
if ((config.package as any)[key]) {
|
|
@@ -222,15 +183,6 @@ export async function publish(
|
|
|
222
183
|
}
|
|
223
184
|
}
|
|
224
185
|
|
|
225
|
-
if (config.requirements) {
|
|
226
|
-
Object.keys(config.requirements).forEach((name) => {
|
|
227
|
-
if (name !== "moc") {
|
|
228
|
-
console.log(chalk.red("Error: ") + `Unknown requirement "${name}"`);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
186
|
let toBackendDep = (dep: Dependency): DependencyV2 => {
|
|
235
187
|
return {
|
|
236
188
|
...dep,
|
package/dist/commands/publish.js
CHANGED
|
@@ -22,19 +22,6 @@ export async function publish(options = {}) {
|
|
|
22
22
|
let rootDir = getRootDir();
|
|
23
23
|
let config = readConfig();
|
|
24
24
|
console.log(`Publishing ${config.package?.name}@${config.package?.version}`);
|
|
25
|
-
// validate
|
|
26
|
-
for (let key of Object.keys(config)) {
|
|
27
|
-
if (![
|
|
28
|
-
"package",
|
|
29
|
-
"dependencies",
|
|
30
|
-
"dev-dependencies",
|
|
31
|
-
"toolchain",
|
|
32
|
-
"requirements",
|
|
33
|
-
].includes(key)) {
|
|
34
|
-
console.log(chalk.red("Error: ") + `Unknown config section [${key}]`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
25
|
// required fields
|
|
39
26
|
if (!config.package) {
|
|
40
27
|
console.log(chalk.red("Error: ") +
|
|
@@ -63,28 +50,6 @@ export async function publish(options = {}) {
|
|
|
63
50
|
}
|
|
64
51
|
}
|
|
65
52
|
}
|
|
66
|
-
let packageKeys = [
|
|
67
|
-
"name",
|
|
68
|
-
"version",
|
|
69
|
-
"keywords",
|
|
70
|
-
"description",
|
|
71
|
-
"repository",
|
|
72
|
-
"documentation",
|
|
73
|
-
"homepage",
|
|
74
|
-
"baseDir",
|
|
75
|
-
"readme",
|
|
76
|
-
"license",
|
|
77
|
-
"files",
|
|
78
|
-
"dfx",
|
|
79
|
-
"moc",
|
|
80
|
-
"donation",
|
|
81
|
-
];
|
|
82
|
-
for (let key of Object.keys(config.package)) {
|
|
83
|
-
if (!packageKeys.includes(key)) {
|
|
84
|
-
console.log(chalk.red("Error: ") + `Unknown config key 'package.${key}'`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
53
|
// disabled fields
|
|
89
54
|
for (let key of ["dfx", "moc", "homepage", "documentation", "donation"]) {
|
|
90
55
|
if (config.package[key]) {
|
|
@@ -167,14 +132,6 @@ export async function publish(options = {}) {
|
|
|
167
132
|
}
|
|
168
133
|
}
|
|
169
134
|
}
|
|
170
|
-
if (config.requirements) {
|
|
171
|
-
Object.keys(config.requirements).forEach((name) => {
|
|
172
|
-
if (name !== "moc") {
|
|
173
|
-
console.log(chalk.red("Error: ") + `Unknown requirement "${name}"`);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
135
|
let toBackendDep = (dep) => {
|
|
179
136
|
return {
|
|
180
137
|
...dep,
|
package/dist/integrity.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export declare function getLocalFileHash(fileId: string): string;
|
|
|
20
20
|
export declare function checkRemote(): Promise<void>;
|
|
21
21
|
export declare function readLockFile(): LockFile | null;
|
|
22
22
|
export declare function checkLockFileLight(): boolean;
|
|
23
|
-
export declare function updateLockFile(
|
|
23
|
+
export declare function updateLockFile({ force, }?: {
|
|
24
|
+
force?: boolean;
|
|
25
|
+
}): Promise<void>;
|
|
24
26
|
export declare function checkLockFile(force?: boolean): Promise<void>;
|
|
25
27
|
export {};
|
package/dist/integrity.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function checkIntegrity(lock) {
|
|
|
13
13
|
lock = process.env["CI"] ? "check" : "update";
|
|
14
14
|
}
|
|
15
15
|
if (lock === "update") {
|
|
16
|
-
await updateLockFile();
|
|
16
|
+
await updateLockFile({ force });
|
|
17
17
|
await checkLockFile(force);
|
|
18
18
|
}
|
|
19
19
|
else if (lock === "check") {
|
|
@@ -31,7 +31,8 @@ async function getResolvedMopsPackageIds() {
|
|
|
31
31
|
let packageIds = Object.entries(resolvedPackages)
|
|
32
32
|
.filter(([_, version]) => getDependencyType(version) === "mops")
|
|
33
33
|
.map(([name, version]) => getPackageId(name, version));
|
|
34
|
-
|
|
34
|
+
// dedupe: aliases like `base@0`, `base@0.16` collapse to the same packageId
|
|
35
|
+
return [...new Set(packageIds)];
|
|
35
36
|
}
|
|
36
37
|
// get hash of local file from '.mops' dir by fileId
|
|
37
38
|
export function getLocalFileHash(fileId) {
|
|
@@ -107,9 +108,11 @@ export function checkLockFileLight() {
|
|
|
107
108
|
}
|
|
108
109
|
return false;
|
|
109
110
|
}
|
|
110
|
-
export async function updateLockFile() {
|
|
111
|
+
export async function updateLockFile({ force = false, } = {}) {
|
|
111
112
|
// if lock file exists and mops.toml hasn't changed, don't update it
|
|
112
|
-
|
|
113
|
+
// (unless forced: `--lock update` must unconditionally regenerate so users
|
|
114
|
+
// can recover from a corrupt lockfile without `rm mops.lock`)
|
|
115
|
+
if (!force && checkLockFileLight()) {
|
|
113
116
|
return;
|
|
114
117
|
}
|
|
115
118
|
let resolvedDeps = await resolvePackages();
|
|
@@ -227,6 +230,9 @@ export async function checkLockFile(force = false) {
|
|
|
227
230
|
console.error(`Mismatched hash for ${fileId}`);
|
|
228
231
|
console.error(`Locked hash: ${lockedHash}`);
|
|
229
232
|
console.error(`Actual hash: ${localHash}`);
|
|
233
|
+
console.error("");
|
|
234
|
+
console.error("If you have not modified files under .mops/, your lockfile may be stale or corrupt.");
|
|
235
|
+
console.error("Run `mops install --lock update` to regenerate it.");
|
|
230
236
|
process.exit(1);
|
|
231
237
|
}
|
|
232
238
|
}
|
package/dist/package.json
CHANGED
package/dist/tests/cli.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
-
import { existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { cli } from "./helpers";
|
|
5
5
|
describe("cli", () => {
|
|
@@ -67,4 +67,53 @@ describe("install", () => {
|
|
|
67
67
|
});
|
|
68
68
|
// mops add/remove/update/sync are not separately tested here because they
|
|
69
69
|
// all route through the same checkIntegrity code path tested above.
|
|
70
|
+
// Regression: aliases pinning the same package@version (e.g. `core` and
|
|
71
|
+
// `core@1` both at "1.0.0") inflated the resolved-packageIds count and
|
|
72
|
+
// tripped the lockfile integrity check with a spurious
|
|
73
|
+
// "Mismatched number of resolved packages" error. See issue #506.
|
|
74
|
+
test("integrity check passes when aliases resolve to the same package@version", async () => {
|
|
75
|
+
const cwd = path.join(import.meta.dirname, "install/aliases");
|
|
76
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
77
|
+
rmSync(lockFile, { force: true });
|
|
78
|
+
try {
|
|
79
|
+
const result = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
80
|
+
expect(result.stderr).not.toMatch(/Mismatched number of resolved packages/);
|
|
81
|
+
expect(result.exitCode).toBe(0);
|
|
82
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
rmSync(lockFile, { force: true });
|
|
86
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// Regression: `install --lock update` used to early-return if mops.toml's
|
|
90
|
+
// deps hash was unchanged, even when the lockfile's per-file hashes were
|
|
91
|
+
// stale/corrupt. The subsequent checkLockFile would then fail and exit 1,
|
|
92
|
+
// so `--lock update` could never recover a broken lock — the only escape
|
|
93
|
+
// was `rm mops.lock`. See issue #514.
|
|
94
|
+
test("--lock update rewrites a lockfile with a corrupt file hash", async () => {
|
|
95
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
96
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
97
|
+
rmSync(lockFile, { force: true });
|
|
98
|
+
try {
|
|
99
|
+
const first = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
100
|
+
expect(first.exitCode).toBe(0);
|
|
101
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
102
|
+
const bad = "BAD0000000000000000000000000000000000000000000000000000000000BAD";
|
|
103
|
+
const original = readFileSync(lockFile, "utf8");
|
|
104
|
+
const corrupted = original.replace(/"core@1\.0\.0\/mops\.toml":\s*"[0-9a-f]{64}"/, `"core@1.0.0/mops.toml": "${bad}"`);
|
|
105
|
+
expect(corrupted).not.toBe(original);
|
|
106
|
+
writeFileSync(lockFile, corrupted);
|
|
107
|
+
const result = await cli(["install", "--lock", "update"], {
|
|
108
|
+
cwd,
|
|
109
|
+
env: { CI: undefined },
|
|
110
|
+
});
|
|
111
|
+
expect(result.exitCode).toBe(0);
|
|
112
|
+
expect(readFileSync(lockFile, "utf8")).not.toContain(bad);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
rmSync(lockFile, { force: true });
|
|
116
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
70
119
|
});
|
package/integrity.ts
CHANGED
|
@@ -41,7 +41,7 @@ export async function checkIntegrity(lock?: "check" | "update" | "ignore") {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
if (lock === "update") {
|
|
44
|
-
await updateLockFile();
|
|
44
|
+
await updateLockFile({ force });
|
|
45
45
|
await checkLockFile(force);
|
|
46
46
|
} else if (lock === "check") {
|
|
47
47
|
await checkLockFile(force);
|
|
@@ -63,7 +63,8 @@ async function getResolvedMopsPackageIds(): Promise<string[]> {
|
|
|
63
63
|
let packageIds = Object.entries(resolvedPackages)
|
|
64
64
|
.filter(([_, version]) => getDependencyType(version) === "mops")
|
|
65
65
|
.map(([name, version]) => getPackageId(name, version));
|
|
66
|
-
|
|
66
|
+
// dedupe: aliases like `base@0`, `base@0.16` collapse to the same packageId
|
|
67
|
+
return [...new Set(packageIds)];
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
// get hash of local file from '.mops' dir by fileId
|
|
@@ -158,9 +159,13 @@ export function checkLockFileLight(): boolean {
|
|
|
158
159
|
return false;
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
export async function updateLockFile(
|
|
162
|
+
export async function updateLockFile({
|
|
163
|
+
force = false,
|
|
164
|
+
}: { force?: boolean } = {}) {
|
|
162
165
|
// if lock file exists and mops.toml hasn't changed, don't update it
|
|
163
|
-
|
|
166
|
+
// (unless forced: `--lock update` must unconditionally regenerate so users
|
|
167
|
+
// can recover from a corrupt lockfile without `rm mops.lock`)
|
|
168
|
+
if (!force && checkLockFileLight()) {
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
171
|
|
|
@@ -312,6 +317,11 @@ export async function checkLockFile(force = false) {
|
|
|
312
317
|
console.error(`Mismatched hash for ${fileId}`);
|
|
313
318
|
console.error(`Locked hash: ${lockedHash}`);
|
|
314
319
|
console.error(`Actual hash: ${localHash}`);
|
|
320
|
+
console.error("");
|
|
321
|
+
console.error(
|
|
322
|
+
"If you have not modified files under .mops/, your lockfile may be stale or corrupt.",
|
|
323
|
+
);
|
|
324
|
+
console.error("Run `mops install --lock update` to regenerate it.");
|
|
315
325
|
process.exit(1);
|
|
316
326
|
}
|
|
317
327
|
}
|
package/package.json
CHANGED
package/tests/cli.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
-
import { existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { cli } from "./helpers";
|
|
5
5
|
|
|
@@ -71,4 +71,61 @@ describe("install", () => {
|
|
|
71
71
|
|
|
72
72
|
// mops add/remove/update/sync are not separately tested here because they
|
|
73
73
|
// all route through the same checkIntegrity code path tested above.
|
|
74
|
+
|
|
75
|
+
// Regression: aliases pinning the same package@version (e.g. `core` and
|
|
76
|
+
// `core@1` both at "1.0.0") inflated the resolved-packageIds count and
|
|
77
|
+
// tripped the lockfile integrity check with a spurious
|
|
78
|
+
// "Mismatched number of resolved packages" error. See issue #506.
|
|
79
|
+
test("integrity check passes when aliases resolve to the same package@version", async () => {
|
|
80
|
+
const cwd = path.join(import.meta.dirname, "install/aliases");
|
|
81
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
82
|
+
rmSync(lockFile, { force: true });
|
|
83
|
+
try {
|
|
84
|
+
const result = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
85
|
+
expect(result.stderr).not.toMatch(
|
|
86
|
+
/Mismatched number of resolved packages/,
|
|
87
|
+
);
|
|
88
|
+
expect(result.exitCode).toBe(0);
|
|
89
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
90
|
+
} finally {
|
|
91
|
+
rmSync(lockFile, { force: true });
|
|
92
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Regression: `install --lock update` used to early-return if mops.toml's
|
|
97
|
+
// deps hash was unchanged, even when the lockfile's per-file hashes were
|
|
98
|
+
// stale/corrupt. The subsequent checkLockFile would then fail and exit 1,
|
|
99
|
+
// so `--lock update` could never recover a broken lock — the only escape
|
|
100
|
+
// was `rm mops.lock`. See issue #514.
|
|
101
|
+
test("--lock update rewrites a lockfile with a corrupt file hash", async () => {
|
|
102
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
103
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
104
|
+
rmSync(lockFile, { force: true });
|
|
105
|
+
try {
|
|
106
|
+
const first = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
107
|
+
expect(first.exitCode).toBe(0);
|
|
108
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
109
|
+
|
|
110
|
+
const bad =
|
|
111
|
+
"BAD0000000000000000000000000000000000000000000000000000000000BAD";
|
|
112
|
+
const original = readFileSync(lockFile, "utf8");
|
|
113
|
+
const corrupted = original.replace(
|
|
114
|
+
/"core@1\.0\.0\/mops\.toml":\s*"[0-9a-f]{64}"/,
|
|
115
|
+
`"core@1.0.0/mops.toml": "${bad}"`,
|
|
116
|
+
);
|
|
117
|
+
expect(corrupted).not.toBe(original);
|
|
118
|
+
writeFileSync(lockFile, corrupted);
|
|
119
|
+
|
|
120
|
+
const result = await cli(["install", "--lock", "update"], {
|
|
121
|
+
cwd,
|
|
122
|
+
env: { CI: undefined },
|
|
123
|
+
});
|
|
124
|
+
expect(result.exitCode).toBe(0);
|
|
125
|
+
expect(readFileSync(lockFile, "utf8")).not.toContain(bad);
|
|
126
|
+
} finally {
|
|
127
|
+
rmSync(lockFile, { force: true });
|
|
128
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
74
131
|
});
|