ic-mops 2.12.3 → 2.13.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 CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## Next
4
4
 
5
+ ## 2.13.0
6
+ - Fix `mops update` and `mops outdated` jumping across major versions (or pre-1.0 minor versions) — they are now caret-bound by default, matching `cargo update`. For example, `core = "2.0.0"` now updates within `2.x.y` instead of jumping to a future `3.0.0`. Use `--major` to opt into cross-major updates.
7
+
5
8
  ## 2.12.3
6
9
  - Fix `mops install --lock update` silently no-op'ing on a corrupt lockfile (#515)
7
10
  - `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)
package/bundle/cli.tgz CHANGED
Binary file
package/cli.ts CHANGED
@@ -627,14 +627,26 @@ program
627
627
  program
628
628
  .command("outdated")
629
629
  .description("Print outdated dependencies specified in mops.toml")
630
- .action(async () => {
631
- await outdated();
630
+ .addOption(
631
+ new Option(
632
+ "--major",
633
+ "Allow updates that cross the caret bound (major versions, or for 0.x.y packages, minor versions)",
634
+ ),
635
+ )
636
+ .action(async (options) => {
637
+ await outdated(options);
632
638
  });
633
639
 
634
640
  // update
635
641
  program
636
642
  .command("update [pkg]")
637
643
  .description("Update dependencies specified in mops.toml")
644
+ .addOption(
645
+ new Option(
646
+ "--major",
647
+ "Allow updates that cross the caret bound (major versions, or for 0.x.y packages, minor versions)",
648
+ ),
649
+ )
638
650
  .addOption(
639
651
  new Option("--lock <action>", "Lockfile action").choices([
640
652
  "update",
@@ -1,14 +1,18 @@
1
1
  import process from "node:process";
2
2
  import chalk from "chalk";
3
+ import semver from "semver";
3
4
  import { mainActor } from "../api/actors.js";
4
5
  import { Config } from "../types.js";
5
6
  import { getDepName, getDepPinnedVersion } from "../helpers/get-dep-name.js";
6
7
  import { SemverPart } from "../declarations/main/main.did.js";
7
8
 
9
+ export type UpdateBound = "caret" | "major";
10
+
8
11
  // [pkg, oldVersion, newVersion]
9
12
  export async function getAvailableUpdates(
10
13
  config: Config,
11
14
  pkg?: string,
15
+ bound: UpdateBound = "caret",
12
16
  ): Promise<Array<[string, string, string]>> {
13
17
  let deps = Object.values(config.dependencies || {});
14
18
  let devDeps = Object.values(config["dev-dependencies"] || {});
@@ -46,8 +50,12 @@ export async function getAvailableUpdates(
46
50
  pinnedVersion.split(".").length === 1
47
51
  ? { minor: null }
48
52
  : { patch: null };
53
+ } else if (bound === "caret") {
54
+ // Caret (cargo-style): ^0.x.y -> 0.x.* (patch only); ^1+ -> same major (minor+patch)
55
+ let major = semver.major(dep.version!);
56
+ semverPart = major === 0 ? { patch: null } : { minor: null };
49
57
  }
50
- return [name, dep.version || "", semverPart];
58
+ return [name, dep.version!, semverPart];
51
59
  }),
52
60
  );
53
61
 
@@ -3,13 +3,17 @@ import { checkConfigFile, readConfig } from "../mops.js";
3
3
  import { getAvailableUpdates } from "./available-updates.js";
4
4
  import { getDepName, getDepPinnedVersion } from "../helpers/get-dep-name.js";
5
5
 
6
- export async function outdated() {
6
+ export async function outdated({ major }: { major?: boolean } = {}) {
7
7
  if (!checkConfigFile()) {
8
8
  return;
9
9
  }
10
10
  let config = readConfig();
11
11
 
12
- let available = await getAvailableUpdates(config);
12
+ let available = await getAvailableUpdates(
13
+ config,
14
+ undefined,
15
+ major ? "major" : "caret",
16
+ );
13
17
 
14
18
  if (available.length === 0) {
15
19
  console.log(chalk.green("All dependencies are up to date!"));
@@ -14,9 +14,13 @@ type UpdateOptions = {
14
14
  verbose?: boolean;
15
15
  dev?: boolean;
16
16
  lock?: "update" | "ignore";
17
+ major?: boolean;
17
18
  };
18
19
 
19
- export async function update(pkg?: string, { lock }: UpdateOptions = {}) {
20
+ export async function update(
21
+ pkg?: string,
22
+ { lock, major }: UpdateOptions = {},
23
+ ) {
20
24
  if (!checkConfigFile()) {
21
25
  return;
22
26
  }
@@ -59,7 +63,11 @@ export async function update(pkg?: string, { lock }: UpdateOptions = {}) {
59
63
  }
60
64
 
61
65
  // update mops packages
62
- let available = await getAvailableUpdates(config, pkg);
66
+ let available = await getAvailableUpdates(
67
+ config,
68
+ pkg,
69
+ major ? "major" : "caret",
70
+ );
63
71
 
64
72
  if (available.length === 0) {
65
73
  if (pkg) {
package/dist/cli.js CHANGED
@@ -492,13 +492,15 @@ program
492
492
  program
493
493
  .command("outdated")
494
494
  .description("Print outdated dependencies specified in mops.toml")
495
- .action(async () => {
496
- await outdated();
495
+ .addOption(new Option("--major", "Allow updates that cross the caret bound (major versions, or for 0.x.y packages, minor versions)"))
496
+ .action(async (options) => {
497
+ await outdated(options);
497
498
  });
498
499
  // update
499
500
  program
500
501
  .command("update [pkg]")
501
502
  .description("Update dependencies specified in mops.toml")
503
+ .addOption(new Option("--major", "Allow updates that cross the caret bound (major versions, or for 0.x.y packages, minor versions)"))
502
504
  .addOption(new Option("--lock <action>", "Lockfile action").choices([
503
505
  "update",
504
506
  "ignore",
@@ -1,2 +1,3 @@
1
1
  import { Config } from "../types.js";
2
- export declare function getAvailableUpdates(config: Config, pkg?: string): Promise<Array<[string, string, string]>>;
2
+ export type UpdateBound = "caret" | "major";
3
+ export declare function getAvailableUpdates(config: Config, pkg?: string, bound?: UpdateBound): Promise<Array<[string, string, string]>>;
@@ -1,9 +1,10 @@
1
1
  import process from "node:process";
2
2
  import chalk from "chalk";
3
+ import semver from "semver";
3
4
  import { mainActor } from "../api/actors.js";
4
5
  import { getDepName, getDepPinnedVersion } from "../helpers/get-dep-name.js";
5
6
  // [pkg, oldVersion, newVersion]
6
- export async function getAvailableUpdates(config, pkg) {
7
+ export async function getAvailableUpdates(config, pkg, bound = "caret") {
7
8
  let deps = Object.values(config.dependencies || {});
8
9
  let devDeps = Object.values(config["dev-dependencies"] || {});
9
10
  let allDeps = [...deps, ...devDeps].filter((dep) => dep.version);
@@ -34,7 +35,12 @@ export async function getAvailableUpdates(config, pkg) {
34
35
  ? { minor: null }
35
36
  : { patch: null };
36
37
  }
37
- return [name, dep.version || "", semverPart];
38
+ else if (bound === "caret") {
39
+ // Caret (cargo-style): ^0.x.y -> 0.x.* (patch only); ^1+ -> same major (minor+patch)
40
+ let major = semver.major(dep.version);
41
+ semverPart = major === 0 ? { patch: null } : { minor: null };
42
+ }
43
+ return [name, dep.version, semverPart];
38
44
  }));
39
45
  if ("err" in res) {
40
46
  console.log(chalk.red("Error:"), res.err);
@@ -1 +1,3 @@
1
- export declare function outdated(): Promise<void>;
1
+ export declare function outdated({ major }?: {
2
+ major?: boolean;
3
+ }): Promise<void>;
@@ -2,12 +2,12 @@ import chalk from "chalk";
2
2
  import { checkConfigFile, readConfig } from "../mops.js";
3
3
  import { getAvailableUpdates } from "./available-updates.js";
4
4
  import { getDepName, getDepPinnedVersion } from "../helpers/get-dep-name.js";
5
- export async function outdated() {
5
+ export async function outdated({ major } = {}) {
6
6
  if (!checkConfigFile()) {
7
7
  return;
8
8
  }
9
9
  let config = readConfig();
10
- let available = await getAvailableUpdates(config);
10
+ let available = await getAvailableUpdates(config, undefined, major ? "major" : "caret");
11
11
  if (available.length === 0) {
12
12
  console.log(chalk.green("All dependencies are up to date!"));
13
13
  }
@@ -2,6 +2,7 @@ type UpdateOptions = {
2
2
  verbose?: boolean;
3
3
  dev?: boolean;
4
4
  lock?: "update" | "ignore";
5
+ major?: boolean;
5
6
  };
6
- export declare function update(pkg?: string, { lock }?: UpdateOptions): Promise<void>;
7
+ export declare function update(pkg?: string, { lock, major }?: UpdateOptions): Promise<void>;
7
8
  export {};
@@ -4,7 +4,7 @@ import { add } from "./add.js";
4
4
  import { getAvailableUpdates } from "./available-updates.js";
5
5
  import { checkIntegrity } from "../integrity.js";
6
6
  import { getDepName, getDepPinnedVersion } from "../helpers/get-dep-name.js";
7
- export async function update(pkg, { lock } = {}) {
7
+ export async function update(pkg, { lock, major } = {}) {
8
8
  if (!checkConfigFile()) {
9
9
  return;
10
10
  }
@@ -36,7 +36,7 @@ export async function update(pkg, { lock } = {}) {
36
36
  }
37
37
  }
38
38
  // update mops packages
39
- let available = await getAvailableUpdates(config, pkg);
39
+ let available = await getAvailableUpdates(config, pkg, major ? "major" : "caret");
40
40
  if (available.length === 0) {
41
41
  if (pkg) {
42
42
  console.log(chalk.green(`Package "${pkg}" is up to date!`));
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "2.12.3",
3
+ "version": "2.13.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "bin/mops.js",
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, jest, test } from "@jest/globals";
2
2
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import path from "path";
4
- import { cli } from "./helpers";
4
+ import { cli, normalizePaths } from "./helpers";
5
5
  describe("cli", () => {
6
6
  test("--version", async () => {
7
7
  expect((await cli(["--version"])).stdout).toMatch(/CLI \d+\.\d+\.\d+/);
@@ -117,3 +117,78 @@ describe("install", () => {
117
117
  }
118
118
  });
119
119
  });
120
+ // `mops update` and `mops outdated` default to caret-bound resolution: stay
121
+ // within `0.x.y` (or `1.x.y`) and never cross majors. Fixture pins:
122
+ // base = "0.14.5" -> caret bumps within 0.14.x; --major jumps past it
123
+ // core = "1.0.0" -> caret stays put (no 1.x.y > 1.0.0); --major jumps to 2.x
124
+ describe("update / outdated bounds", () => {
125
+ jest.setTimeout(120_000);
126
+ const cwd = path.join(import.meta.dirname, "install/update-bound");
127
+ const tomlFile = path.join(cwd, "mops.toml");
128
+ const original = readFileSync(tomlFile, "utf8");
129
+ const cleanup = () => {
130
+ rmSync(path.join(cwd, "mops.lock"), { force: true });
131
+ rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
132
+ writeFileSync(tomlFile, original);
133
+ };
134
+ const baseVersion = (toml) => toml.match(/base = "(0\.\d+\.\d+)"/)?.[1];
135
+ const coreMajor = (toml) => parseInt(toml.match(/core = "(\d+)\./)?.[1] ?? "0");
136
+ test("mops update stays within the caret bound by default", async () => {
137
+ cleanup();
138
+ try {
139
+ await cli(["install"], { cwd, env: { CI: undefined } });
140
+ const result = await cli(["update"], { cwd, env: { CI: undefined } });
141
+ expect(result.exitCode).toBe(0);
142
+ const after = readFileSync(tomlFile, "utf8");
143
+ // base (pre-1.0): bumped within 0.14.x (patch bumps allowed)
144
+ expect(baseVersion(after)).toMatch(/^0\.14\./);
145
+ expect(baseVersion(after)).not.toBe("0.14.5");
146
+ // core (1.x): no 1.x.y > 1.0.0 published, so no bump across majors
147
+ expect(coreMajor(after)).toBe(1);
148
+ }
149
+ finally {
150
+ cleanup();
151
+ }
152
+ });
153
+ test("mops update --major crosses the caret bound", async () => {
154
+ cleanup();
155
+ try {
156
+ await cli(["install"], { cwd, env: { CI: undefined } });
157
+ const result = await cli(["update", "--major"], {
158
+ cwd,
159
+ env: { CI: undefined },
160
+ });
161
+ expect(result.exitCode).toBe(0);
162
+ const after = readFileSync(tomlFile, "utf8");
163
+ // base: jumps past 0.14.x (next minor or major)
164
+ const baseMinor = parseInt(after.match(/base = "0\.(\d+)\./)?.[1] ?? "0");
165
+ expect(baseMinor).toBeGreaterThanOrEqual(15);
166
+ // core: jumps to 2.x or later
167
+ expect(coreMajor(after)).toBeGreaterThanOrEqual(2);
168
+ }
169
+ finally {
170
+ cleanup();
171
+ }
172
+ });
173
+ test("mops outdated honors --major flag", async () => {
174
+ cleanup();
175
+ try {
176
+ await cli(["install"], { cwd, env: { CI: undefined } });
177
+ const caret = normalizePaths((await cli(["outdated"], { cwd, env: { CI: undefined } })).stdout);
178
+ const major = normalizePaths((await cli(["outdated", "--major"], { cwd, env: { CI: undefined } }))
179
+ .stdout);
180
+ // caret-bound: base bumps within 0.14.x; core (if reported) stays in 1.x
181
+ expect(caret).toMatch(/base 0\.14\.5 -> 0\.14\./);
182
+ const caretCore = caret.match(/core 1\.0\.0 -> (\d+)\./)?.[1];
183
+ if (caretCore) {
184
+ expect(parseInt(caretCore)).toBe(1);
185
+ }
186
+ // --major: both bump across their major bounds
187
+ expect(major).toMatch(/base 0\.14\.5 -> 0\.(1[5-9]|[2-9]\d)/);
188
+ expect(major).toMatch(/core 1\.0\.0 -> [2-9]/);
189
+ }
190
+ finally {
191
+ cleanup();
192
+ }
193
+ });
194
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "2.12.3",
3
+ "version": "2.13.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/bin/mops.js",
package/tests/cli.test.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, jest, test } from "@jest/globals";
2
2
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import path from "path";
4
- import { cli } from "./helpers";
4
+ import { cli, normalizePaths } from "./helpers";
5
5
 
6
6
  describe("cli", () => {
7
7
  test("--version", async () => {
@@ -129,3 +129,88 @@ describe("install", () => {
129
129
  }
130
130
  });
131
131
  });
132
+
133
+ // `mops update` and `mops outdated` default to caret-bound resolution: stay
134
+ // within `0.x.y` (or `1.x.y`) and never cross majors. Fixture pins:
135
+ // base = "0.14.5" -> caret bumps within 0.14.x; --major jumps past it
136
+ // core = "1.0.0" -> caret stays put (no 1.x.y > 1.0.0); --major jumps to 2.x
137
+ describe("update / outdated bounds", () => {
138
+ jest.setTimeout(120_000);
139
+
140
+ const cwd = path.join(import.meta.dirname, "install/update-bound");
141
+ const tomlFile = path.join(cwd, "mops.toml");
142
+ const original = readFileSync(tomlFile, "utf8");
143
+
144
+ const cleanup = () => {
145
+ rmSync(path.join(cwd, "mops.lock"), { force: true });
146
+ rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
147
+ writeFileSync(tomlFile, original);
148
+ };
149
+
150
+ const baseVersion = (toml: string) =>
151
+ toml.match(/base = "(0\.\d+\.\d+)"/)?.[1];
152
+ const coreMajor = (toml: string) =>
153
+ parseInt(toml.match(/core = "(\d+)\./)?.[1] ?? "0");
154
+
155
+ test("mops update stays within the caret bound by default", async () => {
156
+ cleanup();
157
+ try {
158
+ await cli(["install"], { cwd, env: { CI: undefined } });
159
+ const result = await cli(["update"], { cwd, env: { CI: undefined } });
160
+ expect(result.exitCode).toBe(0);
161
+ const after = readFileSync(tomlFile, "utf8");
162
+ // base (pre-1.0): bumped within 0.14.x (patch bumps allowed)
163
+ expect(baseVersion(after)).toMatch(/^0\.14\./);
164
+ expect(baseVersion(after)).not.toBe("0.14.5");
165
+ // core (1.x): no 1.x.y > 1.0.0 published, so no bump across majors
166
+ expect(coreMajor(after)).toBe(1);
167
+ } finally {
168
+ cleanup();
169
+ }
170
+ });
171
+
172
+ test("mops update --major crosses the caret bound", async () => {
173
+ cleanup();
174
+ try {
175
+ await cli(["install"], { cwd, env: { CI: undefined } });
176
+ const result = await cli(["update", "--major"], {
177
+ cwd,
178
+ env: { CI: undefined },
179
+ });
180
+ expect(result.exitCode).toBe(0);
181
+ const after = readFileSync(tomlFile, "utf8");
182
+ // base: jumps past 0.14.x (next minor or major)
183
+ const baseMinor = parseInt(after.match(/base = "0\.(\d+)\./)?.[1] ?? "0");
184
+ expect(baseMinor).toBeGreaterThanOrEqual(15);
185
+ // core: jumps to 2.x or later
186
+ expect(coreMajor(after)).toBeGreaterThanOrEqual(2);
187
+ } finally {
188
+ cleanup();
189
+ }
190
+ });
191
+
192
+ test("mops outdated honors --major flag", async () => {
193
+ cleanup();
194
+ try {
195
+ await cli(["install"], { cwd, env: { CI: undefined } });
196
+ const caret = normalizePaths(
197
+ (await cli(["outdated"], { cwd, env: { CI: undefined } })).stdout,
198
+ );
199
+ const major = normalizePaths(
200
+ (await cli(["outdated", "--major"], { cwd, env: { CI: undefined } }))
201
+ .stdout,
202
+ );
203
+ // caret-bound: base bumps within 0.14.x; core (if reported) stays in 1.x
204
+ expect(caret).toMatch(/base 0\.14\.5 -> 0\.14\./);
205
+ const caretCore = caret.match(/core 1\.0\.0 -> (\d+)\./)?.[1];
206
+ if (caretCore) {
207
+ expect(parseInt(caretCore)).toBe(1);
208
+ }
209
+ // --major: both bump across their major bounds
210
+ expect(major).toMatch(/base 0\.14\.5 -> 0\.(1[5-9]|[2-9]\d)/);
211
+ expect(major).toMatch(/core 1\.0\.0 -> [2-9]/);
212
+ } finally {
213
+ cleanup();
214
+ }
215
+ });
216
+ });
@@ -0,0 +1,3 @@
1
+ [dependencies]
2
+ base = "0.14.5"
3
+ core = "1.0.0"