dsh-plugin-rea 2.0.1 → 2.0.3
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/CHANGELOG.md +98 -0
- package/README.md +32 -7
- package/README.zh-CN.md +30 -6
- package/assets/make-screenshots.ps1 +2 -2
- package/assets/screenshot-1.png +0 -0
- package/bin/rea-shim.mjs +112 -19
- package/check-updates.ps1 +90 -15
- package/docs/bundle-design.md +6 -5
- package/manifest/bundle.manifest.json +48 -41
- package/package.json +2 -3
- package/scripts/audit-manifest.mjs +98 -0
- package/scripts/rea-bundle-bootstrap.ps1 +37 -10
- package/scripts/setup.mjs +352 -87
- package/scripts/setup.test.mjs +683 -4
- package/examples/cordis.patch.posix.yml +0 -18
- package/examples/cordis.patch.with-env.yml +0 -42
package/docs/bundle-design.md
CHANGED
|
@@ -31,11 +31,11 @@ Two upstream rules shape the runtime wiring:
|
|
|
31
31
|
|
|
32
32
|
## 2. Verified facts this design rests on
|
|
33
33
|
|
|
34
|
-
Measured on Windows 11 x64, REA 6.
|
|
34
|
+
Measured on Windows 11 x64, REA 6.5.0, Ghidra 12.1.4, Temurin JDK 25.0.4:
|
|
35
35
|
|
|
36
36
|
| Fact | Evidence |
|
|
37
37
|
| --- | --- |
|
|
38
|
-
| A row can launch REA with **no PATH `rea`** | `node <pkg>/scripts/rea.mjs mcp` answered the MCP `initialize` handshake (`serverInfo.name = rea`, version 6.
|
|
38
|
+
| A row can launch REA with **no PATH `rea`** | `node <pkg>/scripts/rea.mjs mcp` answered the MCP `initialize` handshake (`serverInfo.name = rea`, version 6.5.0) and `rea doctor --json` reported 207 tools. `healthy` is `false` on a bundle-only install: 6.5.0 also checks the host's own setup (Hopper, IDA registration, `rea setup` skill/registrations), which provisioning deliberately never touches — the row does not require `healthy`, only a CLI that runs and a catalog it can read |
|
|
39
39
|
| `!!js` expressions are supported in this profile layer | the existing profile patch already used one; the loader evaluates them |
|
|
40
40
|
| A row-level `env:` block reaches the MCP child | `binary_session` reported `registrationEnvironment` = `GHIDRA_INSTALL_DIR`, `JAVA_HOME` |
|
|
41
41
|
| No fixed home/layout is promised | the disk layout differs per install; nothing may assume one |
|
|
@@ -54,8 +54,9 @@ Three responsibilities, kept strictly apart:
|
|
|
54
54
|
checksums, unpacks, runs the readiness probe, and writes `bundle.json`.
|
|
55
55
|
Idempotent and re-runnable; `--reuse` adopts pre-existing installs.
|
|
56
56
|
3. **Patch (wiring, published).** `cordis.patch.yml` on `main`, carrying
|
|
57
|
-
**no machine path**: the row
|
|
58
|
-
shim is responsible for the
|
|
57
|
+
**no machine path**: the row computes `<DSH_HOME>/plugins/rea-bundle/bin/rea-shim.mjs`
|
|
58
|
+
at load time from `DSH_HOME` alone, and that mirrored shim is responsible for the
|
|
59
|
+
rest. (The checkout's own copy is what a human runs; the row never points at it.)
|
|
59
60
|
|
|
60
61
|
The published plugin therefore stays portable, while the machine-specific truth
|
|
61
62
|
lives in one small JSON file under the DSH home.
|
|
@@ -161,7 +162,7 @@ Windows and is what this bundle targets to complete.
|
|
|
161
162
|
| Component | Needed for | Provisioned as |
|
|
162
163
|
| --- | --- | --- |
|
|
163
164
|
| Node runtime | running REA at all | reuse a compatible existing node, else portable `node.exe` |
|
|
164
|
-
| `rea-agents` | the MCP server itself | vendored copy under `vendor/rea-agents` |
|
|
165
|
+
| `rea-agents` | the MCP server itself | vendored copy under `vendor/node_modules/rea-agents` |
|
|
165
166
|
| Temurin JDK 21/25 | Ghidra headless + JADX bridge | portable JDK zip (full JDK, not a JRE) |
|
|
166
167
|
| Ghidra 12.1.x | native analysis | official release zip, extracted |
|
|
167
168
|
| apktool | Android resource decode | `apktool.bat` + jar in one directory |
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
" binaries - workspace-relative entries inside the unpacked directory that are",
|
|
13
13
|
" directly spawnable; the first that exists becomes the component's",
|
|
14
14
|
" recorded path so another component can depend on it.",
|
|
15
|
+
" executable - a placed file that must carry the POSIX exec bit (a wrapper REA",
|
|
16
|
+
" spawns without a shell); setup chmods exactly these to 0755.",
|
|
15
17
|
"Nothing here is redistributed inside the npm package; setup downloads it."
|
|
16
18
|
],
|
|
17
19
|
"manifestVersion": 1,
|
|
18
20
|
"generatedFor": "dsh-plugin-rea one-plugin bundle",
|
|
19
21
|
"pins": {
|
|
20
|
-
"reaAgents": "6.
|
|
22
|
+
"reaAgents": "6.5.0",
|
|
21
23
|
"ghidra": "12.1.4",
|
|
22
24
|
"jdk": "21",
|
|
23
25
|
"apktool": "3.0.3",
|
|
@@ -203,16 +205,16 @@
|
|
|
203
205
|
"win32-x64": [
|
|
204
206
|
{
|
|
205
207
|
"id": "wrapper",
|
|
206
|
-
"version": "
|
|
207
|
-
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/
|
|
208
|
+
"version": "3.0.3",
|
|
209
|
+
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/v3.0.3/scripts/windows/apktool.bat",
|
|
208
210
|
"format": "file",
|
|
209
|
-
"size":
|
|
210
|
-
"sha256": "
|
|
211
|
-
"sha256Source": "measured on 2026-10-
|
|
212
|
-
"sha256VerifiedOn": "2026-10-
|
|
211
|
+
"size": 2386,
|
|
212
|
+
"sha256": "d81a91bd694459a61a88e4a1a8184efc21130390ff7cb5c172f631551ad9842a",
|
|
213
|
+
"sha256Source": "measured on 2026-10-16 from the immutable v3.0.3 tag (raw.githubusercontent.com publishes no checksum for the wrapper). The tag, not master: pinning master meant any upstream commit to the wrapper broke every new install with a hash mismatch, and four retries could not fix a download whose content had legitimately changed.",
|
|
214
|
+
"sha256VerifiedOn": "2026-10-16",
|
|
213
215
|
"notes": [
|
|
214
216
|
"The wrapper pushd's to its own directory and resolves apktool.jar first, then globs apktool_*.jar, so the renamed jar next to it is found.",
|
|
215
|
-
"
|
|
217
|
+
"The previous entry measured master's copy (2,348 bytes) and recorded that it returns exit 0 in 0.2 s while appending 'Press any key to continue . . .' to stdout. This is the tag's copy (2,386 bytes) and that runtime behaviour has NOT been re-measured; treat the trailing-text claim as unverified for this file.",
|
|
216
218
|
"REA is pointed at this .bat (REA_APKTOOL_COMMAND), not at the jar, exactly as the wrapper expects."
|
|
217
219
|
],
|
|
218
220
|
"moveTo": "apktool",
|
|
@@ -222,13 +224,14 @@
|
|
|
222
224
|
"darwin-x64": [
|
|
223
225
|
{
|
|
224
226
|
"id": "wrapper",
|
|
225
|
-
"version": "
|
|
226
|
-
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/
|
|
227
|
+
"version": "3.0.3",
|
|
228
|
+
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/v3.0.3/scripts/osx/apktool",
|
|
227
229
|
"format": "file",
|
|
228
|
-
"size":
|
|
229
|
-
"sha256": "
|
|
230
|
-
"sha256Source": "measured on 2026-10-
|
|
231
|
-
"sha256VerifiedOn": "2026-10-
|
|
230
|
+
"size": 2709,
|
|
231
|
+
"sha256": "3b9a95c41680c4a758b0b8d6e91e122b2ea3f1a189685a915613ca1ccba00fd9",
|
|
232
|
+
"sha256Source": "measured on 2026-10-16 from the v3.0.3 tag (the correct file for macOS; the linux script is a different file). The tag is immutable, so this pin cannot drift under a new install the way master's did.",
|
|
233
|
+
"sha256VerifiedOn": "2026-10-16",
|
|
234
|
+
"executable": true,
|
|
232
235
|
"moveTo": "apktool",
|
|
233
236
|
"saveAs": "apktool"
|
|
234
237
|
}
|
|
@@ -236,13 +239,14 @@
|
|
|
236
239
|
"darwin-arm64": [
|
|
237
240
|
{
|
|
238
241
|
"id": "wrapper",
|
|
239
|
-
"version": "
|
|
240
|
-
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/
|
|
242
|
+
"version": "3.0.3",
|
|
243
|
+
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/v3.0.3/scripts/osx/apktool",
|
|
241
244
|
"format": "file",
|
|
242
|
-
"size":
|
|
243
|
-
"sha256": "
|
|
244
|
-
"sha256Source": "measured on 2026-10-
|
|
245
|
-
"sha256VerifiedOn": "2026-10-
|
|
245
|
+
"size": 2709,
|
|
246
|
+
"sha256": "3b9a95c41680c4a758b0b8d6e91e122b2ea3f1a189685a915613ca1ccba00fd9",
|
|
247
|
+
"sha256Source": "measured on 2026-10-16 from the v3.0.3 tag; the same osx wrapper serves arm64.",
|
|
248
|
+
"sha256VerifiedOn": "2026-10-16",
|
|
249
|
+
"executable": true,
|
|
246
250
|
"moveTo": "apktool",
|
|
247
251
|
"saveAs": "apktool"
|
|
248
252
|
}
|
|
@@ -250,13 +254,14 @@
|
|
|
250
254
|
"linux-x64": [
|
|
251
255
|
{
|
|
252
256
|
"id": "wrapper",
|
|
253
|
-
"version": "
|
|
254
|
-
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/
|
|
257
|
+
"version": "3.0.3",
|
|
258
|
+
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/v3.0.3/scripts/linux/apktool",
|
|
255
259
|
"format": "file",
|
|
256
|
-
"size":
|
|
257
|
-
"sha256": "
|
|
258
|
-
"sha256Source": "measured on 2026-10-
|
|
259
|
-
"sha256VerifiedOn": "2026-10-
|
|
260
|
+
"size": 2685,
|
|
261
|
+
"sha256": "493a9c98df968a25c373ca9df871ca77730c82954be4959b74e90adaf91a0e77",
|
|
262
|
+
"sha256Source": "measured on 2026-10-16 from the v3.0.3 tag.",
|
|
263
|
+
"sha256VerifiedOn": "2026-10-16",
|
|
264
|
+
"executable": true,
|
|
260
265
|
"moveTo": "apktool",
|
|
261
266
|
"saveAs": "apktool"
|
|
262
267
|
}
|
|
@@ -264,13 +269,14 @@
|
|
|
264
269
|
"linux-arm64": [
|
|
265
270
|
{
|
|
266
271
|
"id": "wrapper",
|
|
267
|
-
"version": "
|
|
268
|
-
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/
|
|
272
|
+
"version": "3.0.3",
|
|
273
|
+
"url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/v3.0.3/scripts/linux/apktool",
|
|
269
274
|
"format": "file",
|
|
270
|
-
"size":
|
|
271
|
-
"sha256": "
|
|
272
|
-
"sha256Source": "measured on 2026-10-
|
|
273
|
-
"sha256VerifiedOn": "2026-10-
|
|
275
|
+
"size": 2685,
|
|
276
|
+
"sha256": "493a9c98df968a25c373ca9df871ca77730c82954be4959b74e90adaf91a0e77",
|
|
277
|
+
"sha256Source": "measured on 2026-10-16 from the v3.0.3 tag; the same linux wrapper serves arm64. It is only correct where the platform-tools archive is too — see the arch caveat on androidPlatformTools.",
|
|
278
|
+
"sha256VerifiedOn": "2026-10-16",
|
|
279
|
+
"executable": true,
|
|
274
280
|
"moveTo": "apktool",
|
|
275
281
|
"saveAs": "apktool"
|
|
276
282
|
}
|
|
@@ -337,9 +343,9 @@
|
|
|
337
343
|
"format": "zip",
|
|
338
344
|
"size": 16110554,
|
|
339
345
|
"sha256": "ee39ad5967e95c2a07f04dbcbde96b1a0c916ba376096db5d2f498b7727a5d1d",
|
|
340
|
-
"sha256Source": "Same darwin archive as x64 (measured 2026-10-13);
|
|
346
|
+
"sha256Source": "Same darwin archive as x64 (measured 2026-10-13); Google publishes one darwin zip for both architectures.",
|
|
341
347
|
"sha256VerifiedOn": "2026-10-13",
|
|
342
|
-
"note": "The
|
|
348
|
+
"note": "The darwin archive contains x86_64 binaries. On Apple silicon they run only through Rosetta 2; without it adb cannot start and this optional component is reported unavailable."
|
|
343
349
|
},
|
|
344
350
|
"linux-x64": {
|
|
345
351
|
"version": "latest",
|
|
@@ -349,7 +355,7 @@
|
|
|
349
355
|
"sha256": "d230f13842f60f782a8645f9c813f8f845bf36089ea7289f28c48f17979313f1",
|
|
350
356
|
"sha256Source": "Google publishes no checksum for this rolling asset; measured on 2026-10-13 by downloading the archive and hashing it locally (9,054,187 bytes)",
|
|
351
357
|
"sha256VerifiedOn": "2026-10-13",
|
|
352
|
-
"note": "The
|
|
358
|
+
"note": "The linux archive contains x86_64 binaries. Google publishes no arm64 platform-tools and this bundle does not emulate one, so on linux-arm64 the entry below is offered but its probe is expected to fail — reported as an optional component being unavailable, never as a broken install."
|
|
353
359
|
},
|
|
354
360
|
"linux-arm64": {
|
|
355
361
|
"version": "latest",
|
|
@@ -357,8 +363,9 @@
|
|
|
357
363
|
"format": "zip",
|
|
358
364
|
"size": 9054187,
|
|
359
365
|
"sha256": "d230f13842f60f782a8645f9c813f8f845bf36089ea7289f28c48f17979313f1",
|
|
360
|
-
"sha256Source": "
|
|
361
|
-
"sha256VerifiedOn": "2026-10-13"
|
|
366
|
+
"sha256Source": "Google publishes no arm64 platform-tools; this is the same x86_64 linux archive as x64 (measured 2026-10-13), recorded so the mismatch is explicit instead of implied.",
|
|
367
|
+
"sha256VerifiedOn": "2026-10-13",
|
|
368
|
+
"note": "Kept so an arm64 host reports a precise failure instead of \"no source\". adb here is x86_64: it runs under qemu/binfmt where the host provides it, and otherwise the probe fails and the report names it."
|
|
362
369
|
}
|
|
363
370
|
}
|
|
364
371
|
},
|
|
@@ -370,14 +377,14 @@
|
|
|
370
377
|
"unpack": { "moveTo": "rea-agents" },
|
|
371
378
|
"probe": {
|
|
372
379
|
"argv": ["{node}", "{vendor}/node_modules/rea-agents/scripts/rea.mjs", "--version"],
|
|
373
|
-
"expect": { "regex": "^6\\.
|
|
380
|
+
"expect": { "regex": "^6\\.5\\.0(\\s|$)" }
|
|
374
381
|
},
|
|
375
382
|
"sources": {
|
|
376
383
|
"npm": {
|
|
377
|
-
"version": "6.
|
|
384
|
+
"version": "6.5.0",
|
|
378
385
|
"registry": "https://registry.npmjs.org/rea-agents",
|
|
379
|
-
"integrity": "npm's own dist.integrity is the authoritative check for this tarball: the registry tarball ships NO node_modules (verified:
|
|
380
|
-
"note": "`just unpacking the published tarball cannot work` is a verified fact, not a caution: the CLI would fail with ERR_MODULE_NOT_FOUND on zod."
|
|
386
|
+
"integrity": "npm's own dist.integrity is the authoritative check for this tarball: the registry tarball ships NO node_modules (verified: 1650 entries in 6.5.0, 0 under node_modules), so setup resolves the dependency closure with `npm install --ignore-scripts` instead of unpacking a tarball.",
|
|
387
|
+
"note": "`just unpacking the published tarball cannot work` is a verified fact, not a caution: the CLI would fail with ERR_MODULE_NOT_FOUND on zod. 6.5.0 is the same engines/bin contract as 6.4.0 (^22.19.0 || ^24.11.0 || >=26.0.0, scripts/rea.mjs) and adds 37 MCP tools (170 -> 207 by rea doctor --json on 2026-10-16; the upstream contract files count 206) plus an optional frida dependency; --ignore-scripts means its install script never runs."
|
|
381
388
|
}
|
|
382
389
|
}
|
|
383
390
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-rea",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "One-plugin bundle: provisions Node/REA/JDK/Ghidra for you, then registers the upstream REA MCP server as mcp__rea__* tools. · 一体包:自动准备 Node/REA/JDK/Ghidra,并把上游 REA 的 MCP 服务接入 DeepSeek Harness。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,8 +43,7 @@
|
|
|
43
43
|
"CHANGELOG.md",
|
|
44
44
|
"check-updates.ps1",
|
|
45
45
|
"screenshots.json",
|
|
46
|
-
"assets/"
|
|
47
|
-
"examples/"
|
|
46
|
+
"assets/"
|
|
48
47
|
],
|
|
49
48
|
"dsh": {
|
|
50
49
|
"bundle": {
|
|
@@ -430,6 +430,104 @@ if (manifest) {
|
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
+
/* ------------------------- 11. setup and the manifest describe the same set */
|
|
434
|
+
|
|
435
|
+
// A component added to the manifest but not to setup.mjs's INSTALL_ORDER was
|
|
436
|
+
// silently never provisioned, and nothing here said so. The reverse — a name in
|
|
437
|
+
// INSTALL_ORDER with no manifest entry — is skipped by the loop's
|
|
438
|
+
// `if (!component) continue`, so it is equally silent.
|
|
439
|
+
const installOrderMatch = setupText.match(/const INSTALL_ORDER = \[([^\]]*)\]/);
|
|
440
|
+
if (!installOrderMatch) {
|
|
441
|
+
add("error", "scripts/setup.mjs", "INSTALL_ORDER was not found, so manifest/setup agreement cannot be checked");
|
|
442
|
+
} else {
|
|
443
|
+
const order = [...installOrderMatch[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
444
|
+
const manifestNames = Object.keys(manifest.components ?? {});
|
|
445
|
+
for (const name of manifestNames) {
|
|
446
|
+
if (!order.includes(name)) {
|
|
447
|
+
add("error", "manifest.components", `"${name}" is not in scripts/setup.mjs INSTALL_ORDER, so setup would silently never provision it`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
for (const name of order) {
|
|
451
|
+
if (!manifestNames.includes(name)) {
|
|
452
|
+
add("error", "scripts/setup.mjs", `INSTALL_ORDER names "${name}", which the manifest does not describe`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* ---------------------------- 12. the Node engines rule has one wording too */
|
|
458
|
+
|
|
459
|
+
// REA's engines are restated in the manifest pin, package.json and the node
|
|
460
|
+
// probe regex. Only the pin and package.json can be compared as text, but a
|
|
461
|
+
// drift between those two would advertise one range and enforce another.
|
|
462
|
+
const declaredEngines = manifest.pins?.nodeEngines;
|
|
463
|
+
let packageEngines = null;
|
|
464
|
+
try {
|
|
465
|
+
packageEngines = JSON.parse(readFileSync(path.join(HERE, "..", "package.json"), "utf8")).engines?.node ?? null;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
add("error", "package.json", `could not be read: ${String(error && error.message)}`);
|
|
468
|
+
}
|
|
469
|
+
if (!declaredEngines) {
|
|
470
|
+
add("error", "manifest.pins.nodeEngines", "is missing, so nothing states REA's engines for the reuse path");
|
|
471
|
+
} else if (declaredEngines !== packageEngines) {
|
|
472
|
+
add(
|
|
473
|
+
"error",
|
|
474
|
+
"manifest.pins.nodeEngines",
|
|
475
|
+
`is ${JSON.stringify(declaredEngines)} but package.json engines.node is ${JSON.stringify(packageEngines)}`,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* ------------------- 13. a pinned version's probe must not accept a prefix */
|
|
480
|
+
|
|
481
|
+
// The concern is a probe that accepts a *newer* release as the pin, so test the
|
|
482
|
+
// expression against the versions that would mean that. The audit is not a
|
|
483
|
+
// regex linter: `^6\.4\.0` legitimately rejects 6.4.1 and 6.5.0 — the failure
|
|
484
|
+
// mode is only real when the next patch or minor still matches.
|
|
485
|
+
const reaAgents = manifest.components?.reaAgents;
|
|
486
|
+
const pinnedRea = reaAgents?.sources?.npm?.version;
|
|
487
|
+
const reaProbe = reaAgents?.probe?.expect?.regex;
|
|
488
|
+
if (pinnedRea && reaProbe) {
|
|
489
|
+
let probe = null;
|
|
490
|
+
try {
|
|
491
|
+
probe = new RegExp(reaProbe, "m");
|
|
492
|
+
} catch (error) {
|
|
493
|
+
add("error", "reaAgents.probe.expect.regex", `is not a valid regular expression: ${String(error && error.message)}`);
|
|
494
|
+
}
|
|
495
|
+
if (probe) {
|
|
496
|
+
if (!probe.test(pinnedRea)) {
|
|
497
|
+
add("error", "reaAgents.probe.expect.regex", `does not match the pinned version ${pinnedRea}`);
|
|
498
|
+
}
|
|
499
|
+
const [major, minor, patch] = pinnedRea.split(".").map((part) => Number(part));
|
|
500
|
+
for (const [label, next] of [
|
|
501
|
+
["the next patch", `${major}.${minor}.${patch + 1}`],
|
|
502
|
+
["the next minor", `${major}.${minor + 1}.0`],
|
|
503
|
+
]) {
|
|
504
|
+
if (Number.isFinite(next.split(".")[2]) && probe.test(next)) {
|
|
505
|
+
add("error", "reaAgents.probe.expect.regex", `accepts ${next} (${label}), so a newer release passes as the pin`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* --------------------- 14. a POSIX wrapper must be marked executable */
|
|
512
|
+
|
|
513
|
+
// copyFileSync carries the archive's 0644 and nothing else changes a mode, while
|
|
514
|
+
// REA spawns REA_APKTOOL_COMMAND without a shell: on Linux/macOS an unmarked
|
|
515
|
+
// wrapper fails with EACCES even though the component probe (`java -jar`) passes.
|
|
516
|
+
// Only the manifest can say which files need the bit, so require it here.
|
|
517
|
+
const apktoolWrappers = manifest.components?.apktool?.entries?.byPlatform ?? {};
|
|
518
|
+
for (const [platform, list] of Object.entries(apktoolWrappers)) {
|
|
519
|
+
if (platform.startsWith("win32")) continue;
|
|
520
|
+
for (const entry of list) {
|
|
521
|
+
if (entry.executable !== true) {
|
|
522
|
+
add(
|
|
523
|
+
"error",
|
|
524
|
+
`apktool.entries.byPlatform.${platform}`,
|
|
525
|
+
`the ${platform} wrapper is not marked "executable": true, so it lands 0644 and cannot be spawned without a shell`,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
433
531
|
/* ------------------------------------------------------------- summary */
|
|
434
532
|
|
|
435
533
|
const pinned = fetchEntries.filter((entry) => entry.entry.sha256 && entry.entry.sha256 !== "unverified").length;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# rea-bundle-bootstrap.ps1 — get from "no Node at all" to a provisioned bundle.
|
|
2
2
|
#
|
|
3
3
|
# Why this exists: setup.mjs is written in Node, so a machine with no Node
|
|
4
4
|
# cannot run it. This script uses only PowerShell + Expand-Archive (no
|
|
@@ -39,8 +39,12 @@ $Root = [System.IO.Path]::GetFullPath($Root)
|
|
|
39
39
|
$stack = Join-Path $Root 'stack'
|
|
40
40
|
$cache = Join-Path $Root 'cache'
|
|
41
41
|
$logs = Join-Path $Root 'logs'
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# -DryRun promises that nothing was changed, so it must not create these either:
|
|
43
|
+
# it only reads them, and Test-Path / Get-ChildItem handle a missing path.
|
|
44
|
+
if (-not $DryRun) {
|
|
45
|
+
foreach ($dir in @($Root, $stack, $cache, $logs)) {
|
|
46
|
+
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
$pluginRoot = Split-Path -Parent $PSScriptRoot
|
|
@@ -84,6 +88,32 @@ $nodeFolder = [System.IO.Path]::GetFileNameWithoutExtension($nodeZipName)
|
|
|
84
88
|
if ($nodeSha256 -eq 'unverified') { throw "the manifest pins no checksum for $hostKey; refusing to download unverified bytes" }
|
|
85
89
|
Write-Step "Node source: $hostKey v$nodeVersion"
|
|
86
90
|
|
|
91
|
+
$script:NodeEngines = [string]$manifest.pins.nodeEngines
|
|
92
|
+
if (-not $script:NodeEngines) { throw "the manifest pins no nodeEngines, so Node compatibility cannot be judged" }
|
|
93
|
+
|
|
94
|
+
# REA's engines come from the manifest pin, which the rest of provisioning already
|
|
95
|
+
# treats as the single source of truth. This used to be a second copy of the same
|
|
96
|
+
# numbers in a comment, and two copies drift: the audit now asserts that the pin
|
|
97
|
+
# keeps the shape this parser understands.
|
|
98
|
+
function Test-EnginesMatch([string]$Clean, [string]$Range) {
|
|
99
|
+
$versions = @()
|
|
100
|
+
foreach ($part in $Clean.Split('.')) {
|
|
101
|
+
$n = 0
|
|
102
|
+
if (-not [int]::TryParse($part, [ref]$n)) { return $false }
|
|
103
|
+
$versions += $n
|
|
104
|
+
}
|
|
105
|
+
if ($versions.Count -lt 2) { return $false }
|
|
106
|
+
foreach ($clause in ($Range -split '\|\|')) {
|
|
107
|
+
$c = $clause.Trim()
|
|
108
|
+
if ($c -match '^\^(\d+)\.(\d+)\.(\d+)$') {
|
|
109
|
+
if ($versions[0] -eq [int]$Matches[1] -and $versions[1] -ge [int]$Matches[2]) { return $true }
|
|
110
|
+
} elseif ($c -match '^>=(\d+)\.(\d+)\.(\d+)$') {
|
|
111
|
+
if ($versions[0] -gt [int]$Matches[1] -or ($versions[0] -eq [int]$Matches[1] -and $versions[1] -ge [int]$Matches[2])) { return $true }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return $false
|
|
115
|
+
}
|
|
116
|
+
|
|
87
117
|
function Test-NodeUsable([string]$Exe) {
|
|
88
118
|
if (-not $Exe -or -not (Test-Path $Exe)) { return $null }
|
|
89
119
|
try {
|
|
@@ -91,13 +121,10 @@ function Test-NodeUsable([string]$Exe) {
|
|
|
91
121
|
} catch { return $null }
|
|
92
122
|
if (-not $version) { return $null }
|
|
93
123
|
$clean = $version.TrimStart('v').Trim()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
$
|
|
98
|
-
# REA's engines: ^22.19.0 || ^24.11.0 || >=26.0.0
|
|
99
|
-
$ok = ($major -eq 22 -and $minor -ge 19) -or ($major -eq 24 -and $minor -ge 11) -or ($major -ge 26)
|
|
100
|
-
if (-not $ok) { return $null }
|
|
124
|
+
# A version that is not three numbers (`vX.Y`) used to reach `[int]` and throw
|
|
125
|
+
# under $ErrorActionPreference='Stop', aborting the whole bootstrap instead of
|
|
126
|
+
# falling through to the portable download — the one path this script exists for.
|
|
127
|
+
if (-not (Test-EnginesMatch $clean $script:NodeEngines)) { return $null }
|
|
101
128
|
return [pscustomobject]@{ Path = (Resolve-Path $Exe).Path; Version = $clean }
|
|
102
129
|
}
|
|
103
130
|
|