@stim-cli/core 1.22.0 → 1.23.1

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/dist/state.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { S as tryAcquireClaim, _ as releaseClaim, f as isClaimRefusal, g as readClaimSet } from "./ownership-claim-DHUs_Dir.mjs";
2
2
  import { inspectProcessIdentity } from "./process-identity.mjs";
3
- import { C as workspaceName, H as ambientContext, J as readJsonObject, K as isJsonObject, Q as withDirLockAsync, T as workspaceStateDir, U as ambientEnv, X as withStateReadCache, Y as readStateFile, Z as withDirLock, _ as rotatedLogPath, d as ensurePrivateDir, g as rotateLog, q as readJsonFile, t as LOG_ROTATE_BYTES, u as configDir } from "./core-BTsCO1nW.mjs";
3
+ import { C as workspaceName, H as ambientContext, J as readJsonObject, K as isJsonObject, Q as withDirLockAsync, T as workspaceStateDir, U as ambientEnv, X as withStateReadCache, Y as readStateFile, Z as withDirLock, _ as rotatedLogPath, d as ensurePrivateDir, g as rotateLog, q as readJsonFile, t as LOG_ROTATE_BYTES, u as configDir } from "./core-B23eUZZp.mjs";
4
4
  import { readOrCreateKeyFile } from "./noise-session.mjs";
5
5
  import { validHomeId } from "./workspace-link.mjs";
6
6
  import { a as workspaceSummary, i as statusViews, n as applyKeyedDelta, o as withDerivedFacts, r as SUMMARY_ARCHIVES, s as withGitChip, t as KeyedEntities } from "./keyed-delta-BBoOeR2J.mjs";
@@ -8,6 +8,8 @@ interface ToolchainProbe {
8
8
  isFile(path: string): boolean;
9
9
  read(path: string): string | null;
10
10
  list(path: string): string[];
11
+ /** The target of the symbolic link at `path`, or null when it is not one. */
12
+ link(path: string): string | null;
11
13
  }
12
14
  declare function defaultToolchainProbe(): ToolchainProbe;
13
15
  /**
@@ -27,6 +29,11 @@ interface Toolchain {
27
29
  }
28
30
  /** Whether Node `version`, such as `22.12.0`, satisfies a pin such as `22`, `v22.12` or `22.12.0`. */
29
31
  declare function satisfiesNodePin(version: string, spec: string): boolean;
32
+ /**
33
+ * Whether Node `version` is an acceptable stand-in for a pin no install matches: the pin's major, and not older than
34
+ * the version the pin names.
35
+ */
36
+ declare function meetsNodePinMajor(version: string, spec: string): boolean;
30
37
  /** The `bin` directory of the Node a version manager makes the default in new shells, and where that came from. */
31
38
  declare function defaultNodeBin(env: NodeJS.ProcessEnv, probe: ToolchainProbe): {
32
39
  bin: string;
@@ -44,21 +51,29 @@ declare function discoverToolchain(base: NodeJS.ProcessEnv, probe?: ToolchainPro
44
51
  runningNode?: boolean;
45
52
  platform?: NodeJS.Platform;
46
53
  }): Toolchain;
