@bitmagic/cli 0.1.60-dev.3 → 0.1.60-dev.5

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 (65) hide show
  1. package/README.md +67 -3
  2. package/dist/atomic-json.d.ts +18 -0
  3. package/dist/atomic-json.js +39 -0
  4. package/dist/atomic-json.js.map +1 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/commands/dev.js +79 -35
  7. package/dist/commands/dev.js.map +1 -1
  8. package/dist/commands/publish.d.ts +2 -0
  9. package/dist/commands/publish.js +1 -1
  10. package/dist/commands/publish.js.map +1 -1
  11. package/dist/commands/reload.d.ts +1 -1
  12. package/dist/commands/reload.js +14 -5
  13. package/dist/commands/reload.js.map +1 -1
  14. package/dist/commands/verify.js +134 -118
  15. package/dist/commands/verify.js.map +1 -1
  16. package/dist/editor/motion-panel.d.ts +2 -2
  17. package/dist/editor/motion-panel.js +27 -5
  18. package/dist/editor/motion-panel.js.map +1 -1
  19. package/dist/editor/reload-bus.d.ts +11 -23
  20. package/dist/editor/reload-bus.js +55 -8
  21. package/dist/editor/reload-bus.js.map +1 -1
  22. package/dist/editor/server.d.ts +10 -0
  23. package/dist/editor/server.js +46 -0
  24. package/dist/editor/server.js.map +1 -1
  25. package/dist/editor/shell-page.d.ts +10 -0
  26. package/dist/editor/shell-page.js +58 -3
  27. package/dist/editor/shell-page.js.map +1 -1
  28. package/dist/editor/tab-reuse.d.ts +23 -0
  29. package/dist/editor/tab-reuse.js +61 -0
  30. package/dist/editor/tab-reuse.js.map +1 -0
  31. package/dist/local-port.d.ts +8 -1
  32. package/dist/local-port.js +9 -2
  33. package/dist/local-port.js.map +1 -1
  34. package/dist/machine-lock.d.ts +56 -0
  35. package/dist/machine-lock.js +213 -0
  36. package/dist/machine-lock.js.map +1 -0
  37. package/dist/port-lane.d.ts +27 -0
  38. package/dist/port-lane.js +36 -1
  39. package/dist/port-lane.js.map +1 -1
  40. package/dist/process-alive.d.ts +13 -0
  41. package/dist/process-alive.js +24 -0
  42. package/dist/process-alive.js.map +1 -0
  43. package/dist/project/dev-handle.d.ts +6 -34
  44. package/dist/project/dev-handle.js +9 -59
  45. package/dist/project/dev-handle.js.map +1 -1
  46. package/dist/project/dev-ports.d.ts +67 -0
  47. package/dist/project/dev-ports.js +204 -0
  48. package/dist/project/dev-ports.js.map +1 -0
  49. package/dist/project/game-name.d.ts +9 -0
  50. package/dist/project/game-name.js +21 -0
  51. package/dist/project/game-name.js.map +1 -0
  52. package/dist/project/jobs.js +2 -1
  53. package/dist/project/jobs.js.map +1 -1
  54. package/dist/scaffold/project-files.js +8 -8
  55. package/dist/scaffold/project-files.js.map +1 -1
  56. package/dist/smoke/run.d.ts +4 -0
  57. package/dist/smoke/run.js +5 -2
  58. package/dist/smoke/run.js.map +1 -1
  59. package/dist/verify/iterate.d.ts +5 -0
  60. package/dist/verify/iterate.js +23 -20
  61. package/dist/verify/iterate.js.map +1 -1
  62. package/dist/verify/lock.d.ts +32 -0
  63. package/dist/verify/lock.js +54 -0
  64. package/dist/verify/lock.js.map +1 -0
  65. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+gEtD,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqChB,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"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B;;IAEvC,sBAAsB,EAAE;;;;;;;;4BAQA,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyjEtD,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqChB,CAAC;AACF,CAAC"}
