@frockbot/computer-host-runtime 0.3.16 → 0.3.18
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/package.json +1 -1
- package/src/runtime.test.ts +63 -10
- package/src/runtime.ts +33 -17
package/package.json
CHANGED
package/src/runtime.test.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { dirname, join } from "node:path";
|
|
|
14
14
|
import { describe, expect, test } from "bun:test";
|
|
15
15
|
import {
|
|
16
16
|
APPLET_SDK_VERSION,
|
|
17
|
+
appletSdkInstallScript,
|
|
17
18
|
APPLET_SHIM_PATH,
|
|
18
19
|
appletShimScript,
|
|
19
20
|
APPLETS_ROOT,
|
|
@@ -447,7 +448,7 @@ state running`);
|
|
|
447
448
|
);
|
|
448
449
|
});
|
|
449
450
|
|
|
450
|
-
test("the update runner replaces files and
|
|
451
|
+
test("the update runner replaces files and only repairs Applet dependencies", () => {
|
|
451
452
|
expect(UPDATE_PHASES.map((phase) => phase.name)).toEqual([
|
|
452
453
|
"runtime",
|
|
453
454
|
"applets",
|
|
@@ -459,16 +460,16 @@ state running`);
|
|
|
459
460
|
const updateDocument = UPDATE_PHASES.map((phase) => phase.body).join("\n");
|
|
460
461
|
expect(updateDocument).not.toContain("apt-get");
|
|
461
462
|
expect(updateDocument).not.toContain("playwright-core/cli.js install");
|
|
462
|
-
// An in-place update swaps names over files it owns
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
// is absent, which is exactly when nothing can be running out of it.
|
|
463
|
+
// An in-place update swaps names over files it owns. It installs the SDK
|
|
464
|
+
// only while absent, and fills the four shared React dependencies only
|
|
465
|
+
// when the resolution probe proves an older installation needs repair.
|
|
466
466
|
expect(updateDocument).not.toContain("miniflare@");
|
|
467
467
|
const installs = updateDocument
|
|
468
468
|
.split("\n")
|
|
469
469
|
.filter((line) => line.includes("npm install"));
|
|
470
470
|
expect(installs).toEqual([
|
|
471
471
|
` if npm install --prefix ${APPLETS_ROOT} --no-audit --no-fund @frockbot/applet-sdk@${APPLET_SDK_VERSION}; then`,
|
|
472
|
+
` npm install --prefix ${APPLETS_ROOT} --no-audit --no-fund react@19.2.8 react-dom@19.2.8 @types/react@19.2.18 @types/react-dom@19.2.4 || true`,
|
|
472
473
|
]);
|
|
473
474
|
expect(updateDocument).toContain(
|
|
474
475
|
`if [ ! -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then`,
|
|
@@ -509,18 +510,70 @@ describe("the applets phase", () => {
|
|
|
509
510
|
expect(APPLETS_ROOT.startsWith(`${RUNTIME_ROOT}/`)).toBe(true);
|
|
510
511
|
});
|
|
511
512
|
|
|
512
|
-
test("
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
513
|
+
test("recreates shared Applet dependencies outside every durable source root", () => {
|
|
514
|
+
const provision = PROVISION_PHASES.find(
|
|
515
|
+
(phase) => phase.name === "applets",
|
|
516
|
+
)!.body;
|
|
517
|
+
const update = UPDATE_PHASES.find(
|
|
518
|
+
(phase) => phase.name === "applets",
|
|
519
|
+
)!.body;
|
|
520
|
+
|
|
521
|
+
expect(APPLETS_ROOT).toBe(`${RUNTIME_ROOT}/applets`);
|
|
522
|
+
expect(APPLETS_ROOT).not.toContain("/agent-data/");
|
|
523
|
+
expect(provision).toContain(appletSdkInstallScript);
|
|
524
|
+
expect(update).toContain(appletSdkInstallScript);
|
|
525
|
+
expect(appletSdkInstallScript).toContain(
|
|
526
|
+
`npm install --prefix ${APPLETS_ROOT}`,
|
|
527
|
+
);
|
|
528
|
+
expect(appletSdkInstallScript).not.toContain("user-packages");
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("repairs old SDK installs and verifies every shared build import", () => {
|
|
532
|
+
const fallback =
|
|
533
|
+
`npm install --prefix ${APPLETS_ROOT} --no-audit --no-fund ` +
|
|
534
|
+
"react@19.2.8 react-dom@19.2.8 @types/react@19.2.18 @types/react-dom@19.2.4";
|
|
535
|
+
const fallbackProbe = `cd ${APPLETS_ROOT} && node -e "require.resolve('react-dom/client')"`;
|
|
536
|
+
|
|
537
|
+
expect(appletSdkInstallScript).toContain(fallback);
|
|
538
|
+
expect(appletSdkInstallScript).toContain(fallbackProbe);
|
|
539
|
+
expect(appletSdkInstallScript).toContain(
|
|
540
|
+
`SDK_RESOLUTION_ERROR=$(cd ${APPLETS_ROOT} && node -e`,
|
|
541
|
+
);
|
|
542
|
+
for (const specifier of [
|
|
543
|
+
"react-dom/client",
|
|
544
|
+
"react",
|
|
545
|
+
"@frockbot/applet-sdk/client",
|
|
546
|
+
]) {
|
|
547
|
+
expect(appletSdkInstallScript).toContain(`'${specifier}'`);
|
|
548
|
+
}
|
|
549
|
+
expect(appletSdkInstallScript.indexOf(fallbackProbe)).toBeLessThan(
|
|
550
|
+
appletSdkInstallScript.indexOf(fallback),
|
|
551
|
+
);
|
|
552
|
+
expect(appletSdkInstallScript.indexOf(fallback)).toBeLessThan(
|
|
553
|
+
appletSdkInstallScript.indexOf("SDK_RESOLUTION_ERROR=$(cd"),
|
|
554
|
+
);
|
|
555
|
+
expect(appletSdkInstallScript).toContain(`> ${APPLETS_SDK_FAILURE_PATH}`);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test("an SDK that cannot be fetched or resolved leaves a record and not a failed run", () => {
|
|
559
|
+
// A Computer whose SDK could not be installed or resolved still browses,
|
|
560
|
+
// execs, and syncs, so the phase records the failure for the doctor rather
|
|
561
|
+
// than failing provisioning.
|
|
516
562
|
const applets = PROVISION_PHASES.find((phase) => phase.name === "applets")!;
|
|
517
563
|
expect(applets.body).toContain(`> ${APPLETS_SDK_FAILURE_PATH}`);
|
|
518
564
|
expect(applets.body).toContain(`rm -f ${APPLETS_SDK_FAILURE_PATH}`);
|
|
519
565
|
expect(boxDoctorScript).toContain("record applets-sdk fail");
|
|
520
566
|
expect(boxDoctorScript).toContain("record applets-sdk pass");
|
|
567
|
+
expect(
|
|
568
|
+
boxDoctorScript.indexOf(`[ -f ${APPLETS_SDK_FAILURE_PATH} ]`),
|
|
569
|
+
).toBeLessThan(
|
|
570
|
+
boxDoctorScript.indexOf(
|
|
571
|
+
`[ -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]`,
|
|
572
|
+
),
|
|
573
|
+
);
|
|
521
574
|
});
|
|
522
575
|
|
|
523
|
-
test("runs again rather than once
|
|
576
|
+
test("runs again rather than once so old SDK installs can be repaired", () => {
|
|
524
577
|
const applets = PROVISION_PHASES.find((phase) => phase.name === "applets")!;
|
|
525
578
|
expect(applets.always).toBe(true);
|
|
526
579
|
expect(provisionScript).not.toContain('[ ! -f "$MARKERS/applets"');
|
package/src/runtime.ts
CHANGED
|
@@ -1350,10 +1350,14 @@ ordinary file tools, then use \`applet\` — it is on your PATH:
|
|
|
1350
1350
|
| \`applet dev\` | run it locally on this Computer and print a URL to open in the browser |
|
|
1351
1351
|
|
|
1352
1352
|
The SDK and its runtime live under \`${APPLETS_ROOT}\`, installed when this
|
|
1353
|
-
Computer was provisioned
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1353
|
+
Computer was provisioned and repaired by a runtime update when missing. Applet
|
|
1354
|
+
projects have no durable \`node_modules\`: dependency, cache, VCS, and ordinary
|
|
1355
|
+
build directories are excluded from Workspace sync, and the SDK resolves
|
|
1356
|
+
imports from this shared installation. The three files a publish explicitly
|
|
1357
|
+
requests from \`dist/\` are the only build-output exception. An Applet **never
|
|
1358
|
+
runs for real here**: \`applet dev\` is a preview, and publishing hands the
|
|
1359
|
+
built artifact to the kernel, which runs it. If \`applet\` says the SDK is not
|
|
1360
|
+
installed, run the self-check and read the \`applets-sdk\` line.
|
|
1357
1361
|
`,
|
|
1358
1362
|
},
|
|
1359
1363
|
{
|
|
@@ -1643,19 +1647,22 @@ exec "$APPLET" "$@"
|
|
|
1643
1647
|
`;
|
|
1644
1648
|
|
|
1645
1649
|
/**
|
|
1646
|
-
* Installs the Applets SDK when
|
|
1650
|
+
* Installs the Applets SDK when it is absent and verifies its shared imports.
|
|
1647
1651
|
*
|
|
1648
1652
|
* Guarded, and deliberately not fatal: a Computer that cannot fetch the SDK
|
|
1649
1653
|
* is still a Computer — it browses, execs, and syncs — so the failure is
|
|
1650
1654
|
* recorded as a file the doctor reports under `applets-sdk` rather than as a
|
|
1651
1655
|
* phase that fails and leaves the whole run resumable-but-unfinished.
|
|
1652
1656
|
*
|
|
1653
|
-
* The
|
|
1654
|
-
*
|
|
1655
|
-
* and
|
|
1656
|
-
*
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1657
|
+
* The React fallback repairs SDK releases published before those build-time
|
|
1658
|
+
* dependencies moved from development/peer metadata into dependencies. It is
|
|
1659
|
+
* idempotent and runs only when Node cannot resolve `react-dom/client` from
|
|
1660
|
+
* the shared prefix. The final probe covers React, React DOM, and the SDK's
|
|
1661
|
+
* client entry; its concise failure is durable input to box-doctor.
|
|
1662
|
+
*
|
|
1663
|
+
* Changing this script changes the runtime document digest, which is the
|
|
1664
|
+
* version marker that causes every existing Computer to run UPDATE_PHASES on
|
|
1665
|
+
* its next open.
|
|
1659
1666
|
*/
|
|
1660
1667
|
export const appletSdkInstallScript = `if [ ! -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then
|
|
1661
1668
|
if npm install --prefix ${APPLETS_ROOT} --no-audit --no-fund @frockbot/applet-sdk@${APPLET_SDK_VERSION}; then
|
|
@@ -1663,6 +1670,16 @@ export const appletSdkInstallScript = `if [ ! -d ${APPLETS_ROOT}/node_modules/@f
|
|
|
1663
1670
|
else
|
|
1664
1671
|
printf '%s\\n' "npm could not install @frockbot/applet-sdk@${APPLET_SDK_VERSION}" > ${APPLETS_SDK_FAILURE_PATH}
|
|
1665
1672
|
fi
|
|
1673
|
+
fi
|
|
1674
|
+
if [ -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then
|
|
1675
|
+
if ! (cd ${APPLETS_ROOT} && node -e "require.resolve('react-dom/client')") >/dev/null 2>&1; then
|
|
1676
|
+
npm install --prefix ${APPLETS_ROOT} --no-audit --no-fund react@19.2.8 react-dom@19.2.8 @types/react@19.2.18 @types/react-dom@19.2.4 || true
|
|
1677
|
+
fi
|
|
1678
|
+
if SDK_RESOLUTION_ERROR=$(cd ${APPLETS_ROOT} && node -e "for (const id of ['react-dom/client', 'react', '@frockbot/applet-sdk/client']) { try { require.resolve(id) } catch { console.error('could not resolve ' + id); process.exitCode = 1 } }" 2>&1); then
|
|
1679
|
+
rm -f ${APPLETS_SDK_FAILURE_PATH}
|
|
1680
|
+
else
|
|
1681
|
+
printf '%s\\n' "Applets SDK dependency resolution failed: $SDK_RESOLUTION_ERROR" > ${APPLETS_SDK_FAILURE_PATH}
|
|
1682
|
+
fi
|
|
1666
1683
|
fi`;
|
|
1667
1684
|
|
|
1668
1685
|
/** The files the `applets` phase installs, with their modes. */
|
|
@@ -1883,13 +1900,12 @@ elif [ -x ${APPLET_SHIM_PATH} ]; then
|
|
|
1883
1900
|
else
|
|
1884
1901
|
record applets fail "no ${APPLET_SHIM_PATH}; provisioning installs it, and no Applet can be checked, built, or previewed without it"
|
|
1885
1902
|
fi
|
|
1886
|
-
# Named and non-fatal on purpose:
|
|
1887
|
-
#
|
|
1888
|
-
|
|
1889
|
-
if [ -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then
|
|
1890
|
-
record applets-sdk pass "the Applets SDK is installed under ${APPLETS_ROOT}"
|
|
1891
|
-
elif [ -f ${APPLETS_SDK_FAILURE_PATH} ]; then
|
|
1903
|
+
# Named and non-fatal on purpose: an install or dependency-resolution failure
|
|
1904
|
+
# leaves this file rather than failing the whole Computer run.
|
|
1905
|
+
if [ -f ${APPLETS_SDK_FAILURE_PATH} ]; then
|
|
1892
1906
|
record applets-sdk fail "$(head -n 1 ${APPLETS_SDK_FAILURE_PATH} 2>/dev/null); everything else on this Computer is unaffected"
|
|
1907
|
+
elif [ -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then
|
|
1908
|
+
record applets-sdk pass "the Applets SDK and its shared imports resolve under ${APPLETS_ROOT}"
|
|
1893
1909
|
else
|
|
1894
1910
|
record applets-sdk fail "no Applets SDK under ${APPLETS_ROOT} and no record of an attempt; it installs when this Computer is next provisioned"
|
|
1895
1911
|
fi
|