47
- /** The Node version `root` pins, from `.nvmrc`, `.node-version`, `.tool-versions` or `package.json`'s `volta.node`. */
48
- declare function projectNodePin(root: string, probe?: ToolchainProbe): {
54
+ /**
55
+ * The Node version pinned by `.nvmrc`, `.node-version`, `.tool-versions` or `package.json`'s `volta.node` in `root`,
56
+ * or else in the nearest directory above it up to `top`.
57
+ */
58
+ declare function projectNodePin(root: string, probe?: ToolchainProbe, top?: string): {
49
59
  spec: string;
50
60
  file: string;
51
61
  } | null;
62
+ /** A pin's version spec, with an `lts/...` pin resolved through nvm's aliases, or null when nvm cannot resolve it. */
63
+ declare function resolveNodePinSpec(spec: string, env: NodeJS.ProcessEnv, probe?: ToolchainProbe): string | null;
52
64
  /**
53
- * The `bin` directory of an installed Node that satisfies `root`'s pin, from nvm, fnm, Volta, asdf or mise, or
54
- * nil when the project pins none or no installed version satisfies the pin. An `lts/...` pin resolves through
55
- * nvm's aliases.
65
+ * The `bin` directory of an installed Node for the pin `projectNodePin` finds, from nvm, fnm, Volta, asdf, mise or a
66
+ * linked Homebrew `node` or `node@NN`, or null when the project pins none or no install fits. `match` is `exact` for an
67
+ * install the pin names. When none is, it is `major` for the newest install of the pinned major that is not older than
68
+ * the pinned version, since Homebrew cannot install a chosen patch release. An `lts/...` pin resolves through
69
+ * `resolveNodePinSpec`.
56
70
  */
57
- declare function projectNodeBin(root: string, env: NodeJS.ProcessEnv, probe?: ToolchainProbe): {
71
+ declare function projectNodeBin(root: string, env: NodeJS.ProcessEnv, probe?: ToolchainProbe, top?: string): {
58
72
  bin: string;
59
73
  version: string;
60
74
  pin: string;
61
75
  spec: string;
76
+ match: "exact" | "major";
62
77
  } | null;
63
78
  //#endregion
64
- export { Toolchain, ToolchainEntry, ToolchainProbe, defaultNodeBin, defaultToolchainProbe, discoverToolchain, projectNodeBin, projectNodePin, satisfiesNodePin };
79
+ export { Toolchain, ToolchainEntry, ToolchainProbe, defaultNodeBin, defaultToolchainProbe, discoverToolchain, meetsNodePinMajor, projectNodeBin, projectNodePin, resolveNodePinSpec, satisfiesNodePin };
@@ -1,6 +1,6 @@
1
- import { readFileSync, readdirSync, statSync } from "node:fs";
1
+ import { readFileSync, readdirSync, readlinkSync, statSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { delimiter, dirname, join } from "node:path";
3
+ import { basename, delimiter, dirname, join } from "node:path";
4
4
  //#region toolchain.ts
5
5
  const stat = (path) => statSync(path, { throwIfNoEntry: false });
6
6
  function defaultToolchainProbe() {
@@ -22,6 +22,13 @@ function defaultToolchainProbe() {
22
22
  } catch {
23
23
  return [];
24
24
  }
25
+ },
26
+ link: (path) => {
27
+ try {
28
+ return readlinkSync(path);
29
+ } catch {
30
+ return null;
31
+ }
25
32
  }
26
33
  };
27
34
  }
@@ -38,8 +45,9 @@ function jdkRank(name) {
38
45
  return major === 17 ? 0 : major === 21 ? 1 : 1e4 - major;
39
46
  }
40
47
  const ANDROID_STUDIOS = ["/Applications/Android Studio.app", "/Applications/Android Studio Preview.app"];
48
+ /** Accepts Homebrew's Cellar names too, such as `22.23.2_1`, whose `_1` is the formula revision. */
41
49
  function parseVersion(text) {
42
- const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(text.trim());
50
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:_\d+)?$/.exec(text.trim());
43
51
  return match ? [
44
52
  Number(match[1]),
45
53
  Number(match[2]),
@@ -69,6 +77,24 @@ function newestMatching(names, wanted) {
69
77
  function satisfiesNodePin(version, spec) {
70
78
  return newestMatching([version], spec) !== null;
71
79
  }
80
+ /** The lowest version a pin such as `22.22` or `v22.22.0` names, or null for a pin that names only a major or no number. */
81
+ function pinFloor(spec) {
82
+ const match = /^v?(\d+)\.(\d+)(?:\.(\d+))?$/.exec(spec.trim());
83
+ return match ? [
84
+ Number(match[1]),
85
+ Number(match[2]),
86
+ Number(match[3] ?? 0)
87
+ ] : null;
88
+ }
89
+ /**
90
+ * Whether Node `version` is an acceptable stand-in for a pin no install matches: the pin's major, and not older than
91
+ * the version the pin names.
92
+ */
93
+ function meetsNodePinMajor(version, spec) {
94
+ const parsed = parseVersion(version);
95
+ const floor = pinFloor(spec);
96
+ return !!parsed && !!floor && parsed[0] === floor[0] && compareVersions(parsed, floor) >= 0;
97
+ }
72
98
  function matchesPrefix(version, spec) {
73
99
  if (spec === "" || spec === "node" || spec === "stable") return true;
74
100
  const parts = spec.split(".");
@@ -254,8 +280,17 @@ function javaEntry(env, probe) {
254
280
  source: "missing"
255
281
  };
256
282
  }
257
- /** The Node version `root` pins, from `.nvmrc`, `.node-version`, `.tool-versions` or `package.json`'s `volta.node`. */
258
- function projectNodePin(root, probe = defaultToolchainProbe()) {
283
+ /**
284
+ * The Node version pinned by `.nvmrc`, `.node-version`, `.tool-versions` or `package.json`'s `volta.node` in `root`,
285
+ * or else in the nearest directory above it up to `top`.
286
+ */
287
+ function projectNodePin(root, probe = defaultToolchainProbe(), top = root) {
288
+ for (let dir = root;; dir = dirname(dir)) {
289
+ const pin = nodePinIn(dir, probe);
290
+ if (pin || dir === top || dirname(dir) === dir) return pin;
291
+ }
292
+ }
293
+ function nodePinIn(root, probe) {
259
294
  for (const file of [".nvmrc", ".node-version"]) {
260
295
  const line = probe.read(join(root, file))?.split("\n").map((text) => text.trim()).find((text) => text && !text.startsWith("#"));
261
296
  if (line) return {
@@ -275,52 +310,78 @@ function projectNodePin(root, probe = defaultToolchainProbe()) {
275
310
  file: "package.json"
276
311
  } : null;
277
312
  }
313
+ /** A pin's version spec, with an `lts/...` pin resolved through nvm's aliases, or null when nvm cannot resolve it. */
314
+ function resolveNodePinSpec(spec, env, probe = defaultToolchainProbe()) {
315
+ if (!spec.startsWith("lts/")) return spec;
316
+ const resolved = nvmAlias(nvmDir(env, probe), spec, probe);
317
+ return resolved.startsWith("lts/") ? null : resolved;
318
+ }
278
319
  /**
279
- * The `bin` directory of an installed Node that satisfies `root`'s pin, from nvm, fnm, Volta, asdf or mise, or
280
- * nil when the project pins none or no installed version satisfies the pin. An `lts/...` pin resolves through
281
- * nvm's aliases.
320
+ * The `bin` directory of an installed Node for the pin `projectNodePin` finds, from nvm, fnm, Volta, asdf, mise or a
321
+ * linked Homebrew `node` or `node@NN`, or null when the project pins none or no install fits. `match` is `exact` for an
322
+ * install the pin names. When none is, it is `major` for the newest install of the pinned major that is not older than
323
+ * the pinned version, since Homebrew cannot install a chosen patch release. An `lts/...` pin resolves through
324
+ * `resolveNodePinSpec`.
282
325
  */
283
- function projectNodeBin(root, env, probe = defaultToolchainProbe()) {
284
- const pin = projectNodePin(root, probe);
326
+ function projectNodeBin(root, env, probe = defaultToolchainProbe(), top = root) {
327
+ const pin = projectNodePin(root, probe, top);
285
328
  if (!pin) return null;
329
+ const spec = resolveNodePinSpec(pin.spec, env, probe);
330
+ if (!spec) return null;
286
331
  const nvm = nvmDir(env, probe);
287
- const spec = pin.spec.startsWith("lts/") ? nvmAlias(nvm, pin.spec, probe) : pin.spec;
288
- const stores = [
332
+ const asdf = join(env.ASDF_DATA_DIR || join(probe.home, ".asdf"), "installs", "nodejs");
333
+ const groups = [
289
334
  {
290
335
  dir: join(nvm, "versions", "node"),
291
336
  bin: (name) => join(nvm, "versions", "node", name, "bin")
292
337
  },
293
- {
294
- dir: join(probe.home, ".local", "share", "fnm", "node-versions"),
295
- bin: (name) => join(probe.home, ".local", "share", "fnm", "node-versions", name, "installation", "bin")
296
- },
297
- {
298
- dir: join(probe.home, "Library", "Application Support", "fnm", "node-versions"),
299
- bin: (name) => join(probe.home, "Library", "Application Support", "fnm", "node-versions", name, "installation", "bin")
300
- },
338
+ ...[join(probe.home, ".local", "share", "fnm"), join(probe.home, "Library", "Application Support", "fnm")].map((fnm) => ({
339
+ dir: join(fnm, "node-versions"),
340
+ bin: (name) => join(fnm, "node-versions", name, "installation", "bin")
341
+ })),
301
342
  {
302
343
  dir: join(probe.home, ".volta", "tools", "image", "node"),
303
344
  bin: (name) => join(probe.home, ".volta", "tools", "image", "node", name, "bin")
304
345
  },
305
346
  {
306
- dir: join(env.ASDF_DATA_DIR || join(probe.home, ".asdf"), "installs", "nodejs"),
307
- bin: (name) => join(env.ASDF_DATA_DIR || join(probe.home, ".asdf"), "installs", "nodejs", name, "bin")
347
+ dir: asdf,
348
+ bin: (name) => join(asdf, name, "bin")
308
349
  },
309
350
  {
310
351
  dir: join(probe.home, ".local", "share", "mise", "installs", "node"),
311
352
  bin: (name) => join(probe.home, ".local", "share", "mise", "installs", "node", name, "bin")
312
353
  }
313
- ];
314
- for (const store of stores) {
315
- const name = newestMatching(probe.list(store.dir), spec);
316
- if (name && probe.isFile(join(store.bin(name), "node"))) return {
317
- bin: store.bin(name),
318
- version: name.replace(/^v/, ""),
319
- pin: pin.file,
320
- spec
354
+ ].map(({ dir, bin }) => probe.list(dir).map((name) => ({
355
+ name,
356
+ bin: bin(name)
357
+ })));
358
+ groups.push(HOMEBREW_PREFIXES.flatMap((prefix) => probe.list(join(prefix, "opt")).filter((keg) => /^node(@\d+)?$/.test(keg)).map((keg) => ({
359
+ name: basename(probe.link(join(prefix, "opt", keg)) ?? ""),
360
+ bin: join(prefix, "opt", keg, "bin")
361
+ }))));
362
+ const installs = groups.map((group) => group.filter(({ bin }) => probe.isFile(join(bin, "node"))));
363
+ const found = (install, match) => ({
364
+ bin: install.bin,
365
+ version: install.name.replace(/^v/, "").replace(/_\d+$/, ""),
366
+ pin: pin.file,
367
+ spec,
368
+ match
369
+ });
370
+ for (const group of installs) {
371
+ const name = newestMatching(group.map((install) => install.name), spec);
372
+ const install = group.find((candidate) => candidate.name === name);
373
+ if (install) return found(install, "exact");
374
+ }
375
+ let fallback = null;
376
+ for (const install of installs.flat()) {
377
+ const version = parseVersion(install.name);
378
+ if (!version || !meetsNodePinMajor(install.name, spec)) continue;
379
+ if (!fallback || compareVersions(version, fallback.version) > 0) fallback = {
380
+ install,
381
+ version
321
382
  };
322
383
  }
323
- return null;
384
+ return fallback ? found(fallback.install, "major") : null;
324
385
  }
325
386
  //#endregion
326
- export { defaultNodeBin, defaultToolchainProbe, discoverToolchain, projectNodeBin, projectNodePin, satisfiesNodePin };
387
+ export { defaultNodeBin, defaultToolchainProbe, discoverToolchain, meetsNodePinMajor, projectNodeBin, projectNodePin, resolveNodePinSpec, satisfiesNodePin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stim-cli/core",
3
- "version": "1.22.0",
3
+ "version": "1.23.1",
4
4
  "description": "Shared internal cache and runtime primitives for Stim packages.",
5
5
  "license": "MIT",
6
6
  "repository": {