From ec37902689bc2fdfa0337c13b9665d6733977fe1 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 18:39:28 +0100 Subject: [PATCH 1/3] refactor: rename packages/documents to packages/web packages/documents (the private, unpublished web UI) and packages/documents.js (a different, published package -- the conversion engine) were one character apart and easy to confuse. Renaming the web UI's own directory and npm package name to "web" removes that collision. Updates every reference to the old path and package name across the workspace: package.json (name/repository.directory/homepage), the README badge, ci.yml's deploy-site job, the root README's package table and turbo examples, knip.config.ts's workspace-scoped entry, turbo.json's five package-scoped task keys, SECURITY.md, document-compute.js's README, and the two issue templates' package dropdowns. pnpm-lock.yaml is regenerated via a real pnpm install rather than hand-edited; alongside the renamed importer key, that install also picked up picomatch and fdir refreshing to their current allowed versions -- an ordinary side effect of a full install, not something worth fighting to exclude from a freshly-regenerated lockfile. Also seeds a web@2.3.1 tag on the same commit documents@2.3.1 already tags: the release orchestrator finds a package's last release by its newest matching tag, and without this the very next release would see no prior web@ tag at all, compute a first release, and write 1.0.0 into packages/web/package.json -- a real version downgrade from today's 2.3.2 and a restarted changelog, even though nothing about the package's own release history actually changed. --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/workflows/ci.yml | 4 +- README.md | 8 +- SECURITY.md | 2 +- knip.config.ts | 2 +- packages/document-compute.js/README.md | 2 +- packages/{documents => web}/.gitignore | 0 packages/{documents => web}/AGENTS.md | 0 packages/{documents => web}/CHANGELOG.md | 0 packages/{documents => web}/CLAUDE.md | 0 packages/{documents => web}/LICENSE | 0 packages/{documents => web}/README.md | 6 +- packages/{documents => web}/eslint.config.ts | 0 packages/{documents => web}/index.html | 0 packages/{documents => web}/package.json | 6 +- .../public/apple-touch-icon.png | Bin .../{documents => web}/public/favicon.svg | 0 .../public/icons/icon-192.svg | 0 .../public/icons/icon-512.svg | 0 .../public/icons/maskable-512.svg | 0 .../workerDocumentConverter.ts | 0 .../adapters/fileAccess/createFileAccess.ts | 0 .../adapters/fileAccess/fallbackFileAccess.ts | 0 .../adapters/fileAccess/nativeFileAccess.ts | 0 packages/{documents => web}/src/app.tsx | 0 packages/{documents => web}/src/db/dexie.ts | 0 .../src/design-tokens.test.ts | 0 .../{documents => web}/src/design-tokens.ts | 0 .../src/hooks/useConversions.ts | 0 .../src/hooks/useConvert.ts | 0 .../{documents => web}/src/hooks/useFonts.ts | 0 .../src/hooks/useInspect.ts | 0 .../src/hooks/useMetadata.ts | 0 .../src/hooks/usePdfObjectUrl.ts | 0 .../src/hooks/useRecentFiles.ts | 0 packages/{documents => web}/src/main.tsx | 0 .../src/ports/fileAccess.ts | 0 .../{documents => web}/src/routeTree.gen.ts | 0 packages/{documents => web}/src/router.tsx | 0 .../src/routes/-Sidebar.css.ts | 0 .../src/routes/-Sidebar.tsx | 0 .../{documents => web}/src/routes/__root.tsx | 0 .../src/routes/convert.$source.$target.tsx | 0 .../src/routes/convert.index.tsx | 0 .../{documents => web}/src/routes/convert.tsx | 0 .../{documents => web}/src/routes/fonts.tsx | 0 .../{documents => web}/src/routes/index.tsx | 0 .../{documents => web}/src/routes/inspect.tsx | 0 .../src/routes/metadata.tsx | 0 .../{documents => web}/src/routes/recent.tsx | 0 packages/{documents => web}/src/rpc/client.ts | 0 packages/{documents => web}/src/rpc/router.ts | 0 .../src/shared/contentCounts.test.ts | 0 .../src/shared/contentCounts.ts | 0 .../src/shared/diagnostics.ts | 0 .../src/shared/extensionToFormat.ts | 0 .../src/shared/jsonTree.test.ts | 0 .../{documents => web}/src/shared/jsonTree.ts | 0 .../src/shared/relativeTime.ts | 0 .../src/shared/transferables.test.ts | 0 .../src/shared/transferables.ts | 0 packages/{documents => web}/src/theme.css.ts | 0 packages/{documents => web}/src/theme.ts | 0 .../src/ui/DiagnosticsPanel.tsx | 0 .../src/ui/FileUpload.css.ts | 0 .../{documents => web}/src/ui/FileUpload.tsx | 0 .../src/ui/FormulaPreview.css.ts | 0 .../src/ui/FormulaPreview.tsx | 0 .../src/ui/InspectPanel.tsx | 0 .../src/ui/MarkdownPreview.tsx | 0 .../src/ui/PdfPreview.css.ts | 0 .../{documents => web}/src/ui/PdfPreview.tsx | 0 .../src/ui/RecentFilesPanel.css.ts | 0 .../src/ui/RecentFilesPanel.tsx | 0 .../src/ui/SheetPreview.css.ts | 0 .../src/ui/SheetPreview.tsx | 0 .../src/ui/SlidesPreview.css.ts | 0 .../src/ui/SlidesPreview.tsx | 0 .../src/ui/StructureTree.css.ts | 0 .../src/ui/StructureTree.tsx | 0 .../src/ui/WordProcessingPreview.tsx | 0 .../src/ui/contentBlocks.css.ts | 0 .../src/ui/contentBlocks.tsx | 0 .../src/ui/convertLayout.css.ts | 0 packages/{documents => web}/src/ui/notify.ts | 0 .../src/ui/previewPanel.css.ts | 0 .../src/ui/reopenMailbox.ts | 0 packages/{documents => web}/src/vite-env.d.ts | 0 .../src/workers/documents.worker.ts | 0 packages/{documents => web}/tsconfig.json | 0 .../{documents => web}/tsconfig.node.json | 0 .../{documents => web}/tsconfig.worker.json | 0 packages/{documents => web}/vite.config.ts | 0 pnpm-lock.yaml | 282 +++++++++--------- turbo.json | 20 +- 96 files changed, 170 insertions(+), 166 deletions(-) rename packages/{documents => web}/.gitignore (100%) rename packages/{documents => web}/AGENTS.md (100%) rename packages/{documents => web}/CHANGELOG.md (100%) rename packages/{documents => web}/CLAUDE.md (100%) rename packages/{documents => web}/LICENSE (100%) rename packages/{documents => web}/README.md (89%) rename packages/{documents => web}/eslint.config.ts (100%) rename packages/{documents => web}/index.html (100%) rename packages/{documents => web}/package.json (97%) rename packages/{documents => web}/public/apple-touch-icon.png (100%) rename packages/{documents => web}/public/favicon.svg (100%) rename packages/{documents => web}/public/icons/icon-192.svg (100%) rename packages/{documents => web}/public/icons/icon-512.svg (100%) rename packages/{documents => web}/public/icons/maskable-512.svg (100%) rename packages/{documents => web}/src/adapters/documentConverter/workerDocumentConverter.ts (100%) rename packages/{documents => web}/src/adapters/fileAccess/createFileAccess.ts (100%) rename packages/{documents => web}/src/adapters/fileAccess/fallbackFileAccess.ts (100%) rename packages/{documents => web}/src/adapters/fileAccess/nativeFileAccess.ts (100%) rename packages/{documents => web}/src/app.tsx (100%) rename packages/{documents => web}/src/db/dexie.ts (100%) rename packages/{documents => web}/src/design-tokens.test.ts (100%) rename packages/{documents => web}/src/design-tokens.ts (100%) rename packages/{documents => web}/src/hooks/useConversions.ts (100%) rename packages/{documents => web}/src/hooks/useConvert.ts (100%) rename packages/{documents => web}/src/hooks/useFonts.ts (100%) rename packages/{documents => web}/src/hooks/useInspect.ts (100%) rename packages/{documents => web}/src/hooks/useMetadata.ts (100%) rename packages/{documents => web}/src/hooks/usePdfObjectUrl.ts (100%) rename packages/{documents => web}/src/hooks/useRecentFiles.ts (100%) rename packages/{documents => web}/src/main.tsx (100%) rename packages/{documents => web}/src/ports/fileAccess.ts (100%) rename packages/{documents => web}/src/routeTree.gen.ts (100%) rename packages/{documents => web}/src/router.tsx (100%) rename packages/{documents => web}/src/routes/-Sidebar.css.ts (100%) rename packages/{documents => web}/src/routes/-Sidebar.tsx (100%) rename packages/{documents => web}/src/routes/__root.tsx (100%) rename packages/{documents => web}/src/routes/convert.$source.$target.tsx (100%) rename packages/{documents => web}/src/routes/convert.index.tsx (100%) rename packages/{documents => web}/src/routes/convert.tsx (100%) rename packages/{documents => web}/src/routes/fonts.tsx (100%) rename packages/{documents => web}/src/routes/index.tsx (100%) rename packages/{documents => web}/src/routes/inspect.tsx (100%) rename packages/{documents => web}/src/routes/metadata.tsx (100%) rename packages/{documents => web}/src/routes/recent.tsx (100%) rename packages/{documents => web}/src/rpc/client.ts (100%) rename packages/{documents => web}/src/rpc/router.ts (100%) rename packages/{documents => web}/src/shared/contentCounts.test.ts (100%) rename packages/{documents => web}/src/shared/contentCounts.ts (100%) rename packages/{documents => web}/src/shared/diagnostics.ts (100%) rename packages/{documents => web}/src/shared/extensionToFormat.ts (100%) rename packages/{documents => web}/src/shared/jsonTree.test.ts (100%) rename packages/{documents => web}/src/shared/jsonTree.ts (100%) rename packages/{documents => web}/src/shared/relativeTime.ts (100%) rename packages/{documents => web}/src/shared/transferables.test.ts (100%) rename packages/{documents => web}/src/shared/transferables.ts (100%) rename packages/{documents => web}/src/theme.css.ts (100%) rename packages/{documents => web}/src/theme.ts (100%) rename packages/{documents => web}/src/ui/DiagnosticsPanel.tsx (100%) rename packages/{documents => web}/src/ui/FileUpload.css.ts (100%) rename packages/{documents => web}/src/ui/FileUpload.tsx (100%) rename packages/{documents => web}/src/ui/FormulaPreview.css.ts (100%) rename packages/{documents => web}/src/ui/FormulaPreview.tsx (100%) rename packages/{documents => web}/src/ui/InspectPanel.tsx (100%) rename packages/{documents => web}/src/ui/MarkdownPreview.tsx (100%) rename packages/{documents => web}/src/ui/PdfPreview.css.ts (100%) rename packages/{documents => web}/src/ui/PdfPreview.tsx (100%) rename packages/{documents => web}/src/ui/RecentFilesPanel.css.ts (100%) rename packages/{documents => web}/src/ui/RecentFilesPanel.tsx (100%) rename packages/{documents => web}/src/ui/SheetPreview.css.ts (100%) rename packages/{documents => web}/src/ui/SheetPreview.tsx (100%) rename packages/{documents => web}/src/ui/SlidesPreview.css.ts (100%) rename packages/{documents => web}/src/ui/SlidesPreview.tsx (100%) rename packages/{documents => web}/src/ui/StructureTree.css.ts (100%) rename packages/{documents => web}/src/ui/StructureTree.tsx (100%) rename packages/{documents => web}/src/ui/WordProcessingPreview.tsx (100%) rename packages/{documents => web}/src/ui/contentBlocks.css.ts (100%) rename packages/{documents => web}/src/ui/contentBlocks.tsx (100%) rename packages/{documents => web}/src/ui/convertLayout.css.ts (100%) rename packages/{documents => web}/src/ui/notify.ts (100%) rename packages/{documents => web}/src/ui/previewPanel.css.ts (100%) rename packages/{documents => web}/src/ui/reopenMailbox.ts (100%) rename packages/{documents => web}/src/vite-env.d.ts (100%) rename packages/{documents => web}/src/workers/documents.worker.ts (100%) rename packages/{documents => web}/tsconfig.json (100%) rename packages/{documents => web}/tsconfig.node.json (100%) rename packages/{documents => web}/tsconfig.worker.json (100%) rename packages/{documents => web}/vite.config.ts (100%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 607b3a2df..ec6161003 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -23,7 +23,6 @@ body: - document-mcp - document-outline.js - document-schema.js - - documents - documents.js - epub-codec - markdown-codec @@ -32,6 +31,7 @@ body: - pdf-codec - ppt-codec - rtf-codec + - web - wpd-codec - xls-codec validations: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 98c748543..4bcd69363 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -21,7 +21,6 @@ body: - document-mcp - document-outline.js - document-schema.js - - documents - documents.js - epub-codec - markdown-codec @@ -30,6 +29,7 @@ body: - pdf-codec - ppt-codec - rtf-codec + - web - wpd-codec - xls-codec validations: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9879f395..0b0d4a73d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -623,7 +623,7 @@ jobs: deploy-site: name: Build and deploy the web UI to Pages - # After release, so the deploy is built from the release commit the orchestrator just pushed (it bumps packages/documents/package.json and tags it). For a commit that releases nothing, no new tag is created and this builds the current tip of main. + # After release, so the deploy is built from the release commit the orchestrator just pushed (it bumps packages/web/package.json and tags it). For a commit that releases nothing, no new tag is created and this builds the current tip of main. needs: [commitlint, lint, typecheck, test, test-workers, test-smoke, release] if: github.ref == 'refs/heads/main' && github.event_name == 'push' @@ -666,6 +666,6 @@ jobs: done - uses: actions/upload-pages-artifact@v5 with: - path: packages/documents/dist + path: packages/web/dist - id: deployment uses: actions/deploy-pages@v5 diff --git a/README.md b/README.md index 74fa7c0b8..029cb4ade 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Each exposes the conversion engine through a different surface: | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`document-cli`](packages/document-cli/README.md) | CLI and interactive Ink TUI covering every docx/pptx/odt/odp/ods/odg/odf/pdf/odm/odb/xlsx/markdown conversion, bridge, and editor as a scriptable command or a terminal app. | | [`document-mcp`](packages/document-mcp/README.md) | MCP server exposing the conversion, `.odb`, metadata, and font tooling as MCP tools. | -| [`documents`](packages/documents/README.md) | Client-only, statically-built web UI for every conversion and editing tool in the ecosystem, also depending directly on `markdown-codec`. The one package here that is never published: it deploys to GitHub Pages instead. | +| [`web`](packages/web/README.md) | Client-only, statically-built web UI for every conversion and editing tool in the ecosystem, also depending directly on `markdown-codec`. The one package here that is never published: it deploys to GitHub Pages instead. | ## PDF is an equal peer, not a junction @@ -93,7 +93,7 @@ Individual packages set their own build and test configuration, but as a family - TypeScript with Zod 4 for schema definition and validation. - MIT licensing. - Hand-written, dependency-minimal codecs over pulling in heavyweight format libraries — see each package's own README for what it deliberately avoids depending on. -- The foundation and format-codec packages (`byte-codec`, `document-schema.js`, `document-outline.js`, `archive-codec`, `document-compute.js`, `excel-number-format`, `ooxml.js`, `odf.js`, `markdown-codec`, `pdf-codec`, `epub-codec`, `rtf-codec`, `wpd-codec`, `doc-codec`, `xls-codec`, `ppt-codec`, `documents.js`) are Worker-isomorphic: their published `src/` must not import `node:*`/bare Node builtins or use the Node-only `Buffer` global. The `no-restricted-imports`/`no-restricted-globals` ban enforcing that is defined once in the root's `eslint.shared.ts`, which derives the module list from `node:module`'s own `builtinModules` rather than restating it; a package opts in by passing `isomorphic: true` to `packageLintConfig` rather than declaring the rule itself, and a workerd test suite proves it at runtime. The interface packages (`document-cli`, `document-mcp`, `documents`) are not held to this, since they legitimately run under Node or a browser rather than needing Worker portability. +- The foundation and format-codec packages (`byte-codec`, `document-schema.js`, `document-outline.js`, `archive-codec`, `document-compute.js`, `excel-number-format`, `ooxml.js`, `odf.js`, `markdown-codec`, `pdf-codec`, `epub-codec`, `rtf-codec`, `wpd-codec`, `doc-codec`, `xls-codec`, `ppt-codec`, `documents.js`) are Worker-isomorphic: their published `src/` must not import `node:*`/bare Node builtins or use the Node-only `Buffer` global. The `no-restricted-imports`/`no-restricted-globals` ban enforcing that is defined once in the root's `eslint.shared.ts`, which derives the module list from `node:module`'s own `builtinModules` rather than restating it; a package opts in by passing `isomorphic: true` to `packageLintConfig` rather than declaring the rule itself, and a workerd test suite proves it at runtime. The interface packages (`document-cli`, `document-mcp`, `web`) are not held to this, since they legitimately run under Node or a browser rather than needing Worker portability. ## Working in the workspace @@ -116,7 +116,7 @@ To scope a run, drive turbo directly rather than adding a filter to the scripts ```sh pnpm exec turbo run _test --filter=pdf-codec # one package and its dependencies pnpm exec turbo run _test _build --affected # whatever the current branch changed -pnpm exec turbo run documents#test # the web UI alone +pnpm exec turbo run web#test # the web UI alone ``` Each package also keeps its own scripts, so `pnpm --dir packages/odf.js test:watch` (or running the script from inside that directory) still works for focused work on a single package. @@ -125,7 +125,7 @@ Each package also keeps its own scripts, so `pnpm --dir packages/odf.js test:wat `turbo.json` is the whole story, and two details in it are worth knowing before editing it. -The tasks the root pipeline runs are the underscore-prefixed ones (`_build`, `_lint`, `_test`, …). Each package already used that convention: its public `build` script was `turbo run _build`, and `_build` held the real command. Running the public names from the root would make turbo invoke those wrapper scripts, which invoke turbo again — the recursive-call case Turborepo's own documentation warns about. The root reaches straight past the wrappers to the leaf commands, and the wrappers stay usable inside a single package. The web UI predates the convention and names its scripts plainly, so it gets package-scoped `documents#…` entries instead. +The tasks the root pipeline runs are the underscore-prefixed ones (`_build`, `_lint`, `_test`, …). Each package already used that convention: its public `build` script was `turbo run _build`, and `_build` held the real command. Running the public names from the root would make turbo invoke those wrapper scripts, which invoke turbo again — the recursive-call case Turborepo's own documentation warns about. The root reaches straight past the wrappers to the leaf commands, and the wrappers stay usable inside a single package. The web UI predates the convention and names its scripts plainly, so it gets package-scoped `web#…` entries instead. Every task depends on `^_build` — its dependencies' builds. In the separate repositories a sibling arrived pre-built from the npm registry, so nothing needed building before a typecheck, lint, or test run. Here a sibling is a symlink into `packages/`, whose `dist/` exists only once that package's own build has run, and `tsc`, type-aware ESLint, and vitest all resolve imports through it. diff --git a/SECURITY.md b/SECURITY.md index 12e32f9c5..a444a6f4c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -21,7 +21,7 @@ These packages parse untrusted binary and text input by design — OOXML and Ope - Prototype pollution through a key taken from document content. - Anything that lets a document's bytes reach the filesystem, network, or a subprocess. -`documents` (the web UI) is client-only and statically built, so its scope is the usual browser surface: XSS from document content rendered into the page, and anything that escapes the worker sandbox. +`web` (the web UI) is client-only and statically built, so its scope is the usual browser surface: XSS from document content rendered into the page, and anything that escapes the worker sandbox. ## What is out of scope diff --git a/knip.config.ts b/knip.config.ts index 4ece341cc..a5e1ee278 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -35,7 +35,7 @@ const config: KnipConfig = { // The web UI, restated in full rather than extended. knip resolves a `workspaces` key to the single most specific match and uses it verbatim -- a glob entry and an exact entry do not merge -- so every field this needs has to appear here even where it repeats the glob above. // // Its entries are genuinely different: it is a private Vite app that publishes nothing and has no exports map, so nothing is reachable through a package subpath. The real roots are the HTML document, the browser Web Worker (reached through `new Worker(new URL(...))`, which is not a static import), and the generated router tree. - "packages/documents": { + "packages/web": { // Only the Web Worker needs naming. knip's Vite plugin already finds index.html, src/main.tsx, the generated router tree, and vite.config.ts; the worker is reached through `new Worker(new URL(...))`, which is not a static import and so is invisible to it. entry: ["src/workers/documents.worker.ts"], project: ["src/**/*.{ts,tsx}"], diff --git a/packages/document-compute.js/README.md b/packages/document-compute.js/README.md index f75e0078c..59639669b 100644 --- a/packages/document-compute.js/README.md +++ b/packages/document-compute.js/README.md @@ -131,7 +131,7 @@ Quoting the issue's own scope line directly: **this is deliberately not a CAS in - Worker-isomorphic (see the [family-wide convention](https://github.com/ExaDev/documents.js/blob/main/README.md#conventions)): runtime `src/` must not import `node:*`, a bare Node builtin, or use the `Buffer` global — enforced by a `no-restricted-imports`/`no-restricted-globals` ESLint rule and exercised in CI by running a test suite inside an actual `workerd` isolate (`pnpm test:workers`). Exact-rational arithmetic is plain `BigInt`, never `node:crypto` or any other Node-only primitive, precisely so this holds. - Only `src/index.ts` may be named `index.*` — a custom ESLint rule (`local/no-non-barrel-index`) rejects any other module using an `index` basename, since that would be a hidden entry point the `exports` map in `package.json` doesn't advertise. - Failure is always a thrown, named `Error` subclass (`compute/errors.ts`), never a `{ ok, error }` result wrapper — matching `document-schema.js`'s `schema-io.ts` and `archive-codec`'s own error classes rather than inventing a second convention for this package alone. -- Not wired into the conversion pipeline. This package is a standalone evaluator: it is not a dependency of `documents.js`, `document-cli`, `document-mcp`, or `documents`, and adding it as one is a separate, deliberate decision for whichever of those surfaces first needs a document's formula actually computed. +- Not wired into the conversion pipeline. This package is a standalone evaluator: it is not a dependency of `documents.js`, `document-cli`, `document-mcp`, or `web`, and adding it as one is a separate, deliberate decision for whichever of those surfaces first needs a document's formula actually computed. - Release, CI, and commit-message conventions are all workspace-wide, not package-local — see the [monorepo root README](../../README.md#releases) for the mechanism (topological per-package `semantic-release` via `@exadev/semantic-release-workspace`, OIDC trusted npm publishing, and the post-release republish/attestation jobs). ## Install diff --git a/packages/documents/.gitignore b/packages/web/.gitignore similarity index 100% rename from packages/documents/.gitignore rename to packages/web/.gitignore diff --git a/packages/documents/AGENTS.md b/packages/web/AGENTS.md similarity index 100% rename from packages/documents/AGENTS.md rename to packages/web/AGENTS.md diff --git a/packages/documents/CHANGELOG.md b/packages/web/CHANGELOG.md similarity index 100% rename from packages/documents/CHANGELOG.md rename to packages/web/CHANGELOG.md diff --git a/packages/documents/CLAUDE.md b/packages/web/CLAUDE.md similarity index 100% rename from packages/documents/CLAUDE.md rename to packages/web/CLAUDE.md diff --git a/packages/documents/LICENSE b/packages/web/LICENSE similarity index 100% rename from packages/documents/LICENSE rename to packages/web/LICENSE diff --git a/packages/documents/README.md b/packages/web/README.md similarity index 89% rename from packages/documents/README.md rename to packages/web/README.md index 2f762ae0f..cf380d081 100644 --- a/packages/documents/README.md +++ b/packages/web/README.md @@ -1,6 +1,6 @@ -# documents +# web -[![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/documents.js/tree/main/packages/documents) [![CI](https://img.shields.io/github/actions/workflow/status/ExaDev/documents.js/ci.yml?branch=main)](https://github.com/ExaDev/documents.js/actions) +[![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/documents.js/tree/main/packages/web) [![CI](https://img.shields.io/github/actions/workflow/status/ExaDev/documents.js/ci.yml?branch=main)](https://github.com/ExaDev/documents.js/actions) > A client-only, statically-built web UI for every conversion and editing tool in the [documents.js ecosystem](../../README.md) — convert and edit docx, pptx, xlsx, odt, odp, ods, odg, csv, svg, pdf, and markdown documents entirely in the browser, with no server component. @@ -51,7 +51,7 @@ The app is split into a main-thread UI and a Web Worker that holds the only code ## Gotchas -- The production build is served from `/documents/` on GitHub Pages (set via `base` in `vite.config.ts` when `CI` is set) but from `/` in local dev — a build produced locally with `CI` unset will have the wrong base path if deployed as-is. +- The production build is served from `//` on GitHub Pages (`/documents.js/` today) but from `/` in local dev — `base` in `vite.config.ts` derives the path segment from `GITHUB_REPOSITORY` when `CI` is set, falling back to the name parsed from the git remote, so it never needs a hand-maintained literal and can't silently drift from the actual deploying repository. A build produced locally with `CI` unset will have the wrong base path if deployed as-is. - This is a PWA (`vite-plugin-pwa`, `autoUpdate`). The worker bundle (by far the largest built asset) is deliberately excluded from the Workbox precache list and instead cached at runtime on first use via a `CacheFirst` rule, so it doesn't block install or blow the default precache size budget. - `src/workers/documents.worker.ts` is a browser Web Worker, unrelated to Cloudflare Workers — this repo has no `wrangler` config and doesn't deploy to Cloudflare, unlike some sibling packages in the ecosystem. diff --git a/packages/documents/eslint.config.ts b/packages/web/eslint.config.ts similarity index 100% rename from packages/documents/eslint.config.ts rename to packages/web/eslint.config.ts diff --git a/packages/documents/index.html b/packages/web/index.html similarity index 100% rename from packages/documents/index.html rename to packages/web/index.html diff --git a/packages/documents/package.json b/packages/web/package.json similarity index 97% rename from packages/documents/package.json rename to packages/web/package.json index 39b70bb04..cb0f45c76 100644 --- a/packages/documents/package.json +++ b/packages/web/package.json @@ -1,5 +1,5 @@ { - "name": "documents", + "name": "web", "private": true, "version": "2.3.2", "type": "module", @@ -7,9 +7,9 @@ "repository": { "type": "git", "url": "git+https://github.com/ExaDev/documents.js.git", - "directory": "packages/documents" + "directory": "packages/web" }, - "homepage": "https://github.com/ExaDev/documents.js/tree/main/packages/documents", + "homepage": "https://github.com/ExaDev/documents.js/tree/main/packages/web", "bugs": { "url": "https://github.com/ExaDev/documents.js/issues" }, diff --git a/packages/documents/public/apple-touch-icon.png b/packages/web/public/apple-touch-icon.png similarity index 100% rename from packages/documents/public/apple-touch-icon.png rename to packages/web/public/apple-touch-icon.png diff --git a/packages/documents/public/favicon.svg b/packages/web/public/favicon.svg similarity index 100% rename from packages/documents/public/favicon.svg rename to packages/web/public/favicon.svg diff --git a/packages/documents/public/icons/icon-192.svg b/packages/web/public/icons/icon-192.svg similarity index 100% rename from packages/documents/public/icons/icon-192.svg rename to packages/web/public/icons/icon-192.svg diff --git a/packages/documents/public/icons/icon-512.svg b/packages/web/public/icons/icon-512.svg similarity index 100% rename from packages/documents/public/icons/icon-512.svg rename to packages/web/public/icons/icon-512.svg diff --git a/packages/documents/public/icons/maskable-512.svg b/packages/web/public/icons/maskable-512.svg similarity index 100% rename from packages/documents/public/icons/maskable-512.svg rename to packages/web/public/icons/maskable-512.svg diff --git a/packages/documents/src/adapters/documentConverter/workerDocumentConverter.ts b/packages/web/src/adapters/documentConverter/workerDocumentConverter.ts similarity index 100% rename from packages/documents/src/adapters/documentConverter/workerDocumentConverter.ts rename to packages/web/src/adapters/documentConverter/workerDocumentConverter.ts diff --git a/packages/documents/src/adapters/fileAccess/createFileAccess.ts b/packages/web/src/adapters/fileAccess/createFileAccess.ts similarity index 100% rename from packages/documents/src/adapters/fileAccess/createFileAccess.ts rename to packages/web/src/adapters/fileAccess/createFileAccess.ts diff --git a/packages/documents/src/adapters/fileAccess/fallbackFileAccess.ts b/packages/web/src/adapters/fileAccess/fallbackFileAccess.ts similarity index 100% rename from packages/documents/src/adapters/fileAccess/fallbackFileAccess.ts rename to packages/web/src/adapters/fileAccess/fallbackFileAccess.ts diff --git a/packages/documents/src/adapters/fileAccess/nativeFileAccess.ts b/packages/web/src/adapters/fileAccess/nativeFileAccess.ts similarity index 100% rename from packages/documents/src/adapters/fileAccess/nativeFileAccess.ts rename to packages/web/src/adapters/fileAccess/nativeFileAccess.ts diff --git a/packages/documents/src/app.tsx b/packages/web/src/app.tsx similarity index 100% rename from packages/documents/src/app.tsx rename to packages/web/src/app.tsx diff --git a/packages/documents/src/db/dexie.ts b/packages/web/src/db/dexie.ts similarity index 100% rename from packages/documents/src/db/dexie.ts rename to packages/web/src/db/dexie.ts diff --git a/packages/documents/src/design-tokens.test.ts b/packages/web/src/design-tokens.test.ts similarity index 100% rename from packages/documents/src/design-tokens.test.ts rename to packages/web/src/design-tokens.test.ts diff --git a/packages/documents/src/design-tokens.ts b/packages/web/src/design-tokens.ts similarity index 100% rename from packages/documents/src/design-tokens.ts rename to packages/web/src/design-tokens.ts diff --git a/packages/documents/src/hooks/useConversions.ts b/packages/web/src/hooks/useConversions.ts similarity index 100% rename from packages/documents/src/hooks/useConversions.ts rename to packages/web/src/hooks/useConversions.ts diff --git a/packages/documents/src/hooks/useConvert.ts b/packages/web/src/hooks/useConvert.ts similarity index 100% rename from packages/documents/src/hooks/useConvert.ts rename to packages/web/src/hooks/useConvert.ts diff --git a/packages/documents/src/hooks/useFonts.ts b/packages/web/src/hooks/useFonts.ts similarity index 100% rename from packages/documents/src/hooks/useFonts.ts rename to packages/web/src/hooks/useFonts.ts diff --git a/packages/documents/src/hooks/useInspect.ts b/packages/web/src/hooks/useInspect.ts similarity index 100% rename from packages/documents/src/hooks/useInspect.ts rename to packages/web/src/hooks/useInspect.ts diff --git a/packages/documents/src/hooks/useMetadata.ts b/packages/web/src/hooks/useMetadata.ts similarity index 100% rename from packages/documents/src/hooks/useMetadata.ts rename to packages/web/src/hooks/useMetadata.ts diff --git a/packages/documents/src/hooks/usePdfObjectUrl.ts b/packages/web/src/hooks/usePdfObjectUrl.ts similarity index 100% rename from packages/documents/src/hooks/usePdfObjectUrl.ts rename to packages/web/src/hooks/usePdfObjectUrl.ts diff --git a/packages/documents/src/hooks/useRecentFiles.ts b/packages/web/src/hooks/useRecentFiles.ts similarity index 100% rename from packages/documents/src/hooks/useRecentFiles.ts rename to packages/web/src/hooks/useRecentFiles.ts diff --git a/packages/documents/src/main.tsx b/packages/web/src/main.tsx similarity index 100% rename from packages/documents/src/main.tsx rename to packages/web/src/main.tsx diff --git a/packages/documents/src/ports/fileAccess.ts b/packages/web/src/ports/fileAccess.ts similarity index 100% rename from packages/documents/src/ports/fileAccess.ts rename to packages/web/src/ports/fileAccess.ts diff --git a/packages/documents/src/routeTree.gen.ts b/packages/web/src/routeTree.gen.ts similarity index 100% rename from packages/documents/src/routeTree.gen.ts rename to packages/web/src/routeTree.gen.ts diff --git a/packages/documents/src/router.tsx b/packages/web/src/router.tsx similarity index 100% rename from packages/documents/src/router.tsx rename to packages/web/src/router.tsx diff --git a/packages/documents/src/routes/-Sidebar.css.ts b/packages/web/src/routes/-Sidebar.css.ts similarity index 100% rename from packages/documents/src/routes/-Sidebar.css.ts rename to packages/web/src/routes/-Sidebar.css.ts diff --git a/packages/documents/src/routes/-Sidebar.tsx b/packages/web/src/routes/-Sidebar.tsx similarity index 100% rename from packages/documents/src/routes/-Sidebar.tsx rename to packages/web/src/routes/-Sidebar.tsx diff --git a/packages/documents/src/routes/__root.tsx b/packages/web/src/routes/__root.tsx similarity index 100% rename from packages/documents/src/routes/__root.tsx rename to packages/web/src/routes/__root.tsx diff --git a/packages/documents/src/routes/convert.$source.$target.tsx b/packages/web/src/routes/convert.$source.$target.tsx similarity index 100% rename from packages/documents/src/routes/convert.$source.$target.tsx rename to packages/web/src/routes/convert.$source.$target.tsx diff --git a/packages/documents/src/routes/convert.index.tsx b/packages/web/src/routes/convert.index.tsx similarity index 100% rename from packages/documents/src/routes/convert.index.tsx rename to packages/web/src/routes/convert.index.tsx diff --git a/packages/documents/src/routes/convert.tsx b/packages/web/src/routes/convert.tsx similarity index 100% rename from packages/documents/src/routes/convert.tsx rename to packages/web/src/routes/convert.tsx diff --git a/packages/documents/src/routes/fonts.tsx b/packages/web/src/routes/fonts.tsx similarity index 100% rename from packages/documents/src/routes/fonts.tsx rename to packages/web/src/routes/fonts.tsx diff --git a/packages/documents/src/routes/index.tsx b/packages/web/src/routes/index.tsx similarity index 100% rename from packages/documents/src/routes/index.tsx rename to packages/web/src/routes/index.tsx diff --git a/packages/documents/src/routes/inspect.tsx b/packages/web/src/routes/inspect.tsx similarity index 100% rename from packages/documents/src/routes/inspect.tsx rename to packages/web/src/routes/inspect.tsx diff --git a/packages/documents/src/routes/metadata.tsx b/packages/web/src/routes/metadata.tsx similarity index 100% rename from packages/documents/src/routes/metadata.tsx rename to packages/web/src/routes/metadata.tsx diff --git a/packages/documents/src/routes/recent.tsx b/packages/web/src/routes/recent.tsx similarity index 100% rename from packages/documents/src/routes/recent.tsx rename to packages/web/src/routes/recent.tsx diff --git a/packages/documents/src/rpc/client.ts b/packages/web/src/rpc/client.ts similarity index 100% rename from packages/documents/src/rpc/client.ts rename to packages/web/src/rpc/client.ts diff --git a/packages/documents/src/rpc/router.ts b/packages/web/src/rpc/router.ts similarity index 100% rename from packages/documents/src/rpc/router.ts rename to packages/web/src/rpc/router.ts diff --git a/packages/documents/src/shared/contentCounts.test.ts b/packages/web/src/shared/contentCounts.test.ts similarity index 100% rename from packages/documents/src/shared/contentCounts.test.ts rename to packages/web/src/shared/contentCounts.test.ts diff --git a/packages/documents/src/shared/contentCounts.ts b/packages/web/src/shared/contentCounts.ts similarity index 100% rename from packages/documents/src/shared/contentCounts.ts rename to packages/web/src/shared/contentCounts.ts diff --git a/packages/documents/src/shared/diagnostics.ts b/packages/web/src/shared/diagnostics.ts similarity index 100% rename from packages/documents/src/shared/diagnostics.ts rename to packages/web/src/shared/diagnostics.ts diff --git a/packages/documents/src/shared/extensionToFormat.ts b/packages/web/src/shared/extensionToFormat.ts similarity index 100% rename from packages/documents/src/shared/extensionToFormat.ts rename to packages/web/src/shared/extensionToFormat.ts diff --git a/packages/documents/src/shared/jsonTree.test.ts b/packages/web/src/shared/jsonTree.test.ts similarity index 100% rename from packages/documents/src/shared/jsonTree.test.ts rename to packages/web/src/shared/jsonTree.test.ts diff --git a/packages/documents/src/shared/jsonTree.ts b/packages/web/src/shared/jsonTree.ts similarity index 100% rename from packages/documents/src/shared/jsonTree.ts rename to packages/web/src/shared/jsonTree.ts diff --git a/packages/documents/src/shared/relativeTime.ts b/packages/web/src/shared/relativeTime.ts similarity index 100% rename from packages/documents/src/shared/relativeTime.ts rename to packages/web/src/shared/relativeTime.ts diff --git a/packages/documents/src/shared/transferables.test.ts b/packages/web/src/shared/transferables.test.ts similarity index 100% rename from packages/documents/src/shared/transferables.test.ts rename to packages/web/src/shared/transferables.test.ts diff --git a/packages/documents/src/shared/transferables.ts b/packages/web/src/shared/transferables.ts similarity index 100% rename from packages/documents/src/shared/transferables.ts rename to packages/web/src/shared/transferables.ts diff --git a/packages/documents/src/theme.css.ts b/packages/web/src/theme.css.ts similarity index 100% rename from packages/documents/src/theme.css.ts rename to packages/web/src/theme.css.ts diff --git a/packages/documents/src/theme.ts b/packages/web/src/theme.ts similarity index 100% rename from packages/documents/src/theme.ts rename to packages/web/src/theme.ts diff --git a/packages/documents/src/ui/DiagnosticsPanel.tsx b/packages/web/src/ui/DiagnosticsPanel.tsx similarity index 100% rename from packages/documents/src/ui/DiagnosticsPanel.tsx rename to packages/web/src/ui/DiagnosticsPanel.tsx diff --git a/packages/documents/src/ui/FileUpload.css.ts b/packages/web/src/ui/FileUpload.css.ts similarity index 100% rename from packages/documents/src/ui/FileUpload.css.ts rename to packages/web/src/ui/FileUpload.css.ts diff --git a/packages/documents/src/ui/FileUpload.tsx b/packages/web/src/ui/FileUpload.tsx similarity index 100% rename from packages/documents/src/ui/FileUpload.tsx rename to packages/web/src/ui/FileUpload.tsx diff --git a/packages/documents/src/ui/FormulaPreview.css.ts b/packages/web/src/ui/FormulaPreview.css.ts similarity index 100% rename from packages/documents/src/ui/FormulaPreview.css.ts rename to packages/web/src/ui/FormulaPreview.css.ts diff --git a/packages/documents/src/ui/FormulaPreview.tsx b/packages/web/src/ui/FormulaPreview.tsx similarity index 100% rename from packages/documents/src/ui/FormulaPreview.tsx rename to packages/web/src/ui/FormulaPreview.tsx diff --git a/packages/documents/src/ui/InspectPanel.tsx b/packages/web/src/ui/InspectPanel.tsx similarity index 100% rename from packages/documents/src/ui/InspectPanel.tsx rename to packages/web/src/ui/InspectPanel.tsx diff --git a/packages/documents/src/ui/MarkdownPreview.tsx b/packages/web/src/ui/MarkdownPreview.tsx similarity index 100% rename from packages/documents/src/ui/MarkdownPreview.tsx rename to packages/web/src/ui/MarkdownPreview.tsx diff --git a/packages/documents/src/ui/PdfPreview.css.ts b/packages/web/src/ui/PdfPreview.css.ts similarity index 100% rename from packages/documents/src/ui/PdfPreview.css.ts rename to packages/web/src/ui/PdfPreview.css.ts diff --git a/packages/documents/src/ui/PdfPreview.tsx b/packages/web/src/ui/PdfPreview.tsx similarity index 100% rename from packages/documents/src/ui/PdfPreview.tsx rename to packages/web/src/ui/PdfPreview.tsx diff --git a/packages/documents/src/ui/RecentFilesPanel.css.ts b/packages/web/src/ui/RecentFilesPanel.css.ts similarity index 100% rename from packages/documents/src/ui/RecentFilesPanel.css.ts rename to packages/web/src/ui/RecentFilesPanel.css.ts diff --git a/packages/documents/src/ui/RecentFilesPanel.tsx b/packages/web/src/ui/RecentFilesPanel.tsx similarity index 100% rename from packages/documents/src/ui/RecentFilesPanel.tsx rename to packages/web/src/ui/RecentFilesPanel.tsx diff --git a/packages/documents/src/ui/SheetPreview.css.ts b/packages/web/src/ui/SheetPreview.css.ts similarity index 100% rename from packages/documents/src/ui/SheetPreview.css.ts rename to packages/web/src/ui/SheetPreview.css.ts diff --git a/packages/documents/src/ui/SheetPreview.tsx b/packages/web/src/ui/SheetPreview.tsx similarity index 100% rename from packages/documents/src/ui/SheetPreview.tsx rename to packages/web/src/ui/SheetPreview.tsx diff --git a/packages/documents/src/ui/SlidesPreview.css.ts b/packages/web/src/ui/SlidesPreview.css.ts similarity index 100% rename from packages/documents/src/ui/SlidesPreview.css.ts rename to packages/web/src/ui/SlidesPreview.css.ts diff --git a/packages/documents/src/ui/SlidesPreview.tsx b/packages/web/src/ui/SlidesPreview.tsx similarity index 100% rename from packages/documents/src/ui/SlidesPreview.tsx rename to packages/web/src/ui/SlidesPreview.tsx diff --git a/packages/documents/src/ui/StructureTree.css.ts b/packages/web/src/ui/StructureTree.css.ts similarity index 100% rename from packages/documents/src/ui/StructureTree.css.ts rename to packages/web/src/ui/StructureTree.css.ts diff --git a/packages/documents/src/ui/StructureTree.tsx b/packages/web/src/ui/StructureTree.tsx similarity index 100% rename from packages/documents/src/ui/StructureTree.tsx rename to packages/web/src/ui/StructureTree.tsx diff --git a/packages/documents/src/ui/WordProcessingPreview.tsx b/packages/web/src/ui/WordProcessingPreview.tsx similarity index 100% rename from packages/documents/src/ui/WordProcessingPreview.tsx rename to packages/web/src/ui/WordProcessingPreview.tsx diff --git a/packages/documents/src/ui/contentBlocks.css.ts b/packages/web/src/ui/contentBlocks.css.ts similarity index 100% rename from packages/documents/src/ui/contentBlocks.css.ts rename to packages/web/src/ui/contentBlocks.css.ts diff --git a/packages/documents/src/ui/contentBlocks.tsx b/packages/web/src/ui/contentBlocks.tsx similarity index 100% rename from packages/documents/src/ui/contentBlocks.tsx rename to packages/web/src/ui/contentBlocks.tsx diff --git a/packages/documents/src/ui/convertLayout.css.ts b/packages/web/src/ui/convertLayout.css.ts similarity index 100% rename from packages/documents/src/ui/convertLayout.css.ts rename to packages/web/src/ui/convertLayout.css.ts diff --git a/packages/documents/src/ui/notify.ts b/packages/web/src/ui/notify.ts similarity index 100% rename from packages/documents/src/ui/notify.ts rename to packages/web/src/ui/notify.ts diff --git a/packages/documents/src/ui/previewPanel.css.ts b/packages/web/src/ui/previewPanel.css.ts similarity index 100% rename from packages/documents/src/ui/previewPanel.css.ts rename to packages/web/src/ui/previewPanel.css.ts diff --git a/packages/documents/src/ui/reopenMailbox.ts b/packages/web/src/ui/reopenMailbox.ts similarity index 100% rename from packages/documents/src/ui/reopenMailbox.ts rename to packages/web/src/ui/reopenMailbox.ts diff --git a/packages/documents/src/vite-env.d.ts b/packages/web/src/vite-env.d.ts similarity index 100% rename from packages/documents/src/vite-env.d.ts rename to packages/web/src/vite-env.d.ts diff --git a/packages/documents/src/workers/documents.worker.ts b/packages/web/src/workers/documents.worker.ts similarity index 100% rename from packages/documents/src/workers/documents.worker.ts rename to packages/web/src/workers/documents.worker.ts diff --git a/packages/documents/tsconfig.json b/packages/web/tsconfig.json similarity index 100% rename from packages/documents/tsconfig.json rename to packages/web/tsconfig.json diff --git a/packages/documents/tsconfig.node.json b/packages/web/tsconfig.node.json similarity index 100% rename from packages/documents/tsconfig.node.json rename to packages/web/tsconfig.node.json diff --git a/packages/documents/tsconfig.worker.json b/packages/web/tsconfig.worker.json similarity index 100% rename from packages/documents/tsconfig.worker.json rename to packages/web/tsconfig.worker.json diff --git a/packages/documents/vite.config.ts b/packages/web/vite.config.ts similarity index 100% rename from packages/documents/vite.config.ts rename to packages/web/vite.config.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d76f893d9..2b097e527 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -496,142 +496,6 @@ importers: specifier: ^4.1.10 version: 4.1.11(@types/node@26.2.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) - packages/documents: - dependencies: - '@mantine/core': - specifier: ^9.5.1 - version: 9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@mantine/dropzone': - specifier: ^9.5.1 - version: 9.5.1(@mantine/core@9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@mantine/hooks@9.5.1(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@mantine/hooks': - specifier: ^9.5.1 - version: 9.5.1(react@19.2.8) - '@mantine/notifications': - specifier: ^9.5.1 - version: 9.5.1(@mantine/core@9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@mantine/hooks@9.5.1(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@mantine/vanilla-extract': - specifier: ^9.5.1 - version: 9.5.1(@mantine/core@9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) - '@orpc/client': - specifier: ^1.14.15 - version: 1.15.0 - '@orpc/server': - specifier: ^1.14.15 - version: 1.15.0(ws@8.21.3) - '@tabler/icons-react': - specifier: ^3.46.0 - version: 3.46.0(react@19.2.8) - '@tanstack/react-query': - specifier: ^5.101.4 - version: 5.101.4(react@19.2.8) - '@tanstack/react-router': - specifier: ^1.170.23 - version: 1.170.31(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@vanilla-extract/css': - specifier: ^1.21.2 - version: 1.21.2 - '@vanilla-extract/dynamic': - specifier: ^2.1.5 - version: 2.1.5 - '@vanilla-extract/recipes': - specifier: ^0.5.7 - version: 0.5.7(@vanilla-extract/css@1.21.2) - dexie: - specifier: ^4.4.4 - version: 4.4.5 - dexie-react-hooks: - specifier: ^4.4.0 - version: 4.4.0(dexie@4.4.5)(react@19.2.8) - document-schema.js: - specifier: ^5.5.1 - version: link:../document-schema.js - documents.js: - specifier: ^6.5.0 - version: link:../documents.js - markdown-codec: - specifier: ^6.1.5 - version: link:../markdown-codec - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - zod: - specifier: ^4.4.3 - version: 4.4.3 - devDependencies: - '@playwright/test': - specifier: ^1.62.1 - version: 1.62.1 - '@tanstack/router-plugin': - specifier: ^1.168.27 - version: 1.168.34(@tanstack/react-router@1.170.31(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(esbuild@0.28.2)(rolldown@1.2.5)(rollup@4.63.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) - '@types/eslint-plugin-jsx-a11y': - specifier: ^6.10.1 - version: 6.10.1(jiti@2.7.0) - '@types/node': - specifier: ^26.2.0 - version: 26.2.0 - '@types/react': - specifier: ^19.2.18 - version: 19.2.18 - '@types/react-dom': - specifier: ^19.2.4 - version: 19.2.4(@types/react@19.2.18) - '@types/wicg-file-system-access': - specifier: ^2023.10.7 - version: 2023.10.7 - '@vanilla-extract/vite-plugin': - specifier: ^5.2.6 - version: 5.2.6(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0))(yaml@2.9.0) - '@vitejs/plugin-react': - specifier: ^6.0.5 - version: 6.0.5(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) - '@vitest/coverage-v8': - specifier: ^4.1.10 - version: 4.1.11(vitest@4.1.11) - eslint: - specifier: ^10.8.1 - version: 10.8.1(jiti@2.7.0) - eslint-plugin-jsx-a11y: - specifier: ^6.10.2 - version: 6.10.2(eslint@10.8.1(jiti@2.7.0)) - eslint-plugin-react-hooks: - specifier: ^7.1.1 - version: 7.1.1(eslint@10.8.1(jiti@2.7.0)) - eslint-plugin-react-refresh: - specifier: ^0.5.3 - version: 0.5.4(eslint@10.8.1(jiti@2.7.0)) - globals: - specifier: ^17.9.0 - version: 17.11.0 - husky: - specifier: ^9.1.7 - version: 9.1.7 - jsdom: - specifier: ^30.0.1 - version: 30.0.1 - semantic-release: - specifier: ^25.0.9 - version: 25.0.9(typescript@6.0.3) - typescript: - specifier: ^6.0.3 - version: 6.0.3 - typescript-eslint: - specifier: ^8.66.0 - version: 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) - vite: - specifier: ^8.2.1 - version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0) - vite-plugin-pwa: - specifier: ^1.3.0 - version: 1.3.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0))(workbox-build@7.4.1)(workbox-window@7.4.1) - vitest: - specifier: ^4.1.10 - version: 4.1.11(@types/node@26.2.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) - packages/documents.js: dependencies: archive-codec: @@ -1099,6 +963,142 @@ importers: specifier: ^4.1.10 version: 4.1.11(@types/node@26.2.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) + packages/web: + dependencies: + '@mantine/core': + specifier: ^9.5.1 + version: 9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@mantine/dropzone': + specifier: ^9.5.1 + version: 9.5.1(@mantine/core@9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@mantine/hooks@9.5.1(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@mantine/hooks': + specifier: ^9.5.1 + version: 9.5.1(react@19.2.8) + '@mantine/notifications': + specifier: ^9.5.1 + version: 9.5.1(@mantine/core@9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@mantine/hooks@9.5.1(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@mantine/vanilla-extract': + specifier: ^9.5.1 + version: 9.5.1(@mantine/core@9.5.1(@mantine/hooks@9.5.1(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) + '@orpc/client': + specifier: ^1.14.15 + version: 1.15.0 + '@orpc/server': + specifier: ^1.14.15 + version: 1.15.0(ws@8.21.3) + '@tabler/icons-react': + specifier: ^3.46.0 + version: 3.46.0(react@19.2.8) + '@tanstack/react-query': + specifier: ^5.101.4 + version: 5.101.4(react@19.2.8) + '@tanstack/react-router': + specifier: ^1.170.23 + version: 1.170.31(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@vanilla-extract/css': + specifier: ^1.21.2 + version: 1.21.2 + '@vanilla-extract/dynamic': + specifier: ^2.1.5 + version: 2.1.5 + '@vanilla-extract/recipes': + specifier: ^0.5.7 + version: 0.5.7(@vanilla-extract/css@1.21.2) + dexie: + specifier: ^4.4.4 + version: 4.4.5 + dexie-react-hooks: + specifier: ^4.4.0 + version: 4.4.0(dexie@4.4.5)(react@19.2.8) + document-schema.js: + specifier: ^5.5.1 + version: link:../document-schema.js + documents.js: + specifier: ^6.5.0 + version: link:../documents.js + markdown-codec: + specifier: ^6.1.5 + version: link:../markdown-codec + react: + specifier: ^19.2.8 + version: 19.2.8 + react-dom: + specifier: ^19.2.8 + version: 19.2.8(react@19.2.8) + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@playwright/test': + specifier: ^1.62.1 + version: 1.62.1 + '@tanstack/router-plugin': + specifier: ^1.168.27 + version: 1.168.34(@tanstack/react-router@1.170.31(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(esbuild@0.28.2)(rolldown@1.2.5)(rollup@4.63.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) + '@types/eslint-plugin-jsx-a11y': + specifier: ^6.10.1 + version: 6.10.1(jiti@2.7.0) + '@types/node': + specifier: ^26.2.0 + version: 26.2.0 + '@types/react': + specifier: ^19.2.18 + version: 19.2.18 + '@types/react-dom': + specifier: ^19.2.4 + version: 19.2.4(@types/react@19.2.18) + '@types/wicg-file-system-access': + specifier: ^2023.10.7 + version: 2023.10.7 + '@vanilla-extract/vite-plugin': + specifier: ^5.2.6 + version: 5.2.6(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0))(yaml@2.9.0) + '@vitejs/plugin-react': + specifier: ^6.0.5 + version: 6.0.5(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) + '@vitest/coverage-v8': + specifier: ^4.1.10 + version: 4.1.11(vitest@4.1.11) + eslint: + specifier: ^10.8.1 + version: 10.8.1(jiti@2.7.0) + eslint-plugin-jsx-a11y: + specifier: ^6.10.2 + version: 6.10.2(eslint@10.8.1(jiti@2.7.0)) + eslint-plugin-react-hooks: + specifier: ^7.1.1 + version: 7.1.1(eslint@10.8.1(jiti@2.7.0)) + eslint-plugin-react-refresh: + specifier: ^0.5.3 + version: 0.5.4(eslint@10.8.1(jiti@2.7.0)) + globals: + specifier: ^17.9.0 + version: 17.11.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + jsdom: + specifier: ^30.0.1 + version: 30.0.1 + semantic-release: + specifier: ^25.0.9 + version: 25.0.9(typescript@6.0.3) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: ^8.66.0 + version: 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) + vite: + specifier: ^8.2.1 + version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0) + vite-plugin-pwa: + specifier: ^1.3.0 + version: 1.3.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0))(workbox-build@7.4.1)(workbox-window@7.4.1) + vitest: + specifier: ^4.1.10 + version: 4.1.11(@types/node@26.2.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0)) + packages/wpd-codec: dependencies: archive-codec: @@ -11099,6 +11099,10 @@ snapshots: optionalDependencies: picomatch: 4.0.5 + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + fflate@0.8.3: {} figures@2.0.0: @@ -13332,8 +13336,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 tinyrainbow@3.1.1: {} @@ -13645,7 +13649,7 @@ snapshots: vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 - picomatch: 4.0.5 + picomatch: 4.0.7 postcss: 8.5.26 rolldown: 1.2.5 tinyglobby: 0.2.17 diff --git a/turbo.json b/turbo.json index 8f2223247..c03fa4ae0 100644 --- a/turbo.json +++ b/turbo.json @@ -13,7 +13,7 @@ // Every task name is the underscore-prefixed one each package already used for its real command (`_build` runs tsdown; the package's own `build` script was `turbo run _build`). Running the public names from here instead would make turbo invoke those wrapper scripts, which invoke turbo again -- the recursive-call case Turborepo's own docs warn against ("You should only define these commands in the root package.json"). The wrapper scripts stay in place and stay usable for a single package, but the root pipeline reaches straight past them to the leaf commands. // - // The web UI (`documents`) needs different inputs/outputs/cache settings from the generic _build/_typecheck/etc (a vite.config.ts, a CI-only base-path env var, an uncacheable build since it stamps the commit SHA at build time) -- it uses the same underscore-prefixed task names as every other package via package-scoped overrides below, so a single `turbo run _build` still reaches it; only the settings differ, not the name. It is a leaf: nothing in the workspace depends on it. + // The web UI (`web`) needs different inputs/outputs/cache settings from the generic _build/_typecheck/etc (a vite.config.ts, a CI-only base-path env var, an uncacheable build since it stamps the commit SHA at build time) -- it uses the same underscore-prefixed task names as every other package via package-scoped overrides below, so a single `turbo run _build` still reaches it; only the settings differ, not the name. It is a leaf: nothing in the workspace depends on it. // // Every task depends on `^_build`, its dependencies' builds. In the separate repositories a sibling arrived pre-built from the registry, so typecheck, lint, and test needed nothing built first; here a sibling is a workspace symlink whose dist/ exists only once its own build has run, and tsc, type-aware eslint, and vitest all resolve imports through it. "tasks": { @@ -34,7 +34,7 @@ "inputs": ["$TURBO_DEFAULT$", "tsconfig.json", "tsconfig.node.json"], "outputs": [] }, - // attw --pack inspects the package's own dist/ against its own package.json exports map, so it depends on this package's own _build (which also builds every sibling `_build` depends on) rather than `^_build` -- the only task here that needs its own build rather than its dependencies'. Previously a raw shell loop in ci.yml's Typecheck job (`pnpm --recursive --filter='!documents' exec attw --pack`), preceded by an explicit `pnpm build` purely to give it a dist/ to inspect; both re-ran uncached on every push regardless of what changed. A package that doesn't publish (`documents`) has no `_typecheck:attw` script, so turbo silently skips it here the same way it already skips packages missing `_test:corpus`. + // attw --pack inspects the package's own dist/ against its own package.json exports map, so it depends on this package's own _build (which also builds every sibling `_build` depends on) rather than `^_build` -- the only task here that needs its own build rather than its dependencies'. Previously a raw shell loop in ci.yml's Typecheck job (`pnpm --recursive --filter='!documents' exec attw --pack`), preceded by an explicit `pnpm build` purely to give it a dist/ to inspect; both re-ran uncached on every push regardless of what changed. A package that doesn't publish (`web`) has no `_typecheck:attw` script, so turbo silently skips it here the same way it already skips packages missing `_test:corpus`. "_typecheck:attw": { "dependsOn": ["_build"], "inputs": ["package.json"], @@ -113,7 +113,7 @@ "outputs": [] }, - "documents#_build": { + "web#_build": { "dependsOn": ["^_build"], "inputs": [ "src/**", @@ -124,18 +124,18 @@ "tsconfig.worker.json", "package.json" ], - // CI switches the app's base path to /documents/ for Pages; strict env mode would strip it and silently produce a build with root-relative asset paths that 404 once deployed. - "env": ["CI"], - // Uncacheable on purpose. vite.config.ts embeds the HEAD commit SHA, its exact release tag, and its commit timestamp into the bundle by shelling out to git at config-load time. None of that is a declared input and none of it can be -- so on a commit that leaves packages/documents/** untouched, a cache replay would deploy a bundle stamped with the previous commit's identity. A vite build per deploy is cheaper than shipping wrong version metadata. + // CI switches the app's base path to // (derived from GITHUB_REPOSITORY) for Pages; strict env mode would strip it and silently produce a build with root-relative asset paths that 404 once deployed. + "env": ["CI", "GITHUB_REPOSITORY"], + // Uncacheable on purpose. vite.config.ts embeds the HEAD commit SHA, its exact release tag, and its commit timestamp into the bundle by shelling out to git at config-load time. None of that is a declared input and none of it can be -- so on a commit that leaves packages/web/** untouched, a cache replay would deploy a bundle stamped with the previous commit's identity. A vite build per deploy is cheaper than shipping wrong version metadata. "cache": false, "outputs": ["dist/**"] }, - "documents#_lint": { + "web#_lint": { "dependsOn": ["^_build"], "inputs": ["$TURBO_DEFAULT$", "eslint.config.ts"], "outputs": [".eslintcache"] }, - "documents#_typecheck": { + "web#_typecheck": { "dependsOn": ["^_build"], "inputs": [ "$TURBO_DEFAULT$", @@ -145,12 +145,12 @@ ], "outputs": [] }, - "documents#_test": { + "web#_test": { "dependsOn": ["^_build"], "inputs": ["src/**", "vite.config.ts", "package.json"], "outputs": [] }, - "documents#_test:coverage": { + "web#_test:coverage": { "dependsOn": ["^_build"], "inputs": ["src/**", "vite.config.ts", "package.json"], "outputs": ["coverage/**"] From 803effce28da4a5a7297b468c5d545b2fa4f70bb Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 18:44:50 +0100 Subject: [PATCH 2/3] fix(web): derive the GitHub Pages base path from GITHUB_REPOSITORY vite.config.ts hardcoded the Pages base path as "/documents/", the directory's old name. Every asset the build emitted therefore linked to a path GitHub Pages was no longer serving, and the deployed site at exadev.github.io/documents.js loaded a blank page: the root HTML returned 200, but every JS/CSS/manifest URL inside it 404'd. Deriving the path segment from GITHUB_REPOSITORY (owner/repo, set automatically by every Actions run), falling back to the name parsed from the git remote for a context where that variable is unset, removes the class of bug entirely -- a repo rename can no longer leave a stale literal behind for a future build to silently ship. parseGitHubRepoUrl is renamed to parseGitHubRepo and now returns both the repo name and the full URL, since __APP_REPO_URL__ already needed the URL and the base path now needs the bare name from the same parse. --- packages/web/vite.config.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index 7aad062ff..113bcd563 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -9,9 +9,6 @@ import { defineConfig } from "vitest/config"; import { BACKGROUND_COLOR, BRAND_COLOR } from "./src/design-tokens"; import { name as packageName } from "./package.json" with { type: "json" }; -// GitHub Pages serves this repo at /documents/ (exadev.github.io is already the org's own Pages root, so this app can never live at the bare domain). Local dev stays at '/'. -const base = process.env.CI ? "/documents/" : "/"; - // The sidebar's version link needs the real commit this build was produced from, and whether it happens to be an exact release tag -- read here rather than dry-running semantic-release, because CI's own job graph already guarantees the answer is sitting on disk by build time: the deploy job's checkout runs strictly after the release job (`needs: [..., release]`), re-fetching `ref: main` fresh, so if semantic-release just cut a release its version-bump commit and tag are already the checked-out HEAD. A dry run would only ever predict what real git state already states outright. function execGit(args: string[]): string { return execFileSync("git", args, { encoding: "utf-8" }).trim(); @@ -30,16 +27,28 @@ function tryExecGit(args: string[]): string | undefined { } // Handles both the HTTPS form GitHub Actions' checkout uses (optionally with embedded credentials) and the SSH form a local clone might use -- the regex searches rather than anchors from the start, so a credentials prefix before "github.com" doesn't break the match. The repo group is deliberately non-greedy over "anything" rather than "anything but a dot": a repo name is free to contain dots of its own (this workspace's own origin, ExaDev/documents.js, is exactly such a name), and the non-greedy quantifier already stops at the shortest match that still lets the optional ".git" suffix and end-of-string anchor succeed, so a real ".git" suffix is still stripped correctly either way. -function parseGitHubRepoUrl(remoteUrl: string): string { +function parseGitHubRepo(remoteUrl: string): { repo: string; url: string } { const match = /github\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/.exec(remoteUrl); if (match === null) throw new Error( `Could not parse a GitHub owner/repo from origin remote URL: ${remoteUrl}`, ); const [, owner, repo] = match; - return `https://github.com/${owner}/${repo}`; + if (owner === undefined || repo === undefined) + throw new Error( + `Could not parse a GitHub owner/repo from origin remote URL: ${remoteUrl}`, + ); + return { repo, url: `https://github.com/${owner}/${repo}` }; } +const { repo: repoNameFromRemote, url: repoUrl } = parseGitHubRepo( + execGit(["remote", "get-url", "origin"]), +); +// GitHub Pages serves a project site at // (exadev.github.io is already the org's own Pages root, so this app can never live at the bare domain). GITHUB_REPOSITORY (owner/repo, set automatically by every Actions run) is the primary source, since it names the actual repository this build is running in; the git-remote-derived name above is only a fallback for a context where that env var happens to be unset. Deriving this rather than writing the path segment as a literal is deliberate: a hardcoded path silently drifts the moment the repo is renamed, and every asset the build emits then 404s once deployed, since the browser never sees the mismatch until it tries to load them. Local dev stays at '/'. +const base = process.env.CI + ? `/${process.env.GITHUB_REPOSITORY?.split("/")[1] ?? repoNameFromRemote}/` + : "/"; + const commitSha = execGit(["rev-parse", "HEAD"]); // @exadev/semantic-release-workspace's tagFormat is '${pkg.name}@${version}' (see release-workspace.config.json and the orchestrator's own release.ts), not semantic-release's bare 'v${version}' default -- validated here so an unrelated tag some clone happens to have checked out, or a sibling package's release tag reachable from this same commit, can't be mistaken for this package's own release. const exactTag = tryExecGit(["describe", "--tags", "--exact-match", "HEAD"]); @@ -48,7 +57,6 @@ const releaseTagPattern = new RegExp( ); const releaseTag = exactTag !== undefined && releaseTagPattern.test(exactTag) ? exactTag : null; -const repoUrl = parseGitHubRepoUrl(execGit(["remote", "get-url", "origin"])); // %ct is the committer date as Unix seconds -- for a release commit this is effectively its release time (semantic-release commits, tags, and publishes the release in the same CI step), and for an ordinary commit it's simply when that commit was made. Multiplied to milliseconds for direct use with Date.now()-based relative time. const commitTimestampMs = Number(execGit(["show", "-s", "--format=%ct", "HEAD"])) * 1000; From 54d7b3f36f774e15b5ce331f04e7520e151d77d3 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 18:46:07 +0100 Subject: [PATCH 3/3] ci: verify the deployed Pages site's own assets actually resolve deploy-site's own success only proves actions/deploy-pages accepted the upload -- it says nothing about whether the page GitHub Pages now serves actually works. The base-path bug this branch fixes shipped past every existing check for exactly that reason: the root HTML returned 200 while every asset it referenced 404'd. The new job fetches the live deployed HTML, extracts every script, stylesheet, icon, and manifest URL it references, and fetches each one individually. Named distinctly from the pre-existing test-smoke job (that one exercises each package's own built dist/ as a local artifact; this one exercises the live Pages deployment over the network) so a failure in either is unambiguous about which broke. --- .github/workflows/ci.yml | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b0d4a73d..a60dff3cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -640,6 +640,8 @@ jobs: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} + outputs: + page_url: ${{ steps.deployment.outputs.page_url }} steps: - uses: actions/checkout@v7 with: @@ -669,3 +671,42 @@ jobs: path: packages/web/dist - id: deployment uses: actions/deploy-pages@v5 + + verify-deployed-site: + name: Verify the deployed site's own assets actually resolve + # A 200 on the page URL alone doesn't prove the deploy works: the HTML can serve fine while every asset it references 404s (e.g. a GitHub Pages project-site base-path mismatch, the exact bug this job exists to catch). Fetch the real deployed HTML and confirm every script/stylesheet/icon/manifest it links to actually resolves. + needs: [deploy-site] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Fetch the deployed page and verify its referenced assets resolve + env: + PAGE_URL: ${{ needs.deploy-site.outputs.page_url }} + run: | + set -euo pipefail + + if [ -z "$PAGE_URL" ]; then + echo "::error::deploy-site's page_url output was empty -- actions/deploy-pages didn't report a URL, so there is nothing to verify." + exit 1 + fi + + html="$(curl -sf "$PAGE_URL")" + origin="$(printf '%s' "$PAGE_URL" | sed -E 's#^(https?://[^/]+).*#\1#')" + + # Every script src and link href (stylesheet/preload/icon/manifest) ending in one of these extensions -- the asset kinds a Vite PWA build actually emits and references from the page. Extracted paths are absolute (the build's own base config guarantees this), so they resolve against the origin alone, never by concatenating onto PAGE_URL itself -- PAGE_URL already carries the base path, so that would double it. grep exits non-zero when nothing matches; `|| true` stops that from tripping `set -e`/pipefail before the explicit empty-result check below gets a chance to report it properly. + assets="$(printf '%s' "$html" | grep -oE '(src|href)="[^"]+\.(js|css|svg|png|webmanifest)"' | sed -E 's/^(src|href)="//; s/"$//' | sort -u || true)" + + if [ -z "$assets" ]; then + echo "::error::No JS/CSS/SVG/PNG/webmanifest asset references were found in the HTML at $PAGE_URL -- asset extraction itself is broken, which hides real broken-asset deploys just as effectively as not checking at all." + exit 1 + fi + + while IFS= read -r asset; do + [ -z "$asset" ] && continue + asset_url="${origin}${asset}" + if ! curl -sf -o /dev/null "$asset_url"; then + echo "::error::Deployed asset failed to resolve: $asset_url (referenced from $PAGE_URL)" + exit 1 + fi + done <<< "$assets"