diff --git a/.gitignore b/.gitignore index f4827ea..8fc86ae 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist/ node_modules/ coverage.out pocketstack-demo/ +docs/.vitepress/dist/ +docs/.vitepress/cache/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 7e16f48..c879f0c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -38,11 +38,11 @@ archives: - SECURITY.md - CONTRIBUTING.md - CODE_OF_CONDUCT.md - - docs/*.md + - docs/**/*.md - docs/media/* - examples/**/* - - site/* - - studio/* + - web/site/* + - web/studio/* checksum: name_template: "checksums.txt" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c26d7e..5efd7cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to PocketStack are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -Per-release detail lives in [docs/RELEASE_NOTES_v*.md](docs/). +Per-release detail lives under [docs/release-notes/](docs/release-notes/index.md). ## [1.1.0] - 2026-06-28 @@ -48,14 +48,14 @@ Per-release detail lives in [docs/RELEASE_NOTES_v*.md](docs/). Refocused the product around browser-native readiness: `analyze` reports a readiness score, blockers, suggestions, and next steps; clearer primary unsupported reasons; conversion guide. See -[release notes](docs/RELEASE_NOTES_v1.0.3.md). +[release notes](docs/release-notes/v1.0.3.md). ## [1.0.2] - 2026-05-27 Hosted Studio, generated example demos, and the GitHub Pages site. See -[release notes](docs/RELEASE_NOTES_v1.0.2.md). +[release notes](docs/release-notes/v1.0.2.md). ## [1.0.1] - 2026-05-26 -Hardening release. See [release notes](docs/RELEASE_NOTES_v1.0.1.md). +Hardening release. See [release notes](docs/release-notes/v1.0.1.md). ## [1.0.0] - 2026-05-26 Initial browser-native PocketStack release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fc3117..8cecabd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,8 @@ release. Work on a branch and open a pull request against `main`. Note user-facing changes in [CHANGELOG.md](CHANGELOG.md) under an `Unreleased`/next-version -heading; the [release process](docs/RELEASE.md) covers tagging and publishing. +heading; the [release process](docs/contribute/releasing.md) covers tagging and +publishing. New adapters should add: @@ -38,5 +39,8 @@ New adapters should add: - generated manifest coverage; - browser runtime tests; - an example Compose project; -- documentation in the compatibility matrix; +- documentation under [docs/adapters/](docs/adapters/index.md); - clear unsupported-feature reporting for nearby cases that still cannot work. + +The full developer guide — repo layout, prerequisites, and the new-adapter +walkthrough — lives at [docs/contribute/](docs/contribute/index.md). diff --git a/Makefile b/Makefile index db177d1..4f8990a 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ runtime: npm run build:runtime studio: - python3 -m http.server $(PORT) --directory studio + python3 -m http.server $(PORT) --directory web/studio media: npm run media @@ -42,6 +42,7 @@ smoke: build npm run test:smoke pages: smoke + npm run docs:build npm run pages:build release-check: test lint smoke release-dry-run verify-checksums diff --git a/README.md b/README.md index 6b85c18..d41f88f 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,29 @@ # PocketStack PocketStack turns browser-compatible Docker Compose projects into shareable -demos that run as static browser apps. +demos that run as static browser apps — no hidden server, no runner, and no +Docker at demo time. -> Drop in `docker-compose.yml`, get a static browser-native demo when every -> service can be mapped to browser primitives. +> Drop in a `compose.yaml`, get a static, browser-native demo when every service +> maps to a browser primitive. When one can't, PocketStack tells you why and how +> to reshape it — it never fakes a runner. -PocketStack v1 is intentionally browser-native. Generated demos do not start a -hidden server, upload projects to a runner, require Docker at demo time, or -pretend arbitrary Linux containers can run in a web page. If a service cannot -be represented honestly by a browser adapter, PocketStack gives you a -readiness report and concrete conversion suggestions. - -Try it now: +**[Try it now](https://ramazankara.github.io/pocketstack/)** · +**[Documentation](https://ramazankara.github.io/pocketstack/docs/)** · +**[Studio](https://ramazankara.github.io/pocketstack/studio/)** [![PocketStack announcement video](docs/media/pocketstack-announcement-poster.png)](docs/media/pocketstack-announcement.mp4) -## What You Can Demo - -PocketStack is useful for projects that have a browser-native shape: +## What you can demo -- static sites served by `nginx`, `httpd`, or `caddy`; -- frontend projects that can run with a Node/Bun browser runtime; -- prebuilt WASI modules; -- OpenAPI services that can be mocked from specs and fixtures; -- Postgres-flavored demos that fit PGlite; -- SQLite demos seeded from SQL or database files. +Six browser adapters map Compose services to real browser primitives: +`static-web`, `frontend` (WebContainer), `mock-http` (OpenAPI), `sqlite`, +`postgres-pglite`, and `wasi`. See the [compatibility matrix](docs/adapters/index.md). -It is not a Docker replacement. Privileged containers, arbitrary daemons, -opaque volume behavior, and real Linux networking remain unsupported unless a -specific browser adapter exists. +PocketStack is not a Docker replacement. Privileged containers, arbitrary +daemons, opaque volumes, and real Linux networking stay unsupported unless a +browser adapter exists — in which case `analyze` reports the gap and how to +[convert it](docs/convert/index.md). ## Install @@ -47,104 +41,45 @@ npm run build:runtime go build -o bin/pocketstack ./cmd/pocketstack ``` -The JavaScript toolchain targets the current Node line, Node 26. +Full instructions and prerequisites: [installation](docs/guide/installation.md). -## Quick Start - -Analyze a Compose project: +## Quick start ```sh +# See which services map to a browser adapter pocketstack analyze -f compose.yaml -``` -The analyzer returns a browser-readiness score, supported adapters, blockers, -and next steps for services that need to be converted into browser-native -pieces. - -Generate a browser-only demo: - -```sh +# Generate a static, browser-only demo pocketstack demo -f compose.yaml -o pocketstack-demo ``` -Serve `pocketstack-demo/` from any static host. Frontend/WebContainer and some -WASI demos require COOP/COEP headers; PocketStack emits host config files when -they are needed. See [docs/HOSTING.md](docs/HOSTING.md). +Serve `pocketstack-demo/` from any static host. Some demos need COOP/COEP +headers; PocketStack emits the host config when they do. Walk through it in +[getting started](docs/guide/getting-started.md), then see [hosting](docs/deploy/hosting.md). ## Studio -PocketStack Studio is a static browser page for quick compatibility checks. -Paste Compose YAML, upload a Compose file, and optionally add the project -folder so Studio can inspect mounted assets. +[PocketStack Studio](https://ramazankara.github.io/pocketstack/studio/) is a +static browser page for quick compatibility checks — paste or upload Compose +YAML and read the readiness report, entirely in the tab. Run it locally with +`make studio`. -Use the hosted Studio at , -or run it locally: +## Documentation -```sh -make studio -``` +Full docs are published at **** +(source under [`docs/`](docs/)): -Open . Use `make studio PORT=4174` if that port is -busy. - -Studio runs entirely in the tab. It does not call a PocketStack backend, -Docker daemon, runner, or hidden server. - -## Supported Adapters - -- `static-web`: copies document-root files from `nginx`, `httpd`, or `caddy` - services and previews them in an iframe. -- `frontend`: packages a Node/Bun project for browser runtime execution. -- `wasi`: runs an explicitly labeled prebuilt `.wasm` module. -- `mock-http`: serves OpenAPI routes and JSON fixtures from the demo service - worker. -- `postgres-pglite`: maps supported Postgres demos to resettable PGlite - browser storage. -- `sqlite`: runs SQLite from SQL or seed database assets in the browser. - -Unsupported services are reported with concrete reasons and no server fallback. -See [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md) for exact behavior and -limits. - -## Product Boundary - -PocketStack stays browser-native. It will not add a hidden Docker runner to -make unsupported services appear compatible. - -That means full arbitrary Compose compatibility is not the v1 promise. The -promise is better for static demos: when a stack can become browser-native, -PocketStack packages it; when it cannot, PocketStack explains the gap and how -to reshape the demo. - -## Examples - -The public site includes generated demos for the built-in examples: - -- [static web](https://ramazankara.github.io/pocketstack/demos/static-site/) -- [mock API](https://ramazankara.github.io/pocketstack/demos/mock-api/) -- [SQLite](https://ramazankara.github.io/pocketstack/demos/sqlite/) - -Studio-ready example projects live under -[examples/uploaded](examples/uploaded/README.md). They are small enough to -read, upload, and use as templates for your own Compose files. - -## Labels - -```yaml -labels: - pocketstack.adapter: frontend|wasi|mock-http|postgres-pglite|sqlite - pocketstack.frontend.install: npm install - pocketstack.frontend.start: npm run dev -- --host 0.0.0.0 - pocketstack.frontend.port: "5173" - pocketstack.wasi.module: hello.wasm - pocketstack.wasi.args: "--name PocketStack" - pocketstack.mock.openapi: openapi.yaml - pocketstack.mock.fixtures: fixtures - pocketstack.mock.port: "8080" - pocketstack.db.init: init.sql - pocketstack.db.seed: seed.sql - pocketstack.db.persist: indexeddb|memory -``` +- [Getting started](docs/guide/getting-started.md) · [CLI reference](docs/guide/cli.md) · [Concepts & glossary](docs/guide/concepts.md) · [Troubleshooting](docs/guide/troubleshooting.md) +- [Adapters & compatibility](docs/adapters/index.md) · [Labels](docs/adapters/labels.md) · [Conversion guide](docs/convert/index.md) +- [Hosting](docs/deploy/hosting.md) · [Website integration](docs/deploy/website-integration.md) · [Manifest reference](docs/deploy/manifest.md) +- [Architecture](docs/reference/architecture.md) · [Service URLs](docs/reference/service-urls.md) · [Contributing](docs/contribute/index.md) + +## Product boundary + +PocketStack stays browser-native. It will not add a hidden Docker runner to make +unsupported services appear compatible. When a stack can become browser-native, +PocketStack packages it; when it cannot, it explains the gap. See the +[browser-only contract](docs/guide/concepts.md). ## Commands @@ -154,20 +89,8 @@ pocketstack demo [-f compose.yaml] [-o pocketstack-demo] pocketstack version ``` -## Docs +## Contributing -- [Browser-only contract](docs/BROWSER_ONLY.md) -- [Compatibility matrix](docs/COMPATIBILITY.md) -- [Conversion guide](docs/CONVERSION_GUIDE.md) -- [Architecture](docs/ARCHITECTURE.md) -- [Static hosting](docs/HOSTING.md) -- [Website integration](docs/WEBSITE_INTEGRATION.md) -- [Browser testing](docs/BROWSER_TESTING.md) -- [Release process](docs/RELEASE.md) -- [Studio](studio/README.md) - -## Release Checks - -```sh -make release-check -``` +See [CONTRIBUTING.md](CONTRIBUTING.md) and the +[development guide](docs/contribute/index.md). Report security issues per +[SECURITY.md](SECURITY.md). Licensed under [MIT](LICENSE). diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 0000000..6898caa --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,107 @@ +import { defineConfig } from 'vitepress' + +// The docs site is published under the project Pages subpath: +// https://ramazankara.github.io/pocketstack/docs/ +export default defineConfig({ + title: 'PocketStack', + description: + 'Turn browser-compatible Docker Compose projects into static, browser-native demos.', + base: '/pocketstack/docs/', + lang: 'en-US', + cleanUrls: true, + lastUpdated: true, + // Strict: the build fails on broken internal links. + ignoreDeadLinks: false, + themeConfig: { + nav: [ + { text: 'Guide', link: '/guide/getting-started' }, + { text: 'Adapters', link: '/adapters/' }, + { text: 'Convert', link: '/convert/' }, + { text: 'Deploy', link: '/deploy/hosting' }, + { text: 'Reference', link: '/reference/architecture' }, + { + text: 'Try it', + items: [ + { text: 'Studio', link: 'https://ramazankara.github.io/pocketstack/studio/' }, + { text: 'Demos', link: 'https://ramazankara.github.io/pocketstack/demos/' }, + ], + }, + ], + sidebar: { + '/guide/': [ + { + text: 'Guide', + items: [ + { text: 'Getting started', link: '/guide/getting-started' }, + { text: 'Installation', link: '/guide/installation' }, + { text: 'CLI reference', link: '/guide/cli' }, + { text: 'Concepts & glossary', link: '/guide/concepts' }, + { text: 'Troubleshooting', link: '/guide/troubleshooting' }, + ], + }, + ], + '/adapters/': [ + { + text: 'Adapters', + items: [ + { text: 'Overview & matrix', link: '/adapters/' }, + { text: 'Labels', link: '/adapters/labels' }, + { text: 'Static web', link: '/adapters/static-web' }, + { text: 'Frontend', link: '/adapters/frontend' }, + { text: 'Mock HTTP', link: '/adapters/mock-http' }, + { text: 'SQLite', link: '/adapters/sqlite' }, + { text: 'Postgres (PGlite)', link: '/adapters/postgres-pglite' }, + { text: 'WASI', link: '/adapters/wasi' }, + ], + }, + ], + '/convert/': [ + { text: 'Convert', items: [{ text: 'Conversion guide', link: '/convert/' }] }, + ], + '/deploy/': [ + { + text: 'Deploy', + items: [ + { text: 'Hosting & headers', link: '/deploy/hosting' }, + { text: 'Website integration', link: '/deploy/website-integration' }, + { text: 'Manifest reference', link: '/deploy/manifest' }, + ], + }, + ], + '/reference/': [ + { + text: 'Reference', + items: [ + { text: 'Architecture', link: '/reference/architecture' }, + { text: 'Service URLs', link: '/reference/service-urls' }, + ], + }, + ], + '/contribute/': [ + { + text: 'Contributing', + items: [ + { text: 'Development setup', link: '/contribute/' }, + { text: 'Releasing', link: '/contribute/releasing' }, + { text: 'Browser testing', link: '/contribute/browser-testing' }, + ], + }, + ], + '/release-notes/': [ + { text: 'Release notes', link: '/release-notes/' }, + ], + }, + socialLinks: [ + { icon: 'github', link: 'https://github.com/ramazankara/pocketstack' }, + ], + search: { provider: 'local' }, + editLink: { + pattern: 'https://github.com/ramazankara/pocketstack/edit/main/docs/:path', + text: 'Edit this page on GitHub', + }, + footer: { + message: 'Browser-native demos. No hidden server, no runner, no Docker at demo time.', + copyright: 'MIT © Ramazan Kara', + }, + }, +}) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index 19983ea..0000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,99 +0,0 @@ -# Architecture - -PocketStack is a browser-only Compose demo compiler. It has three main pieces: -the analyzer, the static demo generator, and the browser runtime. - -```text -compose.yaml + project files - | - v - analyzer + adapter registry - | - v - static demo generator - | - v - index.html + app.js + manifest + assets - | - v - browser runtime dashboard -``` - -Read this page when you want to understand where a behavior belongs or how to -add a new adapter without weakening the browser-only contract. - -## Analyzer - -The analyzer reads a Compose file and classifies each service with the adapter -registry. A service is either `browser-native` or `unsupported`; there is no -server fallback state. - -This is intentionally conservative. A false positive would create a misleading -demo, so adapters are allowlisted and unsupported services keep concrete -reasons in the analysis output. The analyzer also produces a readiness score, -service suggestions, and project next steps so unsupported stacks still get a -useful conversion plan. - -Implemented adapters: - -- `static-web`: copies document-root files from `nginx`, `httpd`, or `caddy` - bind mounts and warns about server config that static hosting cannot emulate. -- `frontend`: packages a Node/Bun project for WebContainer-style browser - execution, including env handling, package-manager detection, and bridge - support for PocketStack mock/database endpoints. -- `wasi`: packages an explicitly labeled prebuilt `.wasm` module with browser - WASI preview imports and a Wasmer JS fallback. -- `mock-http`: turns OpenAPI YAML/JSON and JSON fixtures into service-worker - HTTP routes. -- `postgres-pglite`: maps supported Postgres demos to PGlite with SQL bootstrap, - IndexedDB or memory persistence, reset, and query bridge support. -- `sqlite`: runs SQL init/seed assets or seed databases through sql.js in the - browser. - -New adapters should live at this boundary first. The analyzer should be able -to explain why a service is supported, what files must be copied, what host -requirements apply, and what warnings the generated demo should show. - -## Generator - -`pocketstack demo` writes a static directory: - -- `index.html` -- `app.js` -- `mock-sw.js` -- `pocketstack.manifest.json` -- copied assets under `assets//` -- host config files when cross-origin isolation is required - -The manifest is version `2`, sets `browserOnly: true`, carries service adapter -metadata, readiness metadata, copied asset paths, generated warnings, host -requirements, and a stable storage namespace for browser database adapters. - -Generation only succeeds when every service is browser-native. If any service -is unsupported, PocketStack exits with a reasoned incompatibility report -instead of falling back to a server. - -The generator is responsible for making demos portable. Asset paths are copied -into deterministic service folders, static-site references are rewritten when -needed, and host config files are emitted when an adapter requires -cross-origin isolation. - -## Runtime - -Generated demos run as static browser code: - -- static-web services render as iframe previews; -- frontend services boot in a WebContainer-style runtime; -- mock services register routes in `mock-sw.js`; -- database services initialize PGlite or SQLite on demand; -- WASI services execute prebuilt WebAssembly modules; -- logs, status, warnings, previews, reset controls, and query panels live in the - generated dashboard. - -The runtime may load public browser packages or npm dependencies when an -adapter requires them, but it does not call a PocketStack backend. - -Runtime code should stay adapter-shaped. If a feature needs Docker networking, -privileged filesystem behavior, or a long-running server that is not a browser -primitive, it belongs in the unsupported report until there is a real browser -implementation. diff --git a/docs/BROWSER_ONLY.md b/docs/BROWSER_ONLY.md deleted file mode 100644 index 52cc98a..0000000 --- a/docs/BROWSER_ONLY.md +++ /dev/null @@ -1,55 +0,0 @@ -# Browser-Only Contract - -PocketStack demos must be static browser artifacts. That is the product -boundary, the trust boundary, and the compatibility boundary. - -That means: - -- no hidden backend; -- no remote runner; -- no local Docker daemon at demo time; -- no claim that arbitrary Linux containers run inside the browser. - -The CLI may inspect and package a local Compose project. The generated demo -must run as browser code plus static assets. - -PocketStack can analyze any Compose file, but it only generates demos for -browser-native stacks. Analysis should stay useful for unsupported projects by -explaining blockers and suggesting browser-native replacements. - -## Why This Exists - -A good demo is easy to open and easy to trust. If PocketStack says a project is -browser-native, the viewer should be able to put the generated folder on a -static host and know there is no hidden PocketStack service doing work off to -the side. - -That also means PocketStack must be honest about what it cannot do. Docker -Compose can describe arbitrary Linux processes, networking, filesystems, and -privileged behavior. Browsers cannot provide all of that. PocketStack supports -the subset that can be mapped to explicit browser adapters. - -## Compatibility Rule - -A Compose service is supported only when PocketStack can map it to an explicit -browser adapter such as static files, WebAssembly, WebContainer-style frontend -execution, browser databases, or generated mocks. - -Unsupported Compose features should produce clear analysis output. They can -become supported later only by adding an honest browser adapter, not by hiding -server work behind a static demo. - -## What Counts As Honest - -An adapter is honest when it names the browser primitive it uses and preserves -the important behavior of the demo: - -- `static-web` copies files and previews them as static assets. -- `frontend` runs a package-managed frontend in a browser runtime. -- `wasi` runs a prebuilt WebAssembly module. -- `mock-http` serves known HTTP responses from OpenAPI and fixtures. -- `postgres-pglite` and `sqlite` run browser database engines. - -An adapter is not honest if it silently calls a private backend, pretends to -provide full container semantics, or hides unsupported Docker behavior behind a -successful-looking preview. diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md deleted file mode 100644 index 01750bc..0000000 --- a/docs/COMPATIBILITY.md +++ /dev/null @@ -1,205 +0,0 @@ -# Compatibility Matrix - -PocketStack v1 supports Compose projects when every service maps to a real -browser adapter. If even one service needs Docker/container semantics that -PocketStack cannot represent honestly, generation stops with an unsupported -reason. - -`pocketstack analyze` is designed to be useful even when generation is blocked. -It reports a browser-readiness score, service blockers, and conversion -suggestions for turning a Compose stack into a browser-native demo. - -The short version: - -- Static sites are the easiest fit. -- Frontend apps can work when their source and package scripts are available. -- APIs can be mocked from OpenAPI plus fixtures. -- Small database demos can use PGlite or SQLite. -- WASI works for prebuilt `.wasm` modules. -- Arbitrary Linux containers, Docker builds, privileged daemons, and real - container networking are out of scope for browser-only v1. - -## Adapter Matrix - -- `static-web`: supports `nginx`, `httpd`, or `caddy` services with - document-root mounts. Regular files and directories are copied into the - generated demo. -- `frontend`: supports Node/Bun frontend projects with `package.json` and a - runnable command or script. -- `wasi`: supports explicitly labeled prebuilt WASM modules with browser WASI - and a Wasmer JS fallback. -- `mock-http`: supports OpenAPI YAML/JSON plus optional JSON fixtures through - service-worker routes. -- `postgres-pglite`: supports Postgres-shaped demos with SQL init or seed files - through PGlite. -- `sqlite`: supports explicit SQLite services with SQL or database seed assets - through sql.js. -- Unsupported: Docker builds, privileged containers, opaque volumes, arbitrary - daemons, and Linux networking are unsupported in browser-only v1. - -Unsupported does not mean impossible forever. It means there is no honest -browser adapter for that behavior yet. - -## Compose Features - -PocketStack analyzes a single Compose file and maps the default service set to -browser adapters. A few Compose directives are handled specifically: - -- `profiles:` — services gated behind a profile are not started by a default - `docker compose up`, so PocketStack skips them. They do not count toward, or - block, browser readiness, and the analysis warns when services are skipped. -- `extends:` — not supported. PocketStack does not resolve an extended base - service. Flatten the service (inline its image, labels, ports, and volumes) - before analyzing. -- Multiple Compose files / overrides — pass a single file with `-f`. Merge any - overrides yourself first. -- `depends_on:` and `healthcheck:` — parsed but ignored. A static demo has no - startup ordering or health gating. -- Port ranges such as `3000-3005:3000-3005` are accepted; the first port of the - range is used and adapter selection does not depend on the exact port. -- Image references are normalized, so short names (`postgres`), Docker Hub - official names (`library/postgres`), and registry-qualified names - (`docker.io/library/postgres:16`, `ghcr.io/org/app`) resolve to the same - adapter. - -## Readiness Report - -Every analysis result includes: - -- `readiness.status`: `ready`, `partial`, or `blocked`; -- `readiness.score`: percentage of services that are browser-native; -- service-level `unsupported` reasons; -- service-level `suggestions`; -- project-level `nextSteps`. - -The goal is to make unsupported services actionable. For example, a Redis -service is reported as stateful browser-incompatible and points you toward -SQLite, PGlite, fixtures, or in-browser mock state for the demo. - -## Labels - -Use labels when PocketStack cannot safely infer intent: - -```yaml -labels: - pocketstack.adapter: frontend|wasi|mock-http|postgres-pglite|sqlite - pocketstack.frontend.install: npm install - pocketstack.frontend.start: npm run dev -- --host 0.0.0.0 - pocketstack.frontend.port: "5173" - pocketstack.wasi.module: hello.wasm - pocketstack.wasi.args: "--name PocketStack" - pocketstack.mock.openapi: openapi.yaml - pocketstack.mock.fixtures: fixtures - pocketstack.mock.port: "8080" - pocketstack.db.init: init.sql - pocketstack.db.seed: seed.sql - pocketstack.db.persist: indexeddb|memory -``` - -`static-web` is autodetected from the image and document-root mounts. It is not -selected with `pocketstack.adapter`. - -## Static Web - -PocketStack copies files mounted at or below the image document root and -renders them in an iframe preview. It handles whole directories, single files -such as `index.html`, and common output folders such as `dist/`. - -Root-relative URLs in packaged HTML/CSS such as `/assets/app.css` are rewritten -so the copied site still works from the generated demo path. - -PocketStack does not emulate nginx/httpd/caddy redirects, rewrites, custom -headers, auth, compression, or other server configuration. Those cases produce -warnings instead of fake support. - -## Frontend - -Frontend services are meant for projects that can run from source in a browser -runtime. Autodetection requires a Node/Bun image plus `package.json`. -`pocketstack.adapter=frontend` can be used when the service image is not enough -to infer intent. - -PocketStack packages the project root or the bind-mounted `working_dir`, keeps -simple `entrypoint`/`command` start behavior, and passes Compose -`environment:` plus `env_file:` values into the browser runtime. Required env -files must be present in the uploaded/generated project. Optional long-syntax -env files may be missing and are reported as warnings. - -When frontend code points at a `mock-http` service with a Compose-style URL -such as `http://api:8080`, the generated runtime rewrites it to the static -demo's browser mock URL. PocketStack also injects service URL environment -variables such as `POCKETSTACK_API_URL` and `VITE_POCKETSTACK_API_URL`. - -If a frontend needs PocketStack mock or database endpoints from inside the -preview iframe, the generator mounts a small bridge script into the project. -The bridge forwards only known PocketStack demo endpoints. It is not a general -network proxy. - -## Mock HTTP - -Mock services turn OpenAPI specs and JSON fixtures into static browser routes. -YAML parsing is bundled into the generated runtime, so packaged demos do not -need a parser CDN just to read local specs. - -Supported mock features include: - -- OpenAPI YAML or JSON; -- local `#/components/...` and path-item references; -- path templates and required query parameter examples/defaults; -- response status codes, headers, media types, and no-body responses; -- JSON fixture overrides; -- request-aware fixtures using `request.params`, `request.query`, - `request.json`, `request.text`, or `bodyFrom: "request"`; -- CORS and preflight responses for frontend demos. - -Fixture directories package `.json` files only. Other files are skipped with a -warning, and a fixtures-only mock must contain at least one JSON fixture. - -## Browser Databases - -`postgres-pglite` and `sqlite` adapters initialize browser databases from -packaged SQL or seed database assets. Generated demos include a stable storage -namespace so multiple PocketStack demos can share one static origin without -reusing the same IndexedDB keys. - -Both adapters expose a demo-only query endpoint: - -```text -/__pocketstack/db//query -``` - -Send `POST` JSON such as `{"sql":"select 1"}` to query the in-browser -database and receive adapter-native JSON. This is for demos and custom browser -UI. It is not a Postgres TCP server, Docker networking, or a backend proxy. - -## PGlite Postgres - -`postgres-pglite` can package SQL files from `pocketstack.db.init`, -`pocketstack.db.seed`, and local bind mounts under -`/docker-entrypoint-initdb.d`. - -SQL files execute once for persisted databases and again after reset. Shell -scripts, compressed dumps, and other Docker entrypoint behavior are not run in -browser-only mode. - -## SQLite - -`sqlite` can package a single SQL file, a directory of `.sql` files, or a -binary `.db`, `.sqlite`, or `.sqlite3` seed file. SQL directories execute in -sorted order. Binary database seeds are loaded as the starting database image -rather than executed as SQL. - -Frontend environments receive both URL and DB URL variables, for example -`POCKETSTACK_DB_URL`, `VITE_POCKETSTACK_DB_URL`, -`POCKETSTACK_DB_DB_URL`, and `VITE_POCKETSTACK_DB_DB_URL`. - -## WASI - -`wasi` supports prebuilt modules only. PocketStack does not compile source, -run Docker builds, or manufacture a WASI module from a container image. - -The generated demo first tries built-in browser WASI preview imports for common -preview1 modules. If the module needs fuller WASI/WASIX behavior, it falls -back to Wasmer JS with the same argv and Compose environment values. That -fallback requires cross-origin isolation headers and public CDN access, but it -still does not use a PocketStack backend. diff --git a/docs/CONVERSION_GUIDE.md b/docs/CONVERSION_GUIDE.md deleted file mode 100644 index b169ab3..0000000 --- a/docs/CONVERSION_GUIDE.md +++ /dev/null @@ -1,186 +0,0 @@ -# Browser-Native Conversion Guide - -PocketStack does not run arbitrary Docker containers in the browser. It helps -you turn the parts of a Compose project that matter for a demo into -browser-native pieces. - -Use this guide when `pocketstack analyze` reports unsupported services. - -## The Rule - -Ask what the demo needs to prove. - -If the viewer only needs to see UI, API behavior, seeded data, or a workflow, -you can often replace production infrastructure with browser-native demo -adapters. If the viewer needs real Linux processes, real networking, privileged -behavior, or production-grade persistence, that service is not a browser-native -demo candidate. - -## Backend HTTP APIs - -Best browser-native replacement: `mock-http`. - -Use this when the frontend needs predictable API responses. - -```yaml -services: - api: - image: scratch - labels: - pocketstack.adapter: mock-http - pocketstack.mock.openapi: openapi.yaml - pocketstack.mock.fixtures: fixtures - pocketstack.mock.port: "8080" -``` - -What to keep: - -- OpenAPI paths and response examples; -- JSON fixtures for important states; -- request-aware fixtures for small dynamic behavior. - -What not to claim: - -- real backend business logic; -- arbitrary server middleware; -- production auth/session behavior. - -## Redis, Caches, Queues, and Workers - -Best browser-native replacement: fixture state or in-browser state. - -Redis, queues, and background workers usually exist to support app behavior, -not to be inspected directly in a demo. For a browser-native demo, model the -visible result instead: - -- replace cached API responses with fixtures; -- represent queued jobs as seeded records; -- expose worker output as static JSON; -- move small state machines into frontend demo code. - -If the queue itself is the product, PocketStack v1 is not the right runtime. - -## Postgres - -Best browser-native replacement: `postgres-pglite`. - -Keep demo schema and data in SQL files: - -```yaml -services: - db: - image: postgres:16 - volumes: - - ./db:/docker-entrypoint-initdb.d:ro - labels: - pocketstack.db.persist: indexeddb -``` - -Works well for: - -- schemas; -- seed data; -- simple SQL-backed demos; -- resettable browser storage. - -Does not emulate: - -- Postgres TCP wire protocol; -- extensions that PGlite does not support; -- shell scripts in `/docker-entrypoint-initdb.d`; -- replication, users, permissions, or server tuning. - -## MySQL, MariaDB, MongoDB, and Other Databases - -Best browser-native replacement: SQLite, PGlite, or fixtures. - -For demo purposes, choose the smallest representation that still proves the -workflow: - -- use SQLite when the data can be relational and local; -- use PGlite when Postgres SQL behavior matters; -- use JSON fixtures when the UI only needs API responses. - -PocketStack v1 does not run these database servers in the browser. - -## Static Sites - -Best browser-native replacement: `static-web`. - -Mount the built site into a known document root: - -```yaml -services: - web: - image: nginx:alpine - volumes: - - ./dist:/usr/share/nginx/html:ro -``` - -PocketStack copies files and rewrites common root-relative asset paths. It does -not emulate server rewrites, auth, compression, custom headers, or runtime -nginx/httpd/caddy behavior. - -## Frontends - -Best browser-native replacement: `frontend`. - -Make sure the project folder contains `package.json` and a runnable script: - -```yaml -services: - app: - image: node:22-alpine - working_dir: /app - command: npm run dev -- --host 0.0.0.0 - volumes: - - ./app:/app - ports: - - "5173:5173" -``` - -If the frontend calls services by Compose hostnames such as `http://api:8080`, -PocketStack can rewrite known mock/database service URLs for the generated -demo. - -## Dockerfile Builds - -Browser-native demos do not run Docker builds. - -Use one of these instead: - -- commit or generate static build output before running PocketStack; -- mount frontend source directly; -- compile the relevant code to WASI yourself; -- describe backend behavior as OpenAPI mocks and fixtures. - -## WASI - -Best browser-native replacement: `wasi`. - -PocketStack can package a prebuilt `.wasm` module: - -```yaml -services: - tool: - image: scratch - labels: - pocketstack.adapter: wasi - pocketstack.wasi.module: hello.wasm - pocketstack.wasi.args: "--name PocketStack" -``` - -PocketStack does not convert arbitrary container images to WASI. If a project -can compile cleanly to WASI, do that in your build pipeline and point -PocketStack at the result. - -## Conversion Checklist - -For each unsupported service: - -- Identify what the viewer must experience in the demo. -- Replace invisible infrastructure with fixtures or seeded browser state. -- Replace APIs with `mock-http` when examples are enough. -- Replace demo databases with SQLite or PGlite when SQL matters. -- Compile to WASI only when the app genuinely supports WASI. -- Leave the service unsupported when the demo needs real container semantics. diff --git a/docs/HOSTING.md b/docs/HOSTING.md deleted file mode 100644 index 65665fd..0000000 --- a/docs/HOSTING.md +++ /dev/null @@ -1,71 +0,0 @@ -# Static Hosting - -Generated PocketStack demos are static browser artifacts. Host the output -directory on GitHub Pages, Netlify, Cloudflare Pages, Vercel, Azure Static Web -Apps, S3-compatible object storage, or any static web server. - -The public PocketStack site is built for GitHub Pages by `.github/workflows/pages.yml`. -It publishes Studio, selected generated demos, docs links, and media assets. - -## Basic Deploy - -Generate the demo: - -```sh -pocketstack demo -f compose.yaml -o pocketstack-demo -``` - -Upload the whole `pocketstack-demo/` directory. Keep `index.html`, -`pocketstack.manifest.json`, `app.js`, `mock-sw.js`, and `assets/` together; -generated demos use relative paths so they can live under a subpath. - -## Cross-Origin Isolation - -Frontend/WebContainer demos require: - -```text -Cross-Origin-Opener-Policy: same-origin -Cross-Origin-Embedder-Policy: require-corp -``` - -WASI demos also require those headers when they fall back to Wasmer JS. - -When a generated demo needs cross-origin isolation, PocketStack writes host -config files with equivalent headers: - -- `_headers` for Netlify and Cloudflare Pages; -- `vercel.json` for Vercel; -- `staticwebapp.config.json` for Azure Static Web Apps. - -Hosts that do not understand those files must be configured manually with the -same headers. - -GitHub Pages does not let project repositories set arbitrary response headers. -Use it for the public site, Studio, static-web demos, mock demos, and browser -database demos that do not require COOP/COEP. Host WebContainer or Wasmer -fallback demos on a header-capable static host for full behavior. - -## Host Choice - -Use GitHub Pages when you want a public preview, docs site, static-web demo, -mock demo, or browser database demo that does not need custom headers. - -Use Netlify, Cloudflare Pages, Vercel, Azure Static Web Apps, or your own -server when a demo needs COOP/COEP. Those hosts can apply the generated config -files or equivalent manual headers. - -## Network Access - -Some adapters load browser-only runtime packages or install npm dependencies in -the viewer's browser. This is still backend-free: no PocketStack server is -used, but the browser may need internet access to public package/runtime CDNs. - -Adapters that can require network access include: - -- `frontend` for browser-time package installs and WebContainer runtime code; -- `wasi` when the Wasmer JS fallback is needed; -- `postgres-pglite` and `sqlite` for browser database runtime packages. - -If you need a demo that also works offline, prefer `static-web` and -`mock-http`, or vendor the frontend/database runtime assets in a future adapter -before promising offline behavior. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 2a0ca15..0000000 --- a/docs/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# PocketStack Docs - -These docs are written for developers deciding whether PocketStack can turn a -Compose project into a useful live demo. - -Start here: - -- [Compatibility matrix](COMPATIBILITY.md): supported Compose patterns, - labels, readiness reports, adapter behavior, and unsupported boundaries. -- [Browser-native conversion guide](CONVERSION_GUIDE.md): practical ways to - reshape unsupported services into useful static demos. -- [Browser-only contract](BROWSER_ONLY.md): the promise PocketStack keeps when - it says a demo is browser-native. -- [Static hosting](HOSTING.md): COOP/COEP, generated host config files, and - network access expectations. -- [Website integration](WEBSITE_INTEGRATION.md): linking, iframe embedding, - subpath hosting, and custom UI service URLs. - -Then go deeper: - -- [Architecture](ARCHITECTURE.md): analyzer, adapter registry, generated demo - runtime, manifest shape, and where to extend the system. -- [Browser testing](BROWSER_TESTING.md): Chrome/Edge/Safari-class smoke checks - for the public site and generated demos. -- [Release process](RELEASE.md): local checks, GoReleaser, checksums, and tag - publishing. -- [Changelog](../CHANGELOG.md): version history at a glance. -- [v1.1.0 release notes](RELEASE_NOTES_v1.1.0.md): current release highlights - and constraints. - -PocketStack v1 is browser-only. Unsupported Compose features should remain -explicitly unsupported until an honest browser adapter exists for them. diff --git a/docs/WEBSITE_INTEGRATION.md b/docs/WEBSITE_INTEGRATION.md deleted file mode 100644 index 755772f..0000000 --- a/docs/WEBSITE_INTEGRATION.md +++ /dev/null @@ -1,143 +0,0 @@ -# Website Integration - -PocketStack demos are static folders. Any website can link to them, embed them, -or host them under a route. - -Use a link when the demo deserves its own page. Use an iframe when the demo is -part of a docs page, announcement post, changelog, or product page. - -## Generate - -```sh -pocketstack demo -f compose.yaml -o pocketstack-demo -``` - -Upload the full `pocketstack-demo/` directory to your website or static host. -Keep the directory structure intact; the manifest, runtime, service worker, and -assets use relative paths. - -## Link - -The simplest integration is a normal link: - -```html -Open live demo -``` - -This gives the demo its own route, browser history, service-worker scope, and -full viewport. - -## Embed - -You can embed a demo in an iframe: - -```html - -``` - -The iframe page is still a static PocketStack demo. It does not call a -PocketStack backend. - -For embedded demos, give the iframe enough height for the dashboard and preview -pane. `720px` is a good starting point for desktop docs pages. - -## Host Under a Subpath - -PocketStack demos are subpath-safe. These are all valid: - -```text -https://example.com/demo/ -https://example.com/docs/demos/app/ -https://example.com/releases/v1/pocketstack-demo/ -``` - -Upload the whole generated folder under that route. Do not move individual -files out of the folder. - -If your website builder fingerprints or moves assets, configure it to copy the -generated demo folder as a static directory. - -## Headers - -Some adapters need cross-origin isolation: - -```text -Cross-Origin-Opener-Policy: same-origin -Cross-Origin-Embedder-Policy: require-corp -``` - -PocketStack emits `_headers`, `vercel.json`, and -`staticwebapp.config.json` when those headers are required. - -Hosts that support custom headers: - -- Netlify -- Cloudflare Pages -- Vercel -- Azure Static Web Apps -- your own static web server - -GitHub Pages is useful for public static previews and docs, but it does not let -project repositories set arbitrary response headers. Demos that require -COOP/COEP should be hosted on a header-capable static host for full behavior. - -The generated `pocketstack.manifest.json` includes host requirements so a -custom website can warn users before loading a header-dependent demo. - -## Service URLs For Custom UI - -Generated demos expose browser-only URLs that custom frontend code can call: - -```text -/__pocketstack/mock// -/__pocketstack/db//query -``` - -Database query endpoint example: - -```js -const response = await fetch("/__pocketstack/db/db/query", { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ sql: "select 1" }), -}); -const payload = await response.json(); -``` - -These endpoints exist only inside the generated browser demo. They are not -Docker networking, Postgres TCP, or a general backend proxy. - -Use them for custom demo controls, fixture explorers, or small query panels -that live next to an embedded PocketStack demo. - -## Embed In Documentation Sites - -For docs sites, copy the demo output into the published static directory: - -```text -docs-site/ - index.html - demos/ - my-compose-demo/ - index.html - pocketstack.manifest.json - app.js - mock-sw.js - assets/ -``` - -Then link or iframe `/demos/my-compose-demo/`. - -## Checklist - -Before publishing a demo from another website: - -- Confirm `pocketstack.manifest.json` loads from the final URL. -- Open the generated dashboard and start each service. -- Check whether `hostRequirements.crossOriginIsolationRequired` is true. -- If embedding, verify the iframe height at desktop and mobile widths. -- Keep the demo folder intact when copying it through the site build. diff --git a/docs/adapters/frontend.md b/docs/adapters/frontend.md new file mode 100644 index 0000000..7513802 --- /dev/null +++ b/docs/adapters/frontend.md @@ -0,0 +1,63 @@ +# Frontend + +The `frontend` adapter packages Node/Bun projects that can run from source in a +browser runtime, so the demo serves your actual app rather than a prebuilt copy. + +## How it's selected + +A frontend service is **autodetected** when it uses a Node/Bun image and has a +`package.json`. Use `pocketstack.adapter=frontend` when the image alone is not +enough to infer intent. + +```yaml +services: + app: + image: node:22-alpine + working_dir: /app + command: npm run dev -- --host 0.0.0.0 + volumes: + - ./app:/app + ports: + - "5173:5173" + labels: + pocketstack.adapter: frontend + pocketstack.frontend.install: npm install + pocketstack.frontend.start: npm run dev -- --host 0.0.0.0 + pocketstack.frontend.port: "5173" +``` + +## What it packages + +PocketStack packages the project root or the bind-mounted `working_dir` and +keeps simple `entrypoint`/`command` start behavior. + +The `pocketstack.frontend.*` labels make the install command, start command, and +port explicit when they cannot be inferred. See the +[labels reference](/adapters/labels) for accepted values. + +## Environment variables + +Compose `environment:` and `env_file:` values are passed into the browser +runtime. + +- Required env files must be present in the uploaded/generated project. +- Optional long-syntax env files may be missing and are reported as warnings. + +## Talking to mock and database services + +When frontend code points at a `mock-http` service with a Compose-style URL such +as `http://api:8080`, the generated runtime rewrites it to the static demo's +browser mock URL. PocketStack also injects service URL environment variables such +as `POCKETSTACK_API_URL` and `VITE_POCKETSTACK_API_URL`. + +If a frontend needs PocketStack mock or database endpoints from inside the +preview iframe, the generator mounts a small **bridge script** into the project. +The bridge forwards only known PocketStack demo endpoints — it is not a general +network proxy. For the exact endpoint shapes, see the +[service URLs](/reference/service-urls). + +## Cross-origin isolation + +The frontend runtime requires cross-origin isolation (COOP/COEP) headers. +PocketStack emits host config files when they are needed. See +[hosting & headers](/deploy/hosting) for how to serve them. diff --git a/docs/adapters/index.md b/docs/adapters/index.md new file mode 100644 index 0000000..6ab34e5 --- /dev/null +++ b/docs/adapters/index.md @@ -0,0 +1,79 @@ +# Adapters + +PocketStack converts a browser-compatible Docker Compose project into a static, +browser-native demo. Every Compose service maps to one **adapter** — a browser +primitive that runs entirely in the tab — or is reported as unsupported with +concrete conversion suggestions. + +A project generates only when **every** service maps to an adapter. If even one +service needs Docker/container semantics PocketStack cannot represent honestly, +generation stops and you get a [readiness report](#readiness-report) instead. + +## Adapter matrix + +There are six adapters. Five are selected with the `pocketstack.adapter` label; +`static-web` is autodetected and has no label. + +| Adapter | Maps | How it's selected | +| --- | --- | --- | +| [`static-web`](/adapters/static-web) | `nginx` / `httpd` / `caddy` static sites | **Autodetected** from the image + a document-root mount (no label) | +| [`frontend`](/adapters/frontend) | Node/Bun projects that run from source | Autodetected (Node/Bun image + `package.json`), or `pocketstack.adapter=frontend` | +| [`mock-http`](/adapters/mock-http) | OpenAPI + JSON fixtures as browser routes | `pocketstack.adapter=mock-http` | +| [`postgres-pglite`](/adapters/postgres-pglite) | Postgres-shaped demos backed by PGlite | `pocketstack.adapter=postgres-pglite` | +| [`sqlite`](/adapters/sqlite) | SQLite demos seeded from SQL or a `.db` file | `pocketstack.adapter=sqlite` | +| [`wasi`](/adapters/wasi) | Prebuilt `.wasm` modules | `pocketstack.adapter=wasi` | + +See the [labels reference](/adapters/labels) for every label and its accepted +values. + +::: info +Unsupported is not permanent. It means there is no honest browser adapter for +that behavior yet. Docker builds, privileged containers, opaque volumes, +arbitrary daemons, and Linux networking remain unsupported in browser-only v1. +::: + +## Compose features + +PocketStack analyzes a **single** Compose file and maps the default service set +to adapters. A few directives are handled specifically: + +- **`profiles:`** — services gated behind a profile are not started by a default + `docker compose up`, so PocketStack skips them. Skipped services do not count + toward, or block, browser readiness, and the analysis warns when they are + skipped. +- **`extends:`** — unsupported. PocketStack does not resolve an extended base + service. Flatten the service (inline its image, labels, ports, and volumes) + before analyzing. +- **Multiple files / overrides** — pass a single file with `-f`. Merge any + overrides yourself first. +- **Port ranges** such as `3000-3005:3000-3005` are accepted; the first port of + the range is used and adapter selection does not depend on the exact port. +- **Image normalization** — short names (`postgres`), Docker Hub official names + (`library/postgres`), and registry-qualified names + (`docker.io/library/postgres:16`, `ghcr.io/org/app`) all resolve to the same + adapter. +- **`depends_on:` / `healthcheck:`** — parsed but ignored. A static demo has no + startup ordering or health gating. + +## Readiness report + +`pocketstack analyze` is useful even when generation is blocked. Every analysis +result includes: + +- **`readiness.status`** — `ready`, `partial`, or `blocked`; +- **`readiness.score`** — the percentage of services that are browser-native; +- service-level **`unsupported`** reasons; +- service-level **`suggestions`**; +- project-level **`nextSteps`**. + +The goal is to make unsupported services actionable. For example, a Redis +service is reported as stateful browser-incompatible and points you toward +SQLite, PGlite, fixtures, or in-browser mock state for the demo. + +## Unsupported services + +When a service has no honest adapter, reshape the demo rather than expecting a +hidden runner. The [conversion guide](/convert/) walks through replacing +backends with `mock-http`, databases with `sqlite` or `postgres-pglite`, caches +and queues with fixtures or in-browser state, and Dockerfile builds with +prebuilt output. diff --git a/docs/adapters/labels.md b/docs/adapters/labels.md new file mode 100644 index 0000000..6f420bc --- /dev/null +++ b/docs/adapters/labels.md @@ -0,0 +1,70 @@ +# Labels + +Use labels when PocketStack cannot safely infer intent from a service's image +and mounts. This page is the full label reference. + +`static-web` is **autodetected** from a supported image plus a document-root +mount — it has no label. The other five adapters are selected explicitly with +`pocketstack.adapter`. + +```yaml +labels: + pocketstack.adapter: frontend|wasi|mock-http|postgres-pglite|sqlite + pocketstack.frontend.install: npm install + pocketstack.frontend.start: npm run dev -- --host 0.0.0.0 + pocketstack.frontend.port: "5173" + pocketstack.wasi.module: hello.wasm + pocketstack.wasi.args: "--name PocketStack" + pocketstack.mock.openapi: openapi.yaml + pocketstack.mock.fixtures: fixtures + pocketstack.mock.port: "8080" + pocketstack.db.init: init.sql + pocketstack.db.seed: seed.sql + pocketstack.db.persist: indexeddb|memory +``` + +## Selecting an adapter + +- **`pocketstack.adapter`** — selects the adapter for the service. Accepted + values: `frontend`, `wasi`, `mock-http`, `postgres-pglite`, `sqlite`. Omit it + for `static-web`, which is autodetected. + +## Frontend labels + +See the [frontend adapter](/adapters/frontend) for details. + +- **`pocketstack.frontend.install`** — the install command run for the project + (for example `npm install`). +- **`pocketstack.frontend.start`** — the start command for the dev server (for + example `npm run dev -- --host 0.0.0.0`). +- **`pocketstack.frontend.port`** — the port the dev server listens on, as a + quoted string (for example `"5173"`). + +## WASI labels + +See the [wasi adapter](/adapters/wasi) for details. + +- **`pocketstack.wasi.module`** — path to the prebuilt `.wasm` module to run. +- **`pocketstack.wasi.args`** — argv passed to the module (for example + `"--name PocketStack"`). + +## Mock HTTP labels + +See the [mock-http adapter](/adapters/mock-http) for details. + +- **`pocketstack.mock.openapi`** — path to the OpenAPI spec (YAML or JSON). +- **`pocketstack.mock.fixtures`** — directory of `.json` fixture files. A + fixtures-only mock must contain at least one JSON fixture. +- **`pocketstack.mock.port`** — the port the mock service listens on, as a + quoted string (for example `"8080"`). + +## Database labels + +Shared by the [sqlite](/adapters/sqlite) and +[postgres-pglite](/adapters/postgres-pglite) adapters. + +- **`pocketstack.db.init`** — path to a SQL file run to initialize the database + schema. +- **`pocketstack.db.seed`** — path to a SQL file run to seed data. +- **`pocketstack.db.persist`** — persistence mode. Accepted values: + `indexeddb` (persist across reloads) or `memory` (no persistence). diff --git a/docs/adapters/mock-http.md b/docs/adapters/mock-http.md new file mode 100644 index 0000000..ca83a1f --- /dev/null +++ b/docs/adapters/mock-http.md @@ -0,0 +1,50 @@ +# Mock HTTP + +The `mock-http` adapter turns OpenAPI specs and JSON fixtures into static +browser routes served from the demo's service worker. YAML parsing is bundled +into the generated runtime, so packaged demos do not need a parser CDN just to +read local specs. + +## How it's selected + +Set `pocketstack.adapter=mock-http` and point at the spec (and optionally a +fixtures directory). + +```yaml +services: + api: + image: scratch + labels: + pocketstack.adapter: mock-http + pocketstack.mock.openapi: openapi.yaml + pocketstack.mock.fixtures: fixtures + pocketstack.mock.port: "8080" +``` + +See the [labels reference](/adapters/labels) for accepted values. + +## Supported features + +- OpenAPI YAML or JSON; +- local `#/components/...` and path-item references; +- path templates and required query parameter examples/defaults; +- response status codes, headers, media types, and no-body responses; +- JSON fixture overrides; +- request-aware fixtures using `request.params`, `request.query`, + `request.json`, `request.text`, or `bodyFrom: "request"`; +- CORS and preflight responses for frontend demos. + +## Fixtures + +Fixture directories package `.json` files only. Other files are skipped with a +warning. + +::: warning +A fixtures-only mock (no OpenAPI spec) must contain at least one JSON fixture. +::: + +## Route URL + +Mock routes are served under a `/__pocketstack/mock//` path. See +the [service URLs](/reference/service-urls) for the exact route shape and how +`` and `` are resolved. diff --git a/docs/adapters/postgres-pglite.md b/docs/adapters/postgres-pglite.md new file mode 100644 index 0000000..ae8d582 --- /dev/null +++ b/docs/adapters/postgres-pglite.md @@ -0,0 +1,57 @@ +# PGlite Postgres + +The `postgres-pglite` adapter maps Postgres-shaped demos to **PGlite**, a +Postgres build that runs in the browser. It packages your schema and seed SQL and +serves a resettable in-browser database. + +## How it's selected + +Set `pocketstack.adapter=postgres-pglite` and provide SQL through the +`pocketstack.db.*` labels or bind mounts under `/docker-entrypoint-initdb.d`. + +```yaml +services: + db: + image: postgres:16 + volumes: + - ./db:/docker-entrypoint-initdb.d:ro + labels: + pocketstack.adapter: postgres-pglite + pocketstack.db.init: init.sql + pocketstack.db.seed: seed.sql + pocketstack.db.persist: indexeddb +``` + +See the [labels reference](/adapters/labels) for accepted values +(`pocketstack.db.persist` is `indexeddb` or `memory`). + +## SQL sources + +The adapter packages SQL files from: + +- `pocketstack.db.init`; +- `pocketstack.db.seed`; +- local bind mounts under `/docker-entrypoint-initdb.d`. + +Multi-statement SQL is run through PGlite's exec. + +::: warning +Shell scripts, compressed dumps, and other Docker entrypoint behavior are **not** +run in browser-only mode. Only SQL is executed. +::: + +## Persistence and reset + +SQL files execute **once** for persisted databases and **again after reset**. +Use `pocketstack.db.persist: indexeddb` to persist across reloads, or `memory` +for a fresh database each load. Generated demos include a stable storage +namespace so multiple PocketStack demos can share one static origin without +reusing the same IndexedDB keys. + +## Querying the database + +The adapter exposes a demo-only query endpoint at +`/__pocketstack/db//query`. Send `POST` JSON such as `{"sql":"select 1"}` +and receive adapter-native JSON. This is for demos and custom browser UI — it is +not a Postgres TCP server, Docker networking, or a backend proxy. See the +[service URLs](/reference/service-urls) for the full request/response shape. diff --git a/docs/adapters/sqlite.md b/docs/adapters/sqlite.md new file mode 100644 index 0000000..2cd07ba --- /dev/null +++ b/docs/adapters/sqlite.md @@ -0,0 +1,56 @@ +# SQLite + +The `sqlite` adapter runs SQLite in the browser via **sql.js**, seeded from SQL +or a binary database file. It is a good fit for small relational demos that need +real SQL but not a server. + +## How it's selected + +Set `pocketstack.adapter=sqlite` and provide seed assets through the +`pocketstack.db.*` labels or bind mounts. + +```yaml +services: + db: + image: scratch + labels: + pocketstack.adapter: sqlite + pocketstack.db.init: schema.sql + pocketstack.db.seed: seed.sql + pocketstack.db.persist: indexeddb +``` + +See the [labels reference](/adapters/labels) for accepted values +(`pocketstack.db.persist` is `indexeddb` or `memory`). + +## Seed sources + +The `sqlite` adapter can package: + +- a single `.sql` file; +- a directory of `.sql` files — executed in **sorted order**; +- a binary `.db`, `.sqlite`, or `.sqlite3` seed file. + +Binary database seeds are loaded as the starting database image rather than +executed as SQL. + +## Persistence + +Generated demos include a stable storage namespace so multiple PocketStack demos +can share one static origin without reusing the same IndexedDB keys. Use +`pocketstack.db.persist: indexeddb` to persist across reloads, or `memory` for a +fresh database each load. + +## Querying the database + +The adapter exposes a demo-only query endpoint at +`/__pocketstack/db//query`. Send `POST` JSON such as `{"sql":"select 1"}` +and receive adapter-native JSON. This is for demos and custom browser UI — it is +not a database server or backend proxy. See the +[service URLs](/reference/service-urls) for the full request/response shape. + +## Frontend integration + +Frontend environments receive both URL and DB URL variables, for example +`POCKETSTACK_DB_URL`, `VITE_POCKETSTACK_DB_URL`, `POCKETSTACK_DB_DB_URL`, and +`VITE_POCKETSTACK_DB_DB_URL`. diff --git a/docs/adapters/static-web.md b/docs/adapters/static-web.md new file mode 100644 index 0000000..5013933 --- /dev/null +++ b/docs/adapters/static-web.md @@ -0,0 +1,50 @@ +# Static Web + +The `static-web` adapter serves static sites from `nginx`, `httpd`, or `caddy` +services. PocketStack copies the document-root files into the generated demo and +renders them in an iframe preview. + +## How it's selected + +`static-web` is **autodetected** — it is the only adapter not chosen with +`pocketstack.adapter`. PocketStack detects it from a supported image +(`nginx`, `httpd`, or `caddy`) plus a mount at or below the image's document +root. + +```yaml +services: + web: + image: nginx:alpine + volumes: + - ./dist:/usr/share/nginx/html:ro +``` + +## What it packages + +PocketStack copies files mounted at or below the image document root and renders +them in the preview. It handles: + +- whole directories; +- single files such as `index.html`; +- common output folders such as `dist/`. + +Root-relative URLs in packaged HTML/CSS — such as `/assets/app.css` — are +rewritten so the copied site still works from the generated demo path. + +## What is not emulated + +PocketStack copies files; it does not run a web server. It does **not** emulate +nginx/httpd/caddy: + +- redirects; +- rewrites; +- custom headers; +- auth; +- compression; +- other server configuration. + +::: warning +These cases produce warnings instead of fake support. If your demo depends on +server-side behavior (a rewrite rule, an auth gate, a custom header), that +behavior will not be present in the static copy. +::: diff --git a/docs/adapters/wasi.md b/docs/adapters/wasi.md new file mode 100644 index 0000000..aff3b7e --- /dev/null +++ b/docs/adapters/wasi.md @@ -0,0 +1,43 @@ +# WASI + +The `wasi` adapter runs **prebuilt** `.wasm` modules in the browser. PocketStack +does not compile source, run Docker builds, or manufacture a WASI module from a +container image — point it at an already-built module. + +## How it's selected + +Set `pocketstack.adapter=wasi` and provide the module (and optional argv) through +the `pocketstack.wasi.*` labels. + +```yaml +services: + tool: + image: scratch + labels: + pocketstack.adapter: wasi + pocketstack.wasi.module: hello.wasm + pocketstack.wasi.args: "--name PocketStack" +``` + +See the [labels reference](/adapters/labels) for accepted values. + +## What it runs + +The generated demo first tries built-in browser WASI preview1 imports for common +preview1 modules. If the module needs fuller WASI/WASIX behavior, it falls back +to **Wasmer JS** with the same argv and Compose environment values. + +argv (from `pocketstack.wasi.args`) and environment values are passed through to +the module. + +::: warning +Prebuilt modules only. There is **no** source compilation or build step. If a +project can compile cleanly to WASI, do that in your build pipeline and point +PocketStack at the result. +::: + +## Cross-origin isolation + +The Wasmer JS fallback requires cross-origin isolation (COOP/COEP) headers and +public CDN access. It still does not use a PocketStack backend. See +[hosting & headers](/deploy/hosting) for how to serve the required headers. diff --git a/docs/BROWSER_TESTING.md b/docs/contribute/browser-testing.md similarity index 59% rename from docs/BROWSER_TESTING.md rename to docs/contribute/browser-testing.md index 0ba7dbd..4f15d17 100644 --- a/docs/BROWSER_TESTING.md +++ b/docs/contribute/browser-testing.md @@ -9,38 +9,34 @@ npm run test:browsers The suite serves `dist/pages` locally and checks: -- landing page loads; +- the landing page loads; - Studio loads; - Studio sample analysis runs in Chromium-based browsers; -- generated static demo dashboard loads; -- generated manifest loads; -- static demo preview loads. +- the generated static demo dashboard loads; +- the generated manifest loads; +- the static demo preview loads. -CI installs Playwright browsers and runs the suite against: +CI installs Playwright browsers and runs the suite against Chrome/Chromium, +Microsoft Edge, and Safari-class WebKit. Locally, the test skips browsers that +are not installed unless `POCKETSTACK_REQUIRE_BROWSERS=1` is set. -- Chrome/Chromium; -- Microsoft Edge; -- Safari-class WebKit. - -Locally, the test skips browsers that are not installed unless -`POCKETSTACK_REQUIRE_BROWSERS=1` is set. - -## When To Run It +## When to run it Run the browser suite after changes to: -- `site/` -- `studio/` +- `web/site` +- `web/studio` - generated demo HTML/CSS/runtime behavior - `scripts/build-pages-site.mjs` - GitHub Pages workflow configuration -The suite is intentionally a smoke test. It catches broken public navigation, +::: info +This is intentionally a **smoke test**. It catches broken public navigation, missing generated assets, dashboard regressions, and browser-launch issues. It -does not replace adapter unit tests or Playwright coverage for every generated -demo category. +does not replace adapter unit tests or per-demo Playwright coverage. +::: -## Reading Failures +## Reading failures An Edge skip on a local machine usually means Microsoft Edge is not installed. CI treats browser skips as failures because it sets diff --git a/docs/contribute/index.md b/docs/contribute/index.md new file mode 100644 index 0000000..7eecf0f --- /dev/null +++ b/docs/contribute/index.md @@ -0,0 +1,99 @@ +# Contributing + +PocketStack accepts changes that preserve the **browser-only contract**. This +page covers dev setup, the verify commands, and what a good contribution looks +like. + +## The browser-only contract + +Contributions must keep all of these true: + +- no hidden backend; +- no remote runner fallback; +- no Docker daemon requirement at demo time; +- no claims that arbitrary Linux containers run in the browser. + +Good contributions make the supported surface **clearer**. If a Compose feature +cannot be represented by a browser adapter, prefer a precise unsupported reason +over a partial demo that looks more compatible than it is. + +## Prerequisites + +- **Go** — to build the CLI and run `go test` / `go vet`. +- **Node 26** — the JavaScript toolchain targets Node 26. Use `nvm use` to + select it (CI and release workflows pin `node-version: "26"`). + +```sh +nvm use +``` + +## Repo layout + +The pipeline is **compose (model) → analyzer → generator → embedded runtime**: + +- `internal/compose` — Compose file model (types + `LoadFile`); +- `internal/analyzer` — adapter detection, readiness, suggestions, analysis + types; +- `internal/generator` — static demo generation; embeds the built runtime at + `internal/generator/runtime/`; +- `web/runtime` — the browser runtime TypeScript (`web/runtime/src/app.ts`), + bundled by esbuild into `internal/generator/runtime/app.js`; +- `web/studio` — the in-browser Studio analyzer; `web/site` — the landing page; +- `cmd/pocketstack` — CLI entry point. + +For the full picture, including the embedded-runtime build chain and extension +points, see the [architecture reference](/reference/architecture). + +## Verify before opening a change + +Run these before opening a pull request: + +```sh +npm ci +npm run build:wasi-example +npm run build:runtime +npm run test:runtime +go test ./... +go vet ./... +make smoke +``` + +- `npm run build:runtime` rebuilds `internal/generator/runtime/app.js` from + `web/runtime/src/app.ts`. Generated demos do not pick up runtime changes until + you run it. +- `make smoke` builds the binary, regenerates every example demo, and runs the + generated-demo checks. + +::: tip +`make release-check` is the **full gate** — it additionally runs lint/`go vet`, a +GoReleaser snapshot build, and checksum verification. Run it before preparing a +release. See [releasing](/contribute/releasing). +::: + +If your change touches the public site, Studio, or generated demo HTML/CSS/ +runtime behavior, also run the [browser tests](/contribute/browser-testing). + +## Adding a new adapter + +A new adapter starts in the analyzer and flows through the pipeline (see +[architecture: extension points](/reference/architecture#extension-points-adding-an-adapter)). +It should add: + +- analyzer classification tests; +- generated [manifest](/deploy/manifest) coverage; +- browser runtime tests; +- an example Compose project; +- documentation in the [adapter matrix](/adapters/) / compatibility docs; +- clear unsupported-feature reporting for nearby cases that still cannot work. + +## Pull requests + +Work on a branch and open a pull request against `main`. Note user-facing +changes in `CHANGELOG.md` under an `Unreleased` / next-version heading; the +[release process](/contribute/releasing) covers tagging and publishing. + +## More + +- [Releasing](/contribute/releasing) — the maintainer release process. +- [Browser testing](/contribute/browser-testing) — the public-site and demo + smoke suite. diff --git a/docs/RELEASE.md b/docs/contribute/releasing.md similarity index 58% rename from docs/RELEASE.md rename to docs/contribute/releasing.md index 18aeafb..846e605 100644 --- a/docs/RELEASE.md +++ b/docs/contribute/releasing.md @@ -1,12 +1,13 @@ -# Release +# Releasing -PocketStack v1 releases are published from Git tags. +PocketStack v1 releases are published from Git tags. Use this process when a +commit is ready to become a downloadable GitHub release. For docs-only changes +that do not need binary artifacts, a normal merge to `main` is enough. -Use this process when a commit is ready to become a downloadable GitHub -release. For docs-only changes that do not need binary artifacts, a normal -merge to `main` is enough. +This is the maintainer process. For day-to-day contribution checks, see +[contributing](/contribute/). -## Local Gate +## Local gate Run the full local gate before tagging: @@ -16,12 +17,11 @@ make release-check ``` That target runs Go tests, runtime tests, `go vet`, generated-demo smoke tests, -a GoReleaser snapshot, and checksum verification. +a GoReleaser snapshot, and checksum verification. The Node toolchain targets +Node 26 (CI and release workflows use `actions/setup-node` with +`node-version: "26"`). -The Node toolchain targets Node 26; CI and release workflows use -`actions/setup-node` with `node-version: "26"`. - -The individual commands are: +The individual commands `release-check` covers are: ```sh npm ci @@ -51,7 +51,7 @@ git push origin main "$VERSION" The GitHub `release` workflow runs GoReleaser and publishes Linux, macOS, and Windows binaries for amd64 and arm64 with checksums. -## After Publish +## After publish Check the release before announcing it: @@ -68,3 +68,8 @@ examples, or generated demo behavior: ```sh curl -L --fail https://ramazankara.github.io/pocketstack/ ``` + +::: tip +Record user-facing changes in `CHANGELOG.md` and add a per-version entry under +[release notes](/release-notes/) so the published notes match the tag. +::: diff --git a/docs/convert/index.md b/docs/convert/index.md new file mode 100644 index 0000000..4ea84f8 --- /dev/null +++ b/docs/convert/index.md @@ -0,0 +1,171 @@ +# Browser-Native Conversion + +PocketStack does not run arbitrary Docker containers in the browser. It helps +you turn the parts of a Compose project that matter *for a demo* into +browser-native pieces. + +Use this guide when `pocketstack analyze` reports unsupported services. For how +each adapter is selected, see the [adapter matrix](/adapters/). + +## The rule + +Ask what the demo needs to prove. + +If the viewer only needs to see UI, API behavior, seeded data, or a workflow, +you can often replace production infrastructure with browser-native adapters. If +the viewer needs real Linux processes, real networking, privileged behavior, or +production-grade persistence, that service is not a browser-native demo +candidate — leave it unsupported. + +## Backend HTTP APIs → `mock-http` + +Use [`mock-http`](/adapters/mock-http) when the frontend needs predictable API +responses. + +```yaml +services: + api: + image: scratch + labels: + pocketstack.adapter: mock-http + pocketstack.mock.openapi: openapi.yaml + pocketstack.mock.fixtures: fixtures + pocketstack.mock.port: "8080" +``` + +What to keep: + +- OpenAPI paths and response examples; +- JSON fixtures for important states; +- request-aware fixtures for small dynamic behavior. + +What not to claim: + +- real backend business logic; +- arbitrary server middleware; +- production auth/session behavior. + +## Redis, caches, queues, and workers → fixtures or in-browser state + +Redis, queues, and background workers usually exist to support app behavior, not +to be inspected directly in a demo. Model the **visible result** instead: + +- replace cached API responses with fixtures; +- represent queued jobs as seeded records; +- expose worker output as static JSON; +- move small state machines into [`frontend`](/adapters/frontend) demo code. + +::: info +If the queue itself is the product, browser-only PocketStack v1 is not the right +runtime for that service. +::: + +## Postgres → `postgres-pglite` + +Keep demo schema and data in SQL files and map the service to +[`postgres-pglite`](/adapters/postgres-pglite): + +```yaml +services: + db: + image: postgres:16 + volumes: + - ./db:/docker-entrypoint-initdb.d:ro + labels: + pocketstack.db.persist: indexeddb +``` + +Works well for schemas, seed data, simple SQL-backed demos, and resettable +browser storage. Does **not** emulate the Postgres TCP wire protocol, extensions +PGlite does not support, shell scripts in `/docker-entrypoint-initdb.d`, or +replication/users/permissions/tuning. + +## MySQL, MariaDB, MongoDB, and other databases → SQLite / PGlite / fixtures + +PocketStack v1 does not run these database servers in the browser. Choose the +smallest representation that still proves the workflow: + +- use [`sqlite`](/adapters/sqlite) when the data can be relational and local; +- use [`postgres-pglite`](/adapters/postgres-pglite) when Postgres SQL behavior + matters; +- use JSON fixtures via [`mock-http`](/adapters/mock-http) when the UI only needs + API responses. + +## Static sites → `static-web` + +Mount the built site into a known document root and let +[`static-web`](/adapters/static-web) copy it: + +```yaml +services: + web: + image: nginx:alpine + volumes: + - ./dist:/usr/share/nginx/html:ro +``` + +PocketStack copies files and rewrites common root-relative asset paths. It does +not emulate server rewrites, auth, compression, custom headers, or runtime +nginx/httpd/caddy behavior. + +## Frontends → `frontend` + +Make sure the project folder contains `package.json` and a runnable script, then +use [`frontend`](/adapters/frontend): + +```yaml +services: + app: + image: node:22-alpine + working_dir: /app + command: npm run dev -- --host 0.0.0.0 + volumes: + - ./app:/app + ports: + - "5173:5173" +``` + +If the frontend calls services by Compose hostnames such as `http://api:8080`, +PocketStack rewrites known mock/database service URLs for the generated demo. +See [service URLs](/reference/service-urls) for the endpoint shapes. + +## Dockerfile builds → prebuild, then static / frontend / WASI + +Browser-native demos do not run Docker builds. Instead: + +- commit or generate static build output before running PocketStack (→ + [`static-web`](/adapters/static-web)); +- mount frontend source directly (→ [`frontend`](/adapters/frontend)); +- compile the relevant code to WASI yourself (→ [`wasi`](/adapters/wasi)); +- describe backend behavior as OpenAPI mocks and fixtures (→ + [`mock-http`](/adapters/mock-http)). + +## WASI → prebuilt module + +PocketStack can package a prebuilt `.wasm` module with [`wasi`](/adapters/wasi): + +```yaml +services: + tool: + image: scratch + labels: + pocketstack.adapter: wasi + pocketstack.wasi.module: hello.wasm + pocketstack.wasi.args: "--name PocketStack" +``` + +PocketStack does not convert arbitrary container images to WASI. If a project +compiles cleanly to WASI, do that in your build pipeline and point PocketStack at +the result. + +## Conversion checklist + +For each unsupported service: + +- Identify what the viewer must experience in the demo. +- Replace invisible infrastructure with fixtures or seeded browser state. +- Replace APIs with [`mock-http`](/adapters/mock-http) when examples are enough. +- Replace demo databases with [`sqlite`](/adapters/sqlite) or + [`postgres-pglite`](/adapters/postgres-pglite) when SQL matters. +- Compile to [`wasi`](/adapters/wasi) only when the app genuinely supports WASI. +- Leave the service unsupported when the demo needs real container semantics. diff --git a/docs/deploy/hosting.md b/docs/deploy/hosting.md new file mode 100644 index 0000000..b321717 --- /dev/null +++ b/docs/deploy/hosting.md @@ -0,0 +1,142 @@ +# Hosting & Headers + +A generated PocketStack demo is a static folder of browser artifacts. There is +no server, runner, or Docker daemon at demo time — so you can host the output on +any static web host: GitHub Pages, Netlify, Cloudflare Pages, Vercel, Azure +Static Web Apps, S3-compatible object storage, or your own web server. + +This page is the **single source of truth for the COOP/COEP header values**. Any +other page that mentions cross-origin isolation links here rather than restating +the values. + +## Deploy the output + +Generate the demo: + +```sh +pocketstack demo -f compose.yaml -o pocketstack-demo +``` + +Upload the whole `pocketstack-demo/` directory. Keep these together: + +- `index.html` +- `pocketstack.manifest.json` +- `app.js` +- `mock-sw.js` +- `assets/` +- any emitted host-config files (see below) + +Generated demos use relative paths, so the folder can live at the site root or +under a subpath without changes. See +[website integration](/deploy/website-integration) for linking, embedding, and +subpath hosting. + +## Cross-origin isolation + +Some adapters require the browsing context to be **cross-origin isolated**. That +is enabled by two response headers: + +| Header | Value | +| --- | --- | +| `Cross-Origin-Opener-Policy` | `same-origin` | +| `Cross-Origin-Embedder-Policy` | `require-corp` | + +These are needed by: + +- **`frontend`** demos — the WebContainer-style runtime requires cross-origin + isolation; +- **`wasi`** demos — when they fall back to the Wasmer JS runtime for fuller + browser WASI/WASIX execution. + +::: warning +Without these headers, frontend demos fail fast with a clear COOP/COEP message, +and the WASI Wasmer fallback cannot run. Demos that do not need isolation +(`static-web`, `mock-http`, `postgres-pglite`, `sqlite`) work without them. +::: + +The generated `pocketstack.manifest.json` records whether isolation is required +in `hostRequirements.crossOriginIsolationRequired`, so a host or custom UI can +warn users before loading a header-dependent demo. See the +[manifest reference](/deploy/manifest). + +## Emitted host-config files + +When a demo needs cross-origin isolation, PocketStack writes host-config files +next to `index.html` so common static hosts apply the headers automatically: + +| File | Host | Applies headers to | +| --- | --- | --- | +| `_headers` | Netlify, Cloudflare Pages | `/*` | +| `vercel.json` | Vercel | `/(.*)` | +| `staticwebapp.config.json` | Azure Static Web Apps | global headers | + +If isolation is **not** required, none of these files are written. + +`_headers` is a plain-text file: + +```text +/* + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp +``` + +`vercel.json` and `staticwebapp.config.json` carry the same two header values in +each host's native JSON format. + +::: tip +A host that does not understand any of these files must be configured manually +with the same two headers from the table above. +::: + +## Host comparison + +| Host | Custom headers | Good for | +| --- | --- | --- | +| Netlify | Yes (`_headers`) | Any demo, including `frontend`/WASI-fallback | +| Cloudflare Pages | Yes (`_headers`) | Any demo, including `frontend`/WASI-fallback | +| Vercel | Yes (`vercel.json`) | Any demo, including `frontend`/WASI-fallback | +| Azure Static Web Apps | Yes (`staticwebapp.config.json`) | Any demo, including `frontend`/WASI-fallback | +| Your own web server | Yes (manual) | Any demo, when you control response headers | +| GitHub Pages | **No** (project repos) | Static-web, mock, and browser-database demos that do **not** need COOP/COEP | + +### The GitHub Pages limitation + +GitHub Pages does not let project repositories set arbitrary response headers. +It cannot apply COOP/COEP, and the emitted host-config files are ignored. + +What this means in practice: + +- **Fine on GitHub Pages:** the public site, Studio, `static-web` demos, + `mock-http` demos, and `postgres-pglite` / `sqlite` browser-database demos + that do not require isolation. +- **Not fully functional on GitHub Pages:** `frontend` (WebContainer) demos and + `wasi` demos that need the Wasmer JS fallback. Host those on a header-capable + static host (Netlify, Cloudflare Pages, Vercel, Azure Static Web Apps, or your + own server) for full behavior. + +The public PocketStack site itself is built for GitHub Pages by +`.github/workflows/pages.yml`, which publishes Studio, selected generated demos, +docs links, and media assets. + +## Network access at demo time + +A static demo is still **backend-free**: no PocketStack server is involved. +However, some adapters load browser-only runtime packages or install npm +dependencies in the *viewer's* browser, so the browser may need internet access +to public package/runtime CDNs. + +Adapters that can require network/CDN access at demo time: + +- **`frontend`** — browser-time package installs and WebContainer runtime code; +- **`wasi`** — when the Wasmer JS fallback is needed; +- **`postgres-pglite`** and **`sqlite`** — browser database runtime packages. + +The manifest records this in `hostRequirements.networkAccessRequired`. Runtime +packages loaded from public CDNs are version-pinned, so previously generated +demos do not break when an upstream package ships a new major. + +::: info +For a demo that must work fully offline, prefer `static-web` and `mock-http`, +which do not pull runtime packages. See the [adapter matrix](/adapters/) for +which adapter fits each service. +::: diff --git a/docs/deploy/manifest.md b/docs/deploy/manifest.md new file mode 100644 index 0000000..44cd09d --- /dev/null +++ b/docs/deploy/manifest.md @@ -0,0 +1,168 @@ +# Manifest Reference + +Every generated demo includes a `pocketstack.manifest.json` at its root. It +describes the demo in a stable, machine-readable form: the generation mode, +host requirements, readiness, and one entry per service with its adapter, +copied assets, and config. + +A custom website or the browser runtime reads the manifest to render the +dashboard, warn about [host requirements](/deploy/hosting), and locate copied +assets. The manifest is **version `2`** and always sets `browserOnly: true`. + +## Top-level fields + +| Field | Type | Notes | +| --- | --- | --- | +| `version` | string | Manifest schema version. Currently `"2"`. | +| `generatedAt` | string | RFC 3339 UTC timestamp of generation. | +| `mode` | string | Generation mode reported by the analyzer. | +| `browserOnly` | boolean | Always `true`. No backend is involved at demo time. | +| `composeFile` | string | Path to the Compose file the demo was generated from. | +| `storageNamespace` | string | Stable per-project namespace (e.g. `ps-1a2b3c4d…`) used by browser-database adapters for IndexedDB/storage keys. | +| `readiness` | object | Browser-readiness summary (see [readiness](#readiness)). | +| `hostRequirements` | object | Cross-origin isolation / network needs (see [host requirements](#host-requirements)). Omitted when empty. | +| `warnings` | string[] | Project-level warnings (COOP/COEP, network access, skipped profile services). Omitted when empty. | +| `nextSteps` | string[] | Project-level conversion next steps. Omitted when empty. | +| `services` | object[] | One [service entry](#service-fields) per generated service. | + +### readiness + +`readiness` mirrors the analyzer's readiness report: + +| Field | Type | Notes | +| --- | --- | --- | +| `status` | string | `ready`, `partial`, or `blocked`. | +| `browserNativeServices` | number | Count of browser-native services. | +| `totalServices` | number | Count of services considered. | +| `score` | number | Percentage of services that are browser-native. | +| `summary` | string | Human-readable readiness summary. | + +::: info +A demo only generates when every service is browser-native, so a successful +manifest reports a `ready` readiness. The same readiness fields appear in +`pocketstack analyze` output before generation. See the +[readiness report](/adapters/#readiness-report). +::: + +### host requirements + +`hostRequirements` appears at both the top level (for the whole demo) and on +each service. The header **values** live in [hosting & headers](/deploy/hosting). + +| Field | Type | Notes | +| --- | --- | --- | +| `crossOriginIsolationRequired` | boolean | `true` when a COOP/COEP-isolated context is required. Omitted when false. | +| `networkAccessRequired` | boolean | `true` when the viewer's browser needs CDN/network access at demo time. Omitted when false. | +| `headers` | object | Header name/value pairs the host must apply, when present. Omitted when empty. | + +## Service fields + +Each entry in `services[]` is a `ManifestService`: + +| Field | Type | Notes | +| --- | --- | --- | +| `name` | string | Service name from Compose. | +| `image` | string | Original image reference. Omitted when empty. | +| `adapter` | string | Selected adapter: `static-web`, `frontend`, `mock-http`, `postgres-pglite`, `sqlite`, or `wasi`. See the [adapter matrix](/adapters/). | +| `browserNative` | boolean | `true` for every service in a generated demo. | +| `publicPort` | number | The service's public port, when one applies. Omitted when zero. | +| `browserPath` | string | Path to the service's entry document (e.g. a static site's `index.html`), when it has one. Omitted otherwise. | +| `assets` | object[] | Copied [asset entries](#asset-fields). Omitted when empty. | +| `config` | object | String key/value adapter config (e.g. `projectPath`, `openapiPath`, `fixturesPath`, `initScripts`, `seedPath`, `storageNamespace`). Omitted when empty. | +| `warnings` | string[] | Service-level warnings. Omitted when empty. | +| `hostRequirements` | object | Per-service [host requirements](#host-requirements). Omitted when empty. | + +::: tip +`config` keys depend on the adapter. Database adapters +(`postgres-pglite`, `sqlite`) also receive the demo-wide `storageNamespace` in +their `config`. Treat `config` as an open string map keyed by adapter. +::: + +### asset fields + +Each entry in a service's `assets[]` is a `ManifestAsset` describing files +copied under `assets//`: + +| Field | Type | Notes | +| --- | --- | --- | +| `name` | string | Logical asset name (e.g. `static`, `project`, `module`, `openapi`, `fixtures`, `init`, `seed`). | +| `kind` | string | How it was copied: `file`, `directory`, `sql-directory`, or `json-directory`. | +| `path` | string | Root-relative path to the copied asset, e.g. `assets//`. | +| `files` | string[] | For directory kinds, the list of copied files (relative paths). Omitted for single files. | +| `target` | string | Destination path relative to the service asset folder. Omitted when empty. | + +## Example + +A trimmed, realistic manifest for a two-service demo (a `mock-http` API and a +`postgres-pglite` database): + +```json +{ + "version": "2", + "generatedAt": "2026-06-28T10:15:00Z", + "mode": "browser-only", + "browserOnly": true, + "composeFile": "compose.yaml", + "storageNamespace": "ps-1a2b3c4d5e6f7081", + "readiness": { + "status": "ready", + "browserNativeServices": 2, + "totalServices": 2, + "score": 100, + "summary": "All services are browser-native." + }, + "services": [ + { + "name": "api", + "image": "scratch", + "adapter": "mock-http", + "browserNative": true, + "publicPort": 8080, + "assets": [ + { + "name": "openapi", + "kind": "file", + "path": "assets/api/openapi.yaml", + "target": "openapi.yaml" + }, + { + "name": "fixtures", + "kind": "json-directory", + "path": "assets/api/fixtures", + "files": ["users.json", "orders.json"], + "target": "fixtures" + } + ], + "config": { + "openapiPath": "assets/api/openapi.yaml", + "fixturesPath": "assets/api/fixtures" + } + }, + { + "name": "db", + "image": "postgres:16", + "adapter": "postgres-pglite", + "browserNative": true, + "assets": [ + { + "name": "init", + "kind": "sql-directory", + "path": "assets/db/init", + "files": ["001-schema.sql", "002-seed.sql"], + "target": "init" + } + ], + "config": { + "initPath": "assets/db/init", + "storageNamespace": "ps-1a2b3c4d5e6f7081" + } + } + ] +} +``` + +::: info +When a service requires cross-origin isolation, the same generation run also +emits `_headers`, `vercel.json`, and `staticwebapp.config.json` next to the +manifest. See [hosting & headers](/deploy/hosting). +::: diff --git a/docs/deploy/website-integration.md b/docs/deploy/website-integration.md new file mode 100644 index 0000000..74c5d5d --- /dev/null +++ b/docs/deploy/website-integration.md @@ -0,0 +1,127 @@ +# Website Integration + +A PocketStack demo is a static folder. Any website can link to it, embed it in +an iframe, or host it under a route. The demo never calls a PocketStack backend, +so integration is just static-file hosting plus a link or an iframe. + +Use a **link** when the demo deserves its own page. Use an **iframe** when the +demo is part of a docs page, announcement post, changelog, or product page. + +## Generate and upload + +```sh +pocketstack demo -f compose.yaml -o pocketstack-demo +``` + +Upload the full `pocketstack-demo/` directory to your site or static host. Keep +the directory structure intact — the manifest, runtime, service worker, and +assets all use relative paths. For the deploy basics and header requirements, +see [hosting & headers](/deploy/hosting). + +## Link + +The simplest integration is a normal link: + +```html +Open live demo +``` + +This gives the demo its own route, browser history, service-worker scope, and +full viewport. + +## Embed in an iframe + +```html + +``` + +The iframe page is still a static PocketStack demo; it does not call a +PocketStack backend. Give the iframe enough height for the dashboard and preview +pane — `720px` is a good starting point for desktop docs pages. Verify the +height at both desktop and mobile widths. + +## Host under a subpath + +PocketStack demos are subpath-safe. All of these are valid: + +```text +https://example.com/demo/ +https://example.com/docs/demos/app/ +https://example.com/releases/v1/pocketstack-demo/ +``` + +Upload the whole generated folder under that route. Do not move individual files +out of the folder. If your website builder fingerprints or relocates assets, +configure it to copy the generated demo folder as a static directory. + +### Documentation sites + +For docs sites, copy the demo output into the published static directory, then +link or iframe it: + +```text +docs-site/ + index.html + demos/ + my-compose-demo/ + index.html + pocketstack.manifest.json + app.js + mock-sw.js + assets/ +``` + +## Headers + +Some adapters (`frontend`, and `wasi` when it falls back to Wasmer JS) require +cross-origin isolation headers, and PocketStack emits host-config files for +them. The required header values and per-host behavior — including the GitHub +Pages limitation — live in [hosting & headers](/deploy/hosting). + +The generated `pocketstack.manifest.json` includes +`hostRequirements.crossOriginIsolationRequired`, so a custom website can warn +users before loading a header-dependent demo. See the +[manifest reference](/deploy/manifest). + +## Driving service endpoints from custom UI + +A generated demo exposes browser-only URLs that custom frontend code (sitting +next to the embedded demo) can call: + +```text +/__pocketstack/mock// +/__pocketstack/db//query +``` + +For example, a small query panel can post SQL to a database service: + +```js +const response = await fetch("/__pocketstack/db/db/query", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ sql: "select 1" }), +}); +const payload = await response.json(); +``` + +These endpoints exist only inside the generated browser demo and are served by a +service worker in the tab. They are **not** Docker networking, Postgres TCP, or +a general backend proxy. Use them for custom demo controls, fixture explorers, +or small query panels. For the full request/response shapes and more examples, +see [service URLs](/reference/service-urls). + +## Integration checklist + +Before publishing a demo from another website: + +- Confirm `pocketstack.manifest.json` loads from the final URL. +- Open the generated dashboard and start each service. +- Check whether `hostRequirements.crossOriginIsolationRequired` is `true`; if + so, host on a header-capable static host (see + [hosting & headers](/deploy/hosting)). +- If embedding, verify the iframe height at desktop and mobile widths. +- Keep the demo folder intact when copying it through the site build. diff --git a/docs/guide/cli.md b/docs/guide/cli.md new file mode 100644 index 0000000..5d70c83 --- /dev/null +++ b/docs/guide/cli.md @@ -0,0 +1,136 @@ +# CLI Reference + +PocketStack has three commands: `analyze`, `demo`, and `version`. The CLI inspects and packages a local Compose project; it never starts a server or contacts a backend. + +```text +pocketstack analyze [-f compose.yaml] [--json] +pocketstack demo [-f compose.yaml] [-o pocketstack-demo] +pocketstack version +``` + +## Commands + +### `analyze` + +Reads a Compose file and reports browser readiness without writing anything. + +```sh +pocketstack analyze -f compose.yaml +``` + +Flags: + +| Flag | Default | Description | +| --- | --- | --- | +| `-f` | (resolved) | Path to the Compose file. If omitted, PocketStack searches the working directory (see [Compose-file resolution](#compose-file-resolution)). | +| `--json` | `false` | Print the full analysis as JSON instead of the human-readable report. | + +### `demo` + +Generates a static, browser-native demo from a Compose file. This only succeeds when every default service maps to a browser adapter; otherwise it exits with an error and you should run `analyze` to see why. + +```sh +pocketstack demo -f compose.yaml -o pocketstack-demo +``` + +Flags: + +| Flag | Default | Description | +| --- | --- | --- | +| `-f` | (resolved) | Path to the Compose file (same resolution as `analyze`). | +| `-o` | `pocketstack-demo` | Output directory for the generated demo. | + +On success it prints the mode and the absolute output path: + +```text +Generated browser-native demo at /path/to/pocketstack-demo +``` + +Serve that directory from any static host. See [hosting](/deploy/hosting). + +### `version` + +Prints the CLI version. + +```sh +pocketstack version +``` + +## `analyze` output structure + +The human-readable report is printed in this order: + +- `Mode:` — `browser-native` or `unsupported`. +- `Browser readiness: N% (summary)` — the readiness score and a one-line summary. +- **Per-service lines.** A browser-native service prints its adapter and, when known, its asset source: + + ```text + web: static-web adapter from ./site + ``` + + An unsupported service prints its blockers and suggestions: + + ```text + cache: unsupported in browser-native mode + - stateful service has no honest browser adapter + suggestion: replace with SQLite, PGlite, fixtures, or in-browser mock state + ``` + + Browser-native services may also print `- warning:` lines for behavior that can't be reproduced exactly. +- `Warnings:` — a project-level section, shown only when there are warnings. +- `Next steps:` — shown only when the project is **not** fully browser-native. + +## `--json` output shape + +`analyze --json` prints the full analysis object. At a high level: + +```jsonc +{ + "mode": "browser-native", + "browserNative": true, + "readiness": { + "status": "ready", // "ready" | "partial" | "blocked" + "score": 100, // percentage of services that are browser-native + "browserNativeServices": 1, + "totalServices": 1, + "summary": "all services browser-native" + }, + "services": [ + { + "name": "web", + "browserNative": true, + "adapter": "static-web", + "assetSource": "./site", + "warnings": [], + "unsupported": [], // reasons, when not browser-native + "suggestions": [] // conversion hints, when not browser-native + } + ], + "warnings": [], + "nextSteps": [], + "hostRequirements": {} // e.g. cross-origin isolation, when a demo needs it +} +``` + +::: tip +Use `--json` in CI to gate on `readiness.status` or `readiness.score` rather than parsing the text report. +::: + +## Compose-file resolution + +When `-f` is omitted, PocketStack looks for these files in the working directory, in order, and uses the first that exists: + +1. `compose.yaml` +2. `compose.yml` +3. `docker-compose.yml` +4. `docker-compose.yaml` + +If none is found, it exits with an error asking you to pass `-f`. + +## Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success. | +| `1` | Error — bad or missing Compose file, or generation failed. | +| `2` | Usage error — unknown command or bad flags. | diff --git a/docs/guide/concepts.md b/docs/guide/concepts.md new file mode 100644 index 0000000..078e527 --- /dev/null +++ b/docs/guide/concepts.md @@ -0,0 +1,50 @@ +# Concepts + +PocketStack is built on one promise: a generated demo is a static browser artifact and nothing else. This page states that contract and defines the terms the docs use. + +## The browser-only contract + +PocketStack demos must be static browser artifacts. That is the product boundary, the trust boundary, and the compatibility boundary at once. Concretely, a generated demo has: + +- **no hidden backend;** +- **no remote runner;** +- **no local Docker daemon at demo time;** +- **no claim that arbitrary Linux containers run inside the browser.** + +The CLI may inspect and package a local Compose project, but the generated demo runs as browser code plus static assets. PocketStack can *analyze* any Compose file; it only *generates* demos for browser-native stacks. + +The flip side of the contract is honesty about limits: **unsupported stays unsupported.** A feature that has no honest browser adapter is reported with concrete reasons and conversion suggestions — it is never hidden behind a successful-looking preview. Unsupported behavior can become supported later only by adding a real browser adapter, not by smuggling in server work. + +## browser-native vs browser-only + +The docs use two closely related terms. They are not synonyms: + +- **browser-only** is the *boundary and the promise*: nothing runs outside the tab. No backend, no runner, no Docker at demo time. It describes what PocketStack will and won't do. +- **browser-native** is what a generated demo *is*: built from real browser primitives — static files, WebAssembly, a browser runtime, browser databases, service-worker mocks. It describes how the demo is constructed. + +Put together: PocketStack only generates **browser-native** demos because the **browser-only** contract forbids anything else. `analyze` reports `Mode: browser-native` when every service maps to a browser primitive, and `unsupported` when one or more cannot. + +## Glossary + +**Adapter** +: The mapping from a Compose service to a specific browser primitive. PocketStack has six: `static-web`, `frontend`, `mock-http`, `postgres-pglite`, `sqlite`, and `wasi`. `static-web` is autodetected; the others are selected with a `pocketstack.adapter` label. See [adapters](/adapters/). + +**Readiness score** +: The percentage of services in a stack that are browser-native, reported by `analyze` as `Browser readiness: N%`. It comes with a `status` of `ready`, `partial`, or `blocked`, plus per-service blockers and project-level next steps. The point is to make unsupported services *actionable*, not just to grade the project. + +**Honest adapter** +: An adapter that names the browser primitive it uses and preserves the demo's important behavior — for example, `static-web` copies and serves files, `wasi` runs a prebuilt WebAssembly module. An adapter is *not* honest if it silently calls a private backend, pretends to provide full container semantics, or hides unsupported Docker behavior behind a preview that looks like it worked. + +**Demo** +: The static output folder produced by `pocketstack demo`. It contains the entry page, manifest, runtime, any service-worker mock, and packaged assets, all using relative paths so it can be hosted under a subpath. See [hosting](/deploy/hosting). + +**Manifest** +: The `pocketstack.manifest.json` file written into a demo. It describes the demo's services and adapters for the browser runtime. Keep it alongside the rest of the generated files when you deploy. + +**Studio** +: The static browser page for quick compatibility checks. Paste or upload Compose YAML, optionally add the project folder, and Studio reports readiness — entirely in the tab, with no PocketStack backend, Docker daemon, or runner. Hosted at [Studio](https://ramazankara.github.io/pocketstack/studio/). + +## Related + +- [adapters](/adapters/) — what each adapter supports and how assets are mapped. +- [convert a service](/convert/) — turning an unsupported service into a browser-native shape. diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md new file mode 100644 index 0000000..882ec70 --- /dev/null +++ b/docs/guide/getting-started.md @@ -0,0 +1,82 @@ +# Getting Started + +PocketStack is a Go CLI and browser runtime that turns browser-compatible Docker Compose projects into static, browser-native demos that run entirely in a browser tab. When a service can't be represented honestly in the browser, it reports a readiness score and conversion suggestions instead of faking it. + +## Install + +Download the prebuilt binary from GitHub Releases, or build from source. See [installation](/guide/installation) for both paths and prerequisites. Verify your install with: + +```sh +pocketstack version +``` + +::: tip No-install option +You can skip the CLI entirely for a quick compatibility check. [Studio](https://ramazankara.github.io/pocketstack/studio/) is a static browser page where you paste or upload Compose YAML (and optionally add the project folder so it can inspect mounted assets). It runs entirely in the tab — no backend, no Docker. +::: + +## The core loop + +Working with PocketStack is three steps: + +1. **Analyze** — run `pocketstack analyze` on your Compose file to get a browser-readiness report: which services map to a browser adapter, which don't, and what to do about the gaps. +2. **Generate** — run `pocketstack demo` to write a static demo folder when the stack is browser-native. +3. **Serve** — host the output directory on any static host. See [hosting](/deploy/hosting). + +## Worked example: a static site + +Start with a minimal nginx static site. Create `compose.yaml`: + +```yaml +services: + web: + image: nginx:alpine + volumes: + - ./site:/usr/share/nginx/html:ro + ports: + - "8080:80" +``` + +Put an `index.html` (and any assets) in a `./site` directory next to the Compose file. + +### 1. Analyze + +```sh +pocketstack analyze -f compose.yaml +``` + +```text +Mode: browser-native +Browser readiness: 100% (all services browser-native) + web: static-web adapter from ./site +``` + +The `static-web` adapter is autodetected here from the `nginx` image plus the document-root mount — you don't add a label for it. (The other five adapters are opt-in via `pocketstack.adapter`; see [adapters](/adapters/).) + +### 2. Generate the demo + +```sh +pocketstack demo -f compose.yaml -o pocketstack-demo +``` + +```text +Generated browser-native demo at /path/to/pocketstack-demo +``` + +### 3. Open or serve it + +The output is plain static files. Open `pocketstack-demo/index.html` directly to preview, or serve the folder: + +```sh +npx serve pocketstack-demo +``` + +::: tip +A static-web demo previews fine from `file://`, but some adapters use a service worker and need `http(s)`. Serving the folder always works. See [troubleshooting](/guide/troubleshooting) if a demo looks empty when opened directly. +::: + +## Where to next + +- [adapters](/adapters/) — what each adapter can demo, and how assets are mapped. +- [convert a service](/convert/) — what to do when `analyze` reports a service as unsupported. +- [CLI reference](/guide/cli) — every command, flag, and the `--json` output shape. +- [hosting](/deploy/hosting) — static hosts, and when a demo needs cross-origin isolation headers. diff --git a/docs/guide/installation.md b/docs/guide/installation.md new file mode 100644 index 0000000..8031c16 --- /dev/null +++ b/docs/guide/installation.md @@ -0,0 +1,59 @@ +# Installation + +PocketStack ships as a single binary. Download a prebuilt release, or build from source if you want the latest `main` or an unsupported platform. + +## Prebuilt binary + +Download the latest binary from [GitHub Releases](https://github.com/ramazankara/pocketstack/releases/latest). Builds are published for: + +- **Linux** — `amd64`, `arm64` +- **macOS** — `amd64`, `arm64` +- **Windows** — `amd64`, `arm64` + +Put the binary on your `PATH` (for example `/usr/local/bin` on Linux/macOS), then verify: + +```sh +pocketstack version +``` + +::: tip macOS Gatekeeper +On macOS you may need to clear the quarantine attribute the first time: + +```sh +xattr -d com.apple.quarantine ./pocketstack +``` +::: + +## Build from source + +### Prerequisites + +- **Go** — to compile the CLI. +- **Node 26** — the JavaScript toolchain targets the current Node line, Node 26. The repo includes an `.nvmrc`, so `nvm use` selects the right version. + +### Steps + +```sh +git clone https://github.com/ramazankara/pocketstack.git +cd pocketstack +nvm use +npm ci +npm run build:wasi-example +npm run build:runtime +go build -o bin/pocketstack ./cmd/pocketstack +``` + +The `npm run build:*` steps compile the browser runtime and the bundled WASI example that the generated demos embed; run them before `go build` so the binary packages the current runtime assets. + +### Verify + +```sh +bin/pocketstack version +``` + +Move `bin/pocketstack` onto your `PATH` to use it as `pocketstack` from anywhere. + +## Next steps + +- [Getting started](/guide/getting-started) — the analyze → demo → serve loop, with a worked example. +- [CLI reference](/guide/cli) — every command and flag. diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md new file mode 100644 index 0000000..79dee07 --- /dev/null +++ b/docs/guide/troubleshooting.md @@ -0,0 +1,67 @@ +# Troubleshooting + +Common questions and errors when analyzing Compose files and running generated demos, with fixes. + +## A demo looks empty when opened from `file://` + +A `static-web` demo previews fine when you double-click `index.html`, but adapters that use a **service worker** (mock HTTP, browser databases, the frontend runtime) need an `http(s)` origin — browsers don't register service workers on `file://`. Serve the folder instead of opening the file: + +```sh +npx serve pocketstack-demo +``` + +Any static server works (`python3 -m http.server`, a local dev server, or your real host). See [hosting](/deploy/hosting). + +## "frontend demo needs cross-origin isolation" + +`frontend`/WebContainer demos — and `wasi` demos when they fall back to Wasmer JS — require cross-origin isolation, which means the host must send COOP/COEP response headers. When a generated demo needs them, PocketStack writes the matching host config files next to the demo (`_headers`, `vercel.json`, `staticwebapp.config.json`). + +::: warning +GitHub Pages can't set arbitrary response headers, so it can't serve a demo that needs cross-origin isolation. Use a header-capable host (Netlify, Cloudflare Pages, Vercel, Azure Static Web Apps, or your own server) and let it apply the generated config — or set the headers manually. The exact header values and per-host details are in [hosting](/deploy/hosting). +::: + +## A demo that loads from a CDN fails offline + +Some adapters fetch browser-only runtime packages (or install npm dependencies) **in the viewer's browser**. This is still backend-free — no PocketStack server is involved — but the browser needs internet access to public package/runtime CDNs. Adapters that can require network access: + +- `frontend` — browser-time package installs and the WebContainer runtime; +- `wasi` — only when the Wasmer JS fallback is used; +- `postgres-pglite` and `sqlite` — browser database runtime packages. + +::: tip Need offline? +Prefer `static-web` and `mock-http` for demos that must work without network access — they package everything they need into the output. +::: + +## "My service is reported unsupported" + +This is by design, not a bug: PocketStack won't fake a service it can't represent honestly in the browser. Read the readiness report — each unsupported service prints concrete `reasons` and one or more `suggestions`: + +```text + cache: unsupported in browser-native mode + - stateful service has no honest browser adapter + suggestion: replace with SQLite, PGlite, fixtures, or in-browser mock state +``` + +Run `pocketstack analyze -f compose.yaml` to see them, then reshape the service into a browser-native form. The [convert a service](/convert/) guide walks through the common conversions (databases to PGlite/SQLite, APIs to `mock-http`, and so on). + +## Compose parsing questions + +A few Compose details that are handled automatically, so you don't need to work around them: + +- **Port ranges** like `3000-3005:3000-3005` are accepted. The first port of the range is used, and adapter selection does not depend on the exact port. +- **Image name variants** are normalized. Short names (`postgres`), Docker Hub official names (`library/postgres`), and registry-qualified names (`docker.io/library/postgres:16`, `ghcr.io/org/app`) all resolve to the same adapter. +- **`profiles:`** — services gated behind a profile aren't started by a default `docker compose up`, so PocketStack skips them. They don't count toward or block readiness, and you'll see a warning that services were skipped. + +::: info Not handled automatically +`extends:` is not resolved, and multiple Compose files / overrides aren't merged. Flatten the service (inline its image, labels, ports, and volumes) and pass a single file with `-f`. See [adapters](/adapters/) for per-adapter limits. +::: + +## Getting more detail + +Add `--json` to `analyze` for the full machine-readable analysis, including `readiness`, per-service `unsupported`/`suggestions`, `warnings`, `nextSteps`, and `hostRequirements`: + +```sh +pocketstack analyze -f compose.yaml --json +``` + +See the [CLI reference](/guide/cli) for the complete output shape and exit codes. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..8552e0f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,26 @@ +--- +layout: home + +hero: + name: PocketStack + text: Browser-native demos from Docker Compose + tagline: Turn browser-compatible Compose projects into static demos that run entirely in a browser tab — no server, no runner, no Docker at demo time. + actions: + - theme: brand + text: Get started + link: /guide/getting-started + - theme: alt + text: Compatibility matrix + link: /adapters/ + - theme: alt + text: Try Studio + link: https://ramazankara.github.io/pocketstack/studio/ + +features: + - title: Honest by design + details: When a service can become browser-native, PocketStack packages it. When it can't, you get a readiness report and concrete conversion steps — never a hidden runner pretending it works. + - title: Six browser adapters + details: static-web, frontend (WebContainer), mock-http (OpenAPI), sqlite, postgres-pglite, and wasi — each maps a Compose service to a real browser primitive. + - title: Static output, hosted anywhere + details: Generated demos are plain static files. Serve them from any static host; PocketStack emits the COOP/COEP host config when an adapter needs it. +--- diff --git a/docs/reference/architecture.md b/docs/reference/architecture.md new file mode 100644 index 0000000..e8ee203 --- /dev/null +++ b/docs/reference/architecture.md @@ -0,0 +1,186 @@ +# Architecture + +PocketStack is a browser-only Compose demo compiler: a Go CLI converts a +browser-compatible Docker Compose project into a static folder that runs +entirely in a browser tab, plus a TypeScript browser runtime that is bundled and +embedded into the CLI. There is no server, runner, or Docker daemon at demo time. + +Read this page to understand where a behavior belongs, how the embedded runtime +is built, or how to add a new adapter without weakening the browser-only +contract. + +## Pipeline + +```text +compose.yaml + project files + | + v + internal/compose (model: types + LoadFile) + | + v + internal/analyzer (adapter detection, readiness, suggestions) + | + v + internal/generator (static demo + embedded runtime) + | + v + index.html + app.js + mock-sw.js + pocketstack.manifest.json + assets/ + | + v + browser runtime dashboard (in the viewer's tab) +``` + +The CLI entry point is `cmd/pocketstack`. The pipeline is: +**compose (model) → analyzer → generator → embedded browser runtime.** + +## Packages + +### `internal/compose` — the Compose model + +Owns the Compose file **model**: the service/project types and `LoadFile`, which +reads and parses a single Compose file into those types. It is parsing and data +structures only — no adapter logic or browser concerns. + +### `internal/analyzer` — analysis + +Reads the Compose model and classifies each service against the adapter +registry. It owns the analysis types (`Analysis`, `ServiceAnalysis`, +`AssetAnalysis`, `Readiness`, `HostRequirements`) and the `AnalyzeFile` / +`Analyze` entry points. + +A service is either **browser-native** (mapped to an adapter) or **unsupported**; +there is no server-fallback state. This is intentionally conservative — a false +positive would create a misleading demo, so adapters are allowlisted and +unsupported services keep concrete reasons in the analysis output. The analyzer +also produces a readiness score, per-service suggestions, and project next steps +so unsupported stacks still get a useful conversion plan. + +Implemented adapters: + +- **`static-web`** — copies document-root files from `nginx`, `httpd`, or + `caddy` bind mounts; warns about server config static hosting cannot emulate. +- **`frontend`** — packages a Node/Bun project for WebContainer-style browser + execution, including env handling, package-manager detection, and bridge + support for PocketStack mock/database endpoints. +- **`wasi`** — packages an explicitly labeled prebuilt `.wasm` module with + browser WASI preview imports and a Wasmer JS fallback. +- **`mock-http`** — turns OpenAPI YAML/JSON and JSON fixtures into + service-worker HTTP routes. +- **`postgres-pglite`** — maps supported Postgres demos to PGlite with SQL + bootstrap, IndexedDB or memory persistence, reset, and query-bridge support. +- **`sqlite`** — runs SQL init/seed assets or seed databases through sql.js in + the browser. + +See the [adapter matrix](/adapters/) for how each is selected and the +[conversion guide](/convert/) for reshaping unsupported services. New adapter +behavior is decided here first: the analyzer must be able to explain *why* a +service is supported, *what* files to copy, *what* host requirements apply, and +*what* warnings the demo should show. + +### `internal/generator` — static demo generation + +`pocketstack demo` runs the analyzer, and **only succeeds when every service is +browser-native**. If any service is unsupported, it exits with a reasoned +incompatibility report instead of falling back to a server. + +When generation succeeds, it writes a static directory: + +- `index.html` +- `app.js` (the embedded browser runtime) +- `mock-sw.js` (the mock service worker) +- `pocketstack.manifest.json` +- copied assets under `assets//` +- host-config files when cross-origin isolation is required + +The generator makes demos **portable**: assets are copied into deterministic +service folders, root-relative static-site references are rewritten so copied +sites work from a nested path, and host-config files are emitted when an adapter +requires cross-origin isolation. The manifest is version `2`, sets +`browserOnly: true`, and carries adapter metadata, readiness, copied asset +paths, warnings, host requirements, and a stable storage namespace for browser +databases. See the [manifest reference](/deploy/manifest). + +## The embedded runtime build chain + +The browser runtime is TypeScript that lives in `web/runtime`. It is bundled by +esbuild and embedded into the generator binary, so a released CLI is +self-contained and writes the same runtime into every demo. + +```text +web/runtime/src/app.ts + | esbuild --bundle --format=esm --target=es2022 + v +internal/generator/runtime/app.js + | //go:embed runtime/* + v +embedded into the pocketstack binary + | generator writes it next to index.html + v +app.js in every generated demo +``` + +The bundle command (from `package.json`) is: + +```sh +npm run build:runtime +# esbuild web/runtime/src/app.ts --bundle --format=esm --target=es2022 \ +# --outfile=internal/generator/runtime/app.js +``` + +`internal/generator/runtime/` also holds `mock-sw.js`, the mock service worker. +The generator embeds the whole `runtime/` directory with `//go:embed runtime/*` +and copies each file into the output during generation. + +::: warning +Editing `web/runtime/src/*.ts` does **not** change generated demos until you run +`npm run build:runtime` to regenerate `internal/generator/runtime/app.js`. The +Go embed reads the built artifact, not the TypeScript source. CI and +`make release-check` rebuild it before testing. +::: + +## Other web surfaces + +Two web apps are built from the same repo but are not embedded in the CLI: + +- **`web/studio`** — the in-browser Studio analyzer (paste/upload a Compose file + for browser-only triage). +- **`web/site`** — the landing page. + +These ship to the public GitHub Pages site alongside selected generated demos. + +## Runtime behavior + +Generated demos run as static browser code: + +- `static-web` services render as iframe previews; +- `frontend` services boot in a WebContainer-style runtime; +- `mock-http` services register routes in `mock-sw.js`; +- database services initialize PGlite or SQLite on demand; +- `wasi` services execute prebuilt WebAssembly modules; +- logs, status, warnings, previews, reset controls, and query panels live in the + generated dashboard. + +The runtime may load public browser packages or npm dependencies when an adapter +requires them, but it never calls a PocketStack backend. Custom UI can drive the +demo through browser-only [service URLs](/reference/service-urls). + +## Extension points: adding an adapter + +A new adapter starts in `internal/analyzer` and flows through the pipeline: + +1. **Analyzer** — add classification: detect the service, declare which files to + copy (as `AssetAnalysis`), set `HostRequirements`, and emit clear `warnings` + and `unsupported` reasons for nearby cases that still cannot work. +2. **Generator** — ensure the copied assets and `config` keys the runtime needs + land in the manifest (see how `copyServiceAssets` maps asset names to config + in `internal/generator/generator.go`). +3. **Runtime** — implement the browser behavior in `web/runtime/src` and rebuild + with `npm run build:runtime`. +4. **Tests & docs** — analyzer classification tests, manifest coverage, runtime + tests, an example Compose project, and an adapter page under `/adapters/`. + +Keep new behavior **adapter-shaped**. If a feature needs Docker networking, +privileged filesystem access, or a long-running server that is not a browser +primitive, it belongs in the unsupported report until there is a real browser +implementation. The full checklist is in the +[contributing guide](/contribute/). diff --git a/docs/reference/service-urls.md b/docs/reference/service-urls.md new file mode 100644 index 0000000..eb3be91 --- /dev/null +++ b/docs/reference/service-urls.md @@ -0,0 +1,111 @@ +# Service URLs + +A generated demo exposes two families of browser-only URLs that custom frontend +code can call. They are served by a **service worker running inside the demo +tab** — not by a backend. + +```text +/__pocketstack/mock// +/__pocketstack/db//query +``` + +::: warning +These endpoints exist only inside the generated browser demo. They are **not** +Docker networking, Postgres TCP, an HTTP proxy, or a real backend. There is no +server to reach — the service worker answers requests in the tab. Treat `` +and `` as placeholders for your service name and route path. +::: + +Use them for custom demo controls, fixture explorers, or small query panels that +live next to an embedded demo. For embedding context, see +[website integration](/deploy/website-integration). + +## Mock route endpoint + +For a `mock-http` service named ``, routes from its OpenAPI spec and +JSON fixtures are registered at: + +```text +/__pocketstack/mock// +``` + +The `` mirrors the path defined in the service's OpenAPI document or +fixtures. Requests are answered by the mock service worker (`mock-sw.js`) using +the registered response examples and fixtures, with CORS/preflight support. + +Example — fetch a mocked API route for a service named `api`: + +```js +const response = await fetch("/__pocketstack/mock/api/users"); +const users = await response.json(); +``` + +```sh +curl https://example.com/demo/__pocketstack/mock/api/users +``` + +::: info +A `mock-http` service returns the fixture/OpenAPI examples it was generated +with. It does not run real backend logic, middleware, or production auth. See +the [mock-http adapter](/adapters/mock-http) for what to put in the spec and +fixtures. Frontend code that points at a Compose-style URL such as +`http://api:8080` is rewritten to this mock URL automatically — see the +[frontend adapter](/adapters/frontend). +::: + +## Database query endpoint + +For a `postgres-pglite` or `sqlite` service named ``, SQL runs against +the in-browser database at: + +```text +POST /__pocketstack/db//query +``` + +- **Method:** `POST` +- **Request body:** JSON `{"sql": ""}` +- **Response:** adapter-native JSON result + +Multi-statement SQL (`insert …; select …;`) is supported on both database +adapters. + +Example — run a query with `fetch` against a service named `db`: + +```js +const response = await fetch("/__pocketstack/db/db/query", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ sql: "select 1" }), +}); +const payload = await response.json(); +``` + +Example — the same query with `curl`: + +```sh +curl -X POST https://example.com/demo/__pocketstack/db/db/query \ + -H "content-type: application/json" \ + -d '{"sql":"select 1"}' +``` + +The response shape is **adapter-native**: PGlite returns PGlite's result JSON, +and SQLite (sql.js) returns sql.js's result JSON. Read the per-adapter pages for +details: [postgres-pglite](/adapters/postgres-pglite) and +[sqlite](/adapters/sqlite). + +::: warning +This endpoint does not speak the Postgres or MySQL wire protocol and is not a +database server. It runs SQL against a browser database (PGlite or sql.js) seeded +from the demo's assets, and persists only to browser storage under the demo's +`storageNamespace`. +::: + +## Notes for custom UI + +- Use **relative** URLs (`/__pocketstack/...`) so the demo keeps working under a + subpath. Generated demos use relative paths throughout. +- These URLs are only live once the demo's service worker has registered, so + call them from code running inside (or alongside) the loaded demo. +- The available `` names and `` paths come from the generated + [manifest](/deploy/manifest): each service's `name`, `adapter`, and `config` + describe what is registered. diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md new file mode 100644 index 0000000..e8aaf64 --- /dev/null +++ b/docs/release-notes/index.md @@ -0,0 +1,14 @@ +# Release Notes + +Per-release detail, newest first. The summarized changelog lives in +`CHANGELOG.md`; this section reformats the per-version notes as pages. + +| Version | Date | Summary | +| --- | --- | --- | +| [v1.1.0](/release-notes/v1.1.0) | 2026-06-28 | Correctness, honesty, and hardening: analyzer handles port ranges, image normalization, `profiles`, and `extends`; demos fail honestly; runtime packages version-pinned. | +| [v1.0.3](/release-notes/v1.0.3) | 2026-05-27 | Refocused on browser-native readiness: `analyze` reports a readiness score, blockers, suggestions, and next steps; new conversion guide. | +| [v1.0.2](/release-notes/v1.0.2) | 2026-05-27 | Public GitHub Pages site with hosted Studio, live generated demos, and example projects. | +| [v1.0.1](/release-notes/v1.0.1) | 2026-05-26 | First release-grade browser-only build: analyzer, manifest v2 output, and the full adapter set. | +| [v1.0.0](/release-notes/v1.0.0) | 2026-05-26 | Initial browser-native PocketStack release. | + +For the maintainer release process, see [releasing](/contribute/releasing). diff --git a/docs/release-notes/v1.0.0.md b/docs/release-notes/v1.0.0.md new file mode 100644 index 0000000..1138382 --- /dev/null +++ b/docs/release-notes/v1.0.0.md @@ -0,0 +1,15 @@ +# v1.0.0 + +The initial browser-native PocketStack release. + +PocketStack v1.0.0 established the product: a Go CLI plus browser runtime that +converts browser-compatible Docker Compose projects into static, browser-native +demos that run entirely in a browser tab — no server, runner, or Docker daemon at +demo time. + +Subsequent releases hardened and refocused this foundation; see +[v1.0.1](/release-notes/v1.0.1) for the first release-grade build. + +--- + +[Release notes index](/release-notes/) · [v1.0.1 →](/release-notes/v1.0.1) diff --git a/docs/RELEASE_NOTES_v1.0.1.md b/docs/release-notes/v1.0.1.md similarity index 75% rename from docs/RELEASE_NOTES_v1.0.1.md rename to docs/release-notes/v1.0.1.md index df5357d..3807719 100644 --- a/docs/RELEASE_NOTES_v1.0.1.md +++ b/docs/release-notes/v1.0.1.md @@ -1,4 +1,4 @@ -# PocketStack v1.0.1 +# v1.0.1 PocketStack v1.0.1 is the first release-grade browser-only build. @@ -14,26 +14,33 @@ Highlights: - Mock HTTP services auto-register in generated demos, include CORS/preflight support, and rewrite frontend env URLs from Compose-style service URLs to browser mock URLs. -- PGlite and SQLite demos expose a browser-only HTTP query bridge for custom - demo UI without claiming Postgres wire-protocol or Docker networking - compatibility. +- PGlite and SQLite demos expose a browser-only HTTP query bridge for custom demo + UI without claiming Postgres wire-protocol or Docker networking compatibility. - WebContainer frontend demos mount a generated fetch bridge so preview iframes - can reach known PocketStack mock/database endpoints through the parent - browser runtime. + can reach known PocketStack mock/database endpoints through the parent browser + runtime. - The frontend bridge is injected into packaged HTML files and exposed with `POCKETSTACK_BRIDGE_URL` for custom import paths. -- Static web demos rewrite root-relative HTML/CSS asset URLs so copied sites - keep working from the generated nested preview path. +- Static web demos rewrite root-relative HTML/CSS asset URLs so copied sites keep + working from the generated nested preview path. - Subpath-safe mock service-worker routing. -- Cross-origin demos emit `_headers`, `vercel.json`, and `staticwebapp.config.json` hosting config. +- Cross-origin demos emit `_headers`, `vercel.json`, and + `staticwebapp.config.json` hosting config. - Studio supports pasted Compose YAML, uploaded Compose files, and optional project-folder uploads for browser-only triage. -- Live Studio/generated-demo announcement video and poster assets are included under `docs/media/`. +- Live Studio/generated-demo announcement video and poster assets are included + under `docs/media/`. - Reproducible WASI hello-world example built from `hello.wat`. -- Cross-platform GoReleaser archives for Linux, macOS, and Windows on amd64 and arm64. +- Cross-platform GoReleaser archives for Linux, macOS, and Windows on amd64 and + arm64. Known constraints: - No backend, runner, Docker daemon, or full container runtime is included. - Frontend/WebContainer demos require cross-origin isolation headers. -- Some browser adapters load public browser runtime packages or npm dependencies at demo time. +- Some browser adapters load public browser runtime packages or npm dependencies + at demo time. + +--- + +[← v1.0.0](/release-notes/v1.0.0) · [Release notes index](/release-notes/) · [v1.0.2 →](/release-notes/v1.0.2) diff --git a/docs/RELEASE_NOTES_v1.0.2.md b/docs/release-notes/v1.0.2.md similarity index 79% rename from docs/RELEASE_NOTES_v1.0.2.md rename to docs/release-notes/v1.0.2.md index 379cacf..579355f 100644 --- a/docs/RELEASE_NOTES_v1.0.2.md +++ b/docs/release-notes/v1.0.2.md @@ -1,4 +1,4 @@ -# PocketStack v1.0.2 +# v1.0.2 PocketStack v1.0.2 publishes the browser-only release as a live public site and keeps the release artifacts aligned with the demo experience. @@ -7,13 +7,13 @@ Highlights: - Public GitHub Pages site at `https://ramazankara.github.io/pocketstack/`. - Hosted Studio, live generated demos, announcement video, and demo index. -- Three upload-ready example projects for Studio testing: static web, - OpenAPI mock HTTP, and SQLite. +- Three upload-ready example projects for Studio testing: static web, OpenAPI + mock HTTP, and SQLite. - Website integration docs for links, iframes, subpath hosting, headers, and browser-only service URLs. - Chrome, Microsoft Edge, and Safari-class WebKit smoke checks in CI. -- Project JavaScript tooling targets Node 26, and GitHub Actions are refreshed - to current Node 24 action runtimes where available. +- Project JavaScript tooling targets Node 26, and GitHub Actions are refreshed to + current Node 24 action runtimes where available. - GoReleaser archives now include docs, examples, and Studio assets. Known constraints: @@ -21,5 +21,9 @@ Known constraints: - No backend, runner, Docker daemon, or full container runtime is included. - GitHub Pages cannot set arbitrary COOP/COEP headers; WebContainer and Wasmer fallback demos should use a header-capable static host for full behavior. -- Some adapters load public browser runtime packages or npm dependencies at - demo time. +- Some adapters load public browser runtime packages or npm dependencies at demo + time. + +--- + +[← v1.0.1](/release-notes/v1.0.1) · [Release notes index](/release-notes/) · [v1.0.3 →](/release-notes/v1.0.3) diff --git a/docs/RELEASE_NOTES_v1.0.3.md b/docs/release-notes/v1.0.3.md similarity index 72% rename from docs/RELEASE_NOTES_v1.0.3.md rename to docs/release-notes/v1.0.3.md index 4b2a0e5..8c8ca3c 100644 --- a/docs/RELEASE_NOTES_v1.0.3.md +++ b/docs/release-notes/v1.0.3.md @@ -1,8 +1,7 @@ -# PocketStack v1.0.3 +# v1.0.3 -PocketStack v1.0.3 refocuses the product around browser-native readiness -instead of pretending full Docker Compose compatibility can exist inside a -browser tab. +PocketStack v1.0.3 refocuses the product around browser-native readiness instead +of pretending full Docker Compose compatibility can exist inside a browser tab. Highlights: @@ -13,9 +12,9 @@ Highlights: - Studio shows readiness score and conversion suggestions directly in the browser. - Generated manifests include readiness metadata and project next steps. -- New browser-native conversion guide for replacing common unsupported - services such as Redis, workers, backend APIs, Docker builds, and non-browser - databases with demo-safe browser-native representations. +- New browser-native conversion guide for replacing common unsupported services + such as Redis, workers, backend APIs, Docker builds, and non-browser databases + with demo-safe browser-native representations. - Public site links to the conversion guide. Known constraints: @@ -26,3 +25,7 @@ Known constraints: - Unsupported services should be converted to static assets, frontend projects, WASI modules, OpenAPI mocks, SQLite, PGlite, or in-browser state when that is honest for the demo. + +--- + +[← v1.0.2](/release-notes/v1.0.2) · [Release notes index](/release-notes/) · [v1.1.0 →](/release-notes/v1.1.0) diff --git a/docs/RELEASE_NOTES_v1.1.0.md b/docs/release-notes/v1.1.0.md similarity index 90% rename from docs/RELEASE_NOTES_v1.1.0.md rename to docs/release-notes/v1.1.0.md index 26934cd..9647403 100644 --- a/docs/RELEASE_NOTES_v1.1.0.md +++ b/docs/release-notes/v1.1.0.md @@ -1,4 +1,4 @@ -# PocketStack v1.1.0 +# v1.1.0 PocketStack v1.1.0 is a correctness, honesty, and hardening release. The browser-native product boundary is unchanged; this release makes the analyzer @@ -31,9 +31,9 @@ honestly instead of silently. when the host is not cross-origin isolated; mock-http and database demos explain that they need http(s) hosting rather than `file://`; and CDN load failures report which package and URL could not be reached. -- Browser runtime packages loaded from public CDNs (WebContainer, PGlite, sql.js, - Wasmer) are version-pinned so previously generated demos do not break when an - upstream package ships a new major. +- Browser runtime packages loaded from public CDNs (WebContainer, PGlite, + sql.js, Wasmer) are version-pinned so previously generated demos do not break + when an upstream package ships a new major. - WASI `fd_write` now concatenates iovecs before decoding, fixing corruption of multi-byte UTF-8 output split across buffer boundaries. - The PGlite query panel and demo query bridge now run multi-statement SQL via @@ -44,8 +44,8 @@ honestly instead of silently. - The GitHub Pages build skips missing demos and media with a warning instead of failing the whole build when run outside `make pages`. -- New "Compose Features" documentation describes how `profiles`, `extends`, - port ranges, image normalization, and orchestration directives are handled. +- New "Compose Features" documentation describes how `profiles`, `extends`, port + ranges, image normalization, and orchestration directives are handled. - `SECURITY.md` now lists supported versions and a private vulnerability reporting channel. - Added CLI tests plus regression tests for port ranges, image normalization, @@ -61,3 +61,7 @@ honestly instead of silently. - Unsupported services should be converted to static assets, frontend projects, WASI modules, OpenAPI mocks, SQLite, PGlite, or in-browser state when that is honest for the demo. + +--- + +[← v1.0.3](/release-notes/v1.0.3) · [Release notes index](/release-notes/) diff --git a/internal/compose/analyze.go b/internal/analyzer/analyzer.go similarity index 98% rename from internal/compose/analyze.go rename to internal/analyzer/analyzer.go index 0975964..1bef2ca 100644 --- a/internal/compose/analyze.go +++ b/internal/analyzer/analyzer.go @@ -1,4 +1,4 @@ -package compose +package analyzer import ( "encoding/json" @@ -9,6 +9,8 @@ import ( "sort" "strconv" "strings" + + "github.com/ramazankara/pocketstack/internal/compose" ) const ( @@ -96,7 +98,7 @@ type adapter interface { type adapterContext struct { Name string - Service Service + Service compose.Service ProjectRoot string Labels map[string]string Explicit string @@ -107,7 +109,7 @@ func AnalyzeFile(composeFile string) (*Analysis, error) { if err != nil { return nil, err } - project, err := LoadFile(absCompose) + project, err := compose.LoadFile(absCompose) if err != nil { return nil, err } @@ -116,7 +118,7 @@ func AnalyzeFile(composeFile string) (*Analysis, error) { return &analysis, nil } -func Analyze(project *Project, projectRoot, composeFile string) Analysis { +func Analyze(project *compose.Project, projectRoot, composeFile string) Analysis { names := make([]string, 0, len(project.Services)) skippedProfiles := make([]string, 0) for name, service := range project.Services { @@ -168,7 +170,7 @@ func Analyze(project *Project, projectRoot, composeFile string) Analysis { return analysis } -func analyzeService(name string, service Service, projectRoot string) ServiceAnalysis { +func analyzeService(name string, service compose.Service, projectRoot string) ServiceAnalysis { labels := service.LabelMap() explicit := strings.TrimSpace(labels[LabelAdapter]) context := adapterContext{ @@ -796,7 +798,7 @@ func addOptionalSQLPath(result *ServiceAnalysis, projectRoot, rawPath, name, tar result.addAsset(name, "file", path, target) } -func addPostgresInitMounts(result *ServiceAnalysis, projectRoot string, service Service) { +func addPostgresInitMounts(result *ServiceAnalysis, projectRoot string, service compose.Service) { for _, volume := range service.Volumes { if !volume.IsBindLike() { continue @@ -977,7 +979,7 @@ func skipProjectDir(name string) bool { } } -func browserEnvironment(projectRoot string, service Service, result *ServiceAnalysis) ([]string, bool) { +func browserEnvironment(projectRoot string, service compose.Service, result *ServiceAnalysis) ([]string, bool) { values := map[string]string{} usedEnvFile := false for _, envFile := range service.EnvFiles() { @@ -1273,7 +1275,7 @@ func quoteCommandPart(part string) string { return strings.ReplaceAll(part, " ", `\ `) } -func frontendSource(projectRoot string, service Service, filename string) string { +func frontendSource(projectRoot string, service compose.Service, filename string) string { if source := bindSourceForWorkingDir(projectRoot, service.Volumes, service.WorkingDir, filename); source != "" { return source } @@ -1286,7 +1288,7 @@ func frontendSource(projectRoot string, service Service, filename string) string return "" } -func bindSourceForWorkingDir(projectRoot string, volumes []VolumeSpec, workingDir, filename string) string { +func bindSourceForWorkingDir(projectRoot string, volumes []compose.VolumeSpec, workingDir, filename string) string { workingDir = cleanContainerPath(workingDir) if workingDir == "" { return "" @@ -1307,7 +1309,7 @@ func bindSourceForWorkingDir(projectRoot string, volumes []VolumeSpec, workingDi return "" } -func firstBindWithFile(projectRoot string, volumes []VolumeSpec, filename string) string { +func firstBindWithFile(projectRoot string, volumes []compose.VolumeSpec, filename string) string { for _, volume := range volumes { if !volume.IsBindLike() { continue @@ -1387,7 +1389,7 @@ func labelInt(labels map[string]string, key string, fallback int) int { return value } -func firstPort(service Service, fallback int) int { +func firstPort(service compose.Service, fallback int) int { if len(service.Ports) > 0 && service.Ports[0].Target != 0 { return service.Ports[0].Target } @@ -1487,7 +1489,7 @@ func defaultPortForImage(image string) int { } } -func hasExtends(service Service) bool { +func hasExtends(service compose.Service) bool { switch value := service.Extends.(type) { case nil: return false diff --git a/internal/compose/analyze_test.go b/internal/analyzer/analyzer_test.go similarity index 98% rename from internal/compose/analyze_test.go rename to internal/analyzer/analyzer_test.go index 7bdbe72..7647534 100644 --- a/internal/compose/analyze_test.go +++ b/internal/analyzer/analyzer_test.go @@ -1,4 +1,4 @@ -package compose +package analyzer import ( "os" @@ -1216,22 +1216,6 @@ services: } } -func TestParsePortNumberRangesAndJunk(t *testing.T) { - cases := map[string]int{ - "80": 80, - "3000-3005": 3000, - " 8080 ": 8080, - "": 0, - "not-a-port": 0, - "5173/tcp": 0, // protocol is split off before this helper is called - } - for input, want := range cases { - if got := parsePortNumber(input); got != want { - t.Errorf("parsePortNumber(%q) = %d, want %d", input, got, want) - } - } -} - func TestAnalyzeRegistryQualifiedImagesAreRecognized(t *testing.T) { for _, image := range []string{"docker.io/library/postgres:16", "library/postgres", "postgres:16"} { t.Run(image, func(t *testing.T) { diff --git a/internal/cli/cli.go b/internal/cli/cli.go index fd303c2..b8b38c6 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" - "github.com/ramazankara/pocketstack/internal/compose" - "github.com/ramazankara/pocketstack/internal/staticdemo" + "github.com/ramazankara/pocketstack/internal/analyzer" + "github.com/ramazankara/pocketstack/internal/generator" ) var version = "dev" @@ -51,7 +51,7 @@ func analyze(args []string, stdout, stderr io.Writer) int { fmt.Fprintln(stderr, err) return 1 } - analysis, err := compose.AnalyzeFile(resolvedCompose) + analysis, err := analyzer.AnalyzeFile(resolvedCompose) if err != nil { fmt.Fprintln(stderr, err) return 1 @@ -109,7 +109,7 @@ func demo(args []string, stdout, stderr io.Writer) int { fmt.Fprintln(stderr, err) return 1 } - result, err := staticdemo.Generate(staticdemo.Options{ + result, err := generator.Generate(generator.Options{ ComposeFile: resolvedCompose, OutputDir: *outputDir, }) diff --git a/internal/compose/types_test.go b/internal/compose/types_test.go new file mode 100644 index 0000000..8179504 --- /dev/null +++ b/internal/compose/types_test.go @@ -0,0 +1,19 @@ +package compose + +import "testing" + +func TestParsePortNumberRangesAndJunk(t *testing.T) { + cases := map[string]int{ + "80": 80, + "3000-3005": 3000, + " 8080 ": 8080, + "": 0, + "not-a-port": 0, + "5173/tcp": 0, // protocol is split off before this helper is called + } + for input, want := range cases { + if got := parsePortNumber(input); got != want { + t.Errorf("parsePortNumber(%q) = %d, want %d", input, got, want) + } + } +} diff --git a/internal/staticdemo/generator.go b/internal/generator/generator.go similarity index 90% rename from internal/staticdemo/generator.go rename to internal/generator/generator.go index 8b4c396..902b14d 100644 --- a/internal/staticdemo/generator.go +++ b/internal/generator/generator.go @@ -1,4 +1,4 @@ -package staticdemo +package generator import ( "crypto/sha256" @@ -16,7 +16,7 @@ import ( "strings" "time" - "github.com/ramazankara/pocketstack/internal/compose" + "github.com/ramazankara/pocketstack/internal/analyzer" ) //go:embed runtime/* @@ -35,30 +35,30 @@ type Result struct { } type Manifest struct { - Version string `json:"version"` - GeneratedAt string `json:"generatedAt"` - Mode string `json:"mode"` - BrowserOnly bool `json:"browserOnly"` - ComposeFile string `json:"composeFile"` - StorageNamespace string `json:"storageNamespace"` - Readiness compose.Readiness `json:"readiness"` - HostRequirements compose.HostRequirements `json:"hostRequirements,omitempty"` - Warnings []string `json:"warnings,omitempty"` - NextSteps []string `json:"nextSteps,omitempty"` - Services []ManifestService `json:"services"` + Version string `json:"version"` + GeneratedAt string `json:"generatedAt"` + Mode string `json:"mode"` + BrowserOnly bool `json:"browserOnly"` + ComposeFile string `json:"composeFile"` + StorageNamespace string `json:"storageNamespace"` + Readiness analyzer.Readiness `json:"readiness"` + HostRequirements analyzer.HostRequirements `json:"hostRequirements,omitempty"` + Warnings []string `json:"warnings,omitempty"` + NextSteps []string `json:"nextSteps,omitempty"` + Services []ManifestService `json:"services"` } type ManifestService struct { - Name string `json:"name"` - Image string `json:"image,omitempty"` - Adapter string `json:"adapter"` - BrowserNative bool `json:"browserNative"` - PublicPort int `json:"publicPort,omitempty"` - BrowserPath string `json:"browserPath,omitempty"` - Assets []ManifestAsset `json:"assets,omitempty"` - Config map[string]string `json:"config,omitempty"` - Warnings []string `json:"warnings,omitempty"` - HostRequirements compose.HostRequirements `json:"hostRequirements,omitempty"` + Name string `json:"name"` + Image string `json:"image,omitempty"` + Adapter string `json:"adapter"` + BrowserNative bool `json:"browserNative"` + PublicPort int `json:"publicPort,omitempty"` + BrowserPath string `json:"browserPath,omitempty"` + Assets []ManifestAsset `json:"assets,omitempty"` + Config map[string]string `json:"config,omitempty"` + Warnings []string `json:"warnings,omitempty"` + HostRequirements analyzer.HostRequirements `json:"hostRequirements,omitempty"` } type ManifestAsset struct { @@ -73,7 +73,7 @@ func Generate(options Options) (*Result, error) { if options.GeneratedAt.IsZero() { options.GeneratedAt = time.Now().UTC() } - analysis, err := compose.AnalyzeFile(options.ComposeFile) + analysis, err := analyzer.AnalyzeFile(options.ComposeFile) if err != nil { return nil, err } @@ -130,7 +130,7 @@ func Generate(options Options) (*Result, error) { return &Result{OutputDir: options.OutputDir, Mode: analysis.Mode, Manifest: manifest}, nil } -func copyServiceAssets(assetsDir string, service compose.ServiceAnalysis, storageNamespace string) (ManifestService, error) { +func copyServiceAssets(assetsDir string, service analyzer.ServiceAnalysis, storageNamespace string) (ManifestService, error) { serviceDir := filepath.Join(assetsDir, service.Name) manifestService := ManifestService{ Name: service.Name, @@ -142,7 +142,7 @@ func copyServiceAssets(assetsDir string, service compose.ServiceAnalysis, storag Warnings: service.Warnings, HostRequirements: service.HostRequirements, } - if service.Adapter == compose.AdapterPostgresPGlite || service.Adapter == compose.AdapterSQLite { + if service.Adapter == analyzer.AdapterPostgresPGlite || service.Adapter == analyzer.AdapterSQLite { manifestService.Config["storageNamespace"] = storageNamespace } for _, asset := range service.Assets { @@ -257,7 +257,7 @@ func demoStorageNamespace(composeFile string) string { return "ps-" + hex.EncodeToString(sum[:])[:16] } -func unsupportedError(analysis *compose.Analysis) error { +func unsupportedError(analysis *analyzer.Analysis) error { var builder strings.Builder fmt.Fprintf(&builder, "project is not browser-native yet; unsupported services:") for _, service := range analysis.Services { @@ -283,7 +283,7 @@ func copyTreeFiltered(source, destination string, include func(string, os.DirEnt return copyTreeFilteredWithSkip(source, destination, include, skipProjectDir) } -func copyDirectoryAsset(asset compose.AssetAnalysis, destination string) ([]string, error) { +func copyDirectoryAsset(asset analyzer.AssetAnalysis, destination string) ([]string, error) { if asset.Name == "static" { return copyTreeFilteredWithSkipTransform(asset.Source, destination, func(string, os.DirEntry) bool { return true @@ -466,7 +466,7 @@ func writeRuntime(outputDir string) error { }) } -func writeHostConfigs(outputDir string, requirements compose.HostRequirements) error { +func writeHostConfigs(outputDir string, requirements analyzer.HostRequirements) error { if !requirements.CrossOriginIsolationRequired { return nil } diff --git a/internal/staticdemo/generator_test.go b/internal/generator/generator_test.go similarity index 99% rename from internal/staticdemo/generator_test.go rename to internal/generator/generator_test.go index 191957c..1fef898 100644 --- a/internal/staticdemo/generator_test.go +++ b/internal/generator/generator_test.go @@ -1,4 +1,4 @@ -package staticdemo +package generator import ( "os" diff --git a/internal/staticdemo/runtime/app.js b/internal/generator/runtime/app.js similarity index 99% rename from internal/staticdemo/runtime/app.js rename to internal/generator/runtime/app.js index a6033d2..77436c9 100644 --- a/internal/staticdemo/runtime/app.js +++ b/internal/generator/runtime/app.js @@ -1,4 +1,4 @@ -// runtime/src/db-assets.ts +// web/runtime/src/db-assets.ts function configList(value = "") { return String(value || "").split("\n").map((item) => item.trim()).filter(Boolean); } @@ -22,7 +22,7 @@ function sqliteSQLAssetPaths(service, isDatabasePath = () => false) { ]; } -// runtime/src/frontend-adapter.ts +// web/runtime/src/frontend-adapter.ts var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([ ".cjs", ".css", @@ -233,7 +233,7 @@ function splitEnvironmentEntry(entry) { return [text.slice(0, index).trim(), text.slice(index + 1)]; } -// runtime/src/mock-routes.ts +// web/runtime/src/mock-routes.ts var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "patch", "options", "head"]); function statusFromKey(key) { const value = Number.parseInt(key, 10); @@ -450,7 +450,7 @@ function mergeMockRoutes(openAPIRoutes = [], fixtureRoutes = []) { return [...byKey.values()]; } -// runtime/src/pglite-adapter.ts +// web/runtime/src/pglite-adapter.ts var BOOTSTRAP_TABLE = "__pocketstack_bootstrap"; function pglitePersistMode(service) { return service?.config?.persist === "memory" ? "memory" : "indexeddb"; @@ -506,7 +506,7 @@ function storageToken(value) { return String(value || "").trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "demo"; } -// runtime/src/service-urls.ts +// web/runtime/src/service-urls.ts function serviceEnvName(name = "") { return String(name || "service").trim().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase() || "SERVICE"; } @@ -573,7 +573,7 @@ function frontendServiceEnvironment(env = {}, services = [], baseHref = globalTh return next; } -// runtime/src/sqlite-adapter.ts +// web/runtime/src/sqlite-adapter.ts function sqlitePersistMode(service) { return service?.config?.persist === "memory" ? "memory" : "indexeddb"; } @@ -593,7 +593,7 @@ function storageToken2(value) { return String(value || "").trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "demo"; } -// runtime/src/wasi-preview.ts +// web/runtime/src/wasi-preview.ts var WASI_ERRNO = { success: 0, badf: 8, @@ -3528,7 +3528,7 @@ var safeLoad = renamed("safeLoad", "load"); var safeLoadAll = renamed("safeLoadAll", "loadAll"); var safeDump = renamed("safeDump", "dump"); -// runtime/src/app.ts +// web/runtime/src/app.ts var state = { manifest: null, selected: null, diff --git a/internal/staticdemo/runtime/mock-sw.js b/internal/generator/runtime/mock-sw.js similarity index 100% rename from internal/staticdemo/runtime/mock-sw.js rename to internal/generator/runtime/mock-sw.js diff --git a/docs/ANNOUNCEMENT_LINKEDIN.md b/marketing/ANNOUNCEMENT_LINKEDIN.md similarity index 94% rename from docs/ANNOUNCEMENT_LINKEDIN.md rename to marketing/ANNOUNCEMENT_LINKEDIN.md index d7d6bc4..b6198b3 100644 --- a/docs/ANNOUNCEMENT_LINKEDIN.md +++ b/marketing/ANNOUNCEMENT_LINKEDIN.md @@ -52,10 +52,10 @@ Try it here: https://ramazankara.github.io/pocketstack/ GitHub: -https://github.com/RamazanKara/pocketstack +https://github.com/ramazankara/pocketstack Release: -https://github.com/RamazanKara/pocketstack/releases/tag/v1.1.0 +https://github.com/ramazankara/pocketstack/releases/tag/v1.1.0 This is early, but it already feels like the right direction: @@ -91,7 +91,7 @@ Try the hosted Studio: https://ramazankara.github.io/pocketstack/ GitHub: -https://github.com/RamazanKara/pocketstack +https://github.com/ramazankara/pocketstack #opensource #devtools #docker #buildinpublic diff --git a/package-lock.json b/package-lock.json index 03bba93..313d606 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,372 @@ "esbuild": "^0.25.0", "js-yaml": "^4.1.1", "puppeteer-core": "^25.1.0", + "vitepress": "^1.6.4", "wabt": "^1.0.39" }, "engines": { "node": ">=26" } }, + "node_modules/@algolia/abtesting": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.21.1.tgz", + "integrity": "sha512-Wia5/mNTfiU0PIUN25UMfAGGdASkkwuCS9nBAdmhqrNPY/ff7U/6MgBVdwFDPsa3sA1msutPtO50gvOzx6MOXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.55.1.tgz", + "integrity": "sha512-miW8RzAtBgNiEJ9fGEhsOPgWUpekAe64YcVufqXrlykj0Jjmo5nj0a5f/HAzRVX5ZuU1GAVd7BkzFDx7q50P3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.55.1.tgz", + "integrity": "sha512-eR3J3kB9JX6DdCvDRi3I4KPfwO6fR9HWYRXhVke2TXIoOQafMKCRAneg33JRmIrb+DnnJ/eWApJLF1O1CLPERg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.55.1.tgz", + "integrity": "sha512-P5ak7EurwYqgAiDyb95mgA3WRR/Zu8CPMv36lWTISvL2AmlPyqQPy2nX/KEJRTcwaeTWwrk6wJV4/M93GfjOWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.55.1.tgz", + "integrity": "sha512-OVtj9uA//+pjvKQI5INnzbyLrf3ClNv3XRbWswwJ2kHIStQNHtBfHo+LofNB/WhM9xjuXlW5ANn2aMj65UGx7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.55.1.tgz", + "integrity": "sha512-oKlVFlp+qbIEe4p7E54zSiP2gEV/vDu972Ykv8VDMFwEvreS7m0YKA3a8hGGHwc7yiBUGGiR3LlwzMLfnJmy6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.55.1.tgz", + "integrity": "sha512-BOVrld6vdtsFmotVDMTVQfYXwrVplJ+DUvy60JFi+tkWV698q2J9NNPKEO3dr5qxtSLKQP4vHF8n+3U5PDWhOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.55.1.tgz", + "integrity": "sha512-GAqHl9zERhC3bbBfubwUu07G3UXO06gORvOcsiTBZB3et0s3auNUbHlYdYNp4VKa3sUZqH5AcD3OKzU/KDGXjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.55.1.tgz", + "integrity": "sha512-BXZw+C+gsWL7pZvbnhJUnCXASiDLGcQxVV7h55Pyh2DmSzwdZIVccE5xc9RVD2trtrhIqk5smuODTxtaZqd0IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.55.1.tgz", + "integrity": "sha512-9g/ceZrZTqA62FA3588Xj0onRPjDNfu0pVQqefK0rrHp9H6Wblph/YmzGjZ2g8uqbTh0ZGIvAGCzErU8f7MHpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.55.1.tgz", + "integrity": "sha512-cZTIrGyAP+W4A6jDVwvWM/JOaoJKQkD/2a5eLUEeNdKAD45jN7BCpsMDONyhZlosLa4UwL8uiINQzj4iFy9nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.55.1.tgz", + "integrity": "sha512-N6I3leW0UO8Y9Zv90yo2UHgYGuxZO0mjbvzNxDIJDjO0qECEF7Z9XMvSNeUWXQh/iNDA9lr8MfEy3rmZGIcclw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.55.1.tgz", + "integrity": "sha512-ukU5zeeFs44rQkzv+TRdYard+d+3lmPGs8lPZhHtWE8rfz+LlBSF6s9kP3VQ7LeOYL8Dz0u6tZfnyTrqrumbHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.55.1.tgz", + "integrity": "sha512-lCwXyijwPm3vbYHpBXPRomMcD6mgiptmps27gnMCf4HK+u/AOeFPBnIFh4V3l4A5SnP9VRiKBZqwGBpUH0vaTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -434,6 +794,30 @@ "node": ">=18" } }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.88", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.88.tgz", + "integrity": "sha512-+cvi1qCuvReL29ehi6t62L4fb7GDXe+UlGHFcsJcV7I2l9wtqn9XE2IBKcDr3CI5iGUGS5ISnXv699pSGpyx1Q==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@playwright/test": { "version": "1.60.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", @@ -473,293 +857,2390 @@ } } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/chromium-bidi": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz", - "integrity": "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "engines": { - "node": ">=20.19.0 <22.0.0 || >=22.12.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.55.1.tgz", + "integrity": "sha512-FyaFnnsbVPtevQwqSj/SdxE3jAsSsY0BEH8IVLf9rXxEBdAhAmT6VKCVSMWoaPIHVN1Eufh/1w8q6k8URpIkWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.21.1", + "@algolia/client-abtesting": "5.55.1", + "@algolia/client-analytics": "5.55.1", + "@algolia/client-common": "5.55.1", + "@algolia/client-insights": "5.55.1", + "@algolia/client-personalization": "5.55.1", + "@algolia/client-query-suggestions": "5.55.1", + "@algolia/client-search": "5.55.1", + "@algolia/ingestion": "1.55.1", + "@algolia/monitoring": "1.55.1", + "@algolia/recommend": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chromium-bidi": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz", + "integrity": "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==", + "dev": true, + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1624250", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1624250.tgz", + "integrity": "sha512-YFAat/lOiIk0ARmBweG+ygrEcbZrq5B9urRyUoeQKp53MlidHXE2TmTbxKcaXoQj7u/aX+jebDO4BW55rs0WwA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, - "node_modules/devtools-protocol": { - "version": "0.0.1624250", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1624250.tgz", - "integrity": "sha512-YFAat/lOiIk0ARmBweG+ygrEcbZrq5B9urRyUoeQKp53MlidHXE2TmTbxKcaXoQj7u/aX+jebDO4BW55rs0WwA==", - "dev": true + "node_modules/modern-tar": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", + "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.29.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.3.tgz", + "integrity": "sha512-D9NL1GAnJZhc3RndVs4gDdxEeU9TcHgywMrhhOsnpdlvFjdbx0gAsLUnH6JEhlJH5giL7Tx5biWPUSEXE/HPzw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/puppeteer-core": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.1.0.tgz", + "integrity": "sha512-jKzy5y4WG6uNuFbTWgW1D7mqoT9o0nllc/6a1DGF775T1mPmgw3scdFEtEq67yVFikavQmbYq6NLfbTfxHSlqQ==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "3.0.4", + "chromium-bidi": "16.0.1", + "devtools-protocol": "0.0.1624250", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.2", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", "dev": true }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "node": ">=12" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], "dev": true, - "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=12" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=12" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/modern-tar": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", - "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.0.0" + "node": ">=12" } }, - "node_modules/playwright": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", - "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "playwright-core": "1.60.0" - }, - "bin": { - "playwright": "cli.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" + "node": ">=12" } }, - "node_modules/playwright-core": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", - "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], "dev": true, - "bin": { - "playwright-core": "cli.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/puppeteer-core": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.1.0.tgz", - "integrity": "sha512-jKzy5y4WG6uNuFbTWgW1D7mqoT9o0nllc/6a1DGF775T1mPmgw3scdFEtEq67yVFikavQmbYq6NLfbTfxHSlqQ==", + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, - "dependencies": { - "@puppeteer/browsers": "3.0.4", - "chromium-bidi": "16.0.1", - "devtools-protocol": "0.0.1624250", - "typed-query-selector": "^2.12.2", - "webdriver-bidi-protocol": "0.4.2", - "ws": "^8.21.0" + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=22.12.0" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" }, - "engines": { - "node": ">=8" + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/typed-query-selector": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", - "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", - "dev": true - }, "node_modules/wabt": { "version": "1.0.39", "resolved": "https://registry.npmjs.org/wabt/-/wabt-1.0.39.tgz", @@ -865,6 +3346,17 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index d22caff..05adbaf 100644 --- a/package.json +++ b/package.json @@ -7,19 +7,22 @@ "node": ">=26" }, "scripts": { - "build:runtime": "esbuild runtime/src/app.ts --bundle --format=esm --target=es2022 --outfile=internal/staticdemo/runtime/app.js", + "build:runtime": "esbuild web/runtime/src/app.ts --bundle --format=esm --target=es2022 --outfile=internal/generator/runtime/app.js", "build:wasi-example": "wat2wasm examples/wasi/hello.wat -o examples/wasi/hello.wasm", - "test:runtime": "node --test runtime/test/runtime.test.mjs runtime/test/wasi.test.mjs runtime/test/mock-routes.test.mjs runtime/test/mock-sw.test.mjs runtime/test/sqlite-adapter.test.mjs runtime/test/pglite-adapter.test.mjs runtime/test/frontend-adapter.test.mjs runtime/test/db-assets.test.mjs runtime/test/service-urls.test.mjs", - "test:smoke": "node runtime/test/generated-demo.test.mjs", + "test:runtime": "node --test web/runtime/test/runtime.test.mjs web/runtime/test/wasi.test.mjs web/runtime/test/mock-routes.test.mjs web/runtime/test/mock-sw.test.mjs web/runtime/test/sqlite-adapter.test.mjs web/runtime/test/pglite-adapter.test.mjs web/runtime/test/frontend-adapter.test.mjs web/runtime/test/db-assets.test.mjs web/runtime/test/service-urls.test.mjs", + "test:smoke": "node web/runtime/test/generated-demo.test.mjs", "media": "node scripts/record-announcement-demo.mjs", "pages:build": "node scripts/build-pages-site.mjs", - "test:browsers": "node tests/browser/pages-basic.test.mjs" + "test:browsers": "node tests/browser/pages-basic.test.mjs", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs" }, "devDependencies": { "@playwright/test": "^1.60.0", "esbuild": "^0.25.0", "js-yaml": "^4.1.1", "puppeteer-core": "^25.1.0", + "vitepress": "^1.6.4", "wabt": "^1.0.39" } } diff --git a/scripts/build-pages-site.mjs b/scripts/build-pages-site.mjs index 67528de..be282c5 100644 --- a/scripts/build-pages-site.mjs +++ b/scripts/build-pages-site.mjs @@ -20,8 +20,8 @@ const demos = [ await rm(pagesDir, { recursive: true, force: true }); await mkdir(pagesDir, { recursive: true }); -await cp(join(root, "site"), pagesDir, { recursive: true }); -await cp(join(root, "studio"), join(pagesDir, "studio"), { recursive: true }); +await cp(join(root, "web", "site"), pagesDir, { recursive: true }); +await cp(join(root, "web", "studio"), join(pagesDir, "studio"), { recursive: true }); const mediaDir = join(root, "docs", "media"); if (existsSync(mediaDir)) { @@ -30,6 +30,13 @@ if (existsSync(mediaDir)) { console.warn("Skipping docs/media (not found); run `npm run media` to record the announcement clip."); } +const docsDist = join(root, "docs", ".vitepress", "dist"); +if (existsSync(docsDist)) { + await cp(docsDist, join(pagesDir, "docs"), { recursive: true }); +} else { + console.warn("Skipping docs site (docs/.vitepress/dist not found); run `npm run docs:build` first."); +} + await mkdir(join(pagesDir, "demos"), { recursive: true }); const includedDemos = []; diff --git a/scripts/record-announcement-demo.mjs b/scripts/record-announcement-demo.mjs index 1463470..95a1272 100644 --- a/scripts/record-announcement-demo.mjs +++ b/scripts/record-announcement-demo.mjs @@ -7,7 +7,7 @@ import { tmpdir } from "node:os"; import puppeteer from "puppeteer-core"; const root = resolve(new URL("..", import.meta.url).pathname); -const studioDir = join(root, "studio"); +const studioDir = join(root, "web", "studio"); const mediaDir = join(root, "docs", "media"); const framesDir = join(mediaDir, "frames-announcement"); const videoPath = join(mediaDir, "pocketstack-announcement.mp4"); diff --git a/runtime/src/app.ts b/web/runtime/src/app.ts similarity index 100% rename from runtime/src/app.ts rename to web/runtime/src/app.ts diff --git a/runtime/src/db-assets.ts b/web/runtime/src/db-assets.ts similarity index 100% rename from runtime/src/db-assets.ts rename to web/runtime/src/db-assets.ts diff --git a/runtime/src/frontend-adapter.ts b/web/runtime/src/frontend-adapter.ts similarity index 100% rename from runtime/src/frontend-adapter.ts rename to web/runtime/src/frontend-adapter.ts diff --git a/runtime/src/mock-routes.ts b/web/runtime/src/mock-routes.ts similarity index 100% rename from runtime/src/mock-routes.ts rename to web/runtime/src/mock-routes.ts diff --git a/runtime/src/pglite-adapter.ts b/web/runtime/src/pglite-adapter.ts similarity index 100% rename from runtime/src/pglite-adapter.ts rename to web/runtime/src/pglite-adapter.ts diff --git a/runtime/src/service-urls.ts b/web/runtime/src/service-urls.ts similarity index 100% rename from runtime/src/service-urls.ts rename to web/runtime/src/service-urls.ts diff --git a/runtime/src/sqlite-adapter.ts b/web/runtime/src/sqlite-adapter.ts similarity index 100% rename from runtime/src/sqlite-adapter.ts rename to web/runtime/src/sqlite-adapter.ts diff --git a/runtime/src/wasi-preview.ts b/web/runtime/src/wasi-preview.ts similarity index 100% rename from runtime/src/wasi-preview.ts rename to web/runtime/src/wasi-preview.ts diff --git a/runtime/test/db-assets.test.mjs b/web/runtime/test/db-assets.test.mjs similarity index 100% rename from runtime/test/db-assets.test.mjs rename to web/runtime/test/db-assets.test.mjs diff --git a/runtime/test/frontend-adapter.test.mjs b/web/runtime/test/frontend-adapter.test.mjs similarity index 100% rename from runtime/test/frontend-adapter.test.mjs rename to web/runtime/test/frontend-adapter.test.mjs diff --git a/runtime/test/generated-demo.test.mjs b/web/runtime/test/generated-demo.test.mjs similarity index 89% rename from runtime/test/generated-demo.test.mjs rename to web/runtime/test/generated-demo.test.mjs index 05d9abf..e46c7db 100644 --- a/runtime/test/generated-demo.test.mjs +++ b/web/runtime/test/generated-demo.test.mjs @@ -5,7 +5,7 @@ import test from "node:test"; async function readManifest(t, name) { let raw; try { - raw = await readFile(new URL(`../../dist/${name}/pocketstack.manifest.json`, import.meta.url), "utf8"); + raw = await readFile(new URL(`../../../dist/${name}/pocketstack.manifest.json`, import.meta.url), "utf8"); } catch (error) { if (error.code === "ENOENT") { t.skip(`run make smoke to generate dist/${name} first`); @@ -98,15 +98,15 @@ test("generated WASI demo carries args and Compose environment", async (t) => { assert.equal(service.config.env, "POCKETSTACK_MODE=demo"); assert.equal(manifest.hostRequirements.crossOriginIsolationRequired, true); assert.equal(service.hostRequirements.crossOriginIsolationRequired, true); - const headers = await readFile(new URL("../../dist/wasi/_headers", import.meta.url), "utf8"); + const headers = await readFile(new URL("../../../dist/wasi/_headers", import.meta.url), "utf8"); assert.match(headers, /Cross-Origin-Opener-Policy: same-origin/); assert.match(headers, /Cross-Origin-Embedder-Policy: require-corp/); - const vercel = JSON.parse(await readFile(new URL("../../dist/wasi/vercel.json", import.meta.url), "utf8")); + const vercel = JSON.parse(await readFile(new URL("../../../dist/wasi/vercel.json", import.meta.url), "utf8")); assert.deepEqual(vercel.headers[0].headers, [ { key: "Cross-Origin-Opener-Policy", value: "same-origin" }, { key: "Cross-Origin-Embedder-Policy", value: "require-corp" }, ]); - const staticWebApp = JSON.parse(await readFile(new URL("../../dist/wasi/staticwebapp.config.json", import.meta.url), "utf8")); + const staticWebApp = JSON.parse(await readFile(new URL("../../../dist/wasi/staticwebapp.config.json", import.meta.url), "utf8")); assert.equal(staticWebApp.globalHeaders["Cross-Origin-Opener-Policy"], "same-origin"); assert.equal(staticWebApp.globalHeaders["Cross-Origin-Embedder-Policy"], "require-corp"); }); @@ -124,8 +124,8 @@ test("generated full-stack demo wires frontend bridge, mock API, and browser dat assert.equal(byName.api.config.fixturesIndex, "health.json"); assert.equal(byName.db.config.initScripts, "assets/db/init-scripts/01-init.sql"); - const index = await readFile(new URL("../../dist/full-stack/assets/app/project/index.html", import.meta.url), "utf8"); - const runtime = await readFile(new URL("../../dist/full-stack/app.js", import.meta.url), "utf8"); + const index = await readFile(new URL("../../../dist/full-stack/assets/app/project/index.html", import.meta.url), "utf8"); + const runtime = await readFile(new URL("../../../dist/full-stack/app.js", import.meta.url), "utf8"); assert.match(index, /src="\/src\/main\.jsx"/); assert.match(runtime, /__POCKETSTACK_BRIDGE_CONFIG__/); assert.match(runtime, /__pocketstack_bridge\.js/); @@ -147,7 +147,7 @@ test("generated upload-ready examples are real browser-native demos", async (t) assert.equal(manifest.services[0].adapter, adapter); } - const staticHTML = await readFile(new URL("../../dist/uploaded-static-blog/assets/web/static/index.html", import.meta.url), "utf8"); + const staticHTML = await readFile(new URL("../../../dist/uploaded-static-blog/assets/web/static/index.html", import.meta.url), "utf8"); assert.match(staticHTML, /href="\.\/styles\.css"/); const mock = await readManifest(t, "uploaded-mock-catalog"); diff --git a/runtime/test/mock-routes.test.mjs b/web/runtime/test/mock-routes.test.mjs similarity index 100% rename from runtime/test/mock-routes.test.mjs rename to web/runtime/test/mock-routes.test.mjs diff --git a/runtime/test/mock-sw.test.mjs b/web/runtime/test/mock-sw.test.mjs similarity index 98% rename from runtime/test/mock-sw.test.mjs rename to web/runtime/test/mock-sw.test.mjs index a80998d..1aab0d2 100644 --- a/runtime/test/mock-sw.test.mjs +++ b/web/runtime/test/mock-sw.test.mjs @@ -44,7 +44,7 @@ async function loadWorker(options = {}) { skipWaiting: () => Promise.resolve(), }, }; - const source = await readFile(new URL("../../internal/staticdemo/runtime/mock-sw.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/mock-sw.js", import.meta.url), "utf8"); vm.runInNewContext(source, context); return { diff --git a/runtime/test/pglite-adapter.test.mjs b/web/runtime/test/pglite-adapter.test.mjs similarity index 100% rename from runtime/test/pglite-adapter.test.mjs rename to web/runtime/test/pglite-adapter.test.mjs diff --git a/runtime/test/runtime.test.mjs b/web/runtime/test/runtime.test.mjs similarity index 68% rename from runtime/test/runtime.test.mjs rename to web/runtime/test/runtime.test.mjs index ec3820a..18b6284 100644 --- a/runtime/test/runtime.test.mjs +++ b/web/runtime/test/runtime.test.mjs @@ -3,48 +3,48 @@ import assert from "node:assert/strict"; import test from "node:test"; test("runtime script exposes adapter handlers", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); for (const adapter of ["frontend", "wasi", "mock-http", "postgres-pglite", "sqlite", "static-web"]) { assert.match(source, new RegExp(adapter.replace("-", "\\-"))); } }); test("runtime script includes real WASI fallback execution", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); assert.match(source, /runWasix/); assert.match(source, /@wasmer\/sdk@0\.10\.0/); assert.match(source, /wasi_unstable/); }); test("runtime script remains a browser module", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); assert.match(source, /export\s*\{/); assert.match(source, /pocketstack\.manifest\.json/); }); test("runtime script exposes browser database query bridge", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); assert.match(source, /POCKETSTACK_DB_QUERY/); assert.match(source, /__pocketstack\/db/); assert.match(source, /globalThis\.PocketStack/); }); test("runtime script exposes WebContainer frontend bridge", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); assert.match(source, /POCKETSTACK_BRIDGE_FETCH/); assert.match(source, /__pocketstack_bridge\.js/); assert.match(source, /frontendBridgeTargetURL/); }); test("runtime script surfaces service compatibility warnings", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); assert.match(source, /logServiceWarnings/); assert.match(source, /COOP\/COEP required/); assert.match(source, /public browser runtime packages or npm dependencies/); }); test("runtime escapes untrusted values and pins CDN runtime packages", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); // Untrusted Compose/OpenAPI strings must be escaped before innerHTML. assert.match(source, /escapeHTML/); // Browser runtime packages must be version-pinned, not floating on latest. @@ -53,20 +53,20 @@ test("runtime escapes untrusted values and pins CDN runtime packages", async () }); test("mock OpenAPI YAML parser is bundled into generated demos", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/app.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/app.js", import.meta.url), "utf8"); assert.doesNotMatch(source, /esm\.sh\/js-yaml/); assert.match(source, /js-yaml 4\./); }); test("mock service worker handles demos hosted below a path prefix", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/mock-sw.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/mock-sw.js", import.meta.url), "utf8"); assert.match(source, /indexOf\(marker\)/); assert.match(source, /decodeURIComponent\(service\)/); assert.match(source, /clients\.claim/); }); test("mock service worker supports OpenAPI path templates", async () => { - const source = await readFile(new URL("../../internal/staticdemo/runtime/mock-sw.js", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../internal/generator/runtime/mock-sw.js", import.meta.url), "utf8"); assert.match(source, /matchRoute/); assert.match(source, /params\[parameter\[1\]\]/); assert.match(source, /\^\\\{\(\[\^\/\]\+\)\\\}\$/); diff --git a/runtime/test/service-urls.test.mjs b/web/runtime/test/service-urls.test.mjs similarity index 100% rename from runtime/test/service-urls.test.mjs rename to web/runtime/test/service-urls.test.mjs diff --git a/runtime/test/sqlite-adapter.test.mjs b/web/runtime/test/sqlite-adapter.test.mjs similarity index 100% rename from runtime/test/sqlite-adapter.test.mjs rename to web/runtime/test/sqlite-adapter.test.mjs diff --git a/runtime/test/wasi.test.mjs b/web/runtime/test/wasi.test.mjs similarity index 99% rename from runtime/test/wasi.test.mjs rename to web/runtime/test/wasi.test.mjs index 7ee08a6..fbd9a1b 100644 --- a/runtime/test/wasi.test.mjs +++ b/web/runtime/test/wasi.test.mjs @@ -20,7 +20,7 @@ async function loadWASIHelpers() { test("WASI example writes hello output through fd_write", async () => { const { createWASIPreviewImports } = await loadWASIHelpers(); - const bytes = await readFile(new URL("../../examples/wasi/hello.wasm", import.meta.url)); + const bytes = await readFile(new URL("../../../examples/wasi/hello.wasm", import.meta.url)); let instance; const output = []; const imports = createWASIPreviewImports({ diff --git a/site/index.html b/web/site/index.html similarity index 89% rename from site/index.html rename to web/site/index.html index 907e761..9697432 100644 --- a/site/index.html +++ b/web/site/index.html @@ -11,9 +11,10 @@
@@ -69,17 +70,17 @@

Generated by the CLI

Real upload-ready projects

diff --git a/site/styles.css b/web/site/styles.css similarity index 100% rename from site/styles.css rename to web/site/styles.css diff --git a/studio/README.md b/web/studio/README.md similarity index 100% rename from studio/README.md rename to web/studio/README.md diff --git a/studio/app.js b/web/studio/app.js similarity index 100% rename from studio/app.js rename to web/studio/app.js diff --git a/studio/index.html b/web/studio/index.html similarity index 100% rename from studio/index.html rename to web/studio/index.html diff --git a/studio/styles.css b/web/studio/styles.css similarity index 100% rename from studio/styles.css rename to web/studio/styles.css