@frockbot/computer-host-runtime 0.3.17 → 0.3.19

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/computer-host-runtime",
3
- "version": "0.3.17",
3
+ "version": "0.3.19",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -448,7 +448,7 @@ state running`);
448
448
  );
449
449
  });
450
450
 
451
- test("the update runner replaces files and installs nothing", () => {
451
+ test("the update runner replaces files and only repairs Applet dependencies", () => {
452
452
  expect(UPDATE_PHASES.map((phase) => phase.name)).toEqual([
453
453
  "runtime",
454
454
  "applets",
@@ -460,16 +460,16 @@ state running`);
460
460
  const updateDocument = UPDATE_PHASES.map((phase) => phase.body).join("\n");
461
461
  expect(updateDocument).not.toContain("apt-get");
462
462
  expect(updateDocument).not.toContain("playwright-core/cli.js install");
463
- // An in-place update swaps names over files it owns and leaves the SDK's
464
- // dependency tree which an `applet dev` may be running out of — alone.
465
- // The one install it carries is the SDK itself, and only while the SDK
466
- // 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.
467
466
  expect(updateDocument).not.toContain("miniflare@");
468
467
  const installs = updateDocument
469
468
  .split("\n")
470
469
  .filter((line) => line.includes("npm install"));
471
470
  expect(installs).toEqual([
472
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`,
473
473
  ]);
474
474
  expect(updateDocument).toContain(
475
475
  `if [ ! -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then`,
@@ -528,18 +528,52 @@ describe("the applets phase", () => {
528
528
  expect(appletSdkInstallScript).not.toContain("user-packages");
529
529
  });
530
530
 
531
- test("an SDK that cannot be fetched leaves a record and not a failed run", () => {
532
- // `@frockbot/applet-sdk` is not on npm yet. A Computer whose SDK could not
533
- // be installed still browses, execs, and syncs, so the phase records the
534
- // failure for the doctor rather than failing provisioning.
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.
535
562
  const applets = PROVISION_PHASES.find((phase) => phase.name === "applets")!;
536
563
  expect(applets.body).toContain(`> ${APPLETS_SDK_FAILURE_PATH}`);
537
564
  expect(applets.body).toContain(`rm -f ${APPLETS_SDK_FAILURE_PATH}`);
538
565
  expect(boxDoctorScript).toContain("record applets-sdk fail");
539
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
+ );
540
574
  });
541
575
 
542
- test("runs again rather than once, because the SDK has not shipped", () => {
576
+ test("runs again rather than once so old SDK installs can be repaired", () => {
543
577
  const applets = PROVISION_PHASES.find((phase) => phase.name === "applets")!;
544
578
  expect(applets.always).toBe(true);
545
579
  expect(provisionScript).not.toContain('[ ! -f "$MARKERS/applets"');
package/src/runtime.ts CHANGED
@@ -1647,19 +1647,22 @@ exec "$APPLET" "$@"
1647
1647
  `;
1648
1648
 
1649
1649
  /**
1650
- * Installs the Applets SDK when, and only when, it is absent.
1650
+ * Installs the Applets SDK when it is absent and verifies its shared imports.
1651
1651
  *
1652
1652
  * Guarded, and deliberately not fatal: a Computer that cannot fetch the SDK
1653
1653
  * is still a Computer — it browses, execs, and syncs — so the failure is
1654
1654
  * recorded as a file the doctor reports under `applets-sdk` rather than as a
1655
1655
  * phase that fails and leaves the whole run resumable-but-unfinished.
1656
1656
  *
1657
- * The `[ ! -d ]` guard is also what makes this safe inside an in-place
1658
- * update. The only thing that runs out of `${APPLETS_ROOT}` is `applet dev`,
1659
- * and the shim refuses to start it while the SDK is missing, so an install
1660
- * that runs only when the SDK is missing never touches a tree a process is
1661
- * using. Once the SDK is present neither provisioning nor an update installs
1662
- * again; a newer SDK arrives with the next provisioning adoption.
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.
1663
1666
  */
1664
1667
  export const appletSdkInstallScript = `if [ ! -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then
1665
1668
  if npm install --prefix ${APPLETS_ROOT} --no-audit --no-fund @frockbot/applet-sdk@${APPLET_SDK_VERSION}; then
@@ -1667,6 +1670,16 @@ export const appletSdkInstallScript = `if [ ! -d ${APPLETS_ROOT}/node_modules/@f
1667
1670
  else
1668
1671
  printf '%s\\n' "npm could not install @frockbot/applet-sdk@${APPLET_SDK_VERSION}" > ${APPLETS_SDK_FAILURE_PATH}
1669
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
1670
1683
  fi`;
1671
1684
 
1672
1685
  /** The files the `applets` phase installs, with their modes. */
@@ -1887,13 +1900,12 @@ elif [ -x ${APPLET_SHIM_PATH} ]; then
1887
1900
  else
1888
1901
  record applets fail "no ${APPLET_SHIM_PATH}; provisioning installs it, and no Applet can be checked, built, or previewed without it"
1889
1902
  fi
1890
- # Named and non-fatal on purpose: \`@frockbot/applet-sdk\` is not published
1891
- # yet, so the provisioning phase's guarded install is expected to fail and
1892
- # leave this file rather than fail the whole run. This is where that shows up.
1893
- if [ -d ${APPLETS_ROOT}/node_modules/@frockbot/applet-sdk ]; then
1894
- record applets-sdk pass "the Applets SDK is installed under ${APPLETS_ROOT}"
1895
- 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
1896
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}"
1897
1909
  else
1898
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"
1899
1911
  fi