@stim-cli/core 1.23.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.
@@ -1,4 +1,5 @@
1
1
  import { C as quotedPath, S as tryAcquireClaim, _ as releaseClaim, r as ClaimRefusedError, x as sleepSync } from "./ownership-claim-DHUs_Dir.mjs";
2
+ import { projectNodeBin } from "./toolchain.mjs";
2
3
  import fs, { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
3
4
  import os from "node:os";
4
5
  import path, { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
@@ -566,8 +567,12 @@ async function compiledHelper({ dir, name, inputs, version, compile, prune = tru
566
567
  }
567
568
  //#endregion
568
569
  //#region dependency-install.ts
569
- /** The existing worker install command and marker identity for a repository's selected lockfile. */
570
- function dependencyInstall(root, packageName) {
570
+ /**
571
+ * The existing worker install command and marker identity for a repository's selected lockfile. The marker identity
572
+ * also names the major of the Node `projectNodeBin` picks for the pin of the project at `project`, or `path` when it
573
+ * picks none, so that native addons are rebuilt when the pinned Node changes major.
574
+ */
575
+ function dependencyInstall(root, packageName, project = ".", env = process.env) {
571
576
  const file = [
572
577
  "pnpm-lock.yaml",
573
578
  "yarn.lock",
@@ -581,21 +586,22 @@ function dependencyInstall(root, packageName) {
581
586
  ...packageName ? ["--filter", `${packageName}...`] : []
582
587
  ] : command === "yarn" ? ["install", "--frozen-lockfile"] : ["ci"];
583
588
  const bytes = readFileSync(join(root, file));
589
+ const node = projectNodeBin(join(root, project), env, void 0, root)?.version.split(".")[0] ?? "path";
584
590
  return {
585
591
  command,
586
592
  args,
587
593
  lockfile: createHash("sha256").update(bytes).digest("hex"),
588
- digest: createHash("sha256").update(bytes).update(JSON.stringify(args)).digest("hex")
594
+ digest: createHash("sha256").update(bytes).update(JSON.stringify(args)).update(`node ${node}`).digest("hex")
589
595
  };
590
596
  }
591
597
  /** Whether the worker can reuse this project's dependency install without running the package manager. */
592
- function reusableDependencies(area, project, packageName, lockfile) {
598
+ function reusableDependencies(area, project, packageName, lockfile, env = process.env) {
593
599
  try {
594
600
  const root = realpathSync(join(area, "src"));
595
601
  const selected = realpathSync(join(root, project));
596
602
  const path = relative(root, selected);
597
603
  if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path)) return false;
598
- const install = dependencyInstall(root, packageName);
604
+ const install = dependencyInstall(root, packageName, path, env);
599
605
  return install?.lockfile === lockfile && existsSync(join(selected, "node_modules")) && readFileSync(join(area, "dependencies"), "utf8") === install.digest;
600
606
  } catch {
601
607
  return false;
package/dist/index.d.mts CHANGED
@@ -116,15 +116,19 @@ declare function compiledHelper({ dir, name, inputs, version, compile, prune }:
116
116
  }): Promise<string>;
117
117
  //#endregion
118
118
  //#region dependency-install.d.ts
119
- /** The existing worker install command and marker identity for a repository's selected lockfile. */
120
- declare function dependencyInstall(root: string, packageName: string | null): {
119
+ /**
120
+ * The existing worker install command and marker identity for a repository's selected lockfile. The marker identity
121
+ * also names the major of the Node `projectNodeBin` picks for the pin of the project at `project`, or `path` when it
122
+ * picks none, so that native addons are rebuilt when the pinned Node changes major.
123
+ */
124
+ declare function dependencyInstall(root: string, packageName: string | null, project?: string, env?: NodeJS.ProcessEnv): {
121
125
  command: "pnpm" | "yarn" | "npm";
122
126
  args: string[];
123
127
  lockfile: string;
124
128
  digest: string;
125
129
  } | null;
126
130
  /** Whether the worker can reuse this project's dependency install without running the package manager. */
127
- declare function reusableDependencies(area: string, project: string, packageName: string | null, lockfile: string): boolean;
131
+ declare function reusableDependencies(area: string, project: string, packageName: string | null, lockfile: string, env?: NodeJS.ProcessEnv): boolean;
128
132
  //#endregion
129
133
  //#region metro-bridge.d.ts
130
134
  interface MetroBridge {
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { C as quotedPath } from "./ownership-claim-DHUs_Dir.mjs";
2
- import { $ as withDirLockTransaction, A as createMetroGateway, B as unitWords, C as workspaceName, D as reactNativeArtifactIdentity, E as swiftpmArtifactQuery, F as artifactIn, G as withAmbientContext, H as ambientContext, I as resolveArtifact, L as storeArtifact, M as reusableDependencies, N as compiledHelper, O as reactNativeArtifactQuery, P as storeArtifactAsync, Q as withDirLockAsync, R as quoteUnitWord, S as workspaceLogDir, T as workspaceStateDir, U as ambientEnv, V as validMacosResourceDestination, W as ambientWrite, Z as withDirLock, _ as rotatedLogPath, a as buildCacheRoot, b as updateCacheManifest, c as cacheNameSegment, d as ensurePrivateDir, f as hostSimulatorArch, g as rotateLog, h as registerCache, i as buildCacheKey, j as dependencyInstall, k as createMetroBridge, l as cachePathSetting, m as readCacheManifest, n as METRO_NAMED_CACHE_LAYOUT, o as cacheDirRefusal, p as metroCacheRoot, r as STORE_ROOT_TAG, s as cacheManifestLockPath, t as LOG_ROTATE_BYTES, u as configDir, v as sharedStoreRoot, w as workspaceSlug, x as workspaceId, y as tagSharedStore, z as unitLines } from "./core-BTsCO1nW.mjs";
2
+ import { $ as withDirLockTransaction, A as createMetroGateway, B as unitWords, C as workspaceName, D as reactNativeArtifactIdentity, E as swiftpmArtifactQuery, F as artifactIn, G as withAmbientContext, H as ambientContext, I as resolveArtifact, L as storeArtifact, M as reusableDependencies, N as compiledHelper, O as reactNativeArtifactQuery, P as storeArtifactAsync, Q as withDirLockAsync, R as quoteUnitWord, S as workspaceLogDir, T as workspaceStateDir, U as ambientEnv, V as validMacosResourceDestination, W as ambientWrite, Z as withDirLock, _ as rotatedLogPath, a as buildCacheRoot, b as updateCacheManifest, c as cacheNameSegment, d as ensurePrivateDir, f as hostSimulatorArch, g as rotateLog, h as registerCache, i as buildCacheKey, j as dependencyInstall, k as createMetroBridge, l as cachePathSetting, m as readCacheManifest, n as METRO_NAMED_CACHE_LAYOUT, o as cacheDirRefusal, p as metroCacheRoot, r as STORE_ROOT_TAG, s as cacheManifestLockPath, t as LOG_ROTATE_BYTES, u as configDir, v as sharedStoreRoot, w as workspaceSlug, x as workspaceId, y as tagSharedStore, z as unitLines } from "./core-B23eUZZp.mjs";
3
3
  export { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, STORE_ROOT_TAG, ambientContext, ambientEnv, ambientWrite, artifactIn, buildCacheKey, buildCacheRoot, cacheDirRefusal, cacheManifestLockPath, cacheNameSegment, cachePathSetting, compiledHelper, configDir, createMetroBridge, createMetroGateway, dependencyInstall, ensurePrivateDir, hostSimulatorArch, metroCacheRoot, quoteUnitWord, quotedPath, reactNativeArtifactIdentity, reactNativeArtifactQuery, readCacheManifest, registerCache, resolveArtifact, reusableDependencies, rotateLog, rotatedLogPath, sharedStoreRoot, storeArtifact, storeArtifactAsync, swiftpmArtifactQuery, tagSharedStore, unitLines, unitWords, updateCacheManifest, validMacosResourceDestination, withAmbientContext, withDirLock, withDirLockAsync, withDirLockTransaction, workspaceId, workspaceLogDir, workspaceName, workspaceSlug, workspaceStateDir };
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.23.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": {