@@ -0,0 +1,23 @@
1
+ /** Comfortably past Firefox's 5 s reconnect, short enough that a closed tab costs one breath. */
2
+ export declare const TAB_REUSE_WINDOW_MS = 7000;
3
+ export interface DevViewRecord {
4
+ /** Epoch ms when a tab last attached to a `dev` on this port. */
5
+ watcherSeenAt: number;
6
+ editorPort: number;
7
+ }
8
+ export declare function devViewRecordPath(root: string): string;
9
+ export declare function readDevViewRecord(root: string): DevViewRecord | null;
10
+ export declare function writeDevViewRecord(root: string, record: DevViewRecord): void;
11
+ export interface TabDecisionInput {
12
+ /** A tab attached to a previous `dev` on this very port — worth waiting for. */
13
+ evidence: boolean;
14
+ /** Epoch ms when the sidecar began listening; the wait is measured from here, not from now. */
15
+ listeningAt: number;
16
+ hasWatcher: () => boolean;
17
+ }
18
+ export interface TabDecisionDeps {
19
+ now?: () => number;
20
+ sleep?: (ms: number) => Promise<void>;
21
+ }
22
+ /** True when a tab should be opened; false when one is already watching this server. */
23
+ export declare function shouldOpenTab(input: TabDecisionInput, deps?: TabDecisionDeps): Promise<boolean>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Whether `bitmagic dev` should open a browser tab, or whether one is already there.
3
+ *
4
+ * `dev` opens its page itself (open-browser.ts), and it is restarted often — agents start it per
5
+ * session, a background task gets killed, a creator Ctrl-C's it to add `--mobile`. Every restart
6
+ * used to open another tab while the previous one was still there: that tab polls `/api/state`
7
+ * every half second and holds an `EventSource` that reconnects on its own, so it comes back to the
8
+ * new server by itself — and then there were two. After a few prompts, ten.
9
+ *
10
+ * The signal is the SSE reconnect, not the poll. A background tab's `setTimeout` chain is throttled
11
+ * by Chrome to once a second, and to once a MINUTE after five minutes hidden; `EventSource`
12
+ * reconnects on a browser network timer — about 3 s in Chromium, 5 s in Firefox — that is not.
13
+ *
14
+ * The wait is paid only when there is EVIDENCE a tab existed: the previous server wrote
15
+ * `.bitmagic/dev-view.json` the first time a tab attached. A fresh project opens instantly. A
16
+ * restart waits until a tab reconnects, or until `TAB_REUSE_WINDOW_MS` have passed since the
17
+ * sidecar started listening — and the sidecar listens from before the build, so in practice the
18
+ * build has already spent that window and the decision is immediate either way.
19
+ */
20
+ import * as path from 'path';
21
+ import { readJsonFile, writeJsonAtomic } from '../atomic-json.js';
22
+ /** Comfortably past Firefox's 5 s reconnect, short enough that a closed tab costs one breath. */
23
+ export const TAB_REUSE_WINDOW_MS = 7_000;
24
+ const POLL_MS = 100;
25
+ export function devViewRecordPath(root) {
26
+ return path.join(root, '.bitmagic', 'dev-view.json');
27
+ }
28
+ export function readDevViewRecord(root) {
29
+ const parsed = readJsonFile(devViewRecordPath(root));
30
+ if (typeof parsed !== 'object' || parsed === null)
31
+ return null;
32
+ const record = parsed;
33
+ if (typeof record.watcherSeenAt !== 'number' || typeof record.editorPort !== 'number')
34
+ return null;
35
+ return { watcherSeenAt: record.watcherSeenAt, editorPort: record.editorPort };
36
+ }
37
+ export function writeDevViewRecord(root, record) {
38
+ try {
39
+ writeJsonAtomic(devViewRecordPath(root), record);
40
+ }
41
+ catch {
42
+ // A record we cannot write costs one extra tab on the next restart, nothing else.
43
+ }
44
+ }
45
+ /** True when a tab should be opened; false when one is already watching this server. */
46
+ export async function shouldOpenTab(input, deps = {}) {
47
+ const now = deps.now ?? Date.now;
48
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
49
+ if (input.hasWatcher())
50
+ return false;
51
+ if (!input.evidence)
52
+ return true;
53
+ const deadline = input.listeningAt + TAB_REUSE_WINDOW_MS;
54
+ while (now() < deadline) {
55
+ await sleep(POLL_MS);
56
+ if (input.hasWatcher())
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ //# sourceMappingURL=tab-reuse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab-reuse.js","sourceRoot":"","sources":["../../src/editor/tab-reuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAElE,iGAAiG;AACjG,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AACzC,MAAM,OAAO,GAAG,GAAG,CAAC;AAQpB,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,IAAI,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnG,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,MAAqB;IACpE,IAAI,CAAC;QACH,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,kFAAkF;IACpF,CAAC;AACH,CAAC;AAeD,wFAAwF;AACxF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAuB,EAAE,OAAwB,EAAE;IACrF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACtG,IAAI,KAAK,CAAC,UAAU,EAAE;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,GAAG,mBAAmB,CAAC;IACzD,OAAO,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QACxB,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QACrB,IAAI,KAAK,CAAC,UAAU,EAAE;YAAE,OAAO,KAAK,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -82,8 +82,15 @@ export declare function laneExhaustedMessage(lane: PortLane, remedy: string): st
82
82
  * and `forge` alike need a port inside the CDN's CORS window (see the file header) and have
83
83
  * nothing useful to do with a `null`. Bundling the lane lookup, the scan and the refusal here
84
84
  * keeps the three from drifting apart — a caller varies only what it tells the creator to do.
85
+ *
86
+ * Every port a project on this machine has been promised (project/dev-ports.ts) is skipped as
87
+ * well, whether or not that project's `dev` is up right now. A verify sitting on 3021 while
88
+ * project B's dev happened to be down would push B's next `dev` into a drift, which is exactly the
89
+ * instability the registry ends. At most 27 of the lane's hundred ports, so the scan loses nothing.
85
90
  */
86
- export declare function reserveLanePort(remedy: string, exclude?: ReadonlySet<number>): Promise<number>;
91
+ export declare function reserveLanePort(remedy: string, exclude?: ReadonlySet<number>, deps?: {
92
+ registryDir?: string;
93
+ }): Promise<number>;
87
94
  /**
88
95
  * The port to actually bind: `preferred` when it is free, otherwise another one in the window.
89
96
  *
@@ -1,6 +1,7 @@
1
1
  import * as net from 'net';
2
2
  import { CliError } from './errors.js';
3
3
  import { currentPortLane } from './port-lane.js';
4
+ import { registeredSlotPorts } from './project/dev-ports.js';
4
5
  /**
5
6
  * The port window the platform's bucket CORS policy allowlists as an origin.
6
7
  *
@@ -131,10 +132,16 @@ export function laneExhaustedMessage(lane, remedy) {
131
132
  * and `forge` alike need a port inside the CDN's CORS window (see the file header) and have
132
133
  * nothing useful to do with a `null`. Bundling the lane lookup, the scan and the refusal here
133
134
  * keeps the three from drifting apart — a caller varies only what it tells the creator to do.
135
+ *
136
+ * Every port a project on this machine has been promised (project/dev-ports.ts) is skipped as
137
+ * well, whether or not that project's `dev` is up right now. A verify sitting on 3021 while
138
+ * project B's dev happened to be down would push B's next `dev` into a drift, which is exactly the
139
+ * instability the registry ends. At most 27 of the lane's hundred ports, so the scan loses nothing.
134
140
  */
135
- export async function reserveLanePort(remedy, exclude = new Set()) {
141
+ export async function reserveLanePort(remedy, exclude = new Set(), deps = {}) {
136
142
  const lane = currentPortLane();
137
- const port = await reservePortInLane(lane, exclude);
143
+ const promised = registeredSlotPorts(lane, { baseDir: deps.registryDir });
144
+ const port = await reservePortInLane(lane, new Set([...exclude, ...promised]));
138
145
  if (port === null)
139
146
  throw new CliError(laneExhaustedMessage(lane, remedy));
140
147
  return port;
@@ -1 +1 @@
1
- {"version":3,"file":"local-port.js","sourceRoot":"","sources":["../src/local-port.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-port.js","sourceRoot":"","sources":["../src/local-port.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ export interface LockHolder {
2
+ pid: number;
3
+ /** Epoch ms when the holder took the lock. */
4
+ startedAt: number;
5
+ /** The project the holder was working on, for the waiting line. */
6
+ root: string;
7
+ /** The command that took it — `verify`, `dev` — for the same line. */
8
+ command: string;
9
+ }
10
+ export interface MachineLockDeps {
11
+ baseDir?: string;
12
+ now?: () => number;
13
+ sleep?: (ms: number) => Promise<void>;
14
+ isAlive?: (pid: number) => boolean;
15
+ /** Epoch ms of this machine's boot, below which a holder cannot be alive. */
16
+ bootTime?: () => number;
17
+ pid?: number;
18
+ pollMs?: number;
19
+ timeoutMs?: number;
20
+ maxAgeMs?: number;
21
+ /** Called once, the first time the lock turns out to be held by someone else. */
22
+ onWait?: (holder: LockHolder) => void;
23
+ /** Called when a lock is reclaimed for being too old — the one stale case worth a warning. */
24
+ onStale?: (holder: LockHolder) => void;
25
+ }
26
+ export interface MachineLock {
27
+ /** Whether acquiring involved waiting for another holder at all. */
28
+ readonly waited: boolean;
29
+ /** Give it back. Idempotent; never removes a lock someone else has since taken. */
30
+ release(): void;
31
+ }
32
+ /** A poll, not a watch: cheap, portable, and a released lock is noticed within this. */
33
+ export declare const LOCK_POLL_MS = 500;
34
+ /** How long a waiter queues before giving up with the holder's name. */
35
+ export declare const LOCK_TIMEOUT_MS: number;
36
+ /** No holder legitimately keeps a lock this long; beyond it the record is treated as abandoned. */
37
+ export declare const LOCK_MAX_AGE_MS: number;
38
+ export declare function lockPath(name: string, baseDir?: string): string;
39
+ /** The recorded holder, or null for a file that is missing, empty or garbage. */
40
+ export declare function readLockHolder(file: string): LockHolder | null;
41
+ /** Whether the holder is one no longer worth waiting for, and why. */
42
+ export declare function staleReason(holder: LockHolder, deps: {
43
+ now: number;
44
+ isAlive: (pid: number) => boolean;
45
+ bootTime: number;
46
+ maxAgeMs: number;
47
+ }): 'dead' | 'pre-boot' | 'too-old' | null;
48
+ export declare function acquireMachineLock(name: string, info: {
49
+ root: string;
50
+ command: string;
51
+ }, deps?: MachineLockDeps): Promise<MachineLock>;
52
+ /** Hold `name` for the duration of `fn`, releasing however it ends. */
53
+ export declare function withMachineLock<T>(name: string, info: {
54
+ root: string;
55
+ command: string;
56
+ }, fn: (lock: MachineLock) => Promise<T>, deps?: MachineLockDeps): Promise<T>;
@@ -0,0 +1,213 @@
1
+ /**
2
+ * A lock shared by every `bitmagic` process on this machine — the one piece of cross-process
3
+ * coordination the CLI has.
4
+ *
5
+ * Two things need it. `bitmagic verify` measures the game's frame rate in a real browser, and two
6
+ * verifies at once on one laptop starve each other into a verdict about the machine rather than
7
+ * about the game; so verify runs one at a time, and the others wait (verify/lock.ts). The
8
+ * per-project port registry (project/dev-ports.ts) is a read-modify-write of one file that two
9
+ * `bitmagic dev`s starting in two folders at the same second must not both perform.
10
+ *
11
+ * It lives in `~/.bitmagic/locks/<name>.lock`, the directory both published lines already share
12
+ * for credentials, so a dev-line and a release-line install queue behind each other too — the fps
13
+ * problem is about the machine, not about which build is measuring.
14
+ *
15
+ * ── Why a file, and why this shape ────────────────────────────────────────────────────────────
16
+ *
17
+ * No dependency, because this package deliberately keeps `dev` and `verify` offline and light, and
18
+ * the primitives are enough: `open(..., 'wx')` is atomic on every platform Node runs on (it is
19
+ * CREATE_NEW on Windows), so the file's EXISTENCE is the lock. The descriptor is closed the moment
20
+ * the body is written — Windows will not unlink a file another process holds open, and a lock that
21
+ * cannot be reclaimed after its owner dies is worse than no lock.
22
+ *
23
+ * The body names the holder, for two reasons: so a waiter can say WHO it is waiting for (a line
24
+ * that turns "verify hangs" into "verify is queued behind the other project"), and so a stale lock
25
+ * can be told from a live one. Stale means the pid is gone, or the record predates this boot (pids
26
+ * are reused after a restart), or it is simply too old — no verify holds it for half an hour.
27
+ *
28
+ * Reclaiming a stale lock is a rename, then an unlink of the renamed file. Two waiters that both
29
+ * decide "stale" would otherwise race: one unlinks, the other creates a fresh lock, and the first
30
+ * unlinks THAT. Rename is atomic, so exactly one of them moves the file aside; the other gets
31
+ * ENOENT and simply tries `wx` again.
32
+ *
33
+ * A holder interrupted with Ctrl-C leaves its file behind — Node's default SIGINT exit runs no
34
+ * `finally` — and that is fine: its pid is dead, so the next waiter reclaims it on the first poll.
35
+ */
36
+ import * as fs from 'fs';
37
+ import * as os from 'os';
38
+ import * as path from 'path';
39
+ import { CliError } from './errors.js';
40
+ import { defaultBaseDir } from './config/credentials.js';
41
+ import { isProcessAlive } from './process-alive.js';
42
+ import { readJsonFile } from './atomic-json.js';
43
+ /** A poll, not a watch: cheap, portable, and a released lock is noticed within this. */
44
+ export const LOCK_POLL_MS = 500;
45
+ /** How long a waiter queues before giving up with the holder's name. */
46
+ export const LOCK_TIMEOUT_MS = 15 * 60 * 1000;
47
+ /** No holder legitimately keeps a lock this long; beyond it the record is treated as abandoned. */
48
+ export const LOCK_MAX_AGE_MS = 30 * 60 * 1000;
49
+ /**
50
+ * Slack on the boot-time rule. `os.uptime()` may or may not count time spent suspended, so a lock
51
+ * taken just before a laptop lid closed could otherwise read as "from before this boot".
52
+ */
53
+ const BOOT_TOLERANCE_MS = 60 * 1000;
54
+ export function lockPath(name, baseDir = defaultBaseDir()) {
55
+ return path.join(baseDir, 'locks', `${name}.lock`);
56
+ }
57
+ function defaultBootTime() {
58
+ return Date.now() - os.uptime() * 1000;
59
+ }
60
+ function isHolder(value) {
61
+ if (typeof value !== 'object' || value === null)
62
+ return false;
63
+ const v = value;
64
+ return typeof v.pid === 'number' && typeof v.startedAt === 'number'
65
+ && typeof v.root === 'string' && typeof v.command === 'string';
66
+ }
67
+ /** The recorded holder, or null for a file that is missing, empty or garbage. */
68
+ export function readLockHolder(file) {
69
+ const parsed = readJsonFile(file);
70
+ return isHolder(parsed) ? parsed : null;
71
+ }
72
+ /** Whether the holder is one no longer worth waiting for, and why. */
73
+ export function staleReason(holder, deps) {
74
+ if (!deps.isAlive(holder.pid))
75
+ return 'dead';
76
+ if (holder.startedAt < deps.bootTime - BOOT_TOLERANCE_MS)
77
+ return 'pre-boot';
78
+ if (deps.now - holder.startedAt > deps.maxAgeMs)
79
+ return 'too-old';
80
+ return null;
81
+ }
82
+ /** Move a stale lock aside and delete it. False when another waiter got there first. */
83
+ function reclaim(file, pid, now) {
84
+ const aside = `${file}.stale.${pid}.${now}`;
85
+ try {
86
+ fs.renameSync(file, aside);
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ fs.rmSync(aside, { force: true });
92
+ return true;
93
+ }
94
+ /** Try once to create the lock. True when this process now holds it. */
95
+ function tryCreate(file, holder) {
96
+ let fd;
97
+ try {
98
+ fd = fs.openSync(file, 'wx');
99
+ }
100
+ catch (error) {
101
+ if (error.code === 'EEXIST')
102
+ return false;
103
+ throw error;
104
+ }
105
+ try {
106
+ fs.writeSync(fd, `${JSON.stringify(holder)}\n`);
107
+ }
108
+ finally {
109
+ fs.closeSync(fd);
110
+ }
111
+ return true;
112
+ }
113
+ /**
114
+ * How long an unreadable lock file is given to become readable.
115
+ *
116
+ * Between `open('wx')` and the write that follows it, the file exists and is empty; a waiter
117
+ * polling at that instant must not mistake a lock being taken for a corrupt one and delete it.
118
+ * Beyond this, an empty file is a crashed writer's, and is reclaimed.
119
+ */
120
+ const UNREADABLE_GRACE_MS = 5_000;
121
+ function fileAgeMs(file, now) {
122
+ try {
123
+ return now - fs.statSync(file).mtimeMs;
124
+ }
125
+ catch {
126
+ return null;
127
+ }
128
+ }
129
+ export async function acquireMachineLock(name, info, deps = {}) {
130
+ const now = deps.now ?? Date.now;
131
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
132
+ const isAlive = deps.isAlive ?? isProcessAlive;
133
+ const bootTime = deps.bootTime ?? defaultBootTime;
134
+ const pid = deps.pid ?? process.pid;
135
+ const pollMs = deps.pollMs ?? LOCK_POLL_MS;
136
+ const timeoutMs = deps.timeoutMs ?? LOCK_TIMEOUT_MS;
137
+ const maxAgeMs = deps.maxAgeMs ?? LOCK_MAX_AGE_MS;
138
+ const file = lockPath(name, deps.baseDir);
139
+ fs.mkdirSync(path.dirname(file), { recursive: true });
140
+ const startedWaitingAt = now();
141
+ let waited = false;
142
+ let lastHolder = null;
143
+ for (;;) {
144
+ const holder = { pid, startedAt: now(), root: info.root, command: info.command };
145
+ if (tryCreate(file, holder)) {
146
+ return makeLock(file, pid, waited);
147
+ }
148
+ const current = readLockHolder(file);
149
+ if (current === null) {
150
+ // Empty or garbage. Being written right now, or left by a writer that died mid-write.
151
+ const age = fileAgeMs(file, now());
152
+ if (age === null)
153
+ continue; // gone between the failed create and the stat: try again.
154
+ if (age > UNREADABLE_GRACE_MS) {
155
+ reclaim(file, pid, now());
156
+ continue;
157
+ }
158
+ }
159
+ else {
160
+ const reason = staleReason(current, { now: now(), isAlive, bootTime: bootTime(), maxAgeMs });
161
+ if (reason !== null) {
162
+ if (reason === 'too-old')
163
+ deps.onStale?.(current);
164
+ reclaim(file, pid, now());
165
+ continue;
166
+ }
167
+ if (!waited || lastHolder === null || lastHolder.pid !== current.pid) {
168
+ // Once per holder: a queue that moves — A finishes, B takes over — is worth a second line,
169
+ // a holder that is merely slow is not.
170
+ deps.onWait?.(current);
171
+ }
172
+ lastHolder = current;
173
+ }
174
+ waited = true;
175
+ if (now() - startedWaitingAt >= timeoutMs) {
176
+ const who = lastHolder === null
177
+ ? 'an unreadable lock file'
178
+ : `\`bitmagic ${lastHolder.command}\` (pid ${lastHolder.pid}) in ${lastHolder.root}`;
179
+ throw new CliError(`Gave up waiting for ${who} after ${Math.round(timeoutMs / 60_000)} minutes. `
180
+ + `If nothing is actually running, delete ${file} and try again.`);
181
+ }
182
+ await sleep(pollMs);
183
+ }
184
+ }
185
+ function makeLock(file, pid, waited) {
186
+ let released = false;
187
+ const release = () => {
188
+ if (released)
189
+ return;
190
+ released = true;
191
+ process.off('exit', release);
192
+ // Only ours. A lock this process lost to the staleness rules — a laptop asleep for an hour —
193
+ // may by now belong to someone else, and removing theirs would let a third run in beside them.
194
+ const current = readLockHolder(file);
195
+ if (current !== null && current.pid !== pid)
196
+ return;
197
+ fs.rmSync(file, { force: true });
198
+ };
199
+ // Best-effort on a normal exit; a signal death is covered by the dead-pid rule instead.
200
+ process.on('exit', release);
201
+ return { waited, release };
202
+ }
203
+ /** Hold `name` for the duration of `fn`, releasing however it ends. */
204
+ export async function withMachineLock(name, info, fn, deps = {}) {
205
+ const lock = await acquireMachineLock(name, info, deps);
206
+ try {
207
+ return await fn(lock);
208
+ }
209
+ finally {
210
+ lock.release();
211
+ }
212
+ }
213
+ //# sourceMappingURL=machine-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine-lock.js","sourceRoot":"","sources":["../src/machine-lock.ts"],"names":[],"mappings":"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"}
@@ -45,6 +45,31 @@ export declare const RELEASE_LANE: PortLane;
45
45
  export declare const DEV_LANE: PortLane;
46
46
  /** The lane belonging to a published line. */
47
47
  export declare function portLaneFor(tag: UpdateTag): PortLane;
48
+ /**
49
+ * How many projects a lane holds at fixed addresses — see `slotPorts`.
50
+ *
51
+ * Nine, because a slot is ten ports wide and the lane's defaults sit at x10: slots 0-8 span
52
+ * x10-x92, and a tenth would run past the lane's end. Nine games being developed at once on one
53
+ * machine is a lot; a tenth falls back to the scan, and says so.
54
+ */
55
+ export declare const DEV_PORT_SLOTS = 9;
56
+ /**
57
+ * The three ports a project in `slot` of `lane` serves on.
58
+ *
59
+ * `bitmagic dev` used to start every project at the lane's defaults and drift to a random port in
60
+ * the lane when they were busy — so which project answered on 3011 depended on start order,
61
+ * bookmarks went stale, and a `bitmagic reload` falling back to the default could reach the wrong
62
+ * project. A slot is the same numbers, one tens-digit further along per project: slot 0 is the
63
+ * defaults every doc names (3010/3011/3012), slot 1 is 3020/3021/3022, and so on. The hundreds
64
+ * digit still says which install, the tens digit now says which project, and the units digit still
65
+ * reads game / shell / phone. Which slot a project holds is recorded per machine in
66
+ * `~/.bitmagic/dev-ports.json` (project/dev-ports.ts).
67
+ */
68
+ export declare function slotPorts(lane: PortLane, slot: number): {
69
+ gamePort: number;
70
+ editorPort: number;
71
+ mobilePort: number;
72
+ };
48
73
  /**
49
74
  * This install's lane.
50
75
  *
@@ -57,3 +82,5 @@ export declare function portLaneFor(tag: UpdateTag): PortLane;
57
82
  * before this existed. Failing closed here keeps the numbers the docs promise.
58
83
  */
59
84
  export declare function currentPortLane(version?: string): PortLane;
85
+ /** Which published line this install is on — the key the port registry files a project under. */
86
+ export declare function currentLineTag(version?: string): UpdateTag;
package/dist/port-lane.js CHANGED
@@ -49,6 +49,37 @@ export const DEV_LANE = {
49
49
  export function portLaneFor(tag) {
50
50
  return tag === 'dev' ? DEV_LANE : RELEASE_LANE;
51
51
  }
52
+ /**
53
+ * How many projects a lane holds at fixed addresses — see `slotPorts`.
54
+ *
55
+ * Nine, because a slot is ten ports wide and the lane's defaults sit at x10: slots 0-8 span
56
+ * x10-x92, and a tenth would run past the lane's end. Nine games being developed at once on one
57
+ * machine is a lot; a tenth falls back to the scan, and says so.
58
+ */
59
+ export const DEV_PORT_SLOTS = 9;
60
+ /**
61
+ * The three ports a project in `slot` of `lane` serves on.
62
+ *
63
+ * `bitmagic dev` used to start every project at the lane's defaults and drift to a random port in
64
+ * the lane when they were busy — so which project answered on 3011 depended on start order,
65
+ * bookmarks went stale, and a `bitmagic reload` falling back to the default could reach the wrong
66
+ * project. A slot is the same numbers, one tens-digit further along per project: slot 0 is the
67
+ * defaults every doc names (3010/3011/3012), slot 1 is 3020/3021/3022, and so on. The hundreds
68
+ * digit still says which install, the tens digit now says which project, and the units digit still
69
+ * reads game / shell / phone. Which slot a project holds is recorded per machine in
70
+ * `~/.bitmagic/dev-ports.json` (project/dev-ports.ts).
71
+ */
72
+ export function slotPorts(lane, slot) {
73
+ if (!Number.isInteger(slot) || slot < 0 || slot >= DEV_PORT_SLOTS) {
74
+ throw new RangeError(`dev port slot must be 0-${DEV_PORT_SLOTS - 1}, got ${slot}`);
75
+ }
76
+ const offset = 10 * slot;
77
+ return {
78
+ gamePort: lane.gamePort + offset,
79
+ editorPort: lane.editorPort + offset,
80
+ mobilePort: lane.mobilePort + offset,
81
+ };
82
+ }
52
83
  /**
53
84
  * This install's lane.
54
85
  *
@@ -61,6 +92,10 @@ export function portLaneFor(tag) {
61
92
  * before this existed. Failing closed here keeps the numbers the docs promise.
62
93
  */
63
94
  export function currentPortLane(version = currentCliVersion()) {
64
- return portLaneFor(updateTagFor(version));
95
+ return portLaneFor(currentLineTag(version));
96
+ }
97
+ /** Which published line this install is on — the key the port registry files a project under. */
98
+ export function currentLineTag(version = currentCliVersion()) {
99
+ return updateTagFor(version);
65
100
  }
66
101
  //# sourceMappingURL=port-lane.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"port-lane.js","sourceRoot":"","sources":["../src/port-lane.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAc5D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAa;IACpC,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAa;IAChC,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF,8CAA8C;AAC9C,MAAM,UAAU,WAAW,CAAC,GAAc;IACxC,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC;AACjD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkB,iBAAiB,EAAE;IACnE,OAAO,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"port-lane.js","sourceRoot":"","sources":["../src/port-lane.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAc5D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAa;IACpC,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAa;IAChC,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF,8CAA8C;AAC9C,MAAM,UAAU,WAAW,CAAC,GAAc;IACxC,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,IAAc,EAAE,IAAY;IACpD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,cAAc,EAAE,CAAC;QAClE,MAAM,IAAI,UAAU,CAAC,2BAA2B,cAAc,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC;IACzB,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ,GAAG,MAAM;QAChC,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,MAAM;QACpC,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,MAAM;KACrC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkB,iBAAiB,EAAE;IACnE,OAAO,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,cAAc,CAAC,UAAkB,iBAAiB,EAAE;IAClE,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Is the process that wrote a record still running?
3
+ *
4
+ * Signal 0 performs the permission and existence checks without delivering anything. EPERM means
5
+ * the process exists but belongs to someone else — alive, as far as this question goes. Works on
6
+ * Windows too, where Node maps it onto an existence check.
7
+ *
8
+ * Its own module because three unrelated records ask it: `.bitmagic/dev.json` (project/dev-handle),
9
+ * `.bitmagic/jobs/` (project/jobs) and the machine-wide locks in `~/.bitmagic/locks/`
10
+ * (machine-lock). The lock cannot import dev-handle for it — dev-handle reads the port registry,
11
+ * which takes the lock, and that would be a cycle.
12
+ */
13
+ export declare function isProcessAlive(pid: number): boolean;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Is the process that wrote a record still running?
3
+ *
4
+ * Signal 0 performs the permission and existence checks without delivering anything. EPERM means
5
+ * the process exists but belongs to someone else — alive, as far as this question goes. Works on
6
+ * Windows too, where Node maps it onto an existence check.
7
+ *
8
+ * Its own module because three unrelated records ask it: `.bitmagic/dev.json` (project/dev-handle),
9
+ * `.bitmagic/jobs/` (project/jobs) and the machine-wide locks in `~/.bitmagic/locks/`
10
+ * (machine-lock). The lock cannot import dev-handle for it — dev-handle reads the port registry,
11
+ * which takes the lock, and that would be a cycle.
12
+ */
13
+ export function isProcessAlive(pid) {
14
+ if (pid <= 0)
15
+ return false;
16
+ try {
17
+ process.kill(pid, 0);
18
+ return true;
19
+ }
20
+ catch (error) {
21
+ return error.code === 'EPERM';
22
+ }
23
+ }
24
+ //# sourceMappingURL=process-alive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-alive.js","sourceRoot":"","sources":["../src/process-alive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAQ,KAA2B,CAAC,IAAI,KAAK,OAAO,CAAC;IACvD,CAAC;AACH,CAAC"}