stim 1.11.0 → 1.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.
Files changed (82) hide show
  1. package/README.md +3 -1
  2. package/dist/{activity-BtG4Va9l.mjs → activity-DfVKZTEB.mjs} +44 -26
  3. package/dist/{android-BA2i_KAV.mjs → android-BHfAu2mf.mjs} +1 -1
  4. package/dist/{android-C1q4l523.mjs → android-CD8pr8Q2.mjs} +112 -54
  5. package/dist/{android-cas-DfJkwCXW.mjs → android-cas-B9Kv9aIy.mjs} +12 -10
  6. package/dist/{android-C4zG2Tuc.mjs → android-qx_31wJo.mjs} +414 -118
  7. package/dist/{app-install-DHut6JnV.mjs → app-install-CDFY2nUL.mjs} +1 -1
  8. package/dist/budget-E5o91NR1.mjs +1739 -0
  9. package/dist/{build-plan-CeAYgKAx.mjs → build-plan-BTa93o_r.mjs} +125 -107
  10. package/dist/{build-progress-CDwogz6R.mjs → build-progress-l7d-X-ZP.mjs} +76 -8
  11. package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cdp-Dq_ypAlJ.mjs +115 -0
  14. package/dist/chrome-B5hWgQJS.mjs +61 -0
  15. package/dist/cli.mjs +17 -16
  16. package/dist/collector-run.mjs +15 -9
  17. package/dist/{command-output-D5z2_bcF.mjs → command-output-BIL7rlX7.mjs} +1 -1
  18. package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
  19. package/dist/created-devices-Cd558h31.mjs +37 -0
  20. package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
  21. package/dist/{deps-OF7CD5Dx.mjs → deps-2Wo81np9.mjs} +2 -2
  22. package/dist/{device-BwH2iI3O.mjs → device-Cf7vl-so.mjs} +7 -7
  23. package/dist/{device-lease-D83nqeMX.mjs → device-lease-CL9dZf73.mjs} +55 -6
  24. package/dist/{device-pool-DHj7cdPy.mjs → device-pool-MuoiyRcu.mjs} +4 -4
  25. package/dist/{device-remote-BQZSnnmE.mjs → device-remote-DT9ngZiD.mjs} +254 -579
  26. package/dist/{doctor-D8QdMRMf.mjs → doctor-D2iysNNH.mjs} +50 -20
  27. package/dist/{error-diagnostics-UYFSQwRy.mjs → error-diagnostics-BoiOhck8.mjs} +7 -7
  28. package/dist/{gc-COZteyiq.mjs → gc-eA-PRBX8.mjs} +1022 -131
  29. package/dist/{guide-NDvTmMi-.mjs → guide-BfZ0OmiP.mjs} +909 -132
  30. package/dist/{guide-status-CWvti2Lg.mjs → guide-status-BaMYu55M.mjs} +2 -2
  31. package/dist/{idle-NETbeNiQ.mjs → idle-DxLi5OkN.mjs} +43 -10
  32. package/dist/{in-use-DVhlSoyn.mjs → in-use-wYEaKQM9.mjs} +11 -7
  33. package/dist/{ios-CQLKpNfr.mjs → ios-CcfCeZ19.mjs} +103 -58
  34. package/dist/{ios-DArbmwer.mjs → ios-CmEzZiOj.mjs} +31 -22
  35. package/dist/{ios-device-CwDZo0X4.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
  36. package/dist/{launch-verify-CuPDGXdr.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
  37. package/dist/{logs-B44a1UeU.mjs → logs-DcSRbC2t.mjs} +12 -11
  38. package/dist/{merge-state-DDxS-XIZ.mjs → merge-state-ytFQCeh4.mjs} +38 -8
  39. package/dist/{metro-BWLOJ_Wr.mjs → metro-BPeY1YMh.mjs} +5 -5
  40. package/dist/{named-ports-Dc7neAlc.mjs → named-ports-n7tnJsI8.mjs} +78 -6
  41. package/dist/native-runtime-CiiFHt0e.mjs +74 -0
  42. package/dist/{ownership-D2xbLVoz.mjs → ownership-Bz2kETAS.mjs} +2 -2
  43. package/dist/{ownership-BiojdtAp.mjs → ownership-PUP4Dwai.mjs} +175 -217
  44. package/dist/page-BTu7I60K.mjs +29 -0
  45. package/dist/{ports-DwBaRFJs.mjs → ports-C5UutqpJ.mjs} +5 -5
  46. package/dist/{project-BXZzOABt.mjs → project-DGeclhvO.mjs} +55 -87
  47. package/dist/{reload-By9Dwsrt.mjs → reload-C-cV5yy_.mjs} +80 -21
  48. package/dist/remote-cache--aehr4Pr.mjs +526 -0
  49. package/dist/{server-bare-D_RK1RCj.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
  50. package/dist/server-expo-BeYwP_Xy.mjs +2 -0
  51. package/dist/{server-expo-o6H5gcBc.mjs → server-expo-DUCg52-K.mjs} +2 -2
  52. package/dist/{settings-C0ugALSC.mjs → settings-ssVBToDR.mjs} +29 -17
  53. package/dist/{settings-B5oFZEri.mjs → settings-xB0Frkg6.mjs} +13 -4
  54. package/dist/settings.schema.json +74 -2
  55. package/dist/{simslim-CIR3IYJO.mjs → simslim-Bbg9UWrt.mjs} +13 -6
  56. package/dist/{slot-launch-rggO1Esw.mjs → slot-launch-BIWEW-wI.mjs} +4 -4
  57. package/dist/{spawn-claims-BJwXTMPt.mjs → spawn-claims-PT8aQpCa.mjs} +1 -1
  58. package/dist/{start-BSsf2tUs.mjs → start-BZhItkiL.mjs} +110 -18
  59. package/dist/{start-i8zc9VmS.mjs → start-Rw2GD3nV.mjs} +1 -1
  60. package/dist/{state-WGdIbi_d.mjs → state-DwAU2_-D.mjs} +15 -3
  61. package/dist/state-l1pYwOre.mjs +148 -0
  62. package/dist/{stats-DBYJ5bxL.mjs → stats-CaSwmsSZ.mjs} +4 -4
  63. package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
  64. package/dist/{status-CitcBRsC.mjs → status-TJlL0HWo.mjs} +649 -116
  65. package/dist/{status-Q-WDEGCB.mjs → status-sszP4XKU.mjs} +130 -28
  66. package/dist/stim-desktop-CXt8-Z3J.mjs +40 -0
  67. package/dist/{stop-DIhrvu0B.mjs → stop-BZ2qMiBP.mjs} +50 -18
  68. package/dist/{stop-rst5mL14.mjs → stop-DskDKNTU.mjs} +2 -2
  69. package/dist/supervisor-run.mjs +11 -11
  70. package/dist/web-run.d.mts +22 -0
  71. package/dist/web-run.mjs +481 -0
  72. package/dist/web-sWXd5iek.mjs +394 -0
  73. package/dist/{worktree-DhxDTK44.mjs → worktree-3cBa-bQt.mjs} +1 -1
  74. package/dist/{worktree-KOjMX3zA.mjs → worktree-D4Do_XiQ.mjs} +229 -21
  75. package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
  76. package/dist/{xcode-B5SPD--o.mjs → xcode-LM6A8cNZ.mjs} +7 -7
  77. package/package.json +5 -5
  78. package/dist/budget-De3P2rq_.mjs +0 -620
  79. package/dist/native-run-D7yB_AsB.mjs +0 -143
  80. package/dist/server-expo-B-CCUJZj.mjs +0 -2
  81. package/dist/workspace-process-lock-Cx74RSDZ.mjs +0 -57
  82. package/dist/workspaces-COD2xCz-.mjs +0 -1013
@@ -1,12 +1,12 @@
1
- import { D as process_identity_exports, H as projectDeviceSlots, V as parseDeviceSlotOption, g as loadConfig$1, h as isPathPrefix } from "./config-BEq0Qw_a.mjs";
1
+ import { H as parseDeviceSlotOption, O as process_identity_exports, U as projectDeviceSlots, _ as loadConfig$1, g as isPathPrefix } from "./config-0jCcMM0E.mjs";
2
2
  import { n as SOURCES, t as LEVELS } from "./ndjson-CDY9IkVC.mjs";
3
- import { j as workspaceLogsDir, m as readWorkspaceState$1 } from "./metro-BWLOJ_Wr.mjs";
4
- import { o as findProjectRoot } from "./project-BXZzOABt.mjs";
5
- import { f as refuseNoProject, v as launchErrorPreview } from "./command-output-D5z2_bcF.mjs";
6
- import { i as readAgentDeviceRecords, t as agentDeviceLiveness } from "./activity-BtG4Va9l.mjs";
7
- import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-UYFSQwRy.mjs";
3
+ import { M as workspaceLogsDir, h as readWorkspaceState$1 } from "./metro-BPeY1YMh.mjs";
4
+ import { i as findCommandWorkspace } from "./project-DGeclhvO.mjs";
5
+ import { f as refuseNoProject, v as launchErrorPreview } from "./command-output-BIL7rlX7.mjs";
6
+ import { a as readAgentDeviceRecords, t as agentDeviceLiveness } from "./activity-DfVKZTEB.mjs";
7
+ import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-BoiOhck8.mjs";
8
8
  import { closeSync, fstatSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
9
- import { buildCriteria, compileGrep, fileSizes, followLogs, logFiles, parseSince, queryLogs, readLogRecords, recordMatches, sortByTs } from "@stim-cli/core/state";
9
+ import { attachExpoErrorContext, buildCriteria, compileGrep, fileSizes, followLogs, logFiles, parseSince, queryLogs, readLogRecords, recordMatches, sortByTs } from "@stim-cli/core/state";
10
10
  import { join, relative, resolve, sep } from "node:path";
11
11
  import { homedir } from "node:os";
12
12
  import chalk from "chalk";
@@ -359,8 +359,8 @@ const LEVEL_COLOURS = {
359
359
  fatal: chalk.bgRed
360
360
  };
361
361
  function logsCommand(program) {
362
- program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build, agent-device actions). Prints and exits; an existing timeline with nothing matching is a successful, empty result. Use --follow to stream.").option("--slot <name>", "Only records attributed to this device slot", parseDeviceSlotOption).option("--source <s...>", "Only these sources: metro, client, device, build, agent, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
363
- const root = findProjectRoot(process.cwd());
362
+ program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build, agent-device actions). Prints and exits; an existing timeline with nothing matching is a successful, empty result. Use --follow to stream.").option("--slot <name>", "Only records attributed to this device slot", parseDeviceSlotOption).option("--source <s...>", "Only these sources: metro, client, device, build, agent, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports and stim web browser errors. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
363
+ const root = findCommandWorkspace(process.cwd());
364
364
  if (!root) {
365
365
  refuseNoProject({ json: false });
366
366
  return;
@@ -430,7 +430,7 @@ function logsCommand(program) {
430
430
  records: timeline
431
431
  });
432
432
  const supervisorPort = readWorkspaceState$1(root)?.supervisor?.port;
433
- const records = opts.json ? rawRecords : await errorDiagnostics(rawRecords, {
433
+ const records = opts.json ? opts.errors ? attachExpoErrorContext(timeline, rawRecords, "field") : rawRecords : await errorDiagnostics(rawRecords, {
434
434
  root,
435
435
  logsDir: dir,
436
436
  port: typeof supervisorPort === "number" ? supervisorPort : null,
@@ -463,7 +463,8 @@ function logsCommand(program) {
463
463
  dir,
464
464
  offsets,
465
465
  criteria,
466
- onRecord: emit
466
+ onRecord: emit,
467
+ errorContext: opts.errors ? opts.json ? "field" : "msg" : void 0
467
468
  });
468
469
  const agentTimer = readAgent ? setInterval(() => {
469
470
  for (const record of readAgent()) if (recordMatches(record, criteria)) emit(record);
@@ -117,7 +117,9 @@ function committedOn(path, branch, head) {
117
117
  });
118
118
  }
119
119
  /**
120
- * Whether the worktree's HEAD is merged into `target`. The signals, all local git:
120
+ * Whether the worktree's HEAD is merged into `target`, and when: `mergedAt` is the committer date, in epoch
121
+ * milliseconds, of the commit on `target` that brought the work in, the latest one for a rebase merge.
122
+ * The signals, all local git:
121
123
  * - HEAD is an ancestor of the default branch, off its first-parent line, and the branch's reflog shows a commit made
122
124
  * on it that HEAD contains, so a merge commit brought the branch's own work in. A branch with no commit of its own
123
125
  * is not merged.
@@ -147,7 +149,21 @@ function mergeState(path, { ref, name }, { timeoutMs = GIT_TIMEOUT_MS } = {}) {
147
149
  timeoutMs,
148
150
  untrimmed: true
149
151
  });
150
- const patchIds = (text) => text ? git(["patch-id", "--verbatim"], text).split("\n").flatMap((line) => line.split(" ")[0] || []) : [];
152
+ const patchIdCommits = (text) => new Map(text ? git(["patch-id", "--verbatim"], text).split("\n").flatMap((line) => {
153
+ const [id, commit] = line.split(" ");
154
+ return id && commit ? [[id, commit]] : [];
155
+ }) : []);
156
+ const patchIds = (text) => [...patchIdCommits(text).keys()];
157
+ const committedAt = (commits) => {
158
+ const times = commits.length ? git([
159
+ "show",
160
+ "-s",
161
+ "--format=%ct",
162
+ ...commits
163
+ ]).split("\n").map(Number) : [];
164
+ if (!times.length || !times.every(Number.isFinite)) throw new Error(`no committer date for ${commits.join(" ")}`);
165
+ return Math.max(...times) * 1e3;
166
+ };
151
167
  const diffOptions = ["--no-color", "--no-ext-diff"];
152
168
  const noOwnCommits = notMerged(`no commits of its own beyond ${name}`);
153
169
  try {
@@ -169,11 +185,24 @@ function mergeState(path, { ref, name }, { timeoutMs = GIT_TIMEOUT_MS } = {}) {
169
185
  "--parents",
170
186
  `${head}..${ref}`
171
187
  ]).split("\n").some((line) => line.split(" ")[1] === head) || !committedOn(path, branch, head)) return noOwnCommits;
188
+ const descendants = new Set(git([
189
+ "rev-list",
190
+ "--ancestry-path",
191
+ `${head}..${ref}`
192
+ ]).split("\n"));
193
+ const landed = git([
194
+ "rev-list",
195
+ "--first-parent",
196
+ "--reverse",
197
+ `${head}..${ref}`
198
+ ]).split("\n").find((commit) => descendants.has(commit));
199
+ if (!landed) throw new Error(`no commit on ${name} merges ${head}`);
172
200
  return {
173
201
  merged: true,
174
202
  into: name,
175
203
  head,
176
- coversUnpushed: false
204
+ coversUnpushed: false,
205
+ mergedAt: committedAt([landed])
177
206
  };
178
207
  }
179
208
  const files = git([
@@ -195,12 +224,13 @@ function mergeState(path, { ref, name }, { timeoutMs = GIT_TIMEOUT_MS } = {}) {
195
224
  "--",
196
225
  ...pathspec
197
226
  ]);
198
- const upstream = new Set(patchIds(log(`${base}..${ref}`, files)));
199
- const patchEquivalent = () => ({
227
+ const upstream = patchIdCommits(log(`${base}..${ref}`, files));
228
+ const patchEquivalent = (ids) => ({
200
229
  merged: true,
201
230
  into: name,
202
231
  head,
203
- coversUnpushed: upstreamGone(path, branch)
232
+ coversUnpushed: upstreamGone(path, branch),
233
+ mergedAt: committedAt(ids.map((id) => upstream.get(id)))
204
234
  });
205
235
  const squash = patchIds(patch([
206
236
  "diff",
@@ -208,14 +238,14 @@ function mergeState(path, { ref, name }, { timeoutMs = GIT_TIMEOUT_MS } = {}) {
208
238
  base,
209
239
  head
210
240
  ]));
211
- if (squash.length === 1 && upstream.has(squash[0])) return patchEquivalent();
241
+ if (squash.length === 1 && upstream.has(squash[0])) return patchEquivalent(squash);
212
242
  if (!git([
213
243
  "rev-list",
214
244
  "--merges",
215
245
  `${base}..${head}`
216
246
  ])) {
217
247
  const own = patchIds(log(`${base}..${head}`));
218
- if (own.length && own.every((id) => upstream.has(id))) return patchEquivalent();
248
+ if (own.length && own.every((id) => upstream.has(id))) return patchEquivalent(own);
219
249
  }
220
250
  return notMerged(`not merged into ${name}`);
221
251
  } catch (error) {
@@ -1,7 +1,7 @@
1
- import { D as process_identity_exports, M as isOnMountedVolume, N as listMountedVolumes, g as loadConfig$1, n as allMetroPorts, r as claimMetroPort, y as releaseMetroPort } from "./config-BEq0Qw_a.mjs";
1
+ import { N as isOnMountedVolume, O as process_identity_exports, P as listMountedVolumes, _ as loadConfig$1, b as releaseMetroPort, n as allMetroPorts, r as claimMetroPort } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
3
  import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
- import { existsSync as existsSync$1, readlinkSync, realpathSync as realpathSync$1 } from "fs";
4
+ import { existsSync as existsSync$1, readlinkSync as readlinkSync$1, realpathSync as realpathSync$1 } from "fs";
5
5
  import { join, resolve, sep } from "path";
6
6
  import { buildCacheRoot as sharedBuildCache, withDirLock, workspaceId, workspaceName as workspaceName$1, workspaceStateDir as workspaceDir } from "@stim-cli/core";
7
7
  import { emulatorLogFile, readJsonObject, readWorkspaceState, readWorkspaceState as readWorkspaceState$1, sharedCcache, sharedCompilationCache, supervisorLogFile, supervisorPidFile, workspaceDerivedData, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock } from "@stim-cli/core/state";
@@ -296,7 +296,7 @@ async function processCwds(pids) {
296
296
  for (const pid of new Set(pids)) {
297
297
  let cwd = null;
298
298
  if (process.platform === "linux") try {
299
- cwd = readlinkSync(`/proc/${pid}/cwd`);
299
+ cwd = readlinkSync$1(`/proc/${pid}/cwd`);
300
300
  } catch {}
301
301
  if (cwd) cwds.set(pid, cwd);
302
302
  else viaLsof.push(pid);
@@ -317,7 +317,7 @@ async function processCwds(pids) {
317
317
  }
318
318
  function processCwd(pid) {
319
319
  if (process.platform === "linux") try {
320
- return readlinkSync(`/proc/${pid}/cwd`);
320
+ return readlinkSync$1(`/proc/${pid}/cwd`);
321
321
  } catch {}
322
322
  return parseLsofCwd(getExecutor().runQuiet(`lsof -a -p ${pid} -d cwd -Fn`));
323
323
  }
@@ -406,4 +406,4 @@ function killMetroTree(leader, processToken) {
406
406
  }
407
407
  }
408
408
  //#endregion
409
- export { workspaceLogErrorIndex as A, sharedCcache as C, workspaceDerivedData as D, supervisorPidFile as E, workspaceName$1 as M, workspaceStateFile as N, workspaceDir as O, withDirLock as P, sharedBuildCache as S, supervisorLogFile as T, withWorkspaceStateLock as _, processCwd as a, emulatorLogFile as b, signalProcessTree as c, reserveMetroPort as d, clearWorkspaceStateKey as f, updateWorkspaceState as g, recordWorkspaceUse as h, pidExists as i, workspaceLogsDir as j, workspaceId as k, isMetroRunning as l, readWorkspaceState$1 as m, listeningPids as n, processCwds as o, clearWorkspaceStateKeys as p, listeningPidsByPort as r, resolveProjectMetro as s, killMetroTree as t, isPortFree as u, workspaceLastUsed as v, sharedCompilationCache as w, ensureWorkspaceStorage as x, writeWorkspaceState as y };
409
+ export { workspaceId as A, sharedBuildCache as C, supervisorPidFile as D, supervisorLogFile as E, withDirLock as F, workspaceLogsDir as M, workspaceName$1 as N, workspaceDerivedData as O, workspaceStateFile as P, ensureWorkspaceStorage as S, sharedCompilationCache as T, updateWorkspaceState as _, pidExists as a, writeWorkspaceState as b, resolveProjectMetro as c, isPortFree as d, reserveMetroPort as f, recordWorkspaceUse as g, readWorkspaceState$1 as h, listeningPidsByPort as i, workspaceLogErrorIndex as j, workspaceDir as k, signalProcessTree as l, clearWorkspaceStateKeys as m, killMetroTree as n, processCwd as o, clearWorkspaceStateKey as p, listeningPids as r, processCwds as s, NOT_OURS_FOREIGN_CWD as t, isMetroRunning as u, withWorkspaceStateLock as v, sharedCcache as w, emulatorLogFile as x, workspaceLastUsed as y };
@@ -1,13 +1,69 @@
1
- import { D as process_identity_exports, T as withConfigLock, f as getProject, g as loadConfig, s as ensureConfig, u as getConfigDir, x as saveConfig } from "./config-BEq0Qw_a.mjs";
1
+ import { A as ownership_claim_exports, E as withConfigLock, O as process_identity_exports, S as saveConfig, _ as loadConfig, c as ensureConfig, d as getConfigDir, p as getProject } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { c as signalProcessTree, n as listeningPids, u as isPortFree } from "./metro-BWLOJ_Wr.mjs";
4
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Cx74RSDZ.mjs";
3
+ import { d as isPortFree, l as signalProcessTree, r as listeningPids } from "./metro-BPeY1YMh.mjs";
4
+ import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-PT8aQpCa.mjs";
5
5
  import { realpathSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ //#region src/engine/workspace-process-lock.ts
8
+ const DEFAULT_WAIT_MS = 6e4;
9
+ const POLL_MS = 25;
10
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
11
+ function workspaceProcessLockError(err) {
12
+ const code = err?.code;
13
+ if (code === "STIM_LOCK_REFUSED") return "refused";
14
+ if (code === "STIM_LOCK_TIMEOUT") return "timeout";
15
+ return null;
16
+ }
17
+ function workspaceProcessLockPath(root, name, external) {
18
+ return external ? join(root, `${name}.lock`) : join(root, ".stim", `${name}.lock`);
19
+ }
20
+ async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, sleep = defaultSleep, waitMs = DEFAULT_WAIT_MS, ownerPurpose, details = {}, onHeld, rejectOwnerPurposes = [], external = false, declareSpawns = false } = {}) {
21
+ const path = workspaceProcessLockPath(root, name, external);
22
+ const deadline = now() + waitMs;
23
+ for (;;) {
24
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
25
+ root: path,
26
+ mode: "exclusive",
27
+ label: `${name} lock`,
28
+ details: ownerPurpose ? {
29
+ ...details,
30
+ purpose: ownerPurpose
31
+ } : details
32
+ });
33
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
34
+ if (attempt.acquired) {
35
+ if (declareSpawns) declareSpawnsOn(attempt.acquired);
36
+ try {
37
+ return await fn(attempt.acquired);
38
+ } finally {
39
+ stopDeclaringSpawnsOn(attempt.acquired);
40
+ (0, ownership_claim_exports.releaseClaim)(attempt.acquired);
41
+ }
42
+ }
43
+ const holder = attempt.held ?? attempt.waitingFor?.[0];
44
+ const purpose = holder?.details.purpose;
45
+ if (typeof purpose === "string" && rejectOwnerPurposes.includes(purpose)) {
46
+ const error = /* @__PURE__ */ new Error(`The ${name} lock at ${path} is held for ${purpose}.`);
47
+ error.code = "STIM_LOCK_REFUSED";
48
+ throw error;
49
+ }
50
+ if (holder && onHeld) await onHeld(holder);
51
+ if (now() >= deadline) {
52
+ const error = /* @__PURE__ */ new Error(`Timed out waiting for the ${name} lock at ${path}.`);
53
+ error.code = "STIM_LOCK_TIMEOUT";
54
+ throw error;
55
+ }
56
+ await sleep(POLL_MS);
57
+ }
58
+ }
59
+ //#endregion
6
60
  //#region src/named-ports.ts
7
61
  const FIRST_PORT = 8900;
8
62
  const LAST_PORT = 8999;
63
+ const BROWSER_LABEL = "web-cdp";
9
64
  function validatePortLabel(label) {
10
65
  if (label === "metro") throw new Error("metro is managed by stim start and stim stop; choose another label.");
66
+ if (label === BROWSER_LABEL) throw new Error(`${BROWSER_LABEL} is managed by stim web and stim stop; choose another label.`);
11
67
  if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(label)) throw new Error("A port label must start with a letter and contain at most 64 letters, digits, underscores or hyphens.");
12
68
  }
13
69
  function portListeners(port, platform) {
@@ -51,8 +107,24 @@ function processCommand(pid, platform) {
51
107
  function withPortsLock(fn) {
52
108
  return withWorkspaceProcessLock(getConfigDir(), "ports", fn, { external: true });
53
109
  }
54
- async function getNamedPort(projectPath, label, { isFree = async (port) => await isPortFree(port) && portListeners(port, process.platform).length === 0, log = console.error } = {}) {
110
+ async function getNamedPort(projectPath, label, options = {}) {
55
111
  validatePortLabel(label);
112
+ return allocateNamedPort(projectPath, label, options);
113
+ }
114
+ /** The loopback DevTools port of the workspace's owned Chrome, allocated like a named port under `web-cdp`. */
115
+ function reserveBrowserPort(projectPath, options = {}) {
116
+ return allocateNamedPort(projectPath, BROWSER_LABEL, options);
117
+ }
118
+ function releaseBrowserPort(projectPath, port) {
119
+ return withPortsLock(async () => withConfigLock(() => {
120
+ const cfg = loadConfig();
121
+ const ports = cfg?.projects?.[projectPath]?.ports;
122
+ if (!cfg || ports?.[BROWSER_LABEL] !== port) return;
123
+ delete ports[BROWSER_LABEL];
124
+ saveConfig(cfg);
125
+ }));
126
+ }
127
+ async function allocateNamedPort(projectPath, label, { isFree = async (port) => await isPortFree(port) && portListeners(port, process.platform).length === 0, log = console.error }) {
56
128
  const root = realpathSync(projectPath);
57
129
  return withPortsLock(async () => {
58
130
  const existing = getProject(root)?.ports;
@@ -121,7 +193,7 @@ async function clearNamedPorts(projectPath, { label, stop = false, dryRun = fals
121
193
  if (label !== void 0) validatePortLabel(label);
122
194
  await withPortsLock(async () => {
123
195
  const ports = getProject(projectPath)?.ports ?? {};
124
- const selected = Object.entries(ports).filter(([name]) => label === void 0 || name === label);
196
+ const selected = Object.entries(ports).filter(([name]) => name !== BROWSER_LABEL && (label === void 0 || name === label));
125
197
  if (stop && selected.length > 0 && platform !== "win32" && !getExecutor().findExecutable("lsof")) throw new Error("Cannot stop named ports: lsof is not installed.");
126
198
  const failures = [];
127
199
  for (const [name, port] of selected) try {
@@ -149,4 +221,4 @@ async function clearNamedPorts(projectPath, { label, stop = false, dryRun = fals
149
221
  });
150
222
  }
151
223
  //#endregion
152
- export { getNamedPort as n, clearNamedPorts as t };
224
+ export { withWorkspaceProcessLock as a, reserveBrowserPort as i, getNamedPort as n, workspaceProcessLockError as o, releaseBrowserPort as r, workspaceProcessLockPath as s, clearNamedPorts as t };
@@ -0,0 +1,74 @@
1
+ import { S as ensureWorkspaceStorage, c as resolveProjectMetro, h as readWorkspaceState$1 } from "./metro-BPeY1YMh.mjs";
2
+ import { l as phaseLine } from "./command-output-BIL7rlX7.mjs";
3
+ import { r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING } from "./launch-verify-CuJaBpEa.mjs";
4
+ import { s as startDevServer } from "./start-BZhItkiL.mjs";
5
+ import { readIdleStop } from "@stim-cli/core/state";
6
+ import chalk from "chalk";
7
+ //#region src/commands/native-runtime.ts
8
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
9
+ async function ensureDevServer({ root, port, settings, remote, note, resolve = resolveProjectMetro, start = startDevServer, readState = readWorkspaceState$1 }) {
10
+ const held = port === null ? null : (await resolve(port, root)).metro;
11
+ if (port !== null && held) return {
12
+ ok: true,
13
+ port,
14
+ pid: held.pid ?? null,
15
+ devServer: null,
16
+ reclaimed: []
17
+ };
18
+ const reason = readIdleStop(readState(root)) ? "stopped (idle)" : "not running";
19
+ note(chalk.dim(phaseLine("metro", `dev server ${reason}; starting it${remote ? " for a remote device" : ""}`)));
20
+ const result = await start({
21
+ root,
22
+ settings,
23
+ remote,
24
+ out: note,
25
+ note
26
+ });
27
+ if (!result.ok) return {
28
+ ok: false,
29
+ code: result.error.code,
30
+ message: `Could not start this workspace's dev server: ${result.error.message}`,
31
+ remedy: result.error.remedy,
32
+ lines: result.lines,
33
+ reclaimed: result.reclaimed
34
+ };
35
+ const { facts } = result;
36
+ return {
37
+ ok: true,
38
+ port: facts.port,
39
+ pid: facts.supervisorPid,
40
+ devServer: facts.alreadyRunning ? null : {
41
+ started: true,
42
+ reason
43
+ },
44
+ reclaimed: result.reclaimed
45
+ };
46
+ }
47
+ async function ensureWorkspaceStorageSafely(root, { note = (_line) => {} } = {}) {
48
+ try {
49
+ return ensureWorkspaceStorage(root);
50
+ } catch (err) {
51
+ note(chalk.dim(`Could not prepare this workspace's Stim state: ${err?.message || err}`));
52
+ throw err;
53
+ }
54
+ }
55
+ function launchOutcomeRecord({ launchState, release, bundleId, configuration, metroPort }) {
56
+ const unverified = launchState === LAUNCH_UNVERIFIED;
57
+ const bundling = launchState === LAUNCH_BUNDLING;
58
+ let msg;
59
+ if (release) msg = unverified ? `${bundleId} could not be verified as running after its ${configuration} launch` : `${bundleId} is running its embedded ${configuration} bundle`;
60
+ else if (unverified) msg = `no bundle request from ${bundleId} reached this workspace's Metro on port ${metroPort}`;
61
+ else if (bundling) msg = `${bundleId} requested a bundle from this workspace's Metro on port ${metroPort}; it was still being built when the launch check ended`;
62
+ else msg = `${bundleId} fetched a bundle from this workspace's Metro on port ${metroPort}`;
63
+ return {
64
+ src: "build",
65
+ level: unverified ? "warn" : "info",
66
+ event: unverified ? "launch_unverified" : bundling ? "launch_bundling" : "launch_verified",
67
+ msg
68
+ };
69
+ }
70
+ function isPhysicalDeviceRequest(flag) {
71
+ return flag !== null && flag !== void 0 && flag !== false;
72
+ }
73
+ //#endregion
74
+ export { sleep as a, launchOutcomeRecord as i, ensureWorkspaceStorageSafely as n, isPhysicalDeviceRequest as r, ensureDevServer as t };
@@ -1,5 +1,5 @@
1
- import { D as process_identity_exports } from "./config-BEq0Qw_a.mjs";
2
- import { _ as withWorkspaceStateLock, i as pidExists, m as readWorkspaceState, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
1
+ import { O as process_identity_exports } from "./config-0jCcMM0E.mjs";
2
+ import { a as pidExists, b as writeWorkspaceState, h as readWorkspaceState, v as withWorkspaceStateLock } from "./metro-BPeY1YMh.mjs";
3
3
  import { realpathSync } from "node:fs";
4
4
  //#region src/collector/state.ts
5
5
  function collectorRoot(root) {