diff --git a/examples/remix/vite.config.ts b/examples/remix/vite.config.ts index 8865eb1c..414163b8 100644 --- a/examples/remix/vite.config.ts +++ b/examples/remix/vite.config.ts @@ -9,5 +9,16 @@ export default defineConfig({ plugins: [remix(), tsconfigPaths()], server: { allowedHosts: true, - } + }, + optimizeDeps: { + // Remix discovers these client-side imports one-by-one during the first + // hydration; each discovery re-runs the dep optimizer, and the page ends up + // with chunks from mixed optimizer generations — two React copies and an + // "Invalid hook call" hydration crash. Prebundle them in the first pass. + include: [ + "handsontable/plugins", + "handsontable/cellTypes", + "@handsontable/react-wrapper", + ], + }, }); diff --git a/runner/apps/authoring/src/App.tsx b/runner/apps/authoring/src/App.tsx index d06a9eab..0a4aaebe 100644 --- a/runner/apps/authoring/src/App.tsx +++ b/runner/apps/authoring/src/App.tsx @@ -49,7 +49,7 @@ function docsPageUrl(framework: string, permalink: string): string { function describeRuntimeError(e: unknown, engine: string): string { const msg = e instanceof Error ? e.message : String(e); if (engine === "container" && /failed to fetch|networkerror|load failed|session start failed|fetch/i.test(msg)) { - return "Vue and Angular examples run on the container engine, which needs the demo server (Cloudflare Sandbox). It isn't reachable here — run the local API worker (requires Docker) or open this example on the deployed demos.handsontable.com."; + return "This example runs on the container engine, which needs the demo server (Cloudflare Sandbox). It isn't reachable here — run the local API worker (requires Docker) or open this example on the deployed demos.handsontable.com."; } return msg; } diff --git a/runner/config/frameworks.json b/runner/config/frameworks.json index c3d2d39a..4f7e9c0b 100644 --- a/runner/config/frameworks.json +++ b/runner/config/frameworks.json @@ -14,7 +14,7 @@ "buildCommand": "tsc && vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "javascript": { "tier": 1, @@ -28,7 +28,7 @@ "buildCommand": "vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "typescript": { "tier": 1, @@ -42,7 +42,7 @@ "buildCommand": "tsc && vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "react": { "tier": 1, @@ -56,7 +56,7 @@ "buildCommand": "tsc -b && vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "react-js": { "tier": 1, @@ -71,7 +71,7 @@ "buildCommand": "vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "ant-design": { "tier": 1, @@ -86,7 +86,7 @@ "buildCommand": "vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "mui": { "tier": 1, @@ -101,7 +101,7 @@ "buildCommand": "tsc -b && vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "base-web": { "tier": 1, @@ -116,7 +116,7 @@ "buildCommand": "tsc -b && vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "vue": { "tier": 1, @@ -130,7 +130,7 @@ "buildCommand": "vite build", "outputDir": "dist", "port": null, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "angular": { "tier": 2, @@ -139,12 +139,12 @@ "htWrappers": ["@handsontable/angular-wrapper"], "entry": "src/main.ts", "htmlEntry": "src/index.html", - "devCommand": "npm run start -- --host 0.0.0.0 --disable-host-check --port 3000", + "devCommand": "pnpm exec ng serve --host 0.0.0.0 --disable-host-check --port 3000", "buildCommand": "ng build", "outputDir": "dist", "outputGlob": "dist/*/browser", "port": 3000, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "next.js": { "tier": 2, @@ -157,7 +157,7 @@ "outputDir": "out", "staticExport": true, "port": 3000, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "next-shadcn.js": { "tier": 2, @@ -170,7 +170,7 @@ "outputDir": "out", "staticExport": true, "port": 3000, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "astro": { "tier": 2, @@ -182,7 +182,7 @@ "buildCommand": "astro build", "outputDir": "dist", "port": 4321, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "nuxt": { "tier": 2, @@ -190,11 +190,11 @@ "container": "nuxt", "htWrappers": ["@handsontable/vue3"], "entry": "app.vue", - "devCommand": "nuxt dev -H 0.0.0.0 -p 3000", + "devCommand": "nuxt dev --host 0.0.0.0 --port 3000", "buildCommand": "nuxt generate", "outputDir": ".output/public", "port": 3000, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" }, "remix": { "tier": 2, @@ -207,7 +207,7 @@ "outputDir": "build/client", "spaMode": true, "port": 5173, - "installCommand": "npm install" + "installCommand": "pnpm install --frozen-lockfile" } } } diff --git a/runner/containers/angular/Dockerfile b/runner/containers/angular/Dockerfile deleted file mode 100644 index cdb44163..00000000 --- a/runner/containers/angular/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by scripts/prepare-container.mjs — do not edit by hand. -# Tier-2 base image with dependencies baked for a fast warm dev server. -# Per-session source files are written into /app at runtime by the Worker. -FROM docker.io/cloudflare/sandbox:0.12.3 - -WORKDIR /app - -COPY package.json ./ -RUN npm install --legacy-peer-deps --no-audit --no-fund --loglevel=error - -EXPOSE 4200 diff --git a/runner/containers/angular/package.json b/runner/containers/angular/package.json deleted file mode 100644 index 19662e85..00000000 --- a/runner/containers/angular/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "sandbox", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve --disable-host-check --port 3000", - "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" - }, - "private": true, - "dependencies": { - "@angular/animations": "^21.1.0", - "@angular/common": "^21.1.0", - "@angular/compiler": "^21.1.0", - "@angular/core": "^21.1.0", - "@angular/forms": "^21.1.0", - "@angular/platform-browser": "^21.1.0", - "@angular/platform-browser-dynamic": "^21.1.0", - "@angular/router": "^21.1.0", - "@handsontable/angular-wrapper": "latest", - "handsontable": "latest", - "rxjs": "~7.8.0", - "tslib": "^2.6.2", - "zone.js": "~0.15.0" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^21.1.0", - "@angular/cli": "^21.1.0", - "@angular/compiler-cli": "^21.1.0", - "@types/jasmine": "3.6.9", - "@types/node": "12.20.7", - "typescript": "~5.9.0" - }, - "config": { - "codesandbox": { - "templateId": "chnr97" - } - } -} diff --git a/runner/containers/astro/Dockerfile b/runner/containers/astro/Dockerfile deleted file mode 100644 index cdd36e95..00000000 --- a/runner/containers/astro/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by scripts/prepare-container.mjs — do not edit by hand. -# Tier-2 base image with dependencies baked for a fast warm dev server. -# Per-session source files are written into /app at runtime by the Worker. -FROM docker.io/cloudflare/sandbox:0.12.3 - -WORKDIR /app - -COPY package.json ./ -RUN npm install --no-audit --no-fund --loglevel=error - -EXPOSE 4321 diff --git a/runner/containers/astro/package.json b/runner/containers/astro/package.json deleted file mode 100644 index 2a5f49b7..00000000 --- a/runner/containers/astro/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@example/basics", - "type": "module", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "astro dev --port 8080", - "start": "astro dev", - "build": "astro build", - "preview": "astro preview", - "astro": "astro" - }, - "dependencies": { - "astro": "^4.16.19", - "handsontable": "18.0.0" - }, - "config": { - "codesandbox": { - "templateId": "438vfj" - }, - "stackblitz": { - "template": "node" - } - } -} diff --git a/runner/containers/next-shadcn/Dockerfile b/runner/containers/next-shadcn/Dockerfile deleted file mode 100644 index 09994b26..00000000 --- a/runner/containers/next-shadcn/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by scripts/prepare-container.mjs — do not edit by hand. -# Tier-2 base image with dependencies baked for a fast warm dev server. -# Per-session source files are written into /app at runtime by the Worker. -FROM docker.io/cloudflare/sandbox:0.12.3 - -WORKDIR /app - -COPY package.json ./ -RUN npm install --no-audit --no-fund --loglevel=error - -EXPOSE 3001 diff --git a/runner/containers/next-shadcn/package.json b/runner/containers/next-shadcn/package.json deleted file mode 100644 index e0bb2fb7..00000000 --- a/runner/containers/next-shadcn/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "my-app", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "eslint" - }, - "dependencies": { - "@handsontable/react-wrapper": "17.0.1", - "handsontable": "17.0.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "lucide-react": "^0.563.0", - "next": "16.1.6", - "radix-ui": "^1.4.3", - "react": "19.2.3", - "react-dom": "19.2.3", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^4", - "@tailwindcss/postcss": "^4", - "tw-animate-css": "^1.4.0" - }, - "devDependencies": { - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "^9", - "eslint-config-next": "16.1.6", - "shadcn": "^3.8.4", - "typescript": "^5" - }, - "config": { - "codesandbox": { - "templateId": "8y5qy5" - } - } -} diff --git a/runner/containers/next/Dockerfile b/runner/containers/next/Dockerfile deleted file mode 100644 index 09994b26..00000000 --- a/runner/containers/next/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by scripts/prepare-container.mjs — do not edit by hand. -# Tier-2 base image with dependencies baked for a fast warm dev server. -# Per-session source files are written into /app at runtime by the Worker. -FROM docker.io/cloudflare/sandbox:0.12.3 - -WORKDIR /app - -COPY package.json ./ -RUN npm install --no-audit --no-fund --loglevel=error - -EXPOSE 3001 diff --git a/runner/containers/next/package.json b/runner/containers/next/package.json deleted file mode 100644 index bbf73327..00000000 --- a/runner/containers/next/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "nextjs", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@handsontable/react-wrapper": "17.0.1", - "handsontable": "17.0.1", - "@types/node": "20.6.2", - "@types/react": "18.2.22", - "@types/react-dom": "18.2.7", - "autoprefixer": "10.4.15", - "eslint": "8.49.0", - "eslint-config-next": "13.5.1", - "next": "13.5.1", - "postcss": "8.4.30", - "react": "18.2.0", - "react-dom": "18.2.0", - "tailwindcss": "3.3.3", - "typescript": "5.2.2" - }, - "config": { - "codesandbox": { - "templateId": "5l26fv" - }, - "stackblitz": { - "template": "node" - } - } -} diff --git a/runner/containers/nuxt/Dockerfile b/runner/containers/nuxt/Dockerfile deleted file mode 100644 index 09994b26..00000000 --- a/runner/containers/nuxt/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by scripts/prepare-container.mjs — do not edit by hand. -# Tier-2 base image with dependencies baked for a fast warm dev server. -# Per-session source files are written into /app at runtime by the Worker. -FROM docker.io/cloudflare/sandbox:0.12.3 - -WORKDIR /app - -COPY package.json ./ -RUN npm install --no-audit --no-fund --loglevel=error - -EXPOSE 3001 diff --git a/runner/containers/nuxt/package.json b/runner/containers/nuxt/package.json deleted file mode 100644 index d894549f..00000000 --- a/runner/containers/nuxt/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "private": true, - "type": "module", - "scripts": { - "build": "nuxt build", - "dev": "HOST=0.0.0.0 PORT=8080 nuxt dev", - "generate": "nuxt generate", - "preview": "nuxt preview", - "postinstall": "nuxt prepare" - }, - "dependencies": { - "@handsontable/vue3": "18.0.0", - "handsontable": "18.0.0", - "nuxt": "^3.21.8" - }, - "config": { - "codesandbox": { - "templateId": "nj3gp2" - } - } -} diff --git a/runner/containers/remix/Dockerfile b/runner/containers/remix/Dockerfile deleted file mode 100644 index 6acedfdd..00000000 --- a/runner/containers/remix/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by scripts/prepare-container.mjs — do not edit by hand. -# Tier-2 base image with dependencies baked for a fast warm dev server. -# Per-session source files are written into /app at runtime by the Worker. -FROM docker.io/cloudflare/sandbox:0.12.3 - -WORKDIR /app - -COPY package.json ./ -RUN npm install --no-audit --no-fund --loglevel=error - -EXPOSE 5173 diff --git a/runner/containers/remix/package.json b/runner/containers/remix/package.json deleted file mode 100644 index 2f078e98..00000000 --- a/runner/containers/remix/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "build": "remix vite:build", - "dev": "remix vite:dev", - "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", - "start": "remix-serve ./build/server/index.js", - "typecheck": "tsc" - }, - "dependencies": { - "@remix-run/node": "^2.17.5", - "@remix-run/react": "^2.17.5", - "@remix-run/serve": "^2.17.5", - "isbot": "^4.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "@handsontable/react-wrapper": "^17.1.0", - "handsontable": "^17.1.0" - }, - "devDependencies": { - "@remix-run/dev": "*", - "@types/react": "^18.2.20", - "@types/react-dom": "^18.2.7", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", - "eslint": "^8.38.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "typescript": "^5.1.6", - "vite": "^5.4.21", - "vite-tsconfig-paths": "^4.2.1" - }, - "engines": { - "node": ">=18.0.0" - }, - "config": { - "codesandbox": { - "templateId": "m4k8vw" - }, - "stackblitz": { - "template": "node" - } - } -} diff --git a/runner/docs/adr/0020-routes-in-deploy-command-not-config.md b/runner/docs/adr/0020-routes-in-deploy-command-not-config.md new file mode 100644 index 00000000..e6558460 --- /dev/null +++ b/runner/docs/adr/0020-routes-in-deploy-command-not-config.md @@ -0,0 +1,37 @@ +# ADR-0020: Worker routes live in the deploy command, not wrangler.jsonc + +**Status:** Accepted + +## Context +When `wrangler.jsonc` declares `routes`, `wrangler dev` simulates the first +route's host on every request: `request.url` and `Host` become +`demos.handsontable.com` no matter what hostname the request actually targeted; +only the pathname passes through. This destroys the +`--.*` preview hostnames that the Sandbox SDK's +`proxyToSandbox()` routes by, so Tier-2 local preview can never work while +routes are in the config — no `PREVIEW_HOST` value helps. Wrangler has no +config key or CLI flag to disable the simulation (`inferOriginFromRoutes` is +only settable via the programmatic `startWorker` API); `--host` merely +simulates a different fixed host. This corrects ADR-0011's claim that local +`wrangler dev` "works via `*.localhost`" — that was true only before the +routes block was added. An alternative (path-based `/__preview//` +proxying with Referer-based asset routing) was rejected: it would exercise a +different code path locally than in production and likely break HMR, since +WebSocket upgrades don't reliably carry `Referer`. + +## Decision +Declare no `routes` in `wrangler.jsonc`. Attach the production routes at +deploy time via `wrangler deploy --routes …` in the api worker's `deploy` +script (package.json), keeping the patterns versioned. Local dev sets +`PREVIEW_HOST="localhost:8787"` in `.dev.vars` (a real value — wrangler +silently ignores empty-string overrides). + +## Consequences +- `wrangler dev` passes the real `Host` through, so `*.localhost:8787` preview + URLs flow through the same `proxyToSandbox()` path as production — verified: + HTML, assets, HMR WebSocket, live file edits, concurrent sessions. +- `--routes` cannot carry `zone_name`; wrangler infers the zone on real + deploys (errors loudly if it can't). Unverified as of this ADR — watch the + first deploy; fallback is dashboard-managed routes. +- Routes must never move back into `wrangler.jsonc`; doing so silently breaks + local Tier-2 preview for every container-engine framework. diff --git a/runner/docs/adr/README.md b/runner/docs/adr/README.md index 356285fe..8ed656e4 100644 --- a/runner/docs/adr/README.md +++ b/runner/docs/adr/README.md @@ -16,7 +16,7 @@ once Accepted. | [0008](0008-per-user-demos-and-fork-flow.md) | Per-user demos + fork flow | Accepted | | [0009](0009-docs-only-embed.md) | Docs-only embeddable URL (frame-ancestors) | Accepted | | [0010](0010-deploy-to-main-handsontable-account.md) | Deploy to the main Handsontable Cloudflare account | Accepted | -| [0011](0011-tier2-preview-urls-need-wildcard-domain.md) | Tier-2 preview URLs need a wildcard custom domain | Accepted | +| [0011](0011-tier2-preview-urls-need-wildcard-domain.md) | Tier-2 preview URLs need a wildcard custom domain | Accepted (local-dev claim corrected by 0020) | | [0012](0012-one-image-and-do-class-per-framework.md) | One container image + DO class per framework | Accepted | | [0013](0013-sandpack-classic-bundler.md) | Sandpack classic in-browser bundler for Tier 1 | Accepted | | [0014](0014-storage-d1-r2-kv.md) | Storage split: D1 + R2 + KV | Accepted | @@ -25,3 +25,4 @@ once Accepted. | [0017](0017-keep-render-ms-until-replaced.md) | Keep render-ms running; compat shim; remove later | Superseded by 0019 | | [0018](0018-no-ai-attribution.md) | No AI attribution in history or code | Accepted | | [0019](0019-docs-guide-examples-and-render-ms-removal.md) | Documentation-guide examples in the runner; remove render-ms | Accepted | +| [0020](0020-routes-in-deploy-command-not-config.md) | Worker routes live in the deploy command, not wrangler.jsonc | Accepted | diff --git a/runner/docs/run-and-deploy.md b/runner/docs/run-and-deploy.md index d78e89c1..21b8dedf 100644 --- a/runner/docs/run-and-deploy.md +++ b/runner/docs/run-and-deploy.md @@ -25,7 +25,7 @@ pnpm --filter @handsontable/demo-runtime build pnpm --filter @handsontable/demo-authoring dev # http://localhost:5173 # Full stack (Tier-2 containers + sharing): needs Docker. -cd workers/api && printf 'DEV_AUTH_EMAIL="dev@handsontable.com"\n' > .dev.vars +cd workers/api && printf 'DEV_AUTH_EMAIL="dev@handsontable.com"\nPREVIEW_HOST="localhost:8787"\n' > .dev.vars npx wrangler d1 execute handsontable-demos --local --file=migrations/0001_init.sql -y npx wrangler d1 execute handsontable-demos --local --file=migrations/0002_buildkey_nonunique.sql -y npx wrangler dev --port 8787 # builds the container images @@ -36,6 +36,19 @@ npx vite --port 5173 ``` `.dev.vars` and `.env.local` are gitignored dev-only bypasses — never used in prod. +`PREVIEW_HOST="localhost:8787"` overrides the `wrangler.jsonc` default +(`demos.handsontable.com`, a real public wildcard that routes to the *deployed* +worker) so container preview URLs come out as `*.localhost:8787`, which browsers +treat as `127.0.0.1` (RFC 6761) and reach your local `wrangler dev`. It must be a +real host value — wrangler silently ignores empty-string `.dev.vars` overrides. +Without it, Tier-2/container sessions boot fine but the preview iframe fails with +`INVALID_TOKEN` — the token is only known to your local session, not to prod. + +This only works because `wrangler.jsonc` declares **no `routes`**: when routes +are present, `wrangler dev` simulates the first route's host on every request, +destroying the preview subdomain before `proxyToSandbox()` can route on it. +That's why the production routes live in the `deploy` script instead — don't +move them back into `wrangler.jsonc`. ## Deploy (main Handsontable account) @@ -46,7 +59,7 @@ export CLOUDFLARE_ACCOUNT_ID=15111272c53ed0aaf84a908f0c9c7f8b cd workers/api npx wrangler d1 execute handsontable-demos --remote --file=migrations/0001_init.sql -y npx wrangler d1 execute handsontable-demos --remote --file=migrations/0002_buildkey_nonunique.sql -y -npx wrangler deploy +pnpm run deploy # wrangler deploy --routes … (attaches the demos.handsontable.com routes) # -> https://handsontable-demos-api.handsoncode.workers.dev # Authoring app (static SPA worker): @@ -61,11 +74,12 @@ npx wrangler deploy Container preview URLs need a wildcard custom domain (`*.workers.dev` won't work — ADR-0011): -1. Create `*.demos.handsontable.com` (proxied) → the `handsontable-demos-api` - Worker (Dashboard → Workers Routes / custom domain on the `handsontable.com` - zone). Requires DNS-edit permission on the zone. -2. Set the Worker var `PREVIEW_HOST=demos.handsontable.com` in `wrangler.jsonc` - and `wrangler deploy`. +1. Create the `*.demos.handsontable.com` DNS record (proxied) on the + `handsontable.com` zone. Requires DNS-edit permission on the zone. +2. Keep the Worker var `PREVIEW_HOST=demos.handsontable.com` in `wrangler.jsonc` + and run `pnpm run deploy` — the worker routes themselves are attached by the + deploy script's `--routes` flags (they are deliberately not in + `wrangler.jsonc`; see "Run locally" above). Static shares (`/d/:id`) and docs embeds (`/embed/:id`) do **not** need this. diff --git a/runner/packages/runtime/src/container.ts b/runner/packages/runtime/src/container.ts index b8de75a1..2f826427 100644 --- a/runner/packages/runtime/src/container.ts +++ b/runner/packages/runtime/src/container.ts @@ -11,6 +11,17 @@ import type { CatalogEntry, DemoRuntime, FilesMap, HandsontableVersionRef } from "./types.js"; import { applyHandsontableVersion } from "./version.js"; +/** The live-session API accepts only relative POSIX paths. */ +function relativeFiles(files: FilesMap): FilesMap { + return Object.fromEntries( + Object.entries(files).map(([path, contents]) => [relativePath(path), contents]), + ); +} + +function relativePath(path: string): string { + return path.startsWith("/") ? path.slice(1) : path; +} + export interface ContainerRuntimeOptions { /** The shell's preview-iframe slot; its src is set to the container preview URL. */ iframe: HTMLIFrameElement; @@ -85,7 +96,7 @@ export class ContainerRuntime implements DemoRuntime { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ framework: this.entry.framework, - files: this.files, + files: relativeFiles(this.files), sessionId: this.opts.sessionId, htVersion: this.opts.version?.ref, }), @@ -169,7 +180,7 @@ export class ContainerRuntime implements DemoRuntime { delete next[path]; this.files = next; void fetch( - `${this.opts.apiBase}/api/session/${this.sessionId}/file?path=${encodeURIComponent(path)}`, + `${this.opts.apiBase}/api/session/${this.sessionId}/file?path=${encodeURIComponent(relativePath(path))}`, { method: "DELETE" }, ).catch(() => {}); } @@ -195,7 +206,7 @@ export class ContainerRuntime implements DemoRuntime { await fetch(`${this.opts.apiBase}/api/session/${this.sessionId}/file`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ path, contents }), + body: JSON.stringify({ path: relativePath(path), contents }), }); } catch (e) { this.emitError(e instanceof Error ? e : new Error(String(e))); diff --git a/runner/pipeline/import-docs.mjs b/runner/pipeline/import-docs.mjs index 6a8d27bc..858ae4fa 100644 --- a/runner/pipeline/import-docs.mjs +++ b/runner/pipeline/import-docs.mjs @@ -99,14 +99,14 @@ const RUNNER = { sandpackTemplate: "vanilla", sandpackEnvironment: "parcel", container: null, htWrappers: [], entry: "/src/main.js", htmlEntry: "/index.html", devCommand: null, buildCommand: "vite build", outputDir: "dist", outputGlob: null, - staticExport: false, spaMode: false, port: null, installCommand: "npm install", + staticExport: false, spaMode: false, port: null, installCommand: "pnpm install", }, typescript: { framework: "typescript", displayName: "TypeScript", tier: 1, engine: "sandpack", sandpackTemplate: "vanilla-ts", sandpackEnvironment: "parcel", container: null, htWrappers: [], entry: "/src/main.ts", htmlEntry: "/index.html", devCommand: null, buildCommand: "vite build", outputDir: "dist", outputGlob: null, - staticExport: false, spaMode: false, port: null, installCommand: "npm install", + staticExport: false, spaMode: false, port: null, installCommand: "pnpm install", }, react: { framework: "react", displayName: "React (TS)", tier: 1, engine: "sandpack", @@ -114,7 +114,7 @@ const RUNNER = { container: null, htWrappers: ["@handsontable/react-wrapper"], entry: "/src/main.tsx", htmlEntry: "/index.html", devCommand: null, buildCommand: "vite build", outputDir: "dist", outputGlob: null, - staticExport: false, spaMode: false, port: null, installCommand: "npm install", + staticExport: false, spaMode: false, port: null, installCommand: "pnpm install", }, "react-js": { framework: "react-js", displayName: "React (JS)", tier: 1, engine: "sandpack", @@ -122,7 +122,7 @@ const RUNNER = { container: null, htWrappers: ["@handsontable/react-wrapper"], entry: "/src/main.jsx", htmlEntry: "/index.html", devCommand: null, buildCommand: "vite build", outputDir: "dist", outputGlob: null, - staticExport: false, spaMode: false, port: null, installCommand: "npm install", + staticExport: false, spaMode: false, port: null, installCommand: "pnpm install", }, // Vue 3 docs examples use `