@stim-cli/server 1.24.0 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -22
- package/dist/{protocol-B8Ztv1K4.mjs → protocol-nY-u8rtw.mjs} +21 -4
- package/dist/protocol.mjs +1 -1
- package/dist/protocol.schema.json +88 -8
- package/dist/{stim-server-Z40-_Git.mjs → stim-server-DU1htjam.mjs} +137 -60
- package/dist/stim-server.mjs +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -49,8 +49,8 @@ and [device-host clients](#device-host-approval). The plain listing shows
|
|
|
49
49
|
[Actions](#actions).
|
|
50
50
|
|
|
51
51
|
`GET http://127.0.0.1:7787/health` answers requests from this machine with the
|
|
52
|
-
server's name, versions, `stimBuild` (the digest of the bundled Stim's build,
|
|
53
|
-
|
|
52
|
+
server's name, versions, `stimBuild` (the digest of the bundled Stim's build),
|
|
53
|
+
protocol, `stimHome`, `startup`, `busy`
|
|
54
54
|
(the offloaded `builds` and `hostedSessions` running now) and the current
|
|
55
55
|
Tailscale state. It answers HTTP 200 once `startup.state` is `ready`; while the
|
|
56
56
|
state is `pending` or `degraded` (see the LaunchAgent notes below) it answers
|
|
@@ -93,7 +93,7 @@ it appends the default Node of nvm, fnm, Volta, mise or asdf, Homebrew,
|
|
|
93
93
|
`JAVA_HOME` to a Homebrew `openjdk` (17, then 21, then the newest), else Android
|
|
94
94
|
Studio's bundled runtime, when no JDK is installed under
|
|
95
95
|
`/Library/Java/JavaVirtualMachines`. It never replaces a variable it was given,
|
|
96
|
-
and its own Node's directory is always the last `PATH` entry. CocoaPods uses the
|
|
96
|
+
and its own Node's directory is always the last `PATH` entry. The build host repeats this discovery for each build offer, toolchain read and build start, so a tool installed after the server started, such as Homebrew's directory, a JDK or the Android SDK, is found without a restart, within the minute the toolchain report is cached. A `PATH` line added to a shell profile is never read. CocoaPods uses the
|
|
97
97
|
project's `.ruby-version` Ruby, else rvm's or rbenv's default Ruby, and reads the
|
|
98
98
|
login shell only when neither exists. Anything else a shell profile sets, such as
|
|
99
99
|
`STIM_*` variables, reaches the server only through its environment or `--env`.
|
|
@@ -275,7 +275,9 @@ reports without starting a CLI process. `desktop.reports.get` takes
|
|
|
275
275
|
Doctor accepts a selected folder before it is registered with Stim. The result is
|
|
276
276
|
`{ "pending": false, "value": { ... }, "measuredAt": "..." }`; a failed read
|
|
277
277
|
returns `stim-failed`. Doctor never repairs the project or records a CLI run, and
|
|
278
|
-
GC never deletes anything.
|
|
278
|
+
GC never deletes anything. The GC report, here and in `machine.details`, takes
|
|
279
|
+
worktree build-state sizes only from the disk-usage cache and runs no `du` for
|
|
280
|
+
them; a stale size is `null`.
|
|
279
281
|
|
|
280
282
|
The local-only `desktop-report-platform` feature adds an optional `"platform": "ios"`
|
|
281
283
|
or `"platform": "android"` to doctor targets. Omitting it checks all selected platforms.
|
|
@@ -291,12 +293,17 @@ Active subscriptions refresh after 60 seconds; no refresh timer runs without
|
|
|
291
293
|
subscribers. `unsubscribe` and disconnect remove the subscription. Cancelling a
|
|
292
294
|
get request does not cancel another reader's shared work.
|
|
293
295
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
`desktop.reports.invalidate` without parameters invalidates all report targets
|
|
297
|
+
and refreshes active subscriptions. With the local-only
|
|
298
|
+
`desktop-report-invalidate-target` feature it also takes a report target, as
|
|
299
|
+
`desktop.reports.get` does, and invalidates only that target, so a fresh read
|
|
300
|
+
of one report does not restart the others. Results started before
|
|
301
|
+
invalidation cannot replace a newer result or satisfy a waiting get; invalidation
|
|
302
|
+
aborts such a probe, so a waiting get waits only for the fresh one. A waiting get
|
|
303
|
+
does not use one of the connection's request slots: a connection can wait for 16
|
|
304
|
+
reports at a time, and more fail with `limit-exceeded`. The subscription cap,
|
|
305
|
+
report byte quota, API execution and cleanup budgets, and 150-second server limit
|
|
306
|
+
apply. At most four shared report probes run
|
|
300
307
|
at once across connections and targets. A new target above that limit returns a
|
|
301
308
|
busy error without starting or queuing work; invalidate and retry after a probe
|
|
302
309
|
finishes. Unsubscribing does not release a probe's slot before it drains.
|
|
@@ -359,7 +366,10 @@ person approves does setup store the ticket's derived PSK and lookup in
|
|
|
359
366
|
gets `unknown-secret`, and its `stim doctor` reports the machine as pending and
|
|
360
367
|
tries again on every run. The requester's next first-contact handshake with the
|
|
361
368
|
ticket records its public key once per approved capability, which spends the
|
|
362
|
-
ticket.
|
|
369
|
+
ticket. While it waits, setup says so ("Waiting for the requesting machine to
|
|
370
|
+
connect. Stim Desktop sends this automatically.", or the `stim doctor`
|
|
371
|
+
instruction for a Doctor-made command) and confirms each approval when it
|
|
372
|
+
arrives. From then on the ticket admits only that key, until it expires, so a
|
|
363
373
|
requester that lost the reply can retry; setup deletes an unspent secret when it
|
|
364
374
|
ends. Build and
|
|
365
375
|
device-host approvals are separate records. A build grant carries no `read` or
|
|
@@ -1360,7 +1370,10 @@ event loop. Until that read returns, `/health` answers HTTP 503 with
|
|
|
1360
1370
|
requests get 503, and no status follower, native helper, file watcher or
|
|
1361
1371
|
recorder starts. A read that does not return within 10 seconds, plus up to one
|
|
1362
1372
|
second to stop the child, leaves the server listening with
|
|
1363
|
-
`startup: { "state": "degraded", "reason": "..." }`
|
|
1373
|
+
`startup: { "state": "degraded", "reason": "...", "cause": "home-unreadable" }`
|
|
1374
|
+
(`start-failed` when starting failed after the read returned) and HTTP 503 on
|
|
1375
|
+
`/health` (a tailnet peer sees only `{ "version", "startup": { "state",
|
|
1376
|
+
"cause" } }`, because the reason names the Stim home path),
|
|
1364
1377
|
logs the reason once, and reads again every 30 seconds. When a read returns,
|
|
1365
1378
|
the server starts its watchers and recorder, `/health` answers 200 with
|
|
1366
1379
|
`startup: { "state": "ready" }`, and no restart is needed. `service status`
|
|
@@ -1615,13 +1628,20 @@ connected since stim-server started keeps its directory when revoked.
|
|
|
1615
1628
|
same Ruby selection and UTF-8 locale defaults as `pod install`; omitting
|
|
1616
1629
|
`rubyVersion` uses the worker's default Ruby. It
|
|
1617
1630
|
returns `toolchain` (`stimBuild`, a digest of the bundled Stim's built code;
|
|
1631
|
+
`compat`, the bundled Stim's build compatibility version, which a client
|
|
1632
|
+
compares instead of `stimBuild` when it is present;
|
|
1618
1633
|
`os`, the OS stim-server runs on as Node names it (`darwin`, `linux`, `win32`);
|
|
1619
1634
|
`arch`; `xcode`; `simulatorSdk`; `macosSdk` (from
|
|
1620
1635
|
`xcrun --sdk macosx --show-sdk-version`); `cocoapods`; `runtimes`, the simulator
|
|
1621
1636
|
runtimes with an iPhone simulator to build for; `jdk`, the major version of
|
|
1622
|
-
the JDK in `JAVA_HOME` or the macOS default;
|
|
1637
|
+
the JDK in `JAVA_HOME` or the macOS default; `jdks`, the majors of every JDK it finds
|
|
1638
|
+
(`JAVA_HOME`, the system JVM directories, Homebrew, Android Studio's JBR, SDKMAN), the default included; `developerDir` (what `xcode-select -p` prints) and `xcodeApps` (the Xcode apps in
|
|
1639
|
+
`/Applications` with `version`, `build` and, while `xcode` is null, the `license` and `firstLaunch` checks of each), on a Mac; and `androidSdk`, the `ndk`,
|
|
1623
1640
|
`buildTools` and `platforms` directories of the SDK in `ANDROID_HOME`, else `~/Library/Android/sdk` on macOS or
|
|
1624
|
-
`~/Android/Sdk` on Linux, null without one), `
|
|
1641
|
+
`~/Android/Sdk` on Linux, null without one), `androidHome` (that directory) and
|
|
1642
|
+
`androidLicenses` (whether `licenses/android-sdk-license` in it has content, so
|
|
1643
|
+
Gradle can download a missing package; both absent from a server older than
|
|
1644
|
+
them, and from a worker without an SDK), `capacity` and `warm`
|
|
1625
1645
|
(`checkout`, `dependencies`, and the legacy `build` field, now always false)
|
|
1626
1646
|
for that repository. For React Native and Expo builds, clients supporting `build-dependency-cache` can send
|
|
1627
1647
|
`dependencies: { project, packageName }` alongside `lockfile`; the dependency
|
|
@@ -1652,8 +1672,10 @@ connected since stim-server started keeps its directory when revoked.
|
|
|
1652
1672
|
architecture and target options. These are exact lookups, not a full inventory.
|
|
1653
1673
|
With `react-native-peer-cache`, a React Native/Expo development offer can send
|
|
1654
1674
|
`reactNativeArtifact: { project, fingerprint, isExpo, platform, toolchain, ios | android }`.
|
|
1655
|
-
The query includes the caller's compatible toolchain
|
|
1656
|
-
|
|
1675
|
+
The query includes the caller's compatible toolchain, whose `toolchain.compat`
|
|
1676
|
+
takes the place of `stimBuild` with `build-compat`, and normalized native
|
|
1677
|
+
build choices; a caller sends React Native and SwiftPM queries only to a
|
|
1678
|
+
server with `build-compat`; the worker constructs its own receipt around compilation.
|
|
1657
1679
|
The returned identity is opaque, not an alias for the caller's local cache key.
|
|
1658
1680
|
The completed receipt binds the worker-verified post-mutation fingerprint
|
|
1659
1681
|
and native options to the toolchain checked before and after compilation.
|
|
@@ -1661,7 +1683,7 @@ connected since stim-server started keeps its directory when revoked.
|
|
|
1661
1683
|
does not discard a matching archive. Older workers receive no new query
|
|
1662
1684
|
parameters and continue through their normal build path.
|
|
1663
1685
|
With `swiftpm-peer-cache`, `swiftpmArtifact` carries `project`, `sourceDigest`,
|
|
1664
|
-
`stimBuild`, complete `inputs` (`ignored`, `outputs`, `localFiles`,
|
|
1686
|
+
`stimBuild` or, with `build-compat`, `compat` in its place, complete `inputs` (`ignored`, `outputs`, `localFiles`,
|
|
1665
1687
|
`environment`) and `build` (`product`, `infoPlist`, `resources`, `assetCatalog`).
|
|
1666
1688
|
Resource and catalog paths are repository-relative; the plist is
|
|
1667
1689
|
project-relative. The worker verifies actual transferred files and full
|
|
@@ -1711,7 +1733,9 @@ connected since stim-server started keeps its directory when revoked.
|
|
|
1711
1733
|
simulator runtime identifier, required for `ios`, null for `android`),
|
|
1712
1734
|
`fingerprint`, `packageName`, `isExpo`, `optimizations`, `android` (for
|
|
1713
1735
|
`android`: `variant`, `abi`, `gradleBuildCache`, `pch` and `compilerCache`,
|
|
1714
|
-
`ccache` or `none`)
|
|
1736
|
+
`ccache` or `none`), `stimBuild` and, from a caller that saw `build-compat`, `compat`, and, for Android from a caller
|
|
1737
|
+
that saw `build-jdk`, `jdk` (the JDK major its Gradle runs on; the build runs with `JAVA_HOME` at an installed JDK of
|
|
1738
|
+
that major, and fails if there is none), after the whole manifest of `repo` was
|
|
1715
1739
|
synced on this connection. A peer-cache-capable React Native caller can set
|
|
1716
1740
|
`cacheArtifact: true` to retain a completed development archive when its cache
|
|
1717
1741
|
write and remote policies allow it. With the `fingerprint-sources` feature, a
|
|
@@ -1728,7 +1752,8 @@ connected since stim-server started keeps its directory when revoked.
|
|
|
1728
1752
|
`build-busy` while the offer would be declined, naming the reason, or while
|
|
1729
1753
|
another build of the same client and repository runs, or while every one
|
|
1730
1754
|
of this machine's `concurrency.maxBuilds` slots is held, and with
|
|
1731
|
-
`build-refused` when `
|
|
1755
|
+
`build-refused` when `compat` differs from this machine's, or, when either
|
|
1756
|
+
side has no `compat`, when `stimBuild` differs. The build runs `offload-worker.mjs` of the bundled Stim with
|
|
1732
1757
|
`STIM_HOME` set to the repository's area. It makes the area's checkout
|
|
1733
1758
|
hold exactly the manifest (a file git lists as untracked and not ignored,
|
|
1734
1759
|
and not in the manifest, is deleted; ignored dependencies and generated
|
|
@@ -1940,7 +1965,7 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
1940
1965
|
retained logs and agent-device session actions locally through core, with the same JSON filters, ordering,
|
|
1941
1966
|
markers and error context. Archive reads are synchronous and queued per
|
|
1942
1967
|
connection, yielding to the event loop between requests. Pending archive
|
|
1943
|
-
reads share the
|
|
1968
|
+
reads share the connection's request limit with commands. Archived session actions
|
|
1944
1969
|
retain available platform and device attribution; device ids and Stim slot
|
|
1945
1970
|
assignments absent from retained files are unavailable after removal. They never follow files or use a hosted relay.
|
|
1946
1971
|
An archive subscription sends its records in the same batches and limits,
|
|
@@ -2350,9 +2375,10 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
2350
2375
|
|
|
2351
2376
|
`workspace` is an environment `path` from a status payload. Any other path is
|
|
2352
2377
|
refused with `unknown-workspace` and runs nothing. A connection holds at most
|
|
2353
|
-
32 subscriptions and runs at most
|
|
2378
|
+
32 subscriptions and runs at most 32 `logs.query`, `stats.get`,
|
|
2354
2379
|
`settings.get`, `build.plan`, `workspace.files` and `workspace.diff` requests at
|
|
2355
|
-
a time
|
|
2380
|
+
a time, actions included. A tailnet device runs at most 16 across all its
|
|
2381
|
+
connections; more fail with `limit-exceeded`. `logs.query`, `stats.get` and `settings.get` fail after 60 seconds,
|
|
2356
2382
|
`build.plan` after 150; these four fail at 32 MiB of output. Closing the
|
|
2357
2383
|
connection stops running requests. When the command refuses with Stim's error
|
|
2358
2384
|
contract on stdout, the `stim-failed` message is its code, message and remedy;
|
|
@@ -290,15 +290,21 @@ const swiftpmArtifactInputs = {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
};
|
|
293
|
+
const buildCompat = {
|
|
294
|
+
type: "integer",
|
|
295
|
+
minimum: 1,
|
|
296
|
+
description: "The Stim build compatibility version, sent to a stim-server with the build-compat feature."
|
|
297
|
+
};
|
|
298
|
+
const stimIdentity = [{ required: ["stimBuild"] }, { required: ["compat"] }];
|
|
293
299
|
const swiftpmArtifactQuery = {
|
|
294
300
|
type: "object",
|
|
295
301
|
required: [
|
|
296
302
|
"project",
|
|
297
303
|
"sourceDigest",
|
|
298
|
-
"stimBuild",
|
|
299
304
|
"inputs",
|
|
300
305
|
"build"
|
|
301
306
|
],
|
|
307
|
+
oneOf: stimIdentity,
|
|
302
308
|
additionalProperties: false,
|
|
303
309
|
properties: {
|
|
304
310
|
project: { type: "string" },
|
|
@@ -308,6 +314,7 @@ const swiftpmArtifactQuery = {
|
|
|
308
314
|
minLength: 1,
|
|
309
315
|
maxLength: 256
|
|
310
316
|
},
|
|
317
|
+
compat: buildCompat,
|
|
311
318
|
inputs: swiftpmArtifactInputs,
|
|
312
319
|
build: {
|
|
313
320
|
type: "object",
|
|
@@ -379,13 +386,14 @@ const reactNativeArtifactQuery = {
|
|
|
379
386
|
platform: { enum: ["ios", "android"] },
|
|
380
387
|
toolchain: {
|
|
381
388
|
type: "object",
|
|
382
|
-
|
|
389
|
+
oneOf: stimIdentity,
|
|
383
390
|
additionalProperties: false,
|
|
384
391
|
properties: {
|
|
385
392
|
stimBuild: {
|
|
386
393
|
type: "string",
|
|
387
394
|
minLength: 1
|
|
388
395
|
},
|
|
396
|
+
compat: buildCompat,
|
|
389
397
|
arch: {
|
|
390
398
|
type: "string",
|
|
391
399
|
minLength: 1
|
|
@@ -2419,10 +2427,10 @@ function protocolJsonSchema() {
|
|
|
2419
2427
|
}),
|
|
2420
2428
|
request("desktop.reports.get", { $ref: "#/$defs/DesktopReportTarget" }),
|
|
2421
2429
|
request("desktop.reports.subscribe", { $ref: "#/$defs/DesktopReportTarget" }),
|
|
2422
|
-
optionalParams("desktop.reports.invalidate", {
|
|
2430
|
+
optionalParams("desktop.reports.invalidate", { oneOf: [{
|
|
2423
2431
|
type: "object",
|
|
2424
2432
|
maxProperties: 0
|
|
2425
|
-
}),
|
|
2433
|
+
}, { $ref: "#/$defs/DesktopReportTarget" }] }),
|
|
2426
2434
|
optionalParams("desktop.settings.schema", {
|
|
2427
2435
|
type: "object",
|
|
2428
2436
|
maxProperties: 0
|
|
@@ -3304,6 +3312,15 @@ function protocolJsonSchema() {
|
|
|
3304
3312
|
stimBuild: {
|
|
3305
3313
|
type: "string",
|
|
3306
3314
|
minLength: 1
|
|
3315
|
+
},
|
|
3316
|
+
compat: {
|
|
3317
|
+
...buildCompat,
|
|
3318
|
+
description: "Compared instead of stimBuild by a machine whose build.offer toolchain reports compat."
|
|
3319
|
+
},
|
|
3320
|
+
jdk: {
|
|
3321
|
+
type: "string",
|
|
3322
|
+
pattern: "^[0-9]{1,3}$",
|
|
3323
|
+
description: "The JDK major an Android build runs on, sent to a stim-server with the build-jdk feature."
|
|
3307
3324
|
}
|
|
3308
3325
|
}
|
|
3309
3326
|
}),
|
package/dist/protocol.mjs
CHANGED
|
@@ -1280,9 +1280,12 @@
|
|
|
1280
1280
|
"hosted-android-process",
|
|
1281
1281
|
"hosted-metro-ports",
|
|
1282
1282
|
"server-update",
|
|
1283
|
+
"build-compat",
|
|
1284
|
+
"build-jdk",
|
|
1283
1285
|
"status-views",
|
|
1284
1286
|
"desktop-reports",
|
|
1285
1287
|
"desktop-report-platform",
|
|
1288
|
+
"desktop-report-invalidate-target",
|
|
1286
1289
|
"desktop-settings",
|
|
1287
1290
|
"desktop-settings-schema",
|
|
1288
1291
|
"desktop-settings-write",
|
|
@@ -4029,8 +4032,15 @@
|
|
|
4029
4032
|
"const": "desktop.reports.invalidate"
|
|
4030
4033
|
},
|
|
4031
4034
|
"params": {
|
|
4032
|
-
"
|
|
4033
|
-
|
|
4035
|
+
"oneOf": [
|
|
4036
|
+
{
|
|
4037
|
+
"type": "object",
|
|
4038
|
+
"maxProperties": 0
|
|
4039
|
+
},
|
|
4040
|
+
{
|
|
4041
|
+
"$ref": "#/$defs/DesktopReportTarget"
|
|
4042
|
+
}
|
|
4043
|
+
]
|
|
4034
4044
|
}
|
|
4035
4045
|
}
|
|
4036
4046
|
},
|
|
@@ -6885,8 +6895,17 @@
|
|
|
6885
6895
|
},
|
|
6886
6896
|
"toolchain": {
|
|
6887
6897
|
"type": "object",
|
|
6888
|
-
"
|
|
6889
|
-
|
|
6898
|
+
"oneOf": [
|
|
6899
|
+
{
|
|
6900
|
+
"required": [
|
|
6901
|
+
"stimBuild"
|
|
6902
|
+
]
|
|
6903
|
+
},
|
|
6904
|
+
{
|
|
6905
|
+
"required": [
|
|
6906
|
+
"compat"
|
|
6907
|
+
]
|
|
6908
|
+
}
|
|
6890
6909
|
],
|
|
6891
6910
|
"additionalProperties": false,
|
|
6892
6911
|
"properties": {
|
|
@@ -6894,6 +6913,11 @@
|
|
|
6894
6913
|
"type": "string",
|
|
6895
6914
|
"minLength": 1
|
|
6896
6915
|
},
|
|
6916
|
+
"compat": {
|
|
6917
|
+
"type": "integer",
|
|
6918
|
+
"minimum": 1,
|
|
6919
|
+
"description": "The Stim build compatibility version, sent to a stim-server with the build-compat feature."
|
|
6920
|
+
},
|
|
6897
6921
|
"arch": {
|
|
6898
6922
|
"type": "string",
|
|
6899
6923
|
"minLength": 1
|
|
@@ -7030,10 +7054,21 @@
|
|
|
7030
7054
|
"required": [
|
|
7031
7055
|
"project",
|
|
7032
7056
|
"sourceDigest",
|
|
7033
|
-
"stimBuild",
|
|
7034
7057
|
"inputs",
|
|
7035
7058
|
"build"
|
|
7036
7059
|
],
|
|
7060
|
+
"oneOf": [
|
|
7061
|
+
{
|
|
7062
|
+
"required": [
|
|
7063
|
+
"stimBuild"
|
|
7064
|
+
]
|
|
7065
|
+
},
|
|
7066
|
+
{
|
|
7067
|
+
"required": [
|
|
7068
|
+
"compat"
|
|
7069
|
+
]
|
|
7070
|
+
}
|
|
7071
|
+
],
|
|
7037
7072
|
"additionalProperties": false,
|
|
7038
7073
|
"properties": {
|
|
7039
7074
|
"project": {
|
|
@@ -7048,6 +7083,11 @@
|
|
|
7048
7083
|
"minLength": 1,
|
|
7049
7084
|
"maxLength": 256
|
|
7050
7085
|
},
|
|
7086
|
+
"compat": {
|
|
7087
|
+
"type": "integer",
|
|
7088
|
+
"minimum": 1,
|
|
7089
|
+
"description": "The Stim build compatibility version, sent to a stim-server with the build-compat feature."
|
|
7090
|
+
},
|
|
7051
7091
|
"inputs": {
|
|
7052
7092
|
"type": "object",
|
|
7053
7093
|
"required": [
|
|
@@ -7652,6 +7692,16 @@
|
|
|
7652
7692
|
"stimBuild": {
|
|
7653
7693
|
"type": "string",
|
|
7654
7694
|
"minLength": 1
|
|
7695
|
+
},
|
|
7696
|
+
"compat": {
|
|
7697
|
+
"type": "integer",
|
|
7698
|
+
"minimum": 1,
|
|
7699
|
+
"description": "Compared instead of stimBuild by a machine whose build.offer toolchain reports compat."
|
|
7700
|
+
},
|
|
7701
|
+
"jdk": {
|
|
7702
|
+
"type": "string",
|
|
7703
|
+
"pattern": "^[0-9]{1,3}$",
|
|
7704
|
+
"description": "The JDK major an Android build runs on, sent to a stim-server with the build-jdk feature."
|
|
7655
7705
|
}
|
|
7656
7706
|
}
|
|
7657
7707
|
}
|
|
@@ -7934,8 +7984,17 @@
|
|
|
7934
7984
|
},
|
|
7935
7985
|
"toolchain": {
|
|
7936
7986
|
"type": "object",
|
|
7937
|
-
"
|
|
7938
|
-
|
|
7987
|
+
"oneOf": [
|
|
7988
|
+
{
|
|
7989
|
+
"required": [
|
|
7990
|
+
"stimBuild"
|
|
7991
|
+
]
|
|
7992
|
+
},
|
|
7993
|
+
{
|
|
7994
|
+
"required": [
|
|
7995
|
+
"compat"
|
|
7996
|
+
]
|
|
7997
|
+
}
|
|
7939
7998
|
],
|
|
7940
7999
|
"additionalProperties": false,
|
|
7941
8000
|
"properties": {
|
|
@@ -7943,6 +8002,11 @@
|
|
|
7943
8002
|
"type": "string",
|
|
7944
8003
|
"minLength": 1
|
|
7945
8004
|
},
|
|
8005
|
+
"compat": {
|
|
8006
|
+
"type": "integer",
|
|
8007
|
+
"minimum": 1,
|
|
8008
|
+
"description": "The Stim build compatibility version, sent to a stim-server with the build-compat feature."
|
|
8009
|
+
},
|
|
7946
8010
|
"arch": {
|
|
7947
8011
|
"type": "string",
|
|
7948
8012
|
"minLength": 1
|
|
@@ -8079,10 +8143,21 @@
|
|
|
8079
8143
|
"required": [
|
|
8080
8144
|
"project",
|
|
8081
8145
|
"sourceDigest",
|
|
8082
|
-
"stimBuild",
|
|
8083
8146
|
"inputs",
|
|
8084
8147
|
"build"
|
|
8085
8148
|
],
|
|
8149
|
+
"oneOf": [
|
|
8150
|
+
{
|
|
8151
|
+
"required": [
|
|
8152
|
+
"stimBuild"
|
|
8153
|
+
]
|
|
8154
|
+
},
|
|
8155
|
+
{
|
|
8156
|
+
"required": [
|
|
8157
|
+
"compat"
|
|
8158
|
+
]
|
|
8159
|
+
}
|
|
8160
|
+
],
|
|
8086
8161
|
"additionalProperties": false,
|
|
8087
8162
|
"properties": {
|
|
8088
8163
|
"project": {
|
|
@@ -8097,6 +8172,11 @@
|
|
|
8097
8172
|
"minLength": 1,
|
|
8098
8173
|
"maxLength": 256
|
|
8099
8174
|
},
|
|
8175
|
+
"compat": {
|
|
8176
|
+
"type": "integer",
|
|
8177
|
+
"minimum": 1,
|
|
8178
|
+
"description": "The Stim build compatibility version, sent to a stim-server with the build-compat feature."
|
|
8179
|
+
},
|
|
8100
8180
|
"inputs": {
|
|
8101
8181
|
"type": "object",
|
|
8102
8182
|
"required": [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as protocol_exports } from "./protocol-
|
|
1
|
+
import { n as protocol_exports } from "./protocol-nY-u8rtw.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
4
4
|
import { appendFileSync, chmodSync, closeSync, constants, copyFileSync, cpSync, createReadStream, existsSync, futimesSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, statfsSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
@@ -661,16 +661,16 @@ function validateServeEnvironment(env, pathPrepend, serving = false) {
|
|
|
661
661
|
* environment launchd gave the process; one that `source` lacks is skipped with a line on stderr. `STIM_HOME` and
|
|
662
662
|
* `SHELL` entries are skipped the same way: the job takes them from the installing process.
|
|
663
663
|
*/
|
|
664
|
-
function applyServeEnvironment(base, env, pathPrepend, source = process.env) {
|
|
664
|
+
function applyServeEnvironment(base, env, pathPrepend, source = process.env, warn = console.error) {
|
|
665
665
|
const result = { ...base };
|
|
666
666
|
for (const entry of env) {
|
|
667
667
|
const parsed = parseEnvAssignment(entry);
|
|
668
668
|
const name = typeof parsed === "string" ? entry : parsed.key;
|
|
669
|
-
if (JOB_KEYS.includes(name))
|
|
669
|
+
if (JOB_KEYS.includes(name)) warn(`stim-server: ignoring --env ${name}; the service takes it from the process that installed it.`);
|
|
670
670
|
else if (typeof parsed !== "string") result[name] = parsed.value;
|
|
671
671
|
else if (ENV_KEY_PATTERN.test(entry)) {
|
|
672
672
|
const value = source[entry];
|
|
673
|
-
if (value === void 0)
|
|
673
|
+
if (value === void 0) warn(`stim-server: --env ${entry} has no value in the service environment; skipping it.`);
|
|
674
674
|
else result[entry] = value;
|
|
675
675
|
}
|
|
676
676
|
}
|
|
@@ -3283,7 +3283,7 @@ function macosBuildOptions(params) {
|
|
|
3283
3283
|
const artifactQuery = options.artifactInputs === void 0 ? null : swiftpmArtifactQuery({
|
|
3284
3284
|
project: params.project,
|
|
3285
3285
|
sourceDigest: params.fingerprint,
|
|
3286
|
-
stimBuild: params.stimBuild,
|
|
3286
|
+
...typeof params.compat === "number" ? { compat: params.compat } : { stimBuild: params.stimBuild },
|
|
3287
3287
|
inputs: options.artifactInputs,
|
|
3288
3288
|
build: {
|
|
3289
3289
|
product: options.product,
|
|
@@ -3457,6 +3457,9 @@ var BuildHost = class {
|
|
|
3457
3457
|
} catch {}
|
|
3458
3458
|
}
|
|
3459
3459
|
}
|
|
3460
|
+
environment() {
|
|
3461
|
+
return this.options.currentEnv?.() ?? this.options.env;
|
|
3462
|
+
}
|
|
3460
3463
|
root() {
|
|
3461
3464
|
return buildWorkerRoot();
|
|
3462
3465
|
}
|
|
@@ -3472,7 +3475,7 @@ var BuildHost = class {
|
|
|
3472
3475
|
const value = new Promise((resolve) => {
|
|
3473
3476
|
const args = native ? [`offer-native-${native}`] : ["offer", ...rubyVersion ? [rubyVersion] : []];
|
|
3474
3477
|
const child = spawn(process.execPath, [this.options.worker, ...args], {
|
|
3475
|
-
env: this.
|
|
3478
|
+
env: this.environment(),
|
|
3476
3479
|
stdio: [
|
|
3477
3480
|
"ignore",
|
|
3478
3481
|
"pipe",
|
|
@@ -3548,7 +3551,7 @@ var BuildHost = class {
|
|
|
3548
3551
|
},
|
|
3549
3552
|
warm: {
|
|
3550
3553
|
checkout: existsSync(join(area, "src")),
|
|
3551
|
-
dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, params.lockfile, this.
|
|
3554
|
+
dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, params.lockfile, this.environment()),
|
|
3552
3555
|
build: false
|
|
3553
3556
|
},
|
|
3554
3557
|
...artifact !== void 0 ? { artifact } : {}
|
|
@@ -3559,7 +3562,7 @@ var BuildHost = class {
|
|
|
3559
3562
|
}
|
|
3560
3563
|
workerEnvironment(clientDir, area) {
|
|
3561
3564
|
return {
|
|
3562
|
-
...this.
|
|
3565
|
+
...this.environment(),
|
|
3563
3566
|
STIM_HOME: join(area, "home"),
|
|
3564
3567
|
CP_HOME_DIR: join(clientDir, "cache", "cocoapods-home"),
|
|
3565
3568
|
CP_CACHE_DIR: join(clientDir, "cache", "cocoapods"),
|
|
@@ -3636,7 +3639,7 @@ var BuildHost = class {
|
|
|
3636
3639
|
const query = swiftpmArtifactQuery({
|
|
3637
3640
|
project: job.project,
|
|
3638
3641
|
sourceDigest: job.expectedFingerprint,
|
|
3639
|
-
stimBuild: job.stimBuild,
|
|
3642
|
+
...typeof job.compat === "number" ? { compat: job.compat } : { stimBuild: job.stimBuild },
|
|
3640
3643
|
inputs: options.artifactInputs,
|
|
3641
3644
|
build: {
|
|
3642
3645
|
product: options.product,
|
|
@@ -4352,7 +4355,11 @@ var BuildSession = class {
|
|
|
4352
4355
|
for (const file of this.files.values()) if (!existsSync(this.blobPath(file.sha256))) return refusal("bad-request", `The blob of ${file.path} is missing; sync again.`);
|
|
4353
4356
|
const toolchain = await this.host.toolchain(null, native?.provider);
|
|
4354
4357
|
if (this.closed) return refusal("build-refused", "The connection closed.");
|
|
4355
|
-
|
|
4358
|
+
const compat = typeof params.compat === "number" ? params.compat : void 0;
|
|
4359
|
+
if (!toolchain) return refusal("build-refused", "This machine runs Stim build unknown.");
|
|
4360
|
+
if (compat !== void 0 && toolchain.compat !== void 0) {
|
|
4361
|
+
if (toolchain.compat !== compat) return refusal("build-refused", `This machine runs Stim build ${toolchain.stimBuild ?? "unknown"} with build compatibility ${toolchain.compat}, not ${compat}.`);
|
|
4362
|
+
} else if (toolchain.stimBuild !== params.stimBuild) return refusal("build-refused", `This machine runs Stim build ${toolchain.stimBuild ?? "unknown"}.`);
|
|
4356
4363
|
const launched = this.host.launch({
|
|
4357
4364
|
client: this.client,
|
|
4358
4365
|
repo: params.repo,
|
|
@@ -4368,6 +4375,8 @@ var BuildSession = class {
|
|
|
4368
4375
|
isExpo: params.isExpo === true,
|
|
4369
4376
|
cacheArtifact: params.cacheArtifact === true,
|
|
4370
4377
|
stimBuild: params.stimBuild,
|
|
4378
|
+
...compat !== void 0 && toolchain.compat !== void 0 ? { compat } : {},
|
|
4379
|
+
...typeof params.jdk === "string" ? { jdk: params.jdk } : {},
|
|
4371
4380
|
configuration: optional(params.configuration),
|
|
4372
4381
|
scheme: optional(params.scheme),
|
|
4373
4382
|
iosProjectPath: optional(params.iosProjectPath),
|
|
@@ -4967,7 +4976,7 @@ function toolChecks(tools, stimBuild, platform) {
|
|
|
4967
4976
|
],
|
|
4968
4977
|
[
|
|
4969
4978
|
"Android SDK",
|
|
4970
|
-
!!tools.androidSdk
|
|
4979
|
+
!!tools.androidSdk,
|
|
4971
4980
|
tools.androidSdk ? JSON.stringify(tools.androidSdk) : "Missing Android SDK",
|
|
4972
4981
|
platform === "linux" ? "Install the Android SDK in ~/Android/Sdk, or rerun setup with --env ANDROID_HOME=<sdk>" : platform === "win32" ? "Install the Android SDK in %LOCALAPPDATA%\\Android\\Sdk, or rerun setup with --env ANDROID_HOME=<sdk>" : "Install Android Studio or set ANDROID_HOME"
|
|
4973
4982
|
],
|
|
@@ -5309,7 +5318,8 @@ async function runSetup(args, version, deps) {
|
|
|
5309
5318
|
try {
|
|
5310
5319
|
deps.storeSecret(opts.ticket, approved, opts.expiresAt, deps.now());
|
|
5311
5320
|
secretStored = true;
|
|
5312
|
-
|
|
5321
|
+
const waiting = deps.fromDoctor ? "Waiting for the requesting machine to reconnect. Run `stim doctor` on that machine to finish." : "Waiting for the requesting machine to connect. Stim Desktop sends this automatically.";
|
|
5322
|
+
step("approve", "running", "Access approval", waiting, void 0, { running: waiting });
|
|
5313
5323
|
while (deps.now() < Date.parse(opts.expiresAt)) {
|
|
5314
5324
|
changed = false;
|
|
5315
5325
|
for (const grant of recorded(approved)) note(grant, false);
|
|
@@ -12131,6 +12141,7 @@ var UsageRecorder = class {
|
|
|
12131
12141
|
//#endregion
|
|
12132
12142
|
//#region src/desktop-reports.ts
|
|
12133
12143
|
const MAX_REPORT_READS = 4;
|
|
12144
|
+
const reportKey = (target) => target.report === "gc" ? "gc" : `doctor:${target.platform ?? "all"}:${target.cwd}`;
|
|
12134
12145
|
var DesktopReports = class {
|
|
12135
12146
|
entries = /* @__PURE__ */ new Map();
|
|
12136
12147
|
closed = false;
|
|
@@ -12201,9 +12212,13 @@ var DesktopReports = class {
|
|
|
12201
12212
|
}
|
|
12202
12213
|
};
|
|
12203
12214
|
}
|
|
12204
|
-
|
|
12205
|
-
|
|
12215
|
+
/** Invalidates `target`, or every target without one. */
|
|
12216
|
+
invalidate(target) {
|
|
12217
|
+
const only = target && this.entries.get(reportKey(target));
|
|
12218
|
+
if (target && !only) return;
|
|
12219
|
+
for (const entry of only ? [only] : this.entries.values()) {
|
|
12206
12220
|
entry.generation++;
|
|
12221
|
+
entry.pending?.abort.abort();
|
|
12207
12222
|
clearTimeout(entry.timer);
|
|
12208
12223
|
entry.timer = void 0;
|
|
12209
12224
|
if (entry.listeners.size && !entry.pending) this.start(entry);
|
|
@@ -12219,7 +12234,7 @@ var DesktopReports = class {
|
|
|
12219
12234
|
}
|
|
12220
12235
|
entry(target) {
|
|
12221
12236
|
for (const [key, entry] of this.entries) if (!entry.readers && !entry.listeners.size && !entry.pending && (!entry.settled || this.now() - entry.settled.at >= this.ttlMs)) this.entries.delete(key);
|
|
12222
|
-
const key = target
|
|
12237
|
+
const key = reportKey(target);
|
|
12223
12238
|
let entry = this.entries.get(key);
|
|
12224
12239
|
if (!entry) {
|
|
12225
12240
|
entry = {
|
|
@@ -12242,6 +12257,7 @@ var DesktopReports = class {
|
|
|
12242
12257
|
const admitted = this.activeLoads < MAX_REPORT_READS;
|
|
12243
12258
|
if (admitted) this.activeLoads++;
|
|
12244
12259
|
const generation = entry.generation;
|
|
12260
|
+
const abort = new AbortController();
|
|
12245
12261
|
const pending = {
|
|
12246
12262
|
promise: Promise.resolve().then(() => {
|
|
12247
12263
|
if (this.closed) return {
|
|
@@ -12252,7 +12268,7 @@ var DesktopReports = class {
|
|
|
12252
12268
|
ok: false,
|
|
12253
12269
|
message: `The server is already running ${MAX_REPORT_READS} shared reports. Try again after a report finishes.`
|
|
12254
12270
|
};
|
|
12255
|
-
return this.load(entry.target);
|
|
12271
|
+
return this.load(entry.target, abort.signal);
|
|
12256
12272
|
}).catch((cause) => ({
|
|
12257
12273
|
ok: false,
|
|
12258
12274
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -12273,7 +12289,8 @@ var DesktopReports = class {
|
|
|
12273
12289
|
this.publish(entry);
|
|
12274
12290
|
this.schedule(entry);
|
|
12275
12291
|
}),
|
|
12276
|
-
generation
|
|
12292
|
+
generation,
|
|
12293
|
+
abort
|
|
12277
12294
|
};
|
|
12278
12295
|
entry.pending = pending;
|
|
12279
12296
|
this.publish(entry);
|
|
@@ -13782,7 +13799,12 @@ function lastCursor(page) {
|
|
|
13782
13799
|
return last ? archiveCursor(last) : null;
|
|
13783
13800
|
}
|
|
13784
13801
|
const STATUS_READ_WAIT_MS = 3e4;
|
|
13785
|
-
const
|
|
13802
|
+
const REQUEST_LIMITS = {
|
|
13803
|
+
connection: 32,
|
|
13804
|
+
device: 16
|
|
13805
|
+
};
|
|
13806
|
+
const MAX_REPORT_WAITS = 16;
|
|
13807
|
+
const MAX_RETAINED_PLANS = 4;
|
|
13786
13808
|
function isWorkspaceChangeMethod(method) {
|
|
13787
13809
|
return method === "workspace.files" || method === "workspace.diff";
|
|
13788
13810
|
}
|
|
@@ -14433,6 +14455,11 @@ async function startServer(options) {
|
|
|
14433
14455
|
return foldBuild;
|
|
14434
14456
|
};
|
|
14435
14457
|
const running = /* @__PURE__ */ new Set();
|
|
14458
|
+
const peerCommands = /* @__PURE__ */ new Map();
|
|
14459
|
+
const requestLimits = {
|
|
14460
|
+
...REQUEST_LIMITS,
|
|
14461
|
+
...options.requestLimits
|
|
14462
|
+
};
|
|
14436
14463
|
const cancelling = new Pending();
|
|
14437
14464
|
const logLimits = {
|
|
14438
14465
|
...LOG_LIMITS,
|
|
@@ -14449,8 +14476,10 @@ async function startServer(options) {
|
|
|
14449
14476
|
const busyWorkspaces = /* @__PURE__ */ new Set();
|
|
14450
14477
|
const retainedLifecycles = /* @__PURE__ */ new Set();
|
|
14451
14478
|
const planQueues = /* @__PURE__ */ new Map();
|
|
14452
|
-
const readDetailsReport = (method, cwd = homedir(), platform) => {
|
|
14479
|
+
const readDetailsReport = (method, cwd = homedir(), platform, signal) => {
|
|
14453
14480
|
const abort = new AbortController();
|
|
14481
|
+
if (signal?.aborted) abort.abort();
|
|
14482
|
+
else signal?.addEventListener("abort", () => abort.abort(), { once: true });
|
|
14454
14483
|
const timeoutMs = options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS;
|
|
14455
14484
|
let task;
|
|
14456
14485
|
const cancel = async () => {
|
|
@@ -14463,10 +14492,14 @@ async function startServer(options) {
|
|
|
14463
14492
|
projectRoot: cwd,
|
|
14464
14493
|
env: options.env
|
|
14465
14494
|
});
|
|
14466
|
-
|
|
14495
|
+
if (method === "doctor") return await stim.doctor({
|
|
14467
14496
|
signal: abort.signal,
|
|
14468
14497
|
platform
|
|
14469
|
-
})
|
|
14498
|
+
});
|
|
14499
|
+
return method === "gc" ? await stim.gc({
|
|
14500
|
+
signal: abort.signal,
|
|
14501
|
+
measureBuildState: false
|
|
14502
|
+
}) : await stim.stats({ signal: abort.signal });
|
|
14470
14503
|
}).then((report) => {
|
|
14471
14504
|
if (method !== "stats" && Buffer.byteLength(JSON.stringify(report)) > commandLimits.maxOutputBytes) return {
|
|
14472
14505
|
ok: false,
|
|
@@ -14486,7 +14519,7 @@ async function startServer(options) {
|
|
|
14486
14519
|
running.add(cancel);
|
|
14487
14520
|
return task;
|
|
14488
14521
|
};
|
|
14489
|
-
const desktopReports = new DesktopReports((target) => readDetailsReport(target.report, target.report === "doctor" ? target.cwd : void 0, target.report === "doctor" ? target.platform : void 0));
|
|
14522
|
+
const desktopReports = new DesktopReports((target, signal) => readDetailsReport(target.report, target.report === "doctor" ? target.cwd : void 0, target.report === "doctor" ? target.platform : void 0, signal));
|
|
14490
14523
|
const machineDetails = new MachineDetailsCache(() => loadMachineDetails(() => readDetailsReport("gc"), loadAudit, () => readDetailsReport("stats")));
|
|
14491
14524
|
const buildMachines = new BuildMachinesCache();
|
|
14492
14525
|
let recordingTurn = Promise.resolve();
|
|
@@ -14578,6 +14611,7 @@ async function startServer(options) {
|
|
|
14578
14611
|
const builds = new BuildHost({
|
|
14579
14612
|
worker: join(dirname(options.stimCli), "offload-worker.mjs"),
|
|
14580
14613
|
env: options.env,
|
|
14614
|
+
currentEnv: options.currentEnv,
|
|
14581
14615
|
limits: options.buildLimits,
|
|
14582
14616
|
ready: () => startup.state === "ready",
|
|
14583
14617
|
finished: ({ client, repo, ok, error, durationMs }) => auditSafely({
|
|
@@ -14758,6 +14792,19 @@ async function startServer(options) {
|
|
|
14758
14792
|
const replays = /* @__PURE__ */ new Map();
|
|
14759
14793
|
let keyframeReads = 0;
|
|
14760
14794
|
const commands = /* @__PURE__ */ new Set();
|
|
14795
|
+
const reportWaits = /* @__PURE__ */ new Set();
|
|
14796
|
+
const holdSlot = (cancel) => {
|
|
14797
|
+
commands.add(cancel);
|
|
14798
|
+
if (peer !== null) peerCommands.set(peer, (peerCommands.get(peer) ?? 0) + 1);
|
|
14799
|
+
};
|
|
14800
|
+
const releaseSlot = (cancel) => {
|
|
14801
|
+
if (!commands.delete(cancel) || peer === null) return;
|
|
14802
|
+
const left = (peerCommands.get(peer) ?? 1) - 1;
|
|
14803
|
+
if (left > 0) peerCommands.set(peer, left);
|
|
14804
|
+
else peerCommands.delete(peer);
|
|
14805
|
+
};
|
|
14806
|
+
const slotsFull = (needed = 1) => commands.size + needed > requestLimits.connection || peer !== null && (peerCommands.get(peer) ?? 0) + needed > requestLimits.device;
|
|
14807
|
+
const slotsMessage = (needed = 1) => peer !== null && commands.size + needed <= requestLimits.connection ? `A remote device can run ${requestLimits.device} requests at a time.` : `A connection can run ${requestLimits.connection} requests at a time.`;
|
|
14761
14808
|
const planRequests = /* @__PURE__ */ new Map();
|
|
14762
14809
|
const persistentActions = /* @__PURE__ */ new Set();
|
|
14763
14810
|
let nextSubscription = 1;
|
|
@@ -14857,6 +14904,7 @@ async function startServer(options) {
|
|
|
14857
14904
|
"desktop-plan",
|
|
14858
14905
|
"desktop-reports",
|
|
14859
14906
|
"desktop-report-platform",
|
|
14907
|
+
"desktop-report-invalidate-target",
|
|
14860
14908
|
"desktop-settings",
|
|
14861
14909
|
"desktop-settings-schema",
|
|
14862
14910
|
"desktop-settings-write",
|
|
@@ -15550,18 +15598,18 @@ async function startServer(options) {
|
|
|
15550
15598
|
subscriptions.set(subscription, cleanup);
|
|
15551
15599
|
}
|
|
15552
15600
|
function archiveRead(id, read) {
|
|
15553
|
-
if (
|
|
15601
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
15554
15602
|
let dropped = false;
|
|
15555
15603
|
const cancel = async () => {
|
|
15556
15604
|
dropped = true;
|
|
15557
15605
|
await task;
|
|
15558
15606
|
};
|
|
15559
|
-
|
|
15607
|
+
holdSlot(cancel);
|
|
15560
15608
|
running.add(cancel);
|
|
15561
15609
|
const task = archiveReads.then(() => new Promise((resolve) => setImmediate(resolve))).then(() => {
|
|
15562
15610
|
if (!dropped && socket.readyState === socket.OPEN) return read();
|
|
15563
15611
|
}).finally(() => {
|
|
15564
|
-
|
|
15612
|
+
releaseSlot(cancel);
|
|
15565
15613
|
running.delete(cancel);
|
|
15566
15614
|
});
|
|
15567
15615
|
archiveReads = task.catch(() => socket.close(1011, "internal error"));
|
|
@@ -15593,14 +15641,14 @@ async function startServer(options) {
|
|
|
15593
15641
|
if (!isJsonObject(params) || typeof params.workspace !== "string" || !params.workspace.length || params.workspace.length > 4096 || params.workspace.includes("\0") || !needsPath && params.group !== "changed" && params.group !== "untracked" || needsPath && (typeof params.path !== "string" || !params.path.length || params.path.length > 4096 || params.path.includes("\0")) || Object.keys(params).some((key) => key !== "workspace" && !(needsPath ? key === "path" : key === "group"))) return error(id, "bad-request", "Workspace diff requires workspace and, for a patch, a file path.");
|
|
15594
15642
|
const cwd = workspaceDir(id, params.workspace, true);
|
|
15595
15643
|
if (!cwd) return;
|
|
15596
|
-
if (
|
|
15644
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
15597
15645
|
const abort = new AbortController();
|
|
15598
15646
|
let task;
|
|
15599
15647
|
const cancel = async () => {
|
|
15600
15648
|
abort.abort();
|
|
15601
15649
|
await task;
|
|
15602
15650
|
};
|
|
15603
|
-
|
|
15651
|
+
holdSlot(cancel);
|
|
15604
15652
|
running.add(cancel);
|
|
15605
15653
|
task = readWorkspaceDiff(cwd, needsPath ? params.path : void 0, options.env, abort.signal, !needsPath ? params.group : void 0).then((result) => {
|
|
15606
15654
|
if (!abort.signal.aborted) send(socket, {
|
|
@@ -15610,13 +15658,13 @@ async function startServer(options) {
|
|
|
15610
15658
|
}, (cause) => {
|
|
15611
15659
|
if (!abort.signal.aborted) error(id, "stim-failed", cause.message);
|
|
15612
15660
|
}).finally(() => {
|
|
15613
|
-
|
|
15661
|
+
releaseSlot(cancel);
|
|
15614
15662
|
running.delete(cancel);
|
|
15615
15663
|
});
|
|
15616
15664
|
}
|
|
15617
|
-
function inProcessRead(id, label, read, timeoutMs = commandLimits.timeoutMs, turn = null) {
|
|
15618
|
-
if (
|
|
15619
|
-
error(id, "limit-exceeded", `A connection can
|
|
15665
|
+
function inProcessRead(id, label, read, timeoutMs = commandLimits.timeoutMs, turn = null, report = false) {
|
|
15666
|
+
if (report ? reportWaits.size >= MAX_REPORT_WAITS : slotsFull()) {
|
|
15667
|
+
error(id, "limit-exceeded", report ? `A connection can wait for ${MAX_REPORT_WAITS} reports at a time.` : slotsMessage());
|
|
15620
15668
|
return null;
|
|
15621
15669
|
}
|
|
15622
15670
|
const abort = new AbortController();
|
|
@@ -15640,14 +15688,16 @@ async function startServer(options) {
|
|
|
15640
15688
|
if (!abort.signal.aborted) error(id, "stim-failed", readFailure(cause));
|
|
15641
15689
|
}).finally(() => {
|
|
15642
15690
|
clearTimeout(deadline);
|
|
15643
|
-
|
|
15691
|
+
releaseSlot(cancel);
|
|
15692
|
+
reportWaits.delete(cancel);
|
|
15644
15693
|
running.delete(cancel);
|
|
15645
15694
|
});
|
|
15646
15695
|
const cancel = async () => {
|
|
15647
15696
|
abort.abort();
|
|
15648
15697
|
await task;
|
|
15649
15698
|
};
|
|
15650
|
-
|
|
15699
|
+
if (report) reportWaits.add(cancel);
|
|
15700
|
+
else holdSlot(cancel);
|
|
15651
15701
|
running.add(cancel);
|
|
15652
15702
|
return {
|
|
15653
15703
|
done: task,
|
|
@@ -15716,7 +15766,7 @@ async function startServer(options) {
|
|
|
15716
15766
|
const { action } = parsed;
|
|
15717
15767
|
const resolved = registeredWorkspace(action.workspace);
|
|
15718
15768
|
if ("code" in resolved) return refuseAction(resolved.code, resolved.message);
|
|
15719
|
-
if (
|
|
15769
|
+
if (slotsFull()) return refuseAction("limit-exceeded", slotsMessage());
|
|
15720
15770
|
if (busyWorkspaces.has(resolved.dir)) return refuseAction("action-busy", `An action is already running in ${resolved.dir}. Try again when it finishes.`);
|
|
15721
15771
|
let admittedCwd = resolved.dir;
|
|
15722
15772
|
let run;
|
|
@@ -15750,7 +15800,7 @@ async function startServer(options) {
|
|
|
15750
15800
|
let finished = false;
|
|
15751
15801
|
const release = (outcome) => {
|
|
15752
15802
|
if (outcome.unresolved) return;
|
|
15753
|
-
|
|
15803
|
+
releaseSlot(cancel);
|
|
15754
15804
|
persistentActions.delete(cancel);
|
|
15755
15805
|
running.delete(cancel);
|
|
15756
15806
|
busyWorkspaces.delete(resolved.dir);
|
|
@@ -15788,7 +15838,7 @@ async function startServer(options) {
|
|
|
15788
15838
|
await run.cancel();
|
|
15789
15839
|
release(await run.drained);
|
|
15790
15840
|
};
|
|
15791
|
-
|
|
15841
|
+
holdSlot(cancel);
|
|
15792
15842
|
persistentActions.add(cancel);
|
|
15793
15843
|
running.add(cancel);
|
|
15794
15844
|
run.outcome.then((outcome) => finish(!outcome.ok && "code" in outcome && outcome.code === "STIM_ACTION_BUSY" ? {
|
|
@@ -15915,7 +15965,7 @@ async function startServer(options) {
|
|
|
15915
15965
|
} catch (cause) {
|
|
15916
15966
|
return error(id, "bad-request", `Cannot reload this workspace: ${readFailure(cause)}`);
|
|
15917
15967
|
}
|
|
15918
|
-
if (
|
|
15968
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
15919
15969
|
if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
|
|
15920
15970
|
const subscription = openSubscription(id);
|
|
15921
15971
|
if (!subscription) return;
|
|
@@ -15956,7 +16006,7 @@ async function startServer(options) {
|
|
|
15956
16006
|
}
|
|
15957
16007
|
});
|
|
15958
16008
|
const cancel = () => run.cancel();
|
|
15959
|
-
|
|
16009
|
+
holdSlot(cancel);
|
|
15960
16010
|
running.add(cancel);
|
|
15961
16011
|
busyWorkspaces.add(cwd);
|
|
15962
16012
|
subscriptions.set(subscription, () => {
|
|
@@ -15966,7 +16016,7 @@ async function startServer(options) {
|
|
|
15966
16016
|
run.outcome.then(finish);
|
|
15967
16017
|
run.drained.then((outcome) => {
|
|
15968
16018
|
if (!outcome.unresolved) {
|
|
15969
|
-
|
|
16019
|
+
releaseSlot(cancel);
|
|
15970
16020
|
running.delete(cancel);
|
|
15971
16021
|
busyWorkspaces.delete(cwd);
|
|
15972
16022
|
busyWorkspaces.delete(admittedCwd);
|
|
@@ -16084,7 +16134,7 @@ async function startServer(options) {
|
|
|
16084
16134
|
} catch (cause) {
|
|
16085
16135
|
return error(id, "bad-request", `Cannot ${kind} this workspace: ${readFailure(cause)}`);
|
|
16086
16136
|
}
|
|
16087
|
-
if (
|
|
16137
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
16088
16138
|
if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
|
|
16089
16139
|
const subscription = openSubscription(id);
|
|
16090
16140
|
if (!subscription) return;
|
|
@@ -16125,7 +16175,7 @@ async function startServer(options) {
|
|
|
16125
16175
|
const cancel = async () => {
|
|
16126
16176
|
await run.cancel();
|
|
16127
16177
|
};
|
|
16128
|
-
|
|
16178
|
+
holdSlot(cancel);
|
|
16129
16179
|
running.add(cancel);
|
|
16130
16180
|
busyWorkspaces.add(cwd);
|
|
16131
16181
|
subscriptions.set(subscription, async () => {
|
|
@@ -16140,7 +16190,7 @@ async function startServer(options) {
|
|
|
16140
16190
|
run.drained.then((outcome) => {
|
|
16141
16191
|
if (kind === "gc" || kind === "worktree-remove" || kind === "worktree-warm" || kind === "machine-access") desktopReports.invalidate();
|
|
16142
16192
|
const release = () => {
|
|
16143
|
-
|
|
16193
|
+
releaseSlot(cancel);
|
|
16144
16194
|
running.delete(cancel);
|
|
16145
16195
|
busyWorkspaces.delete(cwd);
|
|
16146
16196
|
for (const workspace of admittedWorkspaces) busyWorkspaces.delete(workspace);
|
|
@@ -16194,7 +16244,7 @@ async function startServer(options) {
|
|
|
16194
16244
|
} catch (cause) {
|
|
16195
16245
|
return error(id, "bad-request", `Cannot change settings here: ${readFailure(cause)}`);
|
|
16196
16246
|
}
|
|
16197
|
-
if (
|
|
16247
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
16198
16248
|
if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
|
|
16199
16249
|
const { key, action } = params;
|
|
16200
16250
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
@@ -16282,7 +16332,7 @@ async function startServer(options) {
|
|
|
16282
16332
|
} });
|
|
16283
16333
|
}).finally(() => {
|
|
16284
16334
|
clearTimeout(deadline);
|
|
16285
|
-
|
|
16335
|
+
releaseSlot(cancel);
|
|
16286
16336
|
running.delete(cancel);
|
|
16287
16337
|
busyWorkspaces.delete(cwd);
|
|
16288
16338
|
busyWorkspaces.delete(admittedCwd);
|
|
@@ -16308,7 +16358,7 @@ async function startServer(options) {
|
|
|
16308
16358
|
abort.abort();
|
|
16309
16359
|
await task;
|
|
16310
16360
|
};
|
|
16311
|
-
|
|
16361
|
+
holdSlot(cancel);
|
|
16312
16362
|
running.add(cancel);
|
|
16313
16363
|
busyWorkspaces.add(cwd);
|
|
16314
16364
|
}
|
|
@@ -16320,7 +16370,7 @@ async function startServer(options) {
|
|
|
16320
16370
|
abort.abort();
|
|
16321
16371
|
await task;
|
|
16322
16372
|
};
|
|
16323
|
-
|
|
16373
|
+
holdSlot(cancel);
|
|
16324
16374
|
running.add(cancel);
|
|
16325
16375
|
task = Promise.resolve().then(() => createStim({
|
|
16326
16376
|
projectRoot: cwd,
|
|
@@ -16330,7 +16380,7 @@ async function startServer(options) {
|
|
|
16330
16380
|
signal: abort.signal
|
|
16331
16381
|
})).then((records) => Buffer.byteLength(records.map((record) => JSON.stringify(record)).join("\n")) > commandLimits.maxOutputBytes ? `stim logs printed more than ${commandLimits.maxOutputBytes} bytes.` : records, (cause) => readFailure(cause)).finally(() => {
|
|
16332
16382
|
clearTimeout(deadline);
|
|
16333
|
-
|
|
16383
|
+
releaseSlot(cancel);
|
|
16334
16384
|
running.delete(cancel);
|
|
16335
16385
|
});
|
|
16336
16386
|
return task;
|
|
@@ -16372,7 +16422,7 @@ async function startServer(options) {
|
|
|
16372
16422
|
archive
|
|
16373
16423
|
});
|
|
16374
16424
|
if (!cwd) return;
|
|
16375
|
-
if (archive === void 0 &&
|
|
16425
|
+
if (archive === void 0 && slotsFull(2)) return error(id, "limit-exceeded", slotsMessage(2));
|
|
16376
16426
|
const slotName = slot ?? "default";
|
|
16377
16427
|
const spans = recordedSpans(listSegments(recordingDir(workspace, platform, slotName, archive), archive !== void 0));
|
|
16378
16428
|
const replayTarget = {
|
|
@@ -16563,7 +16613,7 @@ async function startServer(options) {
|
|
|
16563
16613
|
}, "build.plan");
|
|
16564
16614
|
}
|
|
16565
16615
|
function queuePlan(id, cwd, plan, method, request) {
|
|
16566
|
-
if (request !== void 0 && planRequests.size >=
|
|
16616
|
+
if (request !== void 0 && planRequests.size >= MAX_RETAINED_PLANS) {
|
|
16567
16617
|
error(id, "limit-exceeded", "Acknowledge retained plan refusals before requesting more previews.");
|
|
16568
16618
|
return;
|
|
16569
16619
|
}
|
|
@@ -16826,8 +16876,7 @@ async function startServer(options) {
|
|
|
16826
16876
|
}).settings({ signal }));
|
|
16827
16877
|
}
|
|
16828
16878
|
function desktopReport(id, method, params) {
|
|
16829
|
-
if (method === "desktop.reports.invalidate") {
|
|
16830
|
-
if (params !== void 0 && (!isJsonObject(params) || Object.keys(params).length)) return error(id, "bad-request", "desktop.reports.invalidate takes no parameters.");
|
|
16879
|
+
if (method === "desktop.reports.invalidate" && (params === void 0 || isJsonObject(params) && !Object.keys(params).length)) {
|
|
16831
16880
|
desktopReports.invalidate();
|
|
16832
16881
|
return send(socket, {
|
|
16833
16882
|
id,
|
|
@@ -16840,12 +16889,19 @@ async function startServer(options) {
|
|
|
16840
16889
|
cwd: resolve(String(params.cwd)),
|
|
16841
16890
|
...params.platform === "ios" || params.platform === "android" ? { platform: params.platform } : {}
|
|
16842
16891
|
};
|
|
16892
|
+
if (method === "desktop.reports.invalidate") {
|
|
16893
|
+
desktopReports.invalidate(target);
|
|
16894
|
+
return send(socket, {
|
|
16895
|
+
id,
|
|
16896
|
+
result: {}
|
|
16897
|
+
});
|
|
16898
|
+
}
|
|
16843
16899
|
if (method === "desktop.reports.get") {
|
|
16844
16900
|
inProcessRead(id, `stim ${target.report}`, async (signal) => {
|
|
16845
16901
|
const snapshot = await desktopReports.get(target, signal);
|
|
16846
16902
|
if (snapshot.error) throw new Error(snapshot.error);
|
|
16847
16903
|
return snapshot;
|
|
16848
|
-
}, options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS);
|
|
16904
|
+
}, options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS, null, true);
|
|
16849
16905
|
return;
|
|
16850
16906
|
}
|
|
16851
16907
|
const subscription = openSubscription(id);
|
|
@@ -17217,12 +17273,14 @@ async function startServer(options) {
|
|
|
17217
17273
|
controllers.delete(socket);
|
|
17218
17274
|
for (const unsubscribe of subscriptions.values()) unsubscribe();
|
|
17219
17275
|
subscriptions.clear();
|
|
17220
|
-
for (const cancel of commands) {
|
|
17221
|
-
if (persistentActions.has(cancel))
|
|
17276
|
+
for (const cancel of [...commands, ...reportWaits]) {
|
|
17277
|
+
if (persistentActions.has(cancel)) {
|
|
17278
|
+
releaseSlot(cancel);
|
|
17279
|
+
continue;
|
|
17280
|
+
}
|
|
17222
17281
|
running.delete(cancel);
|
|
17223
17282
|
cancelling.track(cancel());
|
|
17224
17283
|
}
|
|
17225
|
-
commands.clear();
|
|
17226
17284
|
});
|
|
17227
17285
|
}
|
|
17228
17286
|
function upgrade(request, socket, head) {
|
|
@@ -17410,7 +17468,13 @@ async function startServer(options) {
|
|
|
17410
17468
|
return;
|
|
17411
17469
|
}
|
|
17412
17470
|
if (request.method === "GET" && request.url === "/health" && peerAddress(request) !== null && request.headers.origin === void 0 && request.headers["sec-fetch-site"] === void 0) {
|
|
17413
|
-
const peerHealth = {
|
|
17471
|
+
const peerHealth = {
|
|
17472
|
+
version: health.version,
|
|
17473
|
+
...startup.state === "ready" ? {} : { startup: startup.state === "degraded" ? {
|
|
17474
|
+
state: "degraded",
|
|
17475
|
+
cause: startup.cause
|
|
17476
|
+
} : { state: "pending" } }
|
|
17477
|
+
};
|
|
17414
17478
|
response.writeHead(startup.state === "ready" ? 200 : 503, { "content-type": "application/json" }).end(JSON.stringify(peerHealth));
|
|
17415
17479
|
return;
|
|
17416
17480
|
}
|
|
@@ -17595,6 +17659,7 @@ async function startServer(options) {
|
|
|
17595
17659
|
}
|
|
17596
17660
|
startupProbe = probeDirectories(directories, options.env, options.startupProbeMs ?? 1e4, configDir());
|
|
17597
17661
|
let reason = await startupProbe.result;
|
|
17662
|
+
let cause = "home-unreadable";
|
|
17598
17663
|
startupProbe = null;
|
|
17599
17664
|
if (closing) return settle(startup);
|
|
17600
17665
|
if (reason === null) try {
|
|
@@ -17606,6 +17671,7 @@ async function startServer(options) {
|
|
|
17606
17671
|
becomeReady();
|
|
17607
17672
|
} catch (error) {
|
|
17608
17673
|
reason = error.message;
|
|
17674
|
+
cause = "start-failed";
|
|
17609
17675
|
}
|
|
17610
17676
|
if (reason === null) {
|
|
17611
17677
|
if (startup.state === "degraded") console.error("stim-server: the Stim home answers again; serving clients.");
|
|
@@ -17630,7 +17696,8 @@ async function startServer(options) {
|
|
|
17630
17696
|
if (startup.state !== "degraded" || startup.reason !== reason) console.error(`stim-server: listening, but not serving clients until the Stim home answers. ${reason}`);
|
|
17631
17697
|
startup = {
|
|
17632
17698
|
state: "degraded",
|
|
17633
|
-
reason
|
|
17699
|
+
reason,
|
|
17700
|
+
cause
|
|
17634
17701
|
};
|
|
17635
17702
|
settle(startup);
|
|
17636
17703
|
startupRetry = setTimeout(() => void checkHome(), options.startupRetryMs ?? 3e4);
|
|
@@ -17781,12 +17848,21 @@ async function serve(port, extraEnv, pathPrepend, loopbackOnly) {
|
|
|
17781
17848
|
const desktopInput = bootstrapFd === "0" ? await readDesktopBootstrap(process.stdin).catch(() => fail("Desktop bootstrap input is invalid or unavailable.")) : void 0;
|
|
17782
17849
|
delete process.env.NODE_COMPILE_CACHE;
|
|
17783
17850
|
const captureHost = hostFromExecutable(process.env.STIM_HOST_EXECUTABLE);
|
|
17784
|
-
const
|
|
17785
|
-
|
|
17786
|
-
|
|
17851
|
+
const inherited = { ...process.env };
|
|
17852
|
+
const serveEnvironment = (warn) => {
|
|
17853
|
+
const found = applyServeEnvironment(discoverToolchain(inherited).env, extraEnv, pathPrepend, process.env, warn);
|
|
17854
|
+
delete found.CLAUDE_CODE_SESSION_ID;
|
|
17855
|
+
delete found.CODEX_THREAD_ID;
|
|
17856
|
+
return found;
|
|
17857
|
+
};
|
|
17858
|
+
const env = serveEnvironment();
|
|
17787
17859
|
if (!process.env.STIM_HOME && env.STIM_HOME) process.env.STIM_HOME = env.STIM_HOME;
|
|
17788
17860
|
if (process.env.STIM_HOME) env.STIM_HOME = process.env.STIM_HOME;
|
|
17789
17861
|
Object.assign(process.env, env);
|
|
17862
|
+
const currentEnv = () => ({
|
|
17863
|
+
...serveEnvironment(() => {}),
|
|
17864
|
+
...env.STIM_HOME ? { STIM_HOME: env.STIM_HOME } : {}
|
|
17865
|
+
});
|
|
17790
17866
|
const loaded = await loadTransport({
|
|
17791
17867
|
reference: "@stim-cli/transport-tailscale",
|
|
17792
17868
|
load: () => import("@stim-cli/transport-tailscale"),
|
|
@@ -17813,6 +17889,7 @@ async function serve(port, extraEnv, pathPrepend, loopbackOnly) {
|
|
|
17813
17889
|
stimVersion: stim.version,
|
|
17814
17890
|
serverVersion: pkg.version,
|
|
17815
17891
|
env,
|
|
17892
|
+
currentEnv,
|
|
17816
17893
|
transports: [transport],
|
|
17817
17894
|
service: {
|
|
17818
17895
|
label: serviceManagerOrNull()?.runningLabel(process.env) ?? null,
|
package/dist/stim-server.mjs
CHANGED
|
@@ -4,6 +4,6 @@ const encode = (version) => version.split(".", 3).reduce((sum, part) => sum * 1e
|
|
|
4
4
|
if (encode(process.versions.node) < encode("22.12.0")) {
|
|
5
5
|
process.stderr.write(`STIM_NODE_UNSUPPORTED: stim-server needs Node 22.12.0 or later; this is Node ${process.versions.node} at ${process.execPath}. To run stim-server where a project pins an older Node, see https://stim.appandflow.com/docs/requirements#older-node-pins\n`);
|
|
6
6
|
process.exitCode = 1;
|
|
7
|
-
} else import("./stim-server-
|
|
7
|
+
} else import("./stim-server-DU1htjam.mjs");
|
|
8
8
|
//#endregion
|
|
9
9
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stim-cli/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "Serves Stim status to paired clients over Tailscale, and runs reload and stop for clients granted control.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"./protocol.schema.json": "./dist/protocol.schema.json"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@stim-cli/agent-driver": "
|
|
32
|
-
"@stim-cli/agent-driver-agent-device": "
|
|
33
|
-
"@stim-cli/cache": "
|
|
34
|
-
"@stim-cli/core": "
|
|
35
|
-
"@stim-cli/transport": "
|
|
36
|
-
"@stim-cli/transport-tailscale": "
|
|
37
|
-
"stim": "1.
|
|
31
|
+
"@stim-cli/agent-driver": "1.26.0",
|
|
32
|
+
"@stim-cli/agent-driver-agent-device": "1.26.0",
|
|
33
|
+
"@stim-cli/cache": "1.26.0",
|
|
34
|
+
"@stim-cli/core": "1.26.0",
|
|
35
|
+
"@stim-cli/transport": "1.26.0",
|
|
36
|
+
"@stim-cli/transport-tailscale": "1.26.0",
|
|
37
|
+
"stim": "1.26.0",
|
|
38
38
|
"ws": "^8.21.3"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|