@stim-cli/server 1.18.0 → 1.20.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 +24 -7
- package/dist/{protocol-qo5TTavl.mjs → protocol-CjUChTWJ.mjs} +94 -7
- package/dist/protocol.mjs +1 -1
- package/dist/protocol.schema.json +139 -6
- package/dist/stim-frames/EmulatorFrameArtwork.swift +9 -0
- package/dist/stim-frames/SimulatorDisplay.swift +4 -1
- package/dist/stim-frames/SimulatorFrameArtwork.swift +9 -0
- package/dist/{stim-server-BR9FPA63.mjs → stim-server-C5iKxjba.mjs} +230 -62
- package/dist/stim-server.mjs +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,7 +37,8 @@ runs (see [Tailscale](#tailscale)). Stim Desktop starts its own server with the
|
|
|
37
37
|
unless it serves phones.
|
|
38
38
|
|
|
39
39
|
`--env KEY=VALUE` and `--path-prepend <dir>` (each repeatable) apply to the
|
|
40
|
-
serving command: see [Run as a service](#run-as-a-service).
|
|
40
|
+
serving command: see [Run as a service](#run-as-a-service). When serving, a bare
|
|
41
|
+
`--env KEY` takes the value from the server's own environment.
|
|
41
42
|
|
|
42
43
|
`pair --json` prints `{ "qr": <payload>, "expiresAt": "<ISO time>" }`, and
|
|
43
44
|
`devices --json` prints `{ "devices": [...] }` with each device's `id`, `name`,
|
|
@@ -280,6 +281,10 @@ loopback port: a forwarder that omits that header, such as `tailscale serve
|
|
|
280
281
|
|
|
281
282
|
A connection must send `hello` within 5 seconds. Five failed attempts from the
|
|
282
283
|
same peer within a minute block new connections from it for up to a minute.
|
|
284
|
+
The server answers 403 to a WebSocket upgrade that carries `Sec-Fetch-Site`, or
|
|
285
|
+
an `Origin` other than the requested host (`http:` on loopback, `https:`
|
|
286
|
+
through the route), and counts no failed attempt, so a web page can neither
|
|
287
|
+
connect nor block this Mac's clients.
|
|
283
288
|
|
|
284
289
|
Paired devices live in `$STIM_HOME/server/devices.json`. Revoking a device
|
|
285
290
|
closes its open connections. The server checks registrations on file changes
|
|
@@ -1048,9 +1053,15 @@ created a route to another `--port`, run `uninstall` first.
|
|
|
1048
1053
|
the server. stim-server replaces its environment with the login shell's at
|
|
1049
1054
|
start, so a `PATH` or `GEM_HOME` in the plist alone is lost and an entry in
|
|
1050
1055
|
`~/.zshrc` changes the terminal too. These flags apply after that capture, to
|
|
1051
|
-
the server and to the builds it starts
|
|
1052
|
-
`
|
|
1053
|
-
|
|
1056
|
+
the server and to the builds it starts. The plist stores each `--env` value in
|
|
1057
|
+
`EnvironmentVariables`, so it stays out of the server's process arguments, and
|
|
1058
|
+
passes only the name as `--env KEY`; the server reads that variable from the
|
|
1059
|
+
environment launchd gives it. The values sit in plain text in the plist. A
|
|
1060
|
+
plist written by an earlier release keeps `--env KEY=VALUE` in its
|
|
1061
|
+
`ProgramArguments` until the next `service install`. `--path-prepend` puts
|
|
1062
|
+
directories in front of PATH in the order given, as `ProgramArguments`; `--env`
|
|
1063
|
+
cannot set `STIM_HOME` or `SHELL`, which the job takes from the installing
|
|
1064
|
+
process. For a private CocoaPods:
|
|
1054
1065
|
|
|
1055
1066
|
```bash
|
|
1056
1067
|
stim-server service install --serve \
|
|
@@ -1119,7 +1130,9 @@ methods need that approval, not `read`:
|
|
|
1119
1130
|
it does not run as a `stim-server service` and so cannot update itself),
|
|
1120
1131
|
`acceptsClientBuilds`, `running` (the update in progress, with its `state`,
|
|
1121
1132
|
`missing` package bytes and the last lines of its `log`) and `last` (how the
|
|
1122
|
-
label's last `service update` ended, whoever ran it
|
|
1133
|
+
label's last `service update` ended, whoever ran it, or how the update this
|
|
1134
|
+
server started ended when that is newer, such as one the update lock refused,
|
|
1135
|
+
with the last line of its log).
|
|
1123
1136
|
- `server.update.start` takes `{ "release": "<version>" }`, an exact version
|
|
1124
1137
|
that `stim-server service update --release` installs, or
|
|
1125
1138
|
`{ "packages": [{ "name", "size", "sha256" }] }`, at most 8 `.tgz` packages
|
|
@@ -1189,7 +1202,11 @@ closes its connections and cancels its builds.
|
|
|
1189
1202
|
while the worker root's volume has less than 10 GiB free, while `builds`
|
|
1190
1203
|
reaches a non-zero `maxBuilds`, or while `loadPerCore` is at or above
|
|
1191
1204
|
`maxLoadPerCore`. The toolchain is read at most once a minute per Ruby context; capacity on
|
|
1192
|
-
every offer.
|
|
1205
|
+
every offer. With the `native-xcode-toolchain` feature, an offer can set
|
|
1206
|
+
`native: "xcode"` to collect only Stim, CPU, Xcode, iPhoneSimulator SDK and
|
|
1207
|
+
simulator-runtime facts. Unused report fields are null. This report has its own
|
|
1208
|
+
cache, ignores Ruby selection, and is reused for native Xcode build admission.
|
|
1209
|
+
Clients use the complete report on older native-build-capable hosts.
|
|
1193
1210
|
- `build.sync` takes `repo`, `files` and `done`. `files` is one page of the
|
|
1194
1211
|
manifest, each `{ "path", "kind": "file"|"exec"|"link", "size", "sha256" }`,
|
|
1195
1212
|
a link's blob being its target. Pages accumulate until `done`; the next
|
|
@@ -1329,7 +1346,7 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
1329
1346
|
(none without `control`), the paired device, and the new `deviceToken` when
|
|
1330
1347
|
the hello paired. `server.home` is the home folder
|
|
1331
1348
|
of the user the server runs as, so clients can show paths under it as
|
|
1332
|
-
`~/...`.
|
|
1349
|
+
`~/...`. A pending approval result omits it.
|
|
1333
1350
|
- `status.subscribe` returns a subscription id. Each `status` event carries a
|
|
1334
1351
|
full payload as `stim status --watch --json` prints it, including each
|
|
1335
1352
|
environment's `physicalDevices`, the phones it leases, and its `agents`, the
|
|
@@ -120,7 +120,11 @@ function protocolJsonSchema() {
|
|
|
120
120
|
session: { type: "string" },
|
|
121
121
|
attempt: { type: "string" },
|
|
122
122
|
bundleId: { type: "string" },
|
|
123
|
-
mode: { enum: [
|
|
123
|
+
mode: { enum: [
|
|
124
|
+
"development",
|
|
125
|
+
"release",
|
|
126
|
+
"process"
|
|
127
|
+
] },
|
|
124
128
|
devClientScheme: {
|
|
125
129
|
type: "string",
|
|
126
130
|
pattern: "^[a-zA-Z][a-zA-Z0-9+.-]{0,127}$"
|
|
@@ -864,8 +868,7 @@ function protocolJsonSchema() {
|
|
|
864
868
|
required: [
|
|
865
869
|
"name",
|
|
866
870
|
"version",
|
|
867
|
-
"stim"
|
|
868
|
-
"home"
|
|
871
|
+
"stim"
|
|
869
872
|
],
|
|
870
873
|
additionalProperties: false,
|
|
871
874
|
properties: {
|
|
@@ -1706,7 +1709,11 @@ function protocolJsonSchema() {
|
|
|
1706
1709
|
pattern: "^[a-zA-Z0-9_-]{1,128}$"
|
|
1707
1710
|
},
|
|
1708
1711
|
bundleId: { type: "string" },
|
|
1709
|
-
mode: { enum: [
|
|
1712
|
+
mode: { enum: [
|
|
1713
|
+
"development",
|
|
1714
|
+
"release",
|
|
1715
|
+
"process"
|
|
1716
|
+
] },
|
|
1710
1717
|
devClientScheme: {
|
|
1711
1718
|
type: "string",
|
|
1712
1719
|
pattern: "^[a-zA-Z][a-zA-Z0-9+.-]{0,127}$"
|
|
@@ -2217,7 +2224,8 @@ function protocolJsonSchema() {
|
|
|
2217
2224
|
rubyVersion: {
|
|
2218
2225
|
type: "string",
|
|
2219
2226
|
pattern: BUILD_RUBY_VERSION_PATTERN
|
|
2220
|
-
}
|
|
2227
|
+
},
|
|
2228
|
+
native: { enum: ["xcode", "gradle"] }
|
|
2221
2229
|
}
|
|
2222
2230
|
}),
|
|
2223
2231
|
request("build.sync", {
|
|
@@ -2249,7 +2257,8 @@ function protocolJsonSchema() {
|
|
|
2249
2257
|
kind: { enum: [
|
|
2250
2258
|
"file",
|
|
2251
2259
|
"exec",
|
|
2252
|
-
"link"
|
|
2260
|
+
"link",
|
|
2261
|
+
"directory"
|
|
2253
2262
|
] },
|
|
2254
2263
|
size: {
|
|
2255
2264
|
type: "integer",
|
|
@@ -2264,6 +2273,18 @@ function protocolJsonSchema() {
|
|
|
2264
2273
|
}),
|
|
2265
2274
|
request("build.start", {
|
|
2266
2275
|
type: "object",
|
|
2276
|
+
if: {
|
|
2277
|
+
required: ["native"],
|
|
2278
|
+
properties: { native: { properties: { provider: { const: "gradle" } } } }
|
|
2279
|
+
},
|
|
2280
|
+
then: { properties: {
|
|
2281
|
+
platform: { const: "android" },
|
|
2282
|
+
fingerprint: { type: "null" }
|
|
2283
|
+
} },
|
|
2284
|
+
else: { properties: { fingerprint: {
|
|
2285
|
+
type: "string",
|
|
2286
|
+
minLength: 1
|
|
2287
|
+
} } },
|
|
2267
2288
|
required: [
|
|
2268
2289
|
"repo",
|
|
2269
2290
|
"project",
|
|
@@ -2307,9 +2328,75 @@ function protocolJsonSchema() {
|
|
|
2307
2328
|
minLength: 1
|
|
2308
2329
|
},
|
|
2309
2330
|
fingerprint: {
|
|
2310
|
-
type: "string",
|
|
2331
|
+
type: ["string", "null"],
|
|
2311
2332
|
minLength: 1
|
|
2312
2333
|
},
|
|
2334
|
+
native: { oneOf: [{
|
|
2335
|
+
type: "object",
|
|
2336
|
+
required: [
|
|
2337
|
+
"provider",
|
|
2338
|
+
"sourceDigest",
|
|
2339
|
+
"cacheKey",
|
|
2340
|
+
"arch",
|
|
2341
|
+
"parameters"
|
|
2342
|
+
],
|
|
2343
|
+
additionalProperties: false,
|
|
2344
|
+
properties: {
|
|
2345
|
+
provider: { const: "xcode" },
|
|
2346
|
+
sourceDigest: sha256,
|
|
2347
|
+
cacheKey: {
|
|
2348
|
+
type: "string",
|
|
2349
|
+
minLength: 1
|
|
2350
|
+
},
|
|
2351
|
+
arch: { enum: [
|
|
2352
|
+
"arm64",
|
|
2353
|
+
"x86_64",
|
|
2354
|
+
null
|
|
2355
|
+
] },
|
|
2356
|
+
parameters: {
|
|
2357
|
+
type: "object",
|
|
2358
|
+
description: "The non-file parameters of the client artifact identity, to name what differs."
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}, {
|
|
2362
|
+
type: "object",
|
|
2363
|
+
required: [
|
|
2364
|
+
"provider",
|
|
2365
|
+
"sourceDigest",
|
|
2366
|
+
"inputs"
|
|
2367
|
+
],
|
|
2368
|
+
additionalProperties: false,
|
|
2369
|
+
properties: {
|
|
2370
|
+
provider: { const: "gradle" },
|
|
2371
|
+
sourceDigest: sha256,
|
|
2372
|
+
inputs: {
|
|
2373
|
+
type: "object",
|
|
2374
|
+
required: [
|
|
2375
|
+
"complete",
|
|
2376
|
+
"ignored",
|
|
2377
|
+
"outputs"
|
|
2378
|
+
],
|
|
2379
|
+
additionalProperties: false,
|
|
2380
|
+
properties: {
|
|
2381
|
+
complete: { const: true },
|
|
2382
|
+
ignored: {
|
|
2383
|
+
type: "array",
|
|
2384
|
+
items: {
|
|
2385
|
+
type: "string",
|
|
2386
|
+
minLength: 1
|
|
2387
|
+
}
|
|
2388
|
+
},
|
|
2389
|
+
outputs: {
|
|
2390
|
+
type: "array",
|
|
2391
|
+
items: {
|
|
2392
|
+
type: "string",
|
|
2393
|
+
minLength: 1
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
}] },
|
|
2313
2400
|
packageName: { type: ["string", "null"] },
|
|
2314
2401
|
isExpo: { type: "boolean" },
|
|
2315
2402
|
optimizations: { type: ["object", "null"] },
|
package/dist/protocol.mjs
CHANGED
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
"mode": {
|
|
71
71
|
"enum": [
|
|
72
72
|
"development",
|
|
73
|
-
"release"
|
|
73
|
+
"release",
|
|
74
|
+
"process"
|
|
74
75
|
]
|
|
75
76
|
},
|
|
76
77
|
"devClientScheme": {
|
|
@@ -1260,8 +1261,7 @@
|
|
|
1260
1261
|
"required": [
|
|
1261
1262
|
"name",
|
|
1262
1263
|
"version",
|
|
1263
|
-
"stim"
|
|
1264
|
-
"home"
|
|
1264
|
+
"stim"
|
|
1265
1265
|
],
|
|
1266
1266
|
"additionalProperties": false,
|
|
1267
1267
|
"properties": {
|
|
@@ -1312,7 +1312,12 @@
|
|
|
1312
1312
|
"workspace-diff",
|
|
1313
1313
|
"hosted-congestion",
|
|
1314
1314
|
"hosted-ios-data",
|
|
1315
|
+
"hosted-ios-process",
|
|
1316
|
+
"native-xcode-build",
|
|
1317
|
+
"native-xcode-toolchain",
|
|
1318
|
+
"native-gradle-build",
|
|
1315
1319
|
"hosted-android-data",
|
|
1320
|
+
"hosted-android-process",
|
|
1316
1321
|
"server-update"
|
|
1317
1322
|
]
|
|
1318
1323
|
}
|
|
@@ -2778,7 +2783,8 @@
|
|
|
2778
2783
|
"mode": {
|
|
2779
2784
|
"enum": [
|
|
2780
2785
|
"development",
|
|
2781
|
-
"release"
|
|
2786
|
+
"release",
|
|
2787
|
+
"process"
|
|
2782
2788
|
]
|
|
2783
2789
|
},
|
|
2784
2790
|
"devClientScheme": {
|
|
@@ -5224,6 +5230,12 @@
|
|
|
5224
5230
|
"rubyVersion": {
|
|
5225
5231
|
"type": "string",
|
|
5226
5232
|
"pattern": "^(?!\\.{1,2}$)[^/\\\\\\u0000\\r\\n]+$"
|
|
5233
|
+
},
|
|
5234
|
+
"native": {
|
|
5235
|
+
"enum": [
|
|
5236
|
+
"xcode",
|
|
5237
|
+
"gradle"
|
|
5238
|
+
]
|
|
5227
5239
|
}
|
|
5228
5240
|
}
|
|
5229
5241
|
}
|
|
@@ -5280,7 +5292,8 @@
|
|
|
5280
5292
|
"enum": [
|
|
5281
5293
|
"file",
|
|
5282
5294
|
"exec",
|
|
5283
|
-
"link"
|
|
5295
|
+
"link",
|
|
5296
|
+
"directory"
|
|
5284
5297
|
]
|
|
5285
5298
|
},
|
|
5286
5299
|
"size": {
|
|
@@ -5321,6 +5334,38 @@
|
|
|
5321
5334
|
},
|
|
5322
5335
|
"params": {
|
|
5323
5336
|
"type": "object",
|
|
5337
|
+
"if": {
|
|
5338
|
+
"required": [
|
|
5339
|
+
"native"
|
|
5340
|
+
],
|
|
5341
|
+
"properties": {
|
|
5342
|
+
"native": {
|
|
5343
|
+
"properties": {
|
|
5344
|
+
"provider": {
|
|
5345
|
+
"const": "gradle"
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
},
|
|
5351
|
+
"then": {
|
|
5352
|
+
"properties": {
|
|
5353
|
+
"platform": {
|
|
5354
|
+
"const": "android"
|
|
5355
|
+
},
|
|
5356
|
+
"fingerprint": {
|
|
5357
|
+
"type": "null"
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
},
|
|
5361
|
+
"else": {
|
|
5362
|
+
"properties": {
|
|
5363
|
+
"fingerprint": {
|
|
5364
|
+
"type": "string",
|
|
5365
|
+
"minLength": 1
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
},
|
|
5324
5369
|
"required": [
|
|
5325
5370
|
"repo",
|
|
5326
5371
|
"project",
|
|
@@ -5398,9 +5443,97 @@
|
|
|
5398
5443
|
"minLength": 1
|
|
5399
5444
|
},
|
|
5400
5445
|
"fingerprint": {
|
|
5401
|
-
"type":
|
|
5446
|
+
"type": [
|
|
5447
|
+
"string",
|
|
5448
|
+
"null"
|
|
5449
|
+
],
|
|
5402
5450
|
"minLength": 1
|
|
5403
5451
|
},
|
|
5452
|
+
"native": {
|
|
5453
|
+
"oneOf": [
|
|
5454
|
+
{
|
|
5455
|
+
"type": "object",
|
|
5456
|
+
"required": [
|
|
5457
|
+
"provider",
|
|
5458
|
+
"sourceDigest",
|
|
5459
|
+
"cacheKey",
|
|
5460
|
+
"arch",
|
|
5461
|
+
"parameters"
|
|
5462
|
+
],
|
|
5463
|
+
"additionalProperties": false,
|
|
5464
|
+
"properties": {
|
|
5465
|
+
"provider": {
|
|
5466
|
+
"const": "xcode"
|
|
5467
|
+
},
|
|
5468
|
+
"sourceDigest": {
|
|
5469
|
+
"type": "string",
|
|
5470
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
5471
|
+
},
|
|
5472
|
+
"cacheKey": {
|
|
5473
|
+
"type": "string",
|
|
5474
|
+
"minLength": 1
|
|
5475
|
+
},
|
|
5476
|
+
"arch": {
|
|
5477
|
+
"enum": [
|
|
5478
|
+
"arm64",
|
|
5479
|
+
"x86_64",
|
|
5480
|
+
null
|
|
5481
|
+
]
|
|
5482
|
+
},
|
|
5483
|
+
"parameters": {
|
|
5484
|
+
"type": "object",
|
|
5485
|
+
"description": "The non-file parameters of the client artifact identity, to name what differs."
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
},
|
|
5489
|
+
{
|
|
5490
|
+
"type": "object",
|
|
5491
|
+
"required": [
|
|
5492
|
+
"provider",
|
|
5493
|
+
"sourceDigest",
|
|
5494
|
+
"inputs"
|
|
5495
|
+
],
|
|
5496
|
+
"additionalProperties": false,
|
|
5497
|
+
"properties": {
|
|
5498
|
+
"provider": {
|
|
5499
|
+
"const": "gradle"
|
|
5500
|
+
},
|
|
5501
|
+
"sourceDigest": {
|
|
5502
|
+
"type": "string",
|
|
5503
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
5504
|
+
},
|
|
5505
|
+
"inputs": {
|
|
5506
|
+
"type": "object",
|
|
5507
|
+
"required": [
|
|
5508
|
+
"complete",
|
|
5509
|
+
"ignored",
|
|
5510
|
+
"outputs"
|
|
5511
|
+
],
|
|
5512
|
+
"additionalProperties": false,
|
|
5513
|
+
"properties": {
|
|
5514
|
+
"complete": {
|
|
5515
|
+
"const": true
|
|
5516
|
+
},
|
|
5517
|
+
"ignored": {
|
|
5518
|
+
"type": "array",
|
|
5519
|
+
"items": {
|
|
5520
|
+
"type": "string",
|
|
5521
|
+
"minLength": 1
|
|
5522
|
+
}
|
|
5523
|
+
},
|
|
5524
|
+
"outputs": {
|
|
5525
|
+
"type": "array",
|
|
5526
|
+
"items": {
|
|
5527
|
+
"type": "string",
|
|
5528
|
+
"minLength": 1
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
]
|
|
5536
|
+
},
|
|
5404
5537
|
"packageName": {
|
|
5405
5538
|
"type": [
|
|
5406
5539
|
"string",
|
|
@@ -66,6 +66,15 @@ private struct SkinNode {
|
|
|
66
66
|
|
|
67
67
|
@MainActor
|
|
68
68
|
enum EmulatorFrameArtwork {
|
|
69
|
+
private static var cache: [String: DeviceFrameArtwork] = [:]
|
|
70
|
+
|
|
71
|
+
static func cached(avdName: String) -> DeviceFrameArtwork? {
|
|
72
|
+
if let hit = cache[avdName] { return hit }
|
|
73
|
+
let artwork = load(avdName: avdName)
|
|
74
|
+
if let artwork { cache[avdName] = artwork }
|
|
75
|
+
return artwork
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
static func load(avdName: String) -> DeviceFrameArtwork? {
|
|
70
79
|
let environment = ProcessInfo.processInfo.environment
|
|
71
80
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
@@ -66,7 +66,10 @@ public enum CoreSimulator {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
static func simulatorKitPath(_ developerDir: String) -> String {
|
|
69
|
-
"\(developerDir)/../SharedFrameworks/SimulatorKit.framework/SimulatorKit"
|
|
69
|
+
let shared = "\(developerDir)/../SharedFrameworks/SimulatorKit.framework/SimulatorKit"
|
|
70
|
+
let legacy = "\(developerDir)/Library/PrivateFrameworks/SimulatorKit.framework/SimulatorKit"
|
|
71
|
+
return FileManager.default.fileExists(atPath: shared) || !FileManager.default.fileExists(atPath: legacy)
|
|
72
|
+
? shared : legacy
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
static let deviceSet: NSObject? = {
|
|
@@ -7,6 +7,15 @@ import AppKit
|
|
|
7
7
|
|
|
8
8
|
@MainActor
|
|
9
9
|
enum SimulatorFrameArtwork {
|
|
10
|
+
private static var cache: [String: DeviceFrameArtwork] = [:]
|
|
11
|
+
|
|
12
|
+
static func cached(udid: String) -> DeviceFrameArtwork? {
|
|
13
|
+
if let hit = cache[udid] { return hit }
|
|
14
|
+
let artwork = load(udid: udid)
|
|
15
|
+
if let artwork { cache[udid] = artwork }
|
|
16
|
+
return artwork
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
static func load(udid: String) -> DeviceFrameArtwork? {
|
|
11
20
|
guard let resources = resources(udid: udid) else { return nil }
|
|
12
21
|
let profile = plist(resources.appendingPathComponent("profile.plist"))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as protocol_exports } from "./protocol-
|
|
1
|
+
import { n as protocol_exports } from "./protocol-CjUChTWJ.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -187,6 +187,13 @@ const MAX_CLIENT_NAME = 64;
|
|
|
187
187
|
function validClientName(name) {
|
|
188
188
|
return name.length <= MAX_CLIENT_NAME && !/[\p{Cc}\p{Cf}]/u.test(name);
|
|
189
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* A phone sends its OS device name, which the person cannot edit in the app, so pairing cleans it up instead of
|
|
192
|
+
* refusing it. U+200D stays because emoji sequences join with it.
|
|
193
|
+
*/
|
|
194
|
+
function pairingName(name) {
|
|
195
|
+
return name.replace(/(?![\u200d\s])[\p{Cc}\p{Cf}]/gu, "").replace(/\s+/g, " ").trim().slice(0, MAX_CLIENT_NAME).replace(/[\ud800-\udbff]$/, "").trim() || "Phone";
|
|
196
|
+
}
|
|
190
197
|
function newDeviceId() {
|
|
191
198
|
const taken = new Set([
|
|
192
199
|
...readDevices(),
|
|
@@ -331,7 +338,7 @@ function spendPairingToken(token, name, identity, now = Date.now()) {
|
|
|
331
338
|
const at = new Date(now).toISOString();
|
|
332
339
|
const device = {
|
|
333
340
|
id: newDeviceId(),
|
|
334
|
-
name,
|
|
341
|
+
name: pairingName(name),
|
|
335
342
|
tokenHash: hashToken(deviceToken),
|
|
336
343
|
identity,
|
|
337
344
|
pairedAt: at,
|
|
@@ -659,6 +666,7 @@ const DEFAULT_PORT = 7787;
|
|
|
659
666
|
const THROTTLE_SECONDS = 30;
|
|
660
667
|
const LABEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
661
668
|
const ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
669
|
+
const JOB_KEYS = ["STIM_HOME", "SHELL"];
|
|
662
670
|
function validateLabel(label) {
|
|
663
671
|
return LABEL_PATTERN.test(label) ? null : `--label must be letters, digits, ".", "_" or "-", got "${label}".`;
|
|
664
672
|
}
|
|
@@ -679,13 +687,17 @@ const illegal = (value) => [...value].some((char) => {
|
|
|
679
687
|
const code = char.codePointAt(0);
|
|
680
688
|
return code < 32 && code !== 9 && code !== 10 && code !== 13;
|
|
681
689
|
});
|
|
682
|
-
/**
|
|
683
|
-
|
|
690
|
+
/**
|
|
691
|
+
* Returns the first problem in the `--env` and `--path-prepend` values, or null when both are usable. `serving` also
|
|
692
|
+
* accepts a bare `KEY`, which the serving command reads from its own environment, and tolerates `STIM_HOME` and
|
|
693
|
+
* `SHELL`, which a plist written by an earlier release can carry and `applyServeEnvironment` ignores.
|
|
694
|
+
*/
|
|
695
|
+
function validateServeEnvironment(env, pathPrepend, serving = false) {
|
|
684
696
|
if ([...env, ...pathPrepend].some(illegal)) return "--env and --path-prepend values cannot contain control characters.";
|
|
685
697
|
for (const entry of env) {
|
|
686
|
-
const parsed = parseEnvAssignment(entry);
|
|
698
|
+
const parsed = serving && ENV_KEY_PATTERN.test(entry) ? { key: entry } : parseEnvAssignment(entry);
|
|
687
699
|
if (typeof parsed === "string") return parsed;
|
|
688
|
-
if (parsed.key
|
|
700
|
+
if (!serving && JOB_KEYS.includes(parsed.key)) return `--env cannot set ${parsed.key}; start stim-server with ${parsed.key} set instead.`;
|
|
689
701
|
}
|
|
690
702
|
for (const dir of pathPrepend) if (!isAbsolute(dir) || dir.includes(delimiter)) return `--path-prepend takes one absolute directory, got "${dir}".`;
|
|
691
703
|
return null;
|
|
@@ -693,22 +705,43 @@ function validateServeEnvironment(env, pathPrepend) {
|
|
|
693
705
|
/**
|
|
694
706
|
* The environment stim-server runs with: the login shell's, then each `--env` entry, then the `--path-prepend`
|
|
695
707
|
* directories in front of `PATH`, first directory first. Both flags apply after the login shell's environment
|
|
696
|
-
* replaces the process environment, so they survive it.
|
|
708
|
+
* replaces the process environment, so they survive it. A bare `KEY` entry takes its value from `source`, the
|
|
709
|
+
* environment launchd gave the process; one that `source` lacks is skipped with a line on stderr. `STIM_HOME` and
|
|
710
|
+
* `SHELL` entries are skipped the same way: the job takes them from the installing process.
|
|
697
711
|
*/
|
|
698
|
-
function applyServeEnvironment(base, env, pathPrepend) {
|
|
712
|
+
function applyServeEnvironment(base, env, pathPrepend, source = process.env) {
|
|
699
713
|
const result = { ...base };
|
|
700
714
|
for (const entry of env) {
|
|
701
715
|
const parsed = parseEnvAssignment(entry);
|
|
702
|
-
|
|
716
|
+
const name = typeof parsed === "string" ? entry : parsed.key;
|
|
717
|
+
if (JOB_KEYS.includes(name)) console.error(`stim-server: ignoring --env ${name}; the service takes it from the process that installed it.`);
|
|
718
|
+
else if (typeof parsed !== "string") result[name] = parsed.value;
|
|
719
|
+
else if (ENV_KEY_PATTERN.test(entry)) {
|
|
720
|
+
const value = source[entry];
|
|
721
|
+
if (value === void 0) console.error(`stim-server: --env ${entry} has no value in the service environment; skipping it.`);
|
|
722
|
+
else result[entry] = value;
|
|
723
|
+
}
|
|
703
724
|
}
|
|
704
725
|
if (pathPrepend.length) result.PATH = [...pathPrepend, ...result.PATH ? [result.PATH] : []].join(delimiter);
|
|
705
726
|
return result;
|
|
706
727
|
}
|
|
728
|
+
function environmentValues(env) {
|
|
729
|
+
const values = /* @__PURE__ */ new Map();
|
|
730
|
+
for (const entry of env) {
|
|
731
|
+
const parsed = parseEnvAssignment(entry);
|
|
732
|
+
if (typeof parsed !== "string" && !JOB_KEYS.includes(parsed.key)) values.set(parsed.key, parsed.value);
|
|
733
|
+
}
|
|
734
|
+
return values;
|
|
735
|
+
}
|
|
736
|
+
/** `env` as the plist stores it: one `KEY=VALUE` per key, the last value winning, without the keys the job supplies. */
|
|
737
|
+
function normalizeEnv(env) {
|
|
738
|
+
return [...environmentValues(env)].map(([name, value]) => `${name}=${value}`);
|
|
739
|
+
}
|
|
707
740
|
function serverArguments(spec) {
|
|
708
741
|
return [
|
|
709
742
|
"--port",
|
|
710
743
|
String(spec.port),
|
|
711
|
-
...spec.env.flatMap((
|
|
744
|
+
...[...environmentValues(spec.env).keys()].flatMap((name) => ["--env", name]),
|
|
712
745
|
...spec.pathPrepend.flatMap((dir) => ["--path-prepend", dir])
|
|
713
746
|
];
|
|
714
747
|
}
|
|
@@ -736,8 +769,9 @@ function renderPlist(spec) {
|
|
|
736
769
|
].map((argument) => string(argument, " ")),
|
|
737
770
|
" </array>"
|
|
738
771
|
];
|
|
739
|
-
const environment = Object.entries(spec.environment);
|
|
740
|
-
|
|
772
|
+
const environment = new Map(Object.entries(spec.environment));
|
|
773
|
+
for (const [name, value] of environmentValues(spec.env)) environment.set(name, value);
|
|
774
|
+
if (environment.size) {
|
|
741
775
|
lines.push(key("EnvironmentVariables", " "), " <dict>");
|
|
742
776
|
for (const [name, value] of environment) lines.push(key(name, " "), string(value, " "));
|
|
743
777
|
lines.push(" </dict>");
|
|
@@ -758,6 +792,7 @@ function parseInstalledPlist(value) {
|
|
|
758
792
|
const programArguments = strings(value.ProgramArguments);
|
|
759
793
|
const host = programArguments[1] === "run" && programArguments[0]?.endsWith("/Contents/MacOS/stim-host") ? programArguments[0] : null;
|
|
760
794
|
const [node = null, script = null, ...args] = host ? programArguments.slice(2) : programArguments;
|
|
795
|
+
const environment = isJsonObject(value.EnvironmentVariables) ? value.EnvironmentVariables : {};
|
|
761
796
|
const env = [];
|
|
762
797
|
const pathPrepend = [];
|
|
763
798
|
let port = null;
|
|
@@ -766,10 +801,13 @@ function parseInstalledPlist(value) {
|
|
|
766
801
|
const next = args[index + 1];
|
|
767
802
|
if (next === void 0) break;
|
|
768
803
|
if (flag === "--port") port = Number(next);
|
|
769
|
-
if (flag === "--env")
|
|
804
|
+
if (flag === "--env") {
|
|
805
|
+
const stored = environment[next];
|
|
806
|
+
if (next.includes("=")) env.push(next);
|
|
807
|
+
else if (typeof stored === "string") env.push(`${next}=${stored}`);
|
|
808
|
+
}
|
|
770
809
|
if (flag === "--path-prepend") pathPrepend.push(next);
|
|
771
810
|
}
|
|
772
|
-
const environment = isJsonObject(value.EnvironmentVariables) ? value.EnvironmentVariables : {};
|
|
773
811
|
const meta = isJsonObject(value.StimService) ? value.StimService : {};
|
|
774
812
|
const servePort = meta.ServePort;
|
|
775
813
|
return {
|
|
@@ -1582,7 +1620,7 @@ async function installJob(options, setup) {
|
|
|
1582
1620
|
};
|
|
1583
1621
|
const path = plistPath(options.label);
|
|
1584
1622
|
const tailscale = findTailscale(process.env) ?? "tailscale";
|
|
1585
|
-
if (setup && previous?.script === script && previous.port === options.port && previous.host === spec.host && JSON.stringify(previous.env) === JSON.stringify(spec.env) && JSON.stringify(previous.pathPrepend) === JSON.stringify(spec.pathPrepend)) {
|
|
1623
|
+
if (setup && previous?.script === script && previous.port === options.port && previous.host === spec.host && JSON.stringify(normalizeEnv(previous.env)) === JSON.stringify(normalizeEnv(spec.env)) && JSON.stringify(previous.pathPrepend) === JSON.stringify(spec.pathPrepend)) {
|
|
1586
1624
|
const health = await fetchHealth(options.port);
|
|
1587
1625
|
if (health && health.startup?.state !== "pending" && health.startup?.state !== "degraded") return [`${options.label} already runs stim-server ${health.version}.`];
|
|
1588
1626
|
}
|
|
@@ -2521,7 +2559,8 @@ function validBuildPath(path) {
|
|
|
2521
2559
|
}
|
|
2522
2560
|
function validFile(file) {
|
|
2523
2561
|
if (!isJsonObject(file) || !validBuildPath(file.path)) return false;
|
|
2524
|
-
if (file.kind !== "file" && file.kind !== "exec" && file.kind !== "link") return false;
|
|
2562
|
+
if (file.kind !== "file" && file.kind !== "exec" && file.kind !== "link" && file.kind !== "directory") return false;
|
|
2563
|
+
if (file.kind === "directory" && (file.size !== 0 || file.sha256 !== createHash("sha256").update("").digest("hex"))) return false;
|
|
2525
2564
|
const size = file.size;
|
|
2526
2565
|
if (typeof size !== "number" || !Number.isInteger(size) || size < 0 || size > MAX_FILE_BYTES) return false;
|
|
2527
2566
|
return typeof file.sha256 === "string" && /^[0-9a-f]{64}$/.test(file.sha256);
|
|
@@ -2530,21 +2569,27 @@ const validRepo = (repo) => typeof repo === "string" && new RegExp(protocol_expo
|
|
|
2530
2569
|
const gb = (bytes) => (bytes / 1024 ** 3).toFixed(1);
|
|
2531
2570
|
const optional = (value) => typeof value === "string" ? value : null;
|
|
2532
2571
|
const GRADLE_NAME = /^[A-Za-z0-9_-]{1,100}$/;
|
|
2572
|
+
const GRADLE_ROOT = /^[A-Za-z0-9._/ -]{1,200}$/;
|
|
2573
|
+
const GRADLE_MODULE = /^(:(?!\.\.?(?::|$))[A-Za-z0-9._-]+){1,20}$/;
|
|
2533
2574
|
/** The Gradle choices of an Android `build.start`, or null when they are malformed. */
|
|
2534
2575
|
function androidOptions(value) {
|
|
2535
2576
|
if (!isJsonObject(value)) return null;
|
|
2536
|
-
const { variant, abi, gradleBuildCache, pch, compilerCache } = value;
|
|
2577
|
+
const { variant, abi, gradleBuildCache, pch, compilerCache, gradleRoot = null, module = null } = value;
|
|
2537
2578
|
if (variant !== null && !(typeof variant === "string" && GRADLE_NAME.test(variant))) return null;
|
|
2538
2579
|
if (abi !== null && !(typeof abi === "string" && GRADLE_NAME.test(abi))) return null;
|
|
2539
2580
|
if (typeof gradleBuildCache !== "boolean") return null;
|
|
2540
2581
|
if (pch !== "auto" && pch !== "on" && pch !== "off") return null;
|
|
2541
2582
|
if (compilerCache !== "ccache" && compilerCache !== "none") return null;
|
|
2583
|
+
if (gradleRoot !== null && !(typeof gradleRoot === "string" && GRADLE_ROOT.test(gradleRoot))) return null;
|
|
2584
|
+
if (module !== null && !(typeof module === "string" && GRADLE_MODULE.test(module))) return null;
|
|
2542
2585
|
return {
|
|
2543
2586
|
variant,
|
|
2544
2587
|
abi,
|
|
2545
2588
|
gradleBuildCache,
|
|
2546
2589
|
pch,
|
|
2547
|
-
compilerCache
|
|
2590
|
+
compilerCache,
|
|
2591
|
+
gradleRoot,
|
|
2592
|
+
module
|
|
2548
2593
|
};
|
|
2549
2594
|
}
|
|
2550
2595
|
function freeBytes(path) {
|
|
@@ -2649,17 +2694,15 @@ var BuildHost = class {
|
|
|
2649
2694
|
clientDir(client) {
|
|
2650
2695
|
return join(this.root(), client);
|
|
2651
2696
|
}
|
|
2652
|
-
toolchain(rubyVersion = null) {
|
|
2697
|
+
toolchain(rubyVersion = null, native) {
|
|
2698
|
+
const key = JSON.stringify([native ?? null, native ? null : rubyVersion]);
|
|
2653
2699
|
const now = Date.now();
|
|
2654
2700
|
for (const [context, entry] of this.toolchains) if (now - entry.at >= this.limits.toolchainTtlMs) this.toolchains.delete(context);
|
|
2655
|
-
const cached = this.toolchains.get(
|
|
2701
|
+
const cached = this.toolchains.get(key);
|
|
2656
2702
|
if (cached) return cached.value;
|
|
2657
2703
|
const value = new Promise((resolve) => {
|
|
2658
|
-
const
|
|
2659
|
-
|
|
2660
|
-
"offer",
|
|
2661
|
-
...rubyVersion ? [rubyVersion] : []
|
|
2662
|
-
], {
|
|
2704
|
+
const args = native ? [`offer-native-${native}`] : ["offer", ...rubyVersion ? [rubyVersion] : []];
|
|
2705
|
+
const child = spawn(process.execPath, [this.options.worker, ...args], {
|
|
2663
2706
|
env: this.options.env,
|
|
2664
2707
|
stdio: [
|
|
2665
2708
|
"ignore",
|
|
@@ -2682,19 +2725,20 @@ var BuildHost = class {
|
|
|
2682
2725
|
}
|
|
2683
2726
|
});
|
|
2684
2727
|
});
|
|
2685
|
-
this.toolchains.set(
|
|
2728
|
+
this.toolchains.set(key, {
|
|
2686
2729
|
at: now,
|
|
2687
2730
|
value
|
|
2688
2731
|
});
|
|
2689
2732
|
value.then((result) => {
|
|
2690
|
-
if (!result && this.toolchains.get(
|
|
2733
|
+
if (!result && this.toolchains.get(key)?.value === value) this.toolchains.delete(key);
|
|
2691
2734
|
});
|
|
2692
2735
|
return value;
|
|
2693
2736
|
}
|
|
2694
2737
|
async offer(client, params) {
|
|
2695
2738
|
if (!isJsonObject(params) || !validRepo(params.repo)) return refusal("bad-request", "build.offer needs params.repo.");
|
|
2696
2739
|
if (params.rubyVersion !== void 0 && (typeof params.rubyVersion !== "string" || !new RegExp(protocol_exports.BUILD_RUBY_VERSION_PATTERN).test(params.rubyVersion))) return refusal("bad-request", "build.offer needs a Ruby installation name without path separators.");
|
|
2697
|
-
|
|
2740
|
+
if (params.native !== void 0 && params.native !== "xcode" && params.native !== "gradle") return refusal("bad-request", "build.offer supports native Xcode or Gradle toolchains.");
|
|
2741
|
+
const toolchain = await this.toolchain(params.rubyVersion ?? null, params.native);
|
|
2698
2742
|
if (!toolchain) return refusal("build-refused", "This Mac could not read its build toolchain.");
|
|
2699
2743
|
const root = this.root();
|
|
2700
2744
|
mkdirSync(root, {
|
|
@@ -2920,7 +2964,7 @@ var BuildHost = class {
|
|
|
2920
2964
|
job: id,
|
|
2921
2965
|
record: value.record
|
|
2922
2966
|
});
|
|
2923
|
-
else if (value.type === "result") entry.outcome = workerOutcome(value);
|
|
2967
|
+
else if (value.type === "result") entry.outcome = workerOutcome(value, isJsonObject(job.native) && job.native.provider === "gradle" ? "gradle" : void 0);
|
|
2924
2968
|
});
|
|
2925
2969
|
this.jobs.add(entry);
|
|
2926
2970
|
this.owned.set(id, entry);
|
|
@@ -3079,18 +3123,20 @@ var BuildHost = class {
|
|
|
3079
3123
|
await this.sweeping;
|
|
3080
3124
|
}
|
|
3081
3125
|
};
|
|
3082
|
-
function workerOutcome(value) {
|
|
3126
|
+
function workerOutcome(value, native) {
|
|
3083
3127
|
const timings = isJsonObject(value.timings) ? value.timings : {};
|
|
3084
3128
|
if (value.ok === true && isJsonObject(value.artifact)) {
|
|
3085
3129
|
const { name, size, sha256 } = value.artifact;
|
|
3086
|
-
if (typeof name === "string" && typeof size === "number" && typeof sha256 === "string") return {
|
|
3130
|
+
if (typeof name === "string" && typeof size === "number" && typeof sha256 === "string" && (native !== "gradle" || value.fingerprint === null && typeof value.androidPackage === "string" && /^[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+$/.test(value.androidPackage))) return {
|
|
3087
3131
|
ok: true,
|
|
3088
3132
|
artifact: {
|
|
3089
3133
|
name,
|
|
3090
3134
|
size,
|
|
3091
3135
|
sha256
|
|
3092
3136
|
},
|
|
3093
|
-
fingerprint: String(value.fingerprint),
|
|
3137
|
+
fingerprint: native === "gradle" ? null : String(value.fingerprint),
|
|
3138
|
+
...native === "gradle" ? { androidPackage: value.androidPackage } : {},
|
|
3139
|
+
...typeof value.sourceDigest === "string" ? { sourceDigest: value.sourceDigest } : {},
|
|
3094
3140
|
compilationCache: isJsonObject(value.compilationCache) ? value.compilationCache : {},
|
|
3095
3141
|
timings
|
|
3096
3142
|
};
|
|
@@ -3191,11 +3237,34 @@ var BuildSession = class {
|
|
|
3191
3237
|
}
|
|
3192
3238
|
async start(params) {
|
|
3193
3239
|
if (!isJsonObject(params) || !validRepo(params.repo)) return refusal("bad-request", "build.start needs params.repo.");
|
|
3194
|
-
const strings = ["
|
|
3240
|
+
const strings = ["stimBuild"];
|
|
3195
3241
|
if (strings.some((key) => typeof params[key] !== "string" || !params[key])) return refusal("bad-request", `build.start needs params.${strings.join(", params.")}.`);
|
|
3196
3242
|
const platform = params.platform;
|
|
3197
3243
|
if (platform !== "ios" && platform !== "android" && platform !== "macos") return refusal("bad-request", "params.platform must be ios, android or macos.");
|
|
3198
3244
|
if (platform === "ios" && (typeof params.runtime !== "string" || !params.runtime)) return refusal("bad-request", "An ios build needs params.runtime.");
|
|
3245
|
+
let native;
|
|
3246
|
+
if (params.native !== void 0) {
|
|
3247
|
+
const input = params.native;
|
|
3248
|
+
if (!isJsonObject(input) || typeof input.sourceDigest !== "string" || !/^[0-9a-f]{64}$/.test(input.sourceDigest)) return refusal("bad-request", "A native build needs its source transfer digest.");
|
|
3249
|
+
if (input.provider === "gradle") {
|
|
3250
|
+
const inputs = input.inputs;
|
|
3251
|
+
if (platform !== "android" || params.fingerprint !== null || !isJsonObject(inputs) || inputs.complete !== true || Object.keys(inputs).some((key) => ![
|
|
3252
|
+
"complete",
|
|
3253
|
+
"ignored",
|
|
3254
|
+
"outputs"
|
|
3255
|
+
].includes(key)) || !Array.isArray(inputs.ignored) || !inputs.ignored.every(validBuildPath) || !Array.isArray(inputs.outputs) || !inputs.outputs.every(validBuildPath)) return refusal("bad-request", "A native Gradle build needs declared complete inputs and a null artifact fingerprint.");
|
|
3256
|
+
} else {
|
|
3257
|
+
const optimizations = params.optimizations;
|
|
3258
|
+
if (platform !== "ios" || input.provider !== "xcode" || typeof input.cacheKey !== "string" || !input.cacheKey || ![
|
|
3259
|
+
null,
|
|
3260
|
+
"arm64",
|
|
3261
|
+
"x86_64"
|
|
3262
|
+
].includes(input.arch) || !isJsonObject(input.parameters) || !isJsonObject(optimizations) || typeof optimizations.compilationCache !== "boolean" || typeof optimizations.prefixMapping !== "boolean" || optimizations.swiftCompilationCache !== null && typeof optimizations.swiftCompilationCache !== "boolean") return refusal("bad-request", "A native Xcode build needs its source digest, cache key, architecture and compiler options.");
|
|
3263
|
+
}
|
|
3264
|
+
native = input;
|
|
3265
|
+
}
|
|
3266
|
+
if (native?.provider !== "gradle" && (typeof params.fingerprint !== "string" || !params.fingerprint)) return refusal("bad-request", "build.start needs params.fingerprint.");
|
|
3267
|
+
if (!native && [...this.files.values()].some((file) => file.kind === "directory")) return refusal("bad-request", "Directory entries require a native build request.");
|
|
3199
3268
|
const android = platform === "android" ? androidOptions(params.android) : null;
|
|
3200
3269
|
if (platform === "android" && !android) return refusal("bad-request", "An android build needs params.android: variant and abi (string or null), gradleBuildCache, pch and compilerCache.");
|
|
3201
3270
|
let macos = null;
|
|
@@ -3215,7 +3284,7 @@ var BuildSession = class {
|
|
|
3215
3284
|
if (!this.done || this.repo !== params.repo) return refusal("bad-request", "Send the whole manifest of this repo with build.sync first.");
|
|
3216
3285
|
if (this.incoming || [...this.files.values()].some((file) => this.expected.has(file.sha256))) return refusal("bad-request", "Some files of the manifest were not uploaded.");
|
|
3217
3286
|
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.`);
|
|
3218
|
-
const toolchain = await this.host.toolchain();
|
|
3287
|
+
const toolchain = await this.host.toolchain(null, native?.provider);
|
|
3219
3288
|
if (this.closed) return refusal("build-refused", "The connection closed.");
|
|
3220
3289
|
if (!toolchain || toolchain.stimBuild !== params.stimBuild) return refusal("build-refused", `This Mac runs Stim build ${toolchain?.stimBuild ?? "unknown"}.`);
|
|
3221
3290
|
const launched = this.host.launch({
|
|
@@ -3226,6 +3295,7 @@ var BuildSession = class {
|
|
|
3226
3295
|
job: {
|
|
3227
3296
|
manifest: [...this.files.values()],
|
|
3228
3297
|
platform,
|
|
3298
|
+
...native ? { native } : {},
|
|
3229
3299
|
android,
|
|
3230
3300
|
macos,
|
|
3231
3301
|
project: params.project,
|
|
@@ -3233,6 +3303,7 @@ var BuildSession = class {
|
|
|
3233
3303
|
isExpo: params.isExpo === true,
|
|
3234
3304
|
configuration: optional(params.configuration),
|
|
3235
3305
|
scheme: optional(params.scheme),
|
|
3306
|
+
iosProjectPath: optional(params.iosProjectPath),
|
|
3236
3307
|
runtime: optional(params.runtime),
|
|
3237
3308
|
expectedFingerprint: params.fingerprint,
|
|
3238
3309
|
optimizations: isJsonObject(params.optimizations) ? params.optimizations : null
|
|
@@ -6535,9 +6606,11 @@ var DeviceHost = class {
|
|
|
6535
6606
|
reconciling;
|
|
6536
6607
|
draining = null;
|
|
6537
6608
|
limits;
|
|
6609
|
+
debug;
|
|
6538
6610
|
options;
|
|
6539
6611
|
constructor(options) {
|
|
6540
6612
|
this.options = options;
|
|
6613
|
+
this.debug = createDebugLog("server", { env: options.env });
|
|
6541
6614
|
this.limits = {
|
|
6542
6615
|
prepareMs: 3e5,
|
|
6543
6616
|
offerMs: 3e4,
|
|
@@ -7098,12 +7171,13 @@ var DeviceHost = class {
|
|
|
7098
7171
|
appOffer(client, params) {
|
|
7099
7172
|
if (!this.options.allowed(client)) return refused$1("forbidden", "Current device-host approval is required.");
|
|
7100
7173
|
const offer = parseHostedAppOffer(params);
|
|
7101
|
-
if (!offer) return refused$1("bad-request", "App offers need a bounded normalized bundle manifest, bundleId, development or
|
|
7174
|
+
if (!offer) return refused$1("bad-request", "App offers need a bounded normalized bundle manifest, bundleId, development, release or process mode and, optionally, at most 32 arguments of 1024 characters (8192 in total) without NUL or line breaks.");
|
|
7102
7175
|
try {
|
|
7103
7176
|
const record = this.appSession(client, offer);
|
|
7177
|
+
if (offer.mode === "process" && record.platform === "macos") throw new Error("Process-mode app offers require a hosted iOS or Android session.");
|
|
7104
7178
|
if (record.platform !== "macos" && offer.arguments !== void 0) throw new Error("App arguments are supported only for hosted macOS sessions.");
|
|
7105
7179
|
if (record.platform === "macos") {
|
|
7106
|
-
if (offer.mode
|
|
7180
|
+
if (offer.mode !== "release" || offer.devClientScheme !== void 0) throw new Error("Hosted macOS apps require release mode without a development client scheme.");
|
|
7107
7181
|
if (offer.bundleId.startsWith("com.apple.")) throw new Error("Hosted macOS apps cannot use a com.apple. bundle identity.");
|
|
7108
7182
|
if (hostedMacosBundleId(offer.bundleId, record.appSlot).length + 6 > 255) throw new Error("The hosted macOS bundle identity exceeds 249 characters for its preferences plist.");
|
|
7109
7183
|
}
|
|
@@ -7687,6 +7761,15 @@ var DeviceHost = class {
|
|
|
7687
7761
|
if (options.claim) clearClaimChild(options.claim);
|
|
7688
7762
|
throw error;
|
|
7689
7763
|
}
|
|
7764
|
+
const started = performance.now();
|
|
7765
|
+
const mode = "mode" in options.input && typeof options.input.mode === "string" ? options.input.mode : void 0;
|
|
7766
|
+
const diagnostic = (event, fields = {}) => this.debug.log(`device-host.worker.${event}`, {
|
|
7767
|
+
mode,
|
|
7768
|
+
workerPid: child.pid,
|
|
7769
|
+
ms: Math.round(performance.now() - started),
|
|
7770
|
+
...fields
|
|
7771
|
+
});
|
|
7772
|
+
diagnostic("start", { timeoutMs: options.timeoutMs });
|
|
7690
7773
|
let identity = null;
|
|
7691
7774
|
const output = [];
|
|
7692
7775
|
let outputBytes = 0;
|
|
@@ -7709,7 +7792,14 @@ var DeviceHost = class {
|
|
|
7709
7792
|
clearTimeout(killTimer);
|
|
7710
7793
|
clearTimeout(finishTimer);
|
|
7711
7794
|
clearTimeout(groupTimer);
|
|
7712
|
-
const
|
|
7795
|
+
const groupAlive = closed && child.pid ? processGroupAlive(child.pid) : null;
|
|
7796
|
+
const settled = closed && groupAlive !== true;
|
|
7797
|
+
diagnostic("settled", {
|
|
7798
|
+
closed,
|
|
7799
|
+
groupAlive,
|
|
7800
|
+
settled,
|
|
7801
|
+
cancelling
|
|
7802
|
+
});
|
|
7713
7803
|
if (settled && options.claim) clearClaimChild(options.claim);
|
|
7714
7804
|
let value = null;
|
|
7715
7805
|
try {
|
|
@@ -7737,9 +7827,10 @@ var DeviceHost = class {
|
|
|
7737
7827
|
process.kill(-child.pid, name);
|
|
7738
7828
|
} catch {}
|
|
7739
7829
|
};
|
|
7740
|
-
const cancel = () => {
|
|
7830
|
+
const cancel = (reason = "requested") => {
|
|
7741
7831
|
if (finished || cancelling) return;
|
|
7742
7832
|
cancelling = true;
|
|
7833
|
+
diagnostic("cancel", { reason });
|
|
7743
7834
|
notice ??= "Hosted worker was cancelled or exceeded its deadline.";
|
|
7744
7835
|
signal("SIGTERM");
|
|
7745
7836
|
killTimer = setTimeout(() => signal("SIGKILL"), this.limits.killGraceMs);
|
|
@@ -7749,15 +7840,15 @@ var DeviceHost = class {
|
|
|
7749
7840
|
if (finished) return;
|
|
7750
7841
|
if (!child.pid || !processGroupAlive(child.pid)) finish();
|
|
7751
7842
|
else {
|
|
7752
|
-
cancel();
|
|
7843
|
+
cancel("group-after-close");
|
|
7753
7844
|
groupTimer = setTimeout(finishGroup, 25);
|
|
7754
7845
|
}
|
|
7755
7846
|
};
|
|
7756
|
-
const timer = setTimeout(cancel, options.timeoutMs);
|
|
7847
|
+
const timer = setTimeout(() => cancel("deadline"), options.timeoutMs);
|
|
7757
7848
|
child.stdout?.on("data", (chunk) => {
|
|
7758
7849
|
if (outputBytes + chunk.length > options.maxOutputBytes) {
|
|
7759
7850
|
notice = "Hosted worker output exceeded its bound.";
|
|
7760
|
-
cancel();
|
|
7851
|
+
cancel("output-bound");
|
|
7761
7852
|
} else {
|
|
7762
7853
|
output.push(chunk);
|
|
7763
7854
|
outputBytes += chunk.length;
|
|
@@ -7766,13 +7857,18 @@ var DeviceHost = class {
|
|
|
7766
7857
|
child.stderr?.on("data", (chunk) => {
|
|
7767
7858
|
stderr = (stderr + chunk.toString()).slice(-8192);
|
|
7768
7859
|
});
|
|
7769
|
-
child.stdin?.on("error", () => cancel());
|
|
7860
|
+
child.stdin?.on("error", () => cancel("stdin-error"));
|
|
7770
7861
|
child.once("error", (error) => {
|
|
7862
|
+
diagnostic("error", { code: error.code });
|
|
7771
7863
|
notice = error.message;
|
|
7772
7864
|
closed = true;
|
|
7773
7865
|
finishGroup();
|
|
7774
7866
|
});
|
|
7775
|
-
child.once("close", (code) => {
|
|
7867
|
+
child.once("close", (code, closeSignal) => {
|
|
7868
|
+
diagnostic("close", {
|
|
7869
|
+
code,
|
|
7870
|
+
signal: closeSignal
|
|
7871
|
+
});
|
|
7776
7872
|
if (code !== 0) notice ??= stderr.trim() || `Hosted worker exited ${code}.`;
|
|
7777
7873
|
closed = true;
|
|
7778
7874
|
finishGroup();
|
|
@@ -7781,7 +7877,7 @@ var DeviceHost = class {
|
|
|
7781
7877
|
if (!captured?.ok || child.pid === void 0) {
|
|
7782
7878
|
notice = "The worker process identity could not be captured; no native request was sent.";
|
|
7783
7879
|
child.kill("SIGKILL");
|
|
7784
|
-
cancel();
|
|
7880
|
+
cancel("identity-unavailable");
|
|
7785
7881
|
} else {
|
|
7786
7882
|
identity = {
|
|
7787
7883
|
pid: child.pid,
|
|
@@ -7792,7 +7888,7 @@ var DeviceHost = class {
|
|
|
7792
7888
|
child.stdin?.end(JSON.stringify(options.input));
|
|
7793
7889
|
} catch (error) {
|
|
7794
7890
|
notice = error.message;
|
|
7795
|
-
cancel();
|
|
7891
|
+
cancel("request-write");
|
|
7796
7892
|
}
|
|
7797
7893
|
}
|
|
7798
7894
|
return {
|
|
@@ -11515,16 +11611,28 @@ const INTERVAL_MS = 15e3;
|
|
|
11515
11611
|
const POINTS = 40;
|
|
11516
11612
|
/**
|
|
11517
11613
|
* Per-environment and per-device CPU and memory from the status payloads stim-server receives, in 15-second slots, the cadence at which `status --watch` rereads machine usage,
|
|
11518
|
-
* over the last 10 minutes. A later payload in the same slot replaces the earlier one.
|
|
11614
|
+
* over the last 10 minutes. A later payload in the same slot replaces the earlier one. `status --watch` prints no line
|
|
11615
|
+
* while usage stays within a step, so the slots between two payloads repeat the readings of the series the earlier
|
|
11616
|
+
* payload held, and those series continue to now until a payload drops them. An environment sums every
|
|
11519
11617
|
* machine owner of its workspace; a device is a simulator or emulator owner, keyed by its UDID or AVD name.
|
|
11520
11618
|
*/
|
|
11521
11619
|
var UsageRecorder = class {
|
|
11522
11620
|
environments = /* @__PURE__ */ new Map();
|
|
11523
11621
|
devices = /* @__PURE__ */ new Map();
|
|
11622
|
+
lastSlot = 0;
|
|
11623
|
+
liveEnvironments = /* @__PURE__ */ new Set();
|
|
11624
|
+
liveDevices = /* @__PURE__ */ new Set();
|
|
11524
11625
|
record(payload, at = Date.now()) {
|
|
11525
|
-
const owners = payload?.machine?.owners;
|
|
11526
|
-
if (!Array.isArray(owners)) return;
|
|
11527
11626
|
const slot = Math.floor(at / INTERVAL_MS);
|
|
11627
|
+
const owners = payload?.machine?.owners;
|
|
11628
|
+
this.holdThrough(slot - 1);
|
|
11629
|
+
this.lastSlot = Math.max(this.lastSlot, slot);
|
|
11630
|
+
this.liveEnvironments = /* @__PURE__ */ new Set();
|
|
11631
|
+
this.liveDevices = /* @__PURE__ */ new Set();
|
|
11632
|
+
if (!Array.isArray(owners)) {
|
|
11633
|
+
this.prune(slot);
|
|
11634
|
+
return;
|
|
11635
|
+
}
|
|
11528
11636
|
const sums = /* @__PURE__ */ new Map();
|
|
11529
11637
|
for (const owner of owners) {
|
|
11530
11638
|
const reading = {
|
|
@@ -11554,20 +11662,24 @@ var UsageRecorder = class {
|
|
|
11554
11662
|
meta,
|
|
11555
11663
|
slots: slots.set(slot, reading)
|
|
11556
11664
|
});
|
|
11665
|
+
this.liveDevices.add(key);
|
|
11557
11666
|
}
|
|
11558
11667
|
}
|
|
11559
|
-
for (const [workspace, reading] of sums)
|
|
11668
|
+
for (const [workspace, reading] of sums) {
|
|
11669
|
+
this.environments.set(workspace, (this.environments.get(workspace) ?? /* @__PURE__ */ new Map()).set(slot, reading));
|
|
11670
|
+
this.liveEnvironments.add(workspace);
|
|
11671
|
+
}
|
|
11560
11672
|
this.prune(slot);
|
|
11561
11673
|
}
|
|
11562
11674
|
/** The last 10 minutes ending at `now`'s slot, or null when no series has a reading in them. */
|
|
11563
11675
|
history(now = Date.now()) {
|
|
11564
11676
|
const end = Math.floor(now / INTERVAL_MS);
|
|
11565
11677
|
this.prune(end);
|
|
11566
|
-
const series = (slots) => {
|
|
11678
|
+
const series = (slots, live) => {
|
|
11567
11679
|
const cpuPercent = [];
|
|
11568
11680
|
const memoryMb = [];
|
|
11569
11681
|
for (let slot = end - POINTS + 1; slot <= end; slot++) {
|
|
11570
|
-
const reading = slots.get(slot);
|
|
11682
|
+
const reading = slots.get(slot) ?? (live && slot > this.lastSlot ? slots.get(this.lastSlot) : void 0);
|
|
11571
11683
|
cpuPercent.push(reading ? Math.round(reading.cpuPercent * 10) / 10 : null);
|
|
11572
11684
|
memoryMb.push(reading ? Math.round(reading.memoryMb) : null);
|
|
11573
11685
|
}
|
|
@@ -11576,8 +11688,8 @@ var UsageRecorder = class {
|
|
|
11576
11688
|
memoryMb
|
|
11577
11689
|
};
|
|
11578
11690
|
};
|
|
11579
|
-
const environments = [...this.environments].map(([workspace, slots]) => Object.assign({ workspace }, series(slots)));
|
|
11580
|
-
const devices = [...this.devices
|
|
11691
|
+
const environments = [...this.environments].map(([workspace, slots]) => Object.assign({ workspace }, series(slots, this.liveEnvironments.has(workspace))));
|
|
11692
|
+
const devices = [...this.devices].map(([key, { meta, slots }]) => Object.assign({}, meta, series(slots, this.liveDevices.has(key))));
|
|
11581
11693
|
if (!environments.length && !devices.length) return null;
|
|
11582
11694
|
return {
|
|
11583
11695
|
intervalMs: INTERVAL_MS,
|
|
@@ -11586,13 +11698,27 @@ var UsageRecorder = class {
|
|
|
11586
11698
|
devices
|
|
11587
11699
|
};
|
|
11588
11700
|
}
|
|
11701
|
+
/** Ends the live series where the last payload left them: nothing is recorded, or held, while nobody listens. */
|
|
11702
|
+
pause() {
|
|
11703
|
+
this.liveEnvironments = /* @__PURE__ */ new Set();
|
|
11704
|
+
this.liveDevices = /* @__PURE__ */ new Set();
|
|
11705
|
+
}
|
|
11706
|
+
holdThrough(slot) {
|
|
11707
|
+
const hold = (slots) => {
|
|
11708
|
+
const reading = slots?.get(this.lastSlot);
|
|
11709
|
+
if (!slots || !reading) return;
|
|
11710
|
+
for (let at = Math.max(this.lastSlot + 1, slot - POINTS + 1); at <= slot; at++) slots.set(at, reading);
|
|
11711
|
+
};
|
|
11712
|
+
for (const key of this.liveEnvironments) hold(this.environments.get(key));
|
|
11713
|
+
for (const key of this.liveDevices) hold(this.devices.get(key)?.slots);
|
|
11714
|
+
}
|
|
11589
11715
|
prune(slot) {
|
|
11590
|
-
const trim = (slots) => {
|
|
11591
|
-
for (const at of slots.keys()) if (at <= slot - POINTS) slots.delete(at);
|
|
11716
|
+
const trim = (slots, live) => {
|
|
11717
|
+
for (const at of slots.keys()) if (at <= slot - POINTS && !(live && at === this.lastSlot)) slots.delete(at);
|
|
11592
11718
|
return slots.size > 0;
|
|
11593
11719
|
};
|
|
11594
|
-
for (const [key, slots] of this.environments) if (!trim(slots)) this.environments.delete(key);
|
|
11595
|
-
for (const [key, { slots }] of this.devices) if (!trim(slots)) this.devices.delete(key);
|
|
11720
|
+
for (const [key, slots] of this.environments) if (!trim(slots, this.liveEnvironments.has(key))) this.environments.delete(key);
|
|
11721
|
+
for (const [key, { slots }] of this.devices) if (!trim(slots, this.liveDevices.has(key))) this.devices.delete(key);
|
|
11596
11722
|
}
|
|
11597
11723
|
};
|
|
11598
11724
|
//#endregion
|
|
@@ -12427,6 +12553,7 @@ var ServerUpdates = class {
|
|
|
12427
12553
|
running = null;
|
|
12428
12554
|
upload = null;
|
|
12429
12555
|
idle = null;
|
|
12556
|
+
ended = null;
|
|
12430
12557
|
constructor(options) {
|
|
12431
12558
|
this.options = options;
|
|
12432
12559
|
this.sweepIncoming();
|
|
@@ -12480,9 +12607,14 @@ var ServerUpdates = class {
|
|
|
12480
12607
|
service: service ? label : null,
|
|
12481
12608
|
acceptsClientBuilds: this.options.acceptsClientBuilds(),
|
|
12482
12609
|
running: this.progress(),
|
|
12483
|
-
last: service && label ? readLastUpdate(label) : null
|
|
12610
|
+
last: service && label ? this.latest(readLastUpdate(label)) : null
|
|
12484
12611
|
};
|
|
12485
12612
|
}
|
|
12613
|
+
latest(recorded) {
|
|
12614
|
+
if (!this.ended) return recorded;
|
|
12615
|
+
if (!recorded) return this.ended;
|
|
12616
|
+
return Date.parse(recorded.at) >= Date.parse(this.ended.at) ? recorded : this.ended;
|
|
12617
|
+
}
|
|
12486
12618
|
async start(by, params) {
|
|
12487
12619
|
const parsed = parseUpdateStart(params);
|
|
12488
12620
|
if (typeof parsed === "string") return refused("bad-request", parsed);
|
|
@@ -12641,6 +12773,12 @@ var ServerUpdates = class {
|
|
|
12641
12773
|
const settle = (ok, message) => {
|
|
12642
12774
|
this.options.drain(null);
|
|
12643
12775
|
this.clear();
|
|
12776
|
+
this.ended = {
|
|
12777
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12778
|
+
target,
|
|
12779
|
+
ok,
|
|
12780
|
+
message
|
|
12781
|
+
};
|
|
12644
12782
|
this.options.audit({
|
|
12645
12783
|
by: running.by,
|
|
12646
12784
|
target,
|
|
@@ -12653,7 +12791,11 @@ var ServerUpdates = class {
|
|
|
12653
12791
|
child.once("exit", (code) => {
|
|
12654
12792
|
const last = readLastUpdate(this.options.label);
|
|
12655
12793
|
const fresh = last && Date.parse(last.at) >= Date.parse(running.startedAt);
|
|
12656
|
-
|
|
12794
|
+
let reason = "";
|
|
12795
|
+
try {
|
|
12796
|
+
reason = readFileSync(log, "utf8").split("\n").findLast((line) => line.trim() !== "") ?? "";
|
|
12797
|
+
} catch {}
|
|
12798
|
+
settle(code === 0, fresh ? last.message : `The update exited with code ${String(code)}${reason ? `: ${reason}` : ""}; see ${log}.`);
|
|
12657
12799
|
});
|
|
12658
12800
|
return null;
|
|
12659
12801
|
}
|
|
@@ -13144,6 +13286,28 @@ function peerAddress(request) {
|
|
|
13144
13286
|
const forwarded = request.headers["x-forwarded-for"];
|
|
13145
13287
|
return (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(",")[0]?.trim() || null;
|
|
13146
13288
|
}
|
|
13289
|
+
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
13290
|
+
"127.0.0.1",
|
|
13291
|
+
"localhost",
|
|
13292
|
+
"[::1]"
|
|
13293
|
+
]);
|
|
13294
|
+
/**
|
|
13295
|
+
* Whether a WebSocket upgrade comes from a web page. Browsers send `Sec-Fetch-Site` and the page's own `Origin`.
|
|
13296
|
+
* React Native derives `Origin` from the ws URL, so an app's `Origin` names the requested host: over `http:` on loopback,
|
|
13297
|
+
* and `https:` through `tailscale serve`. SocketRocket omits the brackets of an IPv6 origin, so an iOS app on
|
|
13298
|
+
* `ws://[::1]` is refused.
|
|
13299
|
+
*/
|
|
13300
|
+
function upgradeFromWebPage(headers, local) {
|
|
13301
|
+
if (headers["sec-fetch-site"] !== void 0) return true;
|
|
13302
|
+
if (headers.origin === void 0) return false;
|
|
13303
|
+
const scheme = local ? "http:" : "https:";
|
|
13304
|
+
try {
|
|
13305
|
+
const own = new URL(`${scheme}//${headers.host ?? ""}`);
|
|
13306
|
+
return new URL(headers.origin).origin !== own.origin || local && !LOOPBACK_HOSTS.has(own.hostname);
|
|
13307
|
+
} catch {
|
|
13308
|
+
return true;
|
|
13309
|
+
}
|
|
13310
|
+
}
|
|
13147
13311
|
var FailureLimiter = class {
|
|
13148
13312
|
failures = /* @__PURE__ */ new Map();
|
|
13149
13313
|
max;
|
|
@@ -13391,6 +13555,7 @@ async function startServer(options) {
|
|
|
13391
13555
|
if (recorders > 0) return;
|
|
13392
13556
|
stopRecording?.();
|
|
13393
13557
|
stopRecording = null;
|
|
13558
|
+
usage.pause();
|
|
13394
13559
|
};
|
|
13395
13560
|
};
|
|
13396
13561
|
const controllers = /* @__PURE__ */ new Map();
|
|
@@ -13750,8 +13915,7 @@ async function startServer(options) {
|
|
|
13750
13915
|
server: {
|
|
13751
13916
|
name: options.name,
|
|
13752
13917
|
version: options.serverVersion,
|
|
13753
|
-
stim: options.stimVersion
|
|
13754
|
-
home: homedir()
|
|
13918
|
+
stim: options.stimVersion
|
|
13755
13919
|
},
|
|
13756
13920
|
capabilities: [],
|
|
13757
13921
|
features: [...protocol_exports.FEATURES],
|
|
@@ -15139,6 +15303,10 @@ async function startServer(options) {
|
|
|
15139
15303
|
socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
15140
15304
|
return;
|
|
15141
15305
|
}
|
|
15306
|
+
if (upgradeFromWebPage(request.headers, peer === null)) {
|
|
15307
|
+
socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
15308
|
+
return;
|
|
15309
|
+
}
|
|
15142
15310
|
if (limiter.blocked(peer ?? "local")) {
|
|
15143
15311
|
socket.end("HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
15144
15312
|
return;
|
|
@@ -15736,7 +15904,7 @@ async function main() {
|
|
|
15736
15904
|
const service = command === "service";
|
|
15737
15905
|
if ((values.env || values["path-prepend"]) && command !== void 0 && !service) fail(`--env and --path-prepend apply only to serving and \`service install\`.\n${USAGE}`);
|
|
15738
15906
|
if (values.env || values["path-prepend"]) {
|
|
15739
|
-
const problem = validateServeEnvironment(extraEnv, pathPrepend);
|
|
15907
|
+
const problem = validateServeEnvironment(extraEnv, pathPrepend, command === void 0);
|
|
15740
15908
|
if (problem) fail(problem);
|
|
15741
15909
|
}
|
|
15742
15910
|
if ((values.label !== void 0 || values.serve) && !service) fail(`--label and --serve apply only to \`service\`.\n${USAGE}`);
|
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-C5iKxjba.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.20.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,8 +28,8 @@
|
|
|
28
28
|
"./protocol.schema.json": "./dist/protocol.schema.json"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@stim-cli/core": "^1.
|
|
32
|
-
"stim": "1.
|
|
31
|
+
"@stim-cli/core": "^1.20.0",
|
|
32
|
+
"stim": "1.20.0",
|
|
33
33
|
"ws": "^8.21.3"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|