@stim-cli/server 1.19.0 → 1.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/SimulatorFrameArtwork.swift +9 -0
- package/dist/{stim-server-DThEHaj6.mjs → stim-server-Dy0DlR96.mjs} +210 -64
- 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
|
|
@@ -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";
|
|
@@ -130,7 +130,7 @@ function validTimeZone(timeZone) {
|
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
const minuteOfDay$1 = (value) => Number.isInteger(value) && value >= 0 && value < 1440;
|
|
133
|
+
const minuteOfDay$1 = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value < 1440;
|
|
134
134
|
/** Quiet hours as `push.register` takes them, or null when `value` is not valid. */
|
|
135
135
|
function parseQuietHours(value) {
|
|
136
136
|
if (!isJsonObject(value)) return null;
|
|
@@ -144,7 +144,7 @@ function parseQuietHours(value) {
|
|
|
144
144
|
}
|
|
145
145
|
/** Whether `value` is a stuck threshold `push.register` takes: whole minutes from 1 to 240. */
|
|
146
146
|
function validStuckMinutes(value) {
|
|
147
|
-
return Number.isInteger(value) && value >= 1 && value <= 240;
|
|
147
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 240;
|
|
148
148
|
}
|
|
149
149
|
function serverDir() {
|
|
150
150
|
return join(configDir(), "server");
|
|
@@ -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
|
|
@@ -7100,12 +7171,13 @@ var DeviceHost = class {
|
|
|
7100
7171
|
appOffer(client, params) {
|
|
7101
7172
|
if (!this.options.allowed(client)) return refused$1("forbidden", "Current device-host approval is required.");
|
|
7102
7173
|
const offer = parseHostedAppOffer(params);
|
|
7103
|
-
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.");
|
|
7104
7175
|
try {
|
|
7105
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.");
|
|
7106
7178
|
if (record.platform !== "macos" && offer.arguments !== void 0) throw new Error("App arguments are supported only for hosted macOS sessions.");
|
|
7107
7179
|
if (record.platform === "macos") {
|
|
7108
|
-
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.");
|
|
7109
7181
|
if (offer.bundleId.startsWith("com.apple.")) throw new Error("Hosted macOS apps cannot use a com.apple. bundle identity.");
|
|
7110
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.");
|
|
7111
7183
|
}
|
|
@@ -7312,7 +7384,7 @@ var DeviceHost = class {
|
|
|
7312
7384
|
const value = outcome.value;
|
|
7313
7385
|
const installed = !owned.stopping && this.options.allowed(record.client) && !outcome.notice && outcome.settled && isJsonObject(value) && value.state === "installed" && parseHostedPlatformDevice(value.device, record.platform) !== null && record.device !== null && hostedDeviceId(parseHostedPlatformDevice(value.device, record.platform)) === hostedDeviceId(record.device) && (value.launched === true || value.launched === "unverified");
|
|
7314
7386
|
if (installed) owned.installedAttempt = attempt;
|
|
7315
|
-
if (installed && (record.platform !== "macos" || Number.isSafeInteger(value.pid) && value.pid > 0)) await this.startAgent(record, attempt, value.pid);
|
|
7387
|
+
if (installed && (record.platform !== "macos" || typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0)) await this.startAgent(record, attempt, value.pid);
|
|
7316
7388
|
const app = changeHostedApp(record.id, attempt, (current) => {
|
|
7317
7389
|
current.state = installed ? "installed" : "unknown";
|
|
7318
7390
|
current.launched = installed ? value.launched : null;
|
|
@@ -7864,11 +7936,13 @@ function connect$1(url, timeoutMs) {
|
|
|
7864
7936
|
} catch {
|
|
7865
7937
|
return;
|
|
7866
7938
|
}
|
|
7867
|
-
|
|
7939
|
+
if (!isJsonObject(message) || typeof message.id !== "number") return;
|
|
7940
|
+
const waiter = pending.get(message.id);
|
|
7868
7941
|
if (!waiter) return;
|
|
7869
7942
|
pending.delete(message.id);
|
|
7870
|
-
|
|
7871
|
-
|
|
7943
|
+
const { error, result } = message;
|
|
7944
|
+
if (error) waiter.reject(new Error(isJsonObject(error) && typeof error.message === "string" ? error.message : "DevTools command failed."));
|
|
7945
|
+
else waiter.resolve(isJsonObject(result) ? result : {});
|
|
7872
7946
|
});
|
|
7873
7947
|
socket.on("close", () => {
|
|
7874
7948
|
for (const waiter of pending.values()) waiter.reject(/* @__PURE__ */ new Error("The DevTools connection closed."));
|
|
@@ -9824,7 +9898,7 @@ function parseInput(method, params, targetOf) {
|
|
|
9824
9898
|
code: "bad-request",
|
|
9825
9899
|
message: "input.touch needs phase (down, move or up), and x and y from 0 to 1."
|
|
9826
9900
|
};
|
|
9827
|
-
if (display !== void 0 && (!Number.isInteger(display) || display < 0 || display > 3)) return {
|
|
9901
|
+
if (display !== void 0 && (typeof display !== "number" || !Number.isInteger(display) || display < 0 || display > 3)) return {
|
|
9828
9902
|
code: "bad-request",
|
|
9829
9903
|
message: "display must be a display index from 0 to 3."
|
|
9830
9904
|
};
|
|
@@ -11256,7 +11330,7 @@ function parseLogFilter(params) {
|
|
|
11256
11330
|
if (slot !== void 0 && (typeof slot !== "string" || !SLOT_NAME.test(slot))) return { error: "slot must be 1-64 letters, digits, underscores or hyphens." };
|
|
11257
11331
|
if (grep !== void 0 && (typeof grep !== "string" || grep.includes("\0") || compileGrep(grep).error)) return { error: "grep must be a valid regular expression without NUL characters." };
|
|
11258
11332
|
if (errors !== void 0 && typeof errors !== "boolean") return { error: "errors must be true or false." };
|
|
11259
|
-
if (tail !== void 0 && (!Number.isInteger(tail) || tail < 1 || tail > protocol_exports.MAX_LOG_TAIL)) return { error: `tail must be an integer from 1 to ${protocol_exports.MAX_LOG_TAIL}.` };
|
|
11333
|
+
if (tail !== void 0 && (typeof tail !== "number" || !Number.isInteger(tail) || tail < 1 || tail > protocol_exports.MAX_LOG_TAIL)) return { error: `tail must be an integer from 1 to ${protocol_exports.MAX_LOG_TAIL}.` };
|
|
11260
11334
|
return { filter: {
|
|
11261
11335
|
...typeof params.archive === "string" ? { archive: params.archive } : { workspace: params.workspace },
|
|
11262
11336
|
...sources ? { sources } : {},
|
|
@@ -11539,16 +11613,28 @@ const INTERVAL_MS = 15e3;
|
|
|
11539
11613
|
const POINTS = 40;
|
|
11540
11614
|
/**
|
|
11541
11615
|
* 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,
|
|
11542
|
-
* over the last 10 minutes. A later payload in the same slot replaces the earlier one.
|
|
11616
|
+
* over the last 10 minutes. A later payload in the same slot replaces the earlier one. `status --watch` prints no line
|
|
11617
|
+
* while usage stays within a step, so the slots between two payloads repeat the readings of the series the earlier
|
|
11618
|
+
* payload held, and those series continue to now until a payload drops them. An environment sums every
|
|
11543
11619
|
* machine owner of its workspace; a device is a simulator or emulator owner, keyed by its UDID or AVD name.
|
|
11544
11620
|
*/
|
|
11545
11621
|
var UsageRecorder = class {
|
|
11546
11622
|
environments = /* @__PURE__ */ new Map();
|
|
11547
11623
|
devices = /* @__PURE__ */ new Map();
|
|
11624
|
+
lastSlot = 0;
|
|
11625
|
+
liveEnvironments = /* @__PURE__ */ new Set();
|
|
11626
|
+
liveDevices = /* @__PURE__ */ new Set();
|
|
11548
11627
|
record(payload, at = Date.now()) {
|
|
11549
|
-
const owners = payload?.machine?.owners;
|
|
11550
|
-
if (!Array.isArray(owners)) return;
|
|
11551
11628
|
const slot = Math.floor(at / INTERVAL_MS);
|
|
11629
|
+
const owners = payload?.machine?.owners;
|
|
11630
|
+
this.holdThrough(slot - 1);
|
|
11631
|
+
this.lastSlot = Math.max(this.lastSlot, slot);
|
|
11632
|
+
this.liveEnvironments = /* @__PURE__ */ new Set();
|
|
11633
|
+
this.liveDevices = /* @__PURE__ */ new Set();
|
|
11634
|
+
if (!Array.isArray(owners)) {
|
|
11635
|
+
this.prune(slot);
|
|
11636
|
+
return;
|
|
11637
|
+
}
|
|
11552
11638
|
const sums = /* @__PURE__ */ new Map();
|
|
11553
11639
|
for (const owner of owners) {
|
|
11554
11640
|
const reading = {
|
|
@@ -11578,20 +11664,24 @@ var UsageRecorder = class {
|
|
|
11578
11664
|
meta,
|
|
11579
11665
|
slots: slots.set(slot, reading)
|
|
11580
11666
|
});
|
|
11667
|
+
this.liveDevices.add(key);
|
|
11581
11668
|
}
|
|
11582
11669
|
}
|
|
11583
|
-
for (const [workspace, reading] of sums)
|
|
11670
|
+
for (const [workspace, reading] of sums) {
|
|
11671
|
+
this.environments.set(workspace, (this.environments.get(workspace) ?? /* @__PURE__ */ new Map()).set(slot, reading));
|
|
11672
|
+
this.liveEnvironments.add(workspace);
|
|
11673
|
+
}
|
|
11584
11674
|
this.prune(slot);
|
|
11585
11675
|
}
|
|
11586
11676
|
/** The last 10 minutes ending at `now`'s slot, or null when no series has a reading in them. */
|
|
11587
11677
|
history(now = Date.now()) {
|
|
11588
11678
|
const end = Math.floor(now / INTERVAL_MS);
|
|
11589
11679
|
this.prune(end);
|
|
11590
|
-
const series = (slots) => {
|
|
11680
|
+
const series = (slots, live) => {
|
|
11591
11681
|
const cpuPercent = [];
|
|
11592
11682
|
const memoryMb = [];
|
|
11593
11683
|
for (let slot = end - POINTS + 1; slot <= end; slot++) {
|
|
11594
|
-
const reading = slots.get(slot);
|
|
11684
|
+
const reading = slots.get(slot) ?? (live && slot > this.lastSlot ? slots.get(this.lastSlot) : void 0);
|
|
11595
11685
|
cpuPercent.push(reading ? Math.round(reading.cpuPercent * 10) / 10 : null);
|
|
11596
11686
|
memoryMb.push(reading ? Math.round(reading.memoryMb) : null);
|
|
11597
11687
|
}
|
|
@@ -11600,8 +11690,8 @@ var UsageRecorder = class {
|
|
|
11600
11690
|
memoryMb
|
|
11601
11691
|
};
|
|
11602
11692
|
};
|
|
11603
|
-
const environments = [...this.environments].map(([workspace, slots]) => Object.assign({ workspace }, series(slots)));
|
|
11604
|
-
const devices = [...this.devices
|
|
11693
|
+
const environments = [...this.environments].map(([workspace, slots]) => Object.assign({ workspace }, series(slots, this.liveEnvironments.has(workspace))));
|
|
11694
|
+
const devices = [...this.devices].map(([key, { meta, slots }]) => Object.assign({}, meta, series(slots, this.liveDevices.has(key))));
|
|
11605
11695
|
if (!environments.length && !devices.length) return null;
|
|
11606
11696
|
return {
|
|
11607
11697
|
intervalMs: INTERVAL_MS,
|
|
@@ -11610,13 +11700,27 @@ var UsageRecorder = class {
|
|
|
11610
11700
|
devices
|
|
11611
11701
|
};
|
|
11612
11702
|
}
|
|
11703
|
+
/** Ends the live series where the last payload left them: nothing is recorded, or held, while nobody listens. */
|
|
11704
|
+
pause() {
|
|
11705
|
+
this.liveEnvironments = /* @__PURE__ */ new Set();
|
|
11706
|
+
this.liveDevices = /* @__PURE__ */ new Set();
|
|
11707
|
+
}
|
|
11708
|
+
holdThrough(slot) {
|
|
11709
|
+
const hold = (slots) => {
|
|
11710
|
+
const reading = slots?.get(this.lastSlot);
|
|
11711
|
+
if (!slots || !reading) return;
|
|
11712
|
+
for (let at = Math.max(this.lastSlot + 1, slot - POINTS + 1); at <= slot; at++) slots.set(at, reading);
|
|
11713
|
+
};
|
|
11714
|
+
for (const key of this.liveEnvironments) hold(this.environments.get(key));
|
|
11715
|
+
for (const key of this.liveDevices) hold(this.devices.get(key)?.slots);
|
|
11716
|
+
}
|
|
11613
11717
|
prune(slot) {
|
|
11614
|
-
const trim = (slots) => {
|
|
11615
|
-
for (const at of slots.keys()) if (at <= slot - POINTS) slots.delete(at);
|
|
11718
|
+
const trim = (slots, live) => {
|
|
11719
|
+
for (const at of slots.keys()) if (at <= slot - POINTS && !(live && at === this.lastSlot)) slots.delete(at);
|
|
11616
11720
|
return slots.size > 0;
|
|
11617
11721
|
};
|
|
11618
|
-
for (const [key, slots] of this.environments) if (!trim(slots)) this.environments.delete(key);
|
|
11619
|
-
for (const [key, { slots }] of this.devices) if (!trim(slots)) this.devices.delete(key);
|
|
11722
|
+
for (const [key, slots] of this.environments) if (!trim(slots, this.liveEnvironments.has(key))) this.environments.delete(key);
|
|
11723
|
+
for (const [key, { slots }] of this.devices) if (!trim(slots, this.liveDevices.has(key))) this.devices.delete(key);
|
|
11620
11724
|
}
|
|
11621
11725
|
};
|
|
11622
11726
|
//#endregion
|
|
@@ -12451,6 +12555,7 @@ var ServerUpdates = class {
|
|
|
12451
12555
|
running = null;
|
|
12452
12556
|
upload = null;
|
|
12453
12557
|
idle = null;
|
|
12558
|
+
ended = null;
|
|
12454
12559
|
constructor(options) {
|
|
12455
12560
|
this.options = options;
|
|
12456
12561
|
this.sweepIncoming();
|
|
@@ -12504,9 +12609,14 @@ var ServerUpdates = class {
|
|
|
12504
12609
|
service: service ? label : null,
|
|
12505
12610
|
acceptsClientBuilds: this.options.acceptsClientBuilds(),
|
|
12506
12611
|
running: this.progress(),
|
|
12507
|
-
last: service && label ? readLastUpdate(label) : null
|
|
12612
|
+
last: service && label ? this.latest(readLastUpdate(label)) : null
|
|
12508
12613
|
};
|
|
12509
12614
|
}
|
|
12615
|
+
latest(recorded) {
|
|
12616
|
+
if (!this.ended) return recorded;
|
|
12617
|
+
if (!recorded) return this.ended;
|
|
12618
|
+
return Date.parse(recorded.at) >= Date.parse(this.ended.at) ? recorded : this.ended;
|
|
12619
|
+
}
|
|
12510
12620
|
async start(by, params) {
|
|
12511
12621
|
const parsed = parseUpdateStart(params);
|
|
12512
12622
|
if (typeof parsed === "string") return refused("bad-request", parsed);
|
|
@@ -12665,6 +12775,12 @@ var ServerUpdates = class {
|
|
|
12665
12775
|
const settle = (ok, message) => {
|
|
12666
12776
|
this.options.drain(null);
|
|
12667
12777
|
this.clear();
|
|
12778
|
+
this.ended = {
|
|
12779
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12780
|
+
target,
|
|
12781
|
+
ok,
|
|
12782
|
+
message
|
|
12783
|
+
};
|
|
12668
12784
|
this.options.audit({
|
|
12669
12785
|
by: running.by,
|
|
12670
12786
|
target,
|
|
@@ -12677,7 +12793,11 @@ var ServerUpdates = class {
|
|
|
12677
12793
|
child.once("exit", (code) => {
|
|
12678
12794
|
const last = readLastUpdate(this.options.label);
|
|
12679
12795
|
const fresh = last && Date.parse(last.at) >= Date.parse(running.startedAt);
|
|
12680
|
-
|
|
12796
|
+
let reason = "";
|
|
12797
|
+
try {
|
|
12798
|
+
reason = readFileSync(log, "utf8").split("\n").findLast((line) => line.trim() !== "") ?? "";
|
|
12799
|
+
} catch {}
|
|
12800
|
+
settle(code === 0, fresh ? last.message : `The update exited with code ${String(code)}${reason ? `: ${reason}` : ""}; see ${log}.`);
|
|
12681
12801
|
});
|
|
12682
12802
|
return null;
|
|
12683
12803
|
}
|
|
@@ -13168,6 +13288,28 @@ function peerAddress(request) {
|
|
|
13168
13288
|
const forwarded = request.headers["x-forwarded-for"];
|
|
13169
13289
|
return (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(",")[0]?.trim() || null;
|
|
13170
13290
|
}
|
|
13291
|
+
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
13292
|
+
"127.0.0.1",
|
|
13293
|
+
"localhost",
|
|
13294
|
+
"[::1]"
|
|
13295
|
+
]);
|
|
13296
|
+
/**
|
|
13297
|
+
* Whether a WebSocket upgrade comes from a web page. Browsers send `Sec-Fetch-Site` and the page's own `Origin`.
|
|
13298
|
+
* React Native derives `Origin` from the ws URL, so an app's `Origin` names the requested host: over `http:` on loopback,
|
|
13299
|
+
* and `https:` through `tailscale serve`. SocketRocket omits the brackets of an IPv6 origin, so an iOS app on
|
|
13300
|
+
* `ws://[::1]` is refused.
|
|
13301
|
+
*/
|
|
13302
|
+
function upgradeFromWebPage(headers, local) {
|
|
13303
|
+
if (headers["sec-fetch-site"] !== void 0) return true;
|
|
13304
|
+
if (headers.origin === void 0) return false;
|
|
13305
|
+
const scheme = local ? "http:" : "https:";
|
|
13306
|
+
try {
|
|
13307
|
+
const own = new URL(`${scheme}//${headers.host ?? ""}`);
|
|
13308
|
+
return new URL(headers.origin).origin !== own.origin || local && !LOOPBACK_HOSTS.has(own.hostname);
|
|
13309
|
+
} catch {
|
|
13310
|
+
return true;
|
|
13311
|
+
}
|
|
13312
|
+
}
|
|
13171
13313
|
var FailureLimiter = class {
|
|
13172
13314
|
failures = /* @__PURE__ */ new Map();
|
|
13173
13315
|
max;
|
|
@@ -13415,6 +13557,7 @@ async function startServer(options) {
|
|
|
13415
13557
|
if (recorders > 0) return;
|
|
13416
13558
|
stopRecording?.();
|
|
13417
13559
|
stopRecording = null;
|
|
13560
|
+
usage.pause();
|
|
13418
13561
|
};
|
|
13419
13562
|
};
|
|
13420
13563
|
const controllers = /* @__PURE__ */ new Map();
|
|
@@ -13774,8 +13917,7 @@ async function startServer(options) {
|
|
|
13774
13917
|
server: {
|
|
13775
13918
|
name: options.name,
|
|
13776
13919
|
version: options.serverVersion,
|
|
13777
|
-
stim: options.stimVersion
|
|
13778
|
-
home: homedir()
|
|
13920
|
+
stim: options.stimVersion
|
|
13779
13921
|
},
|
|
13780
13922
|
capabilities: [],
|
|
13781
13923
|
features: [...protocol_exports.FEATURES],
|
|
@@ -14071,8 +14213,8 @@ async function startServer(options) {
|
|
|
14071
14213
|
if (video !== void 0 && (!Array.isArray(video) || !video.every((codec) => typeof codec === "string"))) return error(id, "bad-request", "video must be a list of codec names.");
|
|
14072
14214
|
const wantsVideo = video?.includes("h264") === true;
|
|
14073
14215
|
const maxFps = wantsVideo ? protocol_exports.FRAME_FPS.video : protocol_exports.FRAME_FPS.max;
|
|
14074
|
-
if (fps !== void 0 && (!Number.isInteger(fps) || fps < 1 || fps > maxFps)) return error(id, "bad-request", `fps must be a whole number from 1 to ${maxFps}.`);
|
|
14075
|
-
if (maxEdge !== void 0 && (!Number.isInteger(maxEdge) || maxEdge < protocol_exports.FRAME_EDGE.min || maxEdge > protocol_exports.FRAME_EDGE.max)) return error(id, "bad-request", `maxEdge must be a whole number of pixels from ${protocol_exports.FRAME_EDGE.min} to ${protocol_exports.FRAME_EDGE.max}.`);
|
|
14216
|
+
if (fps !== void 0 && (typeof fps !== "number" || !Number.isInteger(fps) || fps < 1 || fps > maxFps)) return error(id, "bad-request", `fps must be a whole number from 1 to ${maxFps}.`);
|
|
14217
|
+
if (maxEdge !== void 0 && (typeof maxEdge !== "number" || !Number.isInteger(maxEdge) || maxEdge < protocol_exports.FRAME_EDGE.min || maxEdge > protocol_exports.FRAME_EDGE.max)) return error(id, "bad-request", `maxEdge must be a whole number of pixels from ${protocol_exports.FRAME_EDGE.min} to ${protocol_exports.FRAME_EDGE.max}.`);
|
|
14076
14218
|
const resolvedDir = hosted ? null : readDir(id, {
|
|
14077
14219
|
workspace,
|
|
14078
14220
|
archive
|
|
@@ -15084,7 +15226,7 @@ async function startServer(options) {
|
|
|
15084
15226
|
if (message.method === "notifications.list") {
|
|
15085
15227
|
const params = message.params ?? {};
|
|
15086
15228
|
const since = isJsonObject(params) ? params.since : void 0;
|
|
15087
|
-
if (!isJsonObject(params) || since !== void 0 && !(Number.isInteger(since) && since >= 0)) return error(id, "bad-request", "notifications.list takes an optional since, a cursor of 0 or more.");
|
|
15229
|
+
if (!isJsonObject(params) || since !== void 0 && !(typeof since === "number" && Number.isInteger(since) && since >= 0)) return error(id, "bad-request", "notifications.list takes an optional since, a cursor of 0 or more.");
|
|
15088
15230
|
listeners.add(socket);
|
|
15089
15231
|
return send(socket, {
|
|
15090
15232
|
id,
|
|
@@ -15163,6 +15305,10 @@ async function startServer(options) {
|
|
|
15163
15305
|
socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
15164
15306
|
return;
|
|
15165
15307
|
}
|
|
15308
|
+
if (upgradeFromWebPage(request.headers, peer === null)) {
|
|
15309
|
+
socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
15310
|
+
return;
|
|
15311
|
+
}
|
|
15166
15312
|
if (limiter.blocked(peer ?? "local")) {
|
|
15167
15313
|
socket.end("HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
15168
15314
|
return;
|
|
@@ -15760,7 +15906,7 @@ async function main() {
|
|
|
15760
15906
|
const service = command === "service";
|
|
15761
15907
|
if ((values.env || values["path-prepend"]) && command !== void 0 && !service) fail(`--env and --path-prepend apply only to serving and \`service install\`.\n${USAGE}`);
|
|
15762
15908
|
if (values.env || values["path-prepend"]) {
|
|
15763
|
-
const problem = validateServeEnvironment(extraEnv, pathPrepend);
|
|
15909
|
+
const problem = validateServeEnvironment(extraEnv, pathPrepend, command === void 0);
|
|
15764
15910
|
if (problem) fail(problem);
|
|
15765
15911
|
}
|
|
15766
15912
|
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-Dy0DlR96.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.1",
|
|
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.1",
|
|
32
|
+
"stim": "1.20.1",
|
|
33
33
|
"ws": "^8.21.3"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|