diff --git a/docs-app/index.html b/docs-app/index.html index 45fcc381..6bfe01bd 100644 --- a/docs-app/index.html +++ b/docs-app/index.html @@ -6,6 +6,26 @@ + + - + + - - -
- Kolay -
-
+ diff --git a/docs-app/package.json b/docs-app/package.json index a37368b6..cfa076d8 100644 --- a/docs-app/package.json +++ b/docs-app/package.json @@ -15,7 +15,7 @@ "./*": "./src/*" }, "scripts": { - "build:app": "vite build", + "build": "vite build", "build:tests": "NODE_ENV=development vite build --mode development", "lint": "pnpm -w turbo --filter docs-app _:lint", "lint:fix": "pnpm -w turbo --filter docs-app _:lint:fix", @@ -26,9 +26,8 @@ "lint:js:fix": "eslint . --fix", "lint:prettier": "prettier . --check", "lint:prettier:fix": "prettier . --write", - "start": "pnpm start:vite", - "start:vite": "vite", - "start:test": "pnpm start:vite --open /tests/", + "start": "vite", + "start:test": "pnpm start --open /tests/", "test:ember": "pnpm build:tests && pnpm test:browser", "test:browser": "testem ci --port 0" }, @@ -41,16 +40,17 @@ "@ember/string": "^4.0.1", "@ember/test-helpers": "^5.4.1", "@ember/test-waiters": "^4.0.0", - "@embroider/compat": "^4.1.13", - "@embroider/core": "^4.4.3", - "@embroider/macros": "^1.19.7", - "@embroider/vite": "^1.5.2", + "@embroider/compat": "^4.1.18", + "@embroider/core": "^4.4.7", + "@embroider/macros": "^1.20.2", + "@embroider/vite": "^1.7.3", "@glimmer/component": "^2.0.0", "@glint/ember-tsc": "1.1.1", "@glint/template": "1.7.4", "@glint/tsserver-plugin": "2.1.0", "@nullvoxpopuli/eslint-configs": "^5.5.0", "@rollup/plugin-babel": "^6.0.4", + "@types/node": "^25.9.1", "@types/qunit": "^2.19.13", "babel-plugin-ember-template-compilation": "^4.0.0", "concurrently": "^9.2.1", @@ -78,6 +78,7 @@ "typescript": "^5.9.3", "unplugin-info": "^1.2.4", "vite": "8.0.14", + "vite-ember-ssr": "^0.1.0", "vite-plugin-inspect": "^11.3.3", "vite-plugin-wasm": "^3.5.0" }, @@ -100,7 +101,6 @@ "ember-async-data": "^2.0.0", "ember-cached-decorator-polyfill": "^1.0.2", "ember-mobile-menu": "^6.0.0", - "ember-route-template": "^1.0.3", "kolay": "workspace:^", "nvp.ui": "^0.5.3", "reactiveweb": "1.9.1", diff --git a/docs-app/src/app-ssr.ts b/docs-app/src/app-ssr.ts new file mode 100644 index 00000000..454a9425 --- /dev/null +++ b/docs-app/src/app-ssr.ts @@ -0,0 +1,187 @@ +import Application from '@ember/application'; +import { settled } from '@ember/test-helpers'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +import PageTitleService from 'ember-page-title/services/page-title'; +import Resolver from 'ember-resolver'; + +import config from './config.ts'; +import Router from './router.ts'; +import SsrApplicationRoute from './routes/application-ssr.ts'; + +// SSR-only resolver registry. Built fresh here (not imported from registry.ts) +// so we can EXCLUDE `./routes/application.ts` from the SSR module graph — +// that route eagerly registers `import('babel-plugin-ember-template-compilation')` +// and other heavy dynamic-import chunks for kolay's runtime markdown compiler. +// Vite static-analyzes those import() calls and bundles the whole transitive +// graph (content-tag's `.cjs`, babel-import-util's CJS surface, etc.) into +// whichever build sees them; in the SSG/SSR pipeline that graph trips +// vite-ember-ssr's CJS shim against rolldown's stricter parser. SSG never +// invokes the runtime compiler, so we swap in the lean SsrApplicationRoute. +const appName = 'docs-app'; + +function formatAsResolverEntries(imports: Record) { + return Object.fromEntries( + Object.entries(imports).map(([k, v]) => [ + k + .replace(/\.gjs\.md$/, '') + .replace(/\.g?(j|t)s$/, '') + .replace(/^\.\//, `${appName}/`), + v, + ]) + ); +} + +const ssrRegistry: Record = { + [`${appName}/router`]: Router, + [`${appName}/services/page-title`]: PageTitleService, + [`${appName}/routes/application`]: { default: SsrApplicationRoute }, + ...formatAsResolverEntries(import.meta.glob('./templates/**/*.{gjs,gts,js,ts}', { eager: true })), + ...formatAsResolverEntries(import.meta.glob('./services/**/*.{js,ts}', { eager: true })), + // Pull in everything under routes/ EXCEPT the client application route. + ...formatAsResolverEntries( + import.meta.glob(['./routes/**/*.{js,ts}', '!./routes/application.{js,ts}'], { + eager: true, + }) + ), +}; + +class App extends Application { + modulePrefix = config.modulePrefix; + Resolver = Resolver.withModules(ssrRegistry); +} + +// Patch fetch for the two cases SSG hits that Node's built-in fetch can't +// handle: +// +// 1. `fetch('//.json')` from kolay's api-docs runtime — Node has +// no HTTP server running at HappyDOM's `http://localhost/` so the fetch +// rejects and `getPromiseState` writes "Promise rejected while waiting +// to resolve" into the prerendered HTML. Read the JSON from disk. +// +// 2. `fetch(new URL('content_tag_bg.wasm', import.meta.url))` from content-tag +// / wasm-bindgen init — that resolves to a `file://` URL, and undici +// returns `Error: not implemented... yet...` for the `file:` scheme. +// Read the wasm bytes from disk with the right content-type. +const LOCAL_ASSET_PREFIXES = ['/docs/', '/api-docs/']; +let fetchPatched = false; + +function patchFetchForLocalAssets() { + if (fetchPatched) return; + fetchPatched = true; + + const realFetch = globalThis.fetch; + const clientDir = resolve(process.cwd(), 'dist/client'); + + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url; + + try { + // file:// — undici can't fetch these, but we can read them. Vite + // emits hashed asset URLs (e.g. `content_tag_bg-CrL58-hw.wasm`) + // only into the client build dir, not the SSG temp build, so look + // up by base name in dist/client/assets/ as a fallback. + if (url.startsWith('file://')) { + const { fileURLToPath } = await import('node:url'); + const path = await import('node:path'); + const filePath = fileURLToPath(url); + let body: Buffer | null = null; + + try { + body = await readFile(filePath); + } catch { + // Try the client assets dir, matching by base-name prefix + // (Vite adds a hash before the extension). + const { readdir } = await import('node:fs/promises'); + const base = path.basename(filePath, path.extname(filePath)); + const ext = path.extname(filePath); + const clientAssets = path.resolve(process.cwd(), 'dist/client/assets'); + const candidates = await readdir(clientAssets); + const match = candidates.find((f) => f.startsWith(base) && f.endsWith(ext)); + + if (match) body = await readFile(path.join(clientAssets, match)); + } + + if (body) { + const contentType = filePath.endsWith('.wasm') + ? 'application/wasm' + : filePath.endsWith('.json') + ? 'application/json' + : 'application/octet-stream'; + + return new Response(new Uint8Array(body), { + status: 200, + headers: { 'content-type': contentType }, + }); + } + } + + // http://localhost/{docs,api-docs}/... — emitted by the client build + // into dist/client, read straight from disk. + const parsed = new URL(url, 'http://localhost/'); + const isLocal = LOCAL_ASSET_PREFIXES.some((p) => parsed.pathname.startsWith(p)); + + if (parsed.host === 'localhost' && isLocal) { + const file = resolve(clientDir, parsed.pathname.slice(1)); + const body = await readFile(file); + + return new Response(body, { + status: 200, + headers: { 'content-type': 'application/json' }, + }); + } + } catch { + // Fall through to real fetch on any parse / read failure; getPromiseState + // will then capture whatever real fetch returns (or its rejection). + } + + return realFetch(input, init); + }) as typeof fetch; +} + +export function createSsrApp() { + const app = App.create({ ...config.APP, autoboot: false }); + + // Patched after limber's app-ssr (apps/repl/app/app-ssr.ts): bake + // `await settled()` into `app.visit` so the visit promise resolves only + // when Ember's run loop, pending timers, and `@ember/test-waiters` + // (reactiveweb's getPromiseState for the .gjs.md page loader, etc.) + // have drained. Without this we'd rely on vite-ember-ssr's worker + // calling `awaitSettled(10_000)` after visit, which fires the 10s + // timeout on cold-start renders and captures the DOM mid-page-load. + const originalVisit = app.visit.bind(app); + + Object.assign(app, { + visit: async (...args: Parameters) => { + // Install the fetch patch lazily, on first visit. vite-ember-ssr's + // worker overwrites globalThis.fetch with its own (shoebox) wrapper + // AFTER our createSsrApp returns, so patching there gets clobbered. + // By the time visit runs the worker setup is complete, and our + // wrapper sits in front of theirs. + patchFetchForLocalAssets(); + + const instance = await originalVisit(...args); + + // Settle in a loop, not just once. Kolay's `` first renders the + // page Prose only after the Selected loader resolves; that Prose may + // contain `` / `` which kick off ANOTHER `fetch()` and + // register a fresh `waitForPromise` waiter for it. If we only awaited + // settled() once, we'd return before that second waiter had a chance + // to be created, capturing the DOM mid-typedoc-fetch with a "Loading + // api docs..." placeholder. Re-checking with a microtask between calls + // catches newly-scheduled async work, and we cap iterations so a + // genuinely-stuck render still terminates. + for (let i = 0; i < 10; i++) { + await settled(); + await new Promise((r) => setTimeout(r, 0)); + // After the microtask drain, if nothing new ran, settled() returns + // immediately on the next iteration and we exit on the next round. + } + + return instance; + }, + }); + + return app; +} diff --git a/docs-app/src/entry.ts b/docs-app/src/entry.ts new file mode 100644 index 00000000..3d94a218 --- /dev/null +++ b/docs-app/src/entry.ts @@ -0,0 +1,37 @@ +import { prefetchPage, prewarmTypedocCaches } from 'kolay'; +import { bootRehydrated, shouldRehydrate } from 'vite-ember-ssr/client'; + +import Application from './app.ts'; +import config from './config.ts'; + +// During SSR rehydration, eagerly load the current URL's page module AND +// every configured typedoc package's JSON BEFORE booting Ember: +// +// - `prefetchPage` populates Kolay's path-keyed page module cache so +// `Selected.loader` returns a synchronous `{ resolved }` state on its +// first access. Without it, Selected would briefly render `<:pending>` +// for one microtask while the dynamic import resolves, flashing the +// loading state over the SSG'd prose. +// +// - `prewarmTypedocCaches` fetches + deserializes every `apiDocs({ packages })` +// entry so the first render of `` / `` has a synchronous +// `request.resolved` and the SSG'd `
` of typedoc declarations +// stays mounted through rehydration. +// +// Skip on plain-boot pages: there's nothing in the DOM to mismatch against. +if (shouldRehydrate()) { + try { + await Promise.all([prefetchPage(window.location.pathname), prewarmTypedocCaches()]); + } catch { + // Page not in manifest, network failure, etc. — fall through to normal + // boot. The async Selected / Load resources will handle it (possibly + // with a brief flash). + } +} + +// When the page was server-rendered, bootRehydrated() creates the app with +// autoboot: false and calls app.visit(url, { _renderMode: 'rehydrate' }) so +// Glimmer attaches to the existing DOM in place instead of replacing it. +// When the page was NOT server-rendered (e.g. `pnpm start:vite` without the +// SSR server), it falls back to a normal Application.create(config.APP). +bootRehydrated(Application, config); diff --git a/docs-app/src/registry.ts b/docs-app/src/registry.ts index 6e1b2f68..723c022f 100644 --- a/docs-app/src/registry.ts +++ b/docs-app/src/registry.ts @@ -22,9 +22,14 @@ function formatAsResolverEntries(imports: Record) { * - we design a new routing system */ const resolverRegistry = { - ...formatAsResolverEntries( - import.meta.glob('./templates/**/*.{gjs,gts,js,ts,md}', { eager: true }) - ), + // .gjs.md / .gts.md / .md files are loaded by kolay's Selected service via + // dynamic imports from the `kolay/compiled-docs:virtual` pages map — they + // don't need to live in Ember's resolver. Including them here as eager + // imports forces every compiled-page chunk to evaluate at app boot, which + // in the production SSR bundle creates a circular import: each chunk's + // top-level `templateOnly()` call runs before app-ssr.js has initialized + // the binding, throwing `templateOnly is not a function`. + ...formatAsResolverEntries(import.meta.glob('./templates/**/*.{gjs,gts,js,ts}', { eager: true })), ...formatAsResolverEntries(import.meta.glob('./services/**/*.{js,ts}', { eager: true })), ...formatAsResolverEntries(import.meta.glob('./routes/**/*.{js,ts}', { eager: true })), [`${appName}/router`]: Router, diff --git a/docs-app/src/routes/application-ssr.ts b/docs-app/src/routes/application-ssr.ts new file mode 100644 index 00000000..fd04aca0 --- /dev/null +++ b/docs-app/src/routes/application-ssr.ts @@ -0,0 +1,30 @@ +import Route from '@ember/routing/route'; + +import { setupKolay } from 'kolay/setup'; + +import type { Manifest } from 'kolay'; + +/** + * Pared-down ApplicationRoute used only by the SSG build (see ./app-ssr.ts). + * + * The client ApplicationRoute (./application.ts) hands setupKolay a `modules` + * map populated with `() => import('babel-plugin-ember-template-compilation')` + * and similar — runtime-compiler dependencies needed when kolay falls back + * to compiling raw `.md` pages in the browser. Vite static-analyzes those + * import() calls and bundles their full transitive graph (content-tag's + * `.cjs`, babel-import-util's CJS surface, etc.) into whatever build it sees + * them in. During SSG that graph trips vite-ember-ssr's CJS shim against + * rolldown's stricter parser. + * + * SSG renders only pre-compiled `.gjs.md` pages — the runtime compiler is + * never invoked. So this SSR-only route skips the `modules` argument and + * the highlighter setup, calling setupKolay with just the manifest plumbing + * it needs to render the navigation chrome. + */ +export default class ApplicationRoute extends Route { + async model(): Promise<{ manifest: Manifest }> { + const manifest = await setupKolay(this); + + return { manifest }; + } +} diff --git a/docs-app/src/templates/application.gts b/docs-app/src/templates/application.gts index d321a905..2e0552fd 100644 --- a/docs-app/src/templates/application.gts +++ b/docs-app/src/templates/application.gts @@ -6,7 +6,6 @@ import { pascalCase, sentenceCase } from 'change-case'; // @ts-expect-error no types for the mobile-menu import MenuWrapper from 'ember-mobile-menu/components/mobile-menu-wrapper'; import { pageTitle } from 'ember-page-title'; -import Route from 'ember-route-template'; import { GroupNav, PageNav } from 'kolay/components'; import { ExternalLink } from 'nvp.ui'; @@ -42,46 +41,37 @@ const SideNav: TOC<{ Element: HTMLElement }> = ; -function removeLoader() { - requestAnimationFrame(() => { - document.querySelector('#kolay__loading')?.remove(); - }); -} + export function nameFor(x: Page) { // We defined componentName via json file diff --git a/docs-app/src/templates/page.gjs b/docs-app/src/templates/page.gjs index a2fd519f..1248154d 100644 --- a/docs-app/src/templates/page.gjs +++ b/docs-app/src/templates/page.gjs @@ -1,11 +1,5 @@ import { Page } from 'kolay/components'; -function removeLoader() { - requestAnimationFrame(() => { - document.querySelector('#kolay__loading')?.remove(); - }); -} - function hasReason(error) { return ( typeof error === 'object' && @@ -43,12 +37,10 @@ function hasReason(error) { {{error}} {{/if}} - {{(removeLoader)}} <:success as |Prose|> - {{(removeLoader)}} diff --git a/docs-app/testem.cjs b/docs-app/testem.cjs index 5966e188..3060e8d5 100644 --- a/docs-app/testem.cjs +++ b/docs-app/testem.cjs @@ -3,7 +3,10 @@ if (typeof module !== 'undefined') { module.exports = { test_page: 'tests/index.html?hidepassed&skipAllLinks', - cwd: 'dist', + // vite-ember-ssr's emberSsr() plugin sets build.outDir to 'dist/client' + // for non-SSR builds so the prod fastify server can serve client/ and + // load server/. build:tests inherits the same outDir. + cwd: 'dist/client', disable_watching: true, launch_in_ci: ['Chrome'], launch_in_dev: ['Chrome'], diff --git a/docs-app/tsconfig.json b/docs-app/tsconfig.json index ec7ba3b4..5e3f8620 100644 --- a/docs-app/tsconfig.json +++ b/docs-app/tsconfig.json @@ -6,6 +6,7 @@ "types": [ "ember-source/types", "kolay/virtual", + "node", "vite/client", "vitest/importMeta", "unplugin-info/client", diff --git a/docs-app/vite.config.js b/docs-app/vite.config.js index ae95b184..531fbfcb 100644 --- a/docs-app/vite.config.js +++ b/docs-app/vite.config.js @@ -1,14 +1,50 @@ import { ember, extensions } from '@embroider/vite'; +import { glob } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; import { babel } from '@rollup/plugin-babel'; import rehypeShiki from '@shikijs/rehype'; import { kolay } from 'kolay/vite'; import info from 'unplugin-info/vite'; import { defineConfig } from 'vite'; +import { emberSsg } from 'vite-ember-ssr/vite-plugin'; import inspect from 'vite-plugin-inspect'; -export default defineConfig(async ({ mode }) => { +/** + * Mirror kolay's setup-plugin URL conventions to produce the SSG route list. + * + * Files in `docs-app/src/templates/` belong to the implicit "Home" group and + * map directly to a URL path (e.g. `usage/setup.gjs.md` → `usage/setup`). + * Files in `../docs/` belong to the configured `Runtime` group (see the + * `kolay({ groups })` call below) and are prefixed with the group name + * (e.g. `util/page.gjs.md` → `Runtime/util/page`). + * + * `emberSsg` requires routes without a leading slash. `'index'` is the + * special-cased landing page name (produces `/index.html` and + * captures whatever the router redirects to from `/`). + */ +async function enumerateDocsRoutes() { + const routes = new Set(['index']); + const stripExt = (entry) => entry.replace(/\.(?:gjs|gts)\.md$|\.md$/, ''); + + const homeRoot = fileURLToPath(new URL('./src/templates/', import.meta.url)); + + for await (const entry of glob('**/*.{gjs.md,gts.md,md}', { cwd: homeRoot })) { + routes.add(stripExt(entry)); + } + + const runtimeRoot = fileURLToPath(new URL('../docs/', import.meta.url)); + + for await (const entry of glob('**/*.{gjs.md,gts.md,md}', { cwd: runtimeRoot })) { + routes.add(`Runtime/${stripExt(entry)}`); + } + + return [...routes]; +} + +export default defineConfig(async ({ mode, isSsrBuild }) => { const isDev = mode === 'development'; + const ssgRoutes = await enumerateDocsRoutes(); return { plugins: [ @@ -45,22 +81,47 @@ export default defineConfig(async ({ mode }) => { babelHelpers: 'runtime', extensions, }), + // Prerender every documented route to static HTML at build time. Without + // emberSsr in the same config, emberSsg outputs straight to `outDir` + // (`dist/client` here, kept stable for vercel.json's outputDirectory). + // ssrEntry defaults to 'app/app-ssr.ts' upstream; this app uses 'src/'. + // Skipped in dev mode (build:tests) to avoid running the full SSR + // render pipeline on every test build. + ...(isDev + ? [] + : [ + emberSsg({ + routes: ssgRoutes, + ssrEntry: 'src/app-ssr.ts', + outDir: 'dist/client', + }), + ]), ], build: { + // Keep the output directory stable across modes. emberSsg also writes + // here in non-dev builds; testem.cjs and vercel.json both point at it. + outDir: 'dist/client', reportCompressedSize: false, - ...(isDev ? { minify: false } : { minify: 'oxc' }), - rolldownOptions: { - output: { - codeSplitting: { - groups: [ - { - name: 'unified', - test: /hast|mdast|remark|rehype|unified|vfile/, + ...(isSsrBuild || isDev ? { minify: false } : { minify: 'oxc' }), + // SSR bundling: skip rolldownOptions code-splitting groups (irrelevant + // for a single-bundle SSR output and rolldown's SSR pipeline rejects + // unknown groups). + ...(isSsrBuild + ? {} + : { + rolldownOptions: { + output: { + codeSplitting: { + groups: [ + { + name: 'unified', + test: /hast|mdast|remark|rehype|unified|vfile/, + }, + ], + }, }, - ], - }, - }, - }, + }, + }), }, optimizeDeps: { // Because we use dep injection diff --git a/package.json b/package.json index 5a5fb4c7..6e97eee0 100644 --- a/package.json +++ b/package.json @@ -226,7 +226,11 @@ } } }, - "patchedDependencies": {} + "patchedDependencies": { + "vite-ember-ssr": "patches/vite-ember-ssr.patch", + "ember-repl": "patches/ember-repl.patch", + "repl-sdk": "patches/repl-sdk.patch" + } }, "ember": { "edition": "octane" diff --git a/patches/ember-repl.patch b/patches/ember-repl.patch new file mode 100644 index 00000000..00dbb9cd --- /dev/null +++ b/patches/ember-repl.patch @@ -0,0 +1,50 @@ +diff --git a/dist/services/compiler.js b/dist/services/compiler.js +index 5487a3c86baaf5e3c2319ebb8d3077c749ebd09d..3b4c8572afe380833a77dc5db077096a4c94aa1a 100644 +--- a/dist/services/compiler.js ++++ b/dist/services/compiler.js +@@ -741,6 +741,25 @@ class CompilerService { + static { + n(this.prototype, "compileMD", [waitFor]); + } ++ ++ /** ++ * Build-time variant of `compile`: returns the compiled JS module source ++ * as a string rather than evaluating + rendering. Used by kolay's ++ * `.gjs.md` plugin to inline live demos into a single Glimmer template ++ * at build time. ++ */ ++ async compileToSource(ext, text, options) { ++ this.messages = []; ++ await Promise.resolve(); ++ const opts = { ...(options ?? {}) }; ++ if (ext === 'hbs') { ++ opts.flavor = 'ember'; ++ } ++ return this.compiler.compileToSource(ext, text, opts); ++ } ++ static { ++ n(this.prototype, "compileToSource", [waitFor]); ++ } + } + function getGlobal() { + return globalThis; +diff --git a/dist/services/known-modules.js b/dist/services/known-modules.js +index 0e7a25daf57f7376edc1d839f8277b764d4597e1..7206f100107a6366b414b7f04195b3fc1d61245c 100644 +--- a/dist/services/known-modules.js ++++ b/dist/services/known-modules.js +@@ -49,7 +49,14 @@ const emberCompilationModules = { + 'ember-source/ember-template-compiler/index.js'), + // Direct Dependencies + '@babel/standalone': () => import('../babel-DbH-RlNa.js').then(function (n) { return n.b; }), +- 'content-tag': () => import('content-tag'), ++ // Use the WASM standalone build. The default `.` export points at ++ // `pkg/node.cjs` (a native binding), but `node.cjs` is unambiguously ++ // CJS and vite-ember-ssr's CJS-to-ESM shim wraps it with an ++ // `export default` that rolldown rejects for `.cjs` files. The ++ // standalone build is pure ESM and works in both browser and Node ++ // (the Node side needs its WASM file read from disk; see the ++ // file:// handler in docs-app/src/app-ssr.ts). ++ 'content-tag': () => import('content-tag/standalone'), + 'decorator-transforms': () => import('decorator-transforms'), + 'decorator-transforms/runtime': () => import('decorator-transforms/runtime'), + 'decorator-transforms/runtime-esm': () => import('decorator-transforms/runtime-esm'), diff --git a/patches/repl-sdk.patch b/patches/repl-sdk.patch new file mode 100644 index 00000000..98944585 --- /dev/null +++ b/patches/repl-sdk.patch @@ -0,0 +1,1100 @@ +diff --git a/package.json b/package.json +index d56e59c82301b9a9999aa781ba382afa922897f8..b278201f98679035eca6b6b14ed2f0299f4d3af4 100644 +--- a/package.json ++++ b/package.json +@@ -10,6 +10,9 @@ + "./markdown/parse": { + "types": "./src/compilers/markdown/parse.d.ts", + "default": "./src/compilers/markdown/parse.js" ++ }, ++ "./render-to-string": { ++ "default": "./src/render-to-string.js" + } + }, + "repository": { +diff --git a/src/compilers/ember/gmd.js b/src/compilers/ember/gmd.js +index 87f925e75fc418c3b6885dfb049ba7dad488392b..74e4e3077c1e43513fd1d0ab67d8c0ab47fc481b 100644 +--- a/src/compilers/ember/gmd.js ++++ b/src/compilers/ember/gmd.js +@@ -1,6 +1,7 @@ + /** + * @typedef {import('unified').Plugin} Plugin + */ ++import { buildGmdModule } from '../../render-to-string.js'; + import { assert, isRecord } from '../../utils.js'; + import { buildCodeFenceMetaUtils } from '../markdown/utils.js'; + +@@ -55,7 +56,28 @@ export async function compiler(config, api) { + * @type {import('../../types.ts').Compiler} + */ + const gmdCompiler = { +- compile: async (text, options) => { ++ /** ++ * The runtime and renderToString paths share most of their work — both ++ * parse the markdown, recursively compile every live demo to a JS ++ * source string, then call `buildGmdModule` to inline those demos into ++ * a single `.gjs`-shaped module. ++ * ++ * The only forks are: ++ * ++ * - Which `@ember/template-compiler` to import. Runtime form uses ++ * `/runtime` (the parse-and-compile-at-execution-time variant); ++ * renderToString uses the build-time form so the host app's babel ++ * pipeline can precompile the `template()` call to wire format. ++ * ++ * - How runtime scope crosses the source boundary. The build-time ++ * form can't reference a live JS object, so renderToString gets ++ * an empty scope. The runtime form registers the live scope object ++ * behind a virtual ES module specifier via `api.provide` and emits ++ * a module that `import * as __scope__ from ''`. The ++ * Compiler's existing `manual:` resolver handles the bridge, so ++ * gmd never touches `globalThis` directly. ++ */ ++ compile: async (text, options, compileApi) => { + const compileOptions = filterOptions(options); + const result = await parseMarkdown(text, { + remarkPlugins: [...userOptions.remarkPlugins, ...compileOptions.remarkPlugins], +@@ -68,22 +90,66 @@ export async function compiler(config, api) { + getFlavorFromMeta, + }); + +- const { template } = await api.tryResolve('@ember/template-compiler/runtime'); ++ /** @type {Array<{ name: string, placeholderId: string, source: string }>} */ ++ const demos = []; + ++ let nth = 0; ++ ++ for (const info of result.codeBlocks) { ++ const { format, flavor, code, placeholderId } = info; ++ ++ if (!api.canCompile(format, flavor).result) continue; ++ ++ nth++; ++ ++ const sub = await api.compileToSource(format, code, { ++ ...(options ?? {}), ++ flavor, ++ }); ++ ++ demos.push({ name: `Demo${nth}`, placeholderId, source: sub.source }); ++ } ++ ++ const renderToString = isRecord(options) && options.renderToString === true; + const scope = { +- ...filterOptions(userOptions).scope, +- ...filterOptions(options).scope, ++ ...userOptions.scope, ++ ...compileOptions.scope, + }; + +- const component = template(result.text, { +- scope: () => ({ +- ...scope, +- // TODO: compile all the components from "result" and add them to scope here +- // would this be better than the markdown style multiple islands +- }), ++ if (renderToString) { ++ const source = buildGmdModule({ ++ prose: result.text, ++ demos, ++ templateModule: '@ember/template-compiler', ++ scope: null, ++ }); ++ ++ return { source }; ++ } ++ ++ // `compileApi.provideScope` registers the live scope behind a ++ // Compiler-generated specifier and tracks it as part of this ++ // compile's lifecycle. The Compiler releases it when destroy fires; ++ // gmd doesn't need to track an unregister callback. ++ assert( ++ `gmd needs the per-compile API (3rd argument to compile) to provide its scope. ` + ++ `It looks like the Compiler did not pass one.`, ++ compileApi ++ ); ++ ++ const { specifier } = compileApi.provideScope(scope); ++ ++ const source = buildGmdModule({ ++ prose: result.text, ++ demos, ++ templateModule: '@ember/template-compiler/runtime', ++ scope: { ++ specifier, ++ keys: Object.keys(scope), ++ }, + }); + +- return { compiled: component, ...result, scope }; ++ return { compiled: source, ...result, scope }; + }, + render: async (element, compiled, extra, compiler) => { + /** +@@ -98,71 +164,19 @@ export async function compiler(config, api) { + + const { renderComponent } = await compiler.tryResolve('@ember/renderer'); + ++ const args = /** @type {Record | undefined} */ ( ++ extra && typeof extra === 'object' && 'args' in extra ++ ? /** @type {Record} */ (extra).args ++ : undefined ++ ); ++ + const result = renderComponent(compiled, { + into: element, + owner: userOptions.owner, ++ ...(args ? { args } : {}), + }); + +- const destroy = () => result.destroy(); +- +- /** +- * @type {(() => void)[]} +- */ +- const destroyables = []; +- +- await Promise.all( +- /** @type {unknown[]} */ (extra.codeBlocks).map(async (/** @type {unknown} */ info) => { +- /** @type {Record} */ +- const infoObj = /** @type {Record} */ (info); +- +- if ( +- !api.canCompile( +- /** @type {string} */ (infoObj.format), +- /** @type {string} */ (infoObj.flavor) +- ) +- ) { +- return; +- } +- +- const flavor = /** @type {string} */ (infoObj.flavor); +- const hasScope = +- flavor === 'ember' || infoObj.format === 'gjs' || infoObj.format === 'hbs'; +- const subRender = await compiler.compile( +- /** @type {string} */ (infoObj.format), +- /** @type {string} */ (infoObj.code), +- { +- ...compiler.optionsFor(/** @type {string} */ (infoObj.format), flavor), +- flavor: flavor, +- // @ts-ignore +- ...(hasScope +- ? { +- scope: extra.scope, +- } +- : {}), +- } +- ); +- +- const selector = `#${/** @type {string} */ (infoObj.placeholderId)}`; +- const target = element.querySelector(selector); +- +- assert( +- `Could not find placeholder / target element (using selector: \`${selector}\`). ` + +- `Could not render ${/** @type {string} */ (infoObj.format)} block.`, +- target +- ); +- +- destroyables.push(subRender.destroy); +- target.appendChild(subRender.element); +- }) +- ); +- +- return () => { +- for (const subDestroy of destroyables) { +- subDestroy(); +- } +- +- destroy(); +- }; ++ return () => result.destroy(); + }, + }; + +diff --git a/src/compilers/ember/hbs.js b/src/compilers/ember/hbs.js +index 49bd11da2a8da29087d9f28a8162c4a32b777f40..60c027a6c031e871bfa54cd881c6a7bb4d56a26f 100644 +--- a/src/compilers/ember/hbs.js ++++ b/src/compilers/ember/hbs.js +@@ -26,6 +26,23 @@ export async function compiler(config, api) { + */ + const hbsCompiler = { + compile: async (text, options) => { ++ if (isRecord(options) && options.renderToString) { ++ // Build-time form: emit a JS module that imports `template` from the ++ // build-time template-compiler. The host app's content-tag/babel ++ // pipeline will precompile the `template(...)` call to wire format. ++ // ++ // We can't serialize a runtime `scope` object, so renderToString ++ // ignores `options.scope` — any identifiers the hbs body references ++ // must be in scope at the *consumer* (e.g. provided by the gmd ++ // wrapper's own imports/locals). ++ const source = ++ `import { template } from '@ember/template-compiler';\n` + ++ `const _component = template(${JSON.stringify(text)}, { scope: () => ({}) });\n` + ++ `export default _component;\n`; ++ ++ return source; ++ } ++ + const { template } = await api.tryResolve('@ember/template-compiler/runtime'); + + const component = template(text, { +@@ -67,7 +84,18 @@ export async function compiler(config, api) { + + const { renderComponent } = await compiler.tryResolve('@ember/renderer'); + const owner = makeOwner(config.owner); +- const result = renderComponent(compiled, { into: element, owner }); ++ const args = /** @type {Record | undefined} */ ( ++ extra && typeof extra === 'object' && 'args' in extra ++ ? /** @type {Record} */ (extra).args ++ : undefined ++ ); ++ const result = renderComponent(compiled, { ++ into: element, ++ owner, ++ ...(args ? { args } : {}), ++ }); ++ ++ compiler.announce('info', 'Ember Island Rendered'); + + return () => result.destroy(); + }, +diff --git a/src/compilers/markdown/parse.d.ts b/src/compilers/markdown/parse.d.ts +index 90ced9a4b028b5d8d38d379d7632cf1752cba078..77bf8bd73da51100870b89bf69065b0ac7308ab4 100644 +--- a/src/compilers/markdown/parse.d.ts ++++ b/src/compilers/markdown/parse.d.ts +@@ -1,14 +1,28 @@ + import type { InternalOptions } from './types'; + ++/** ++ * One live code fence's worth of information collected during the parse pass. ++ * Mirrors what `liveCodeExtraction` pushes onto `file.data.liveCode`. ++ */ ++export interface LiveCodeBlock { ++ /** Language id from the code fence (e.g. `gjs`, `hbs`, `python`). */ ++ format: string; ++ /** Optional flavor parsed from the meta (e.g. `ember`, `react`). */ ++ flavor: string | undefined; ++ /** The fence body, trimmed. */ ++ code: string; ++ /** Stable id used by the placeholder `
` in the rendered HTML. */ ++ placeholderId: string; ++ /** Raw meta string from the fence. */ ++ meta: string | undefined; ++} ++ + export function parseMarkdown( + input: string, + options: InternalOptions + ): Promise<{ + text: string; +- codeBlocks: Array<{ +- lang: string; +- format: string; +- code: string; +- name: string; +- }>; ++ codeBlocks: LiveCodeBlock[]; + }>; ++ ++export function buildCompiler(options: InternalOptions): unknown; +diff --git a/src/index.d.ts b/src/index.d.ts +index 2cb65ae12cd3f2aa5b41114336b12c40ca29b59f..7578e7eefbf7e366bbb02f86470407229fa91a89 100644 +--- a/src/index.d.ts ++++ b/src/index.d.ts +@@ -15,9 +15,24 @@ export class Compiler { + options?: { + flavor?: string; + fileName?: string; ++ [key: string]: unknown; + } + ): Promise<{ element: HTMLElement; destroy: () => void }>; + ++ /** ++ * Build-time variant of {@link Compiler.compile}: returns the compiled JS ++ * module source as a string instead of evaluating and rendering. ++ * ++ * Intended for SSG / pre-rendering pipelines that want to hand the ++ * compiled output to their own bundler rather than evaluate it in the ++ * browser at boot. ++ */ ++ compileToSource( ++ format: string, ++ text: string, ++ options?: Record ++ ): Promise<{ source: string }>; ++ + optionsFor(format: string, flavor?: string): Omit; + + /** +diff --git a/src/index.js b/src/index.js +index edabc53c47a059a2c3247244aecbf75cbbe77b7c..3ca872b4afafd629b1704f4e5f9279fffa111f7c 100644 +--- a/src/index.js ++++ b/src/index.js +@@ -24,6 +24,14 @@ export class Compiler { + /** @type {Options} */ + #options; + ++ /** ++ * Monotonic counter that namespaces the virtual specifiers ++ * `api.provideScope` hands back. Lives on the Compiler so concurrent ++ * compiles (or repeated compiles of the same source) never collide on ++ * the same `cache.resolves` entry. ++ */ ++ #scopeCounter = 0; ++ + /** + * Options may be passed to the compiler to add to its behavior. + * @param {Partial} options +@@ -37,6 +45,54 @@ export class Compiler { + window.addEventListener('unhandledrejection', this.#handleUnhandledRejection); + } + ++ /** ++ * Build a per-compile API: the same shape as the shared `PublicMethods` ++ * but with an extra `provideScope(value)` method whose registrations are ++ * tracked by `registry`. After the compile (and any render it triggers) ++ * finishes, the Compiler calls `dispose()` to release everything in ++ * `registry` — so individual compilers never have to remember to clean ++ * up the values they expose through `provideScope`. ++ * ++ * @returns {{ ++ * api: import('./types.ts').CompileAPI, ++ * dispose: () => void, ++ * }} ++ */ ++ #createCompileScope() { ++ /** @type {Set} */ ++ const registry = new Set(); ++ ++ const provideScope = (/** @type {unknown} */ value) => { ++ const specifier = `repl-sdk:scope:${++this.#scopeCounter}`; ++ ++ this.#options.resolve ??= {}; ++ this.#options.resolve[specifier] = value; ++ cache.resolves[specifier] = value; ++ registry.add(specifier); ++ ++ return { specifier }; ++ }; ++ ++ const dispose = () => { ++ for (const specifier of registry) { ++ if (this.#options.resolve) { ++ delete this.#options.resolve[specifier]; ++ } ++ ++ delete cache.resolves[specifier]; ++ } ++ ++ registry.clear(); ++ }; ++ ++ const api = /** @type {import('./types.ts').CompileAPI} */ ({ ++ ...this.#nestedPublicAPI, ++ provideScope, ++ }); ++ ++ return { api, dispose }; ++ } ++ + /** + * + * @param {HTMLElement} element +@@ -345,26 +401,121 @@ export class Compiler { + /** + * @param {string} format + * @param {string} text +- * @param {{ fileName?: string, flavor?: string, [key: string]: unknown }} [ options ] ++ * @param {{ fileName?: string, flavor?: string, args?: Record, [key: string]: unknown }} [ options ] + * @returns {Promise<{ element: HTMLElement, destroy: () => void }>} + */ + async compile(format, text, options = {}) { ++ return this.#runCompile(this.#compile, format, text, options); ++ } ++ ++ /** ++ * Build-time variant of {@link Compiler.compile}: returns the compiled JS ++ * module source as a string instead of evaluating and rendering. Each ++ * configured compiler sees `renderToString: true` on its options and is ++ * expected to emit a source string (`string` or `{ source: string, … }`) ++ * rather than a runtime component. ++ * ++ * @param {string} format ++ * @param {string} text ++ * @param {Record} [options] ++ * @returns {Promise<{ source: string }>} ++ */ ++ async compileToSource(format, text, options = {}) { ++ return this.#runCompile(this.#compileToSource, format, text, options); ++ } ++ ++ /** ++ * Shared wrapper for `compile` / `compileToSource`: announces lifecycle ++ * messages and forwards any thrown error through `on.log` before letting ++ * it propagate. Both public entry points share this so the user-visible ++ * logging is identical whether they're rendering or just getting source. ++ * ++ * @template T ++ * @param {(format: string, text: string, options: Record) => Promise} impl ++ * @param {string} format ++ * @param {string} text ++ * @param {Record} options ++ * @returns {Promise} ++ */ ++ async #runCompile(impl, format, text, options) { + this.#announce('info', `Compiling ${format}`); + + try { +- return await this.#compile(format, text, options); ++ return await impl.call(this, format, text, options); + } catch (e) { +- // for on.log usage + const message = e instanceof Error ? e.message : e; + + this.#announce('error', String(message)); +- +- // Don't hide errors! + this.#error(e); + throw e; + } + } + ++ /** ++ * Build-time variant of `#compile`: returns the compiled JavaScript source ++ * as a string rather than loading it via a blob URL and rendering. ++ * ++ * Useful for SSG / pre-rendering pipelines that want to take the compiled ++ * output of a live demo (or a `gmd` document containing live demos) and ++ * hand it to their own bundler instead of evaluating it in the browser. ++ * ++ * Each compiler is asked to `compile(text, { renderToString: true, ... })` ++ * — it's the compiler's responsibility to honor the flag and return a ++ * source string (`string` or `{ source: string }`). The `gmd` compiler ++ * recursively threads `renderToString` through its per-format dispatch and ++ * inlines every demo into one self-contained module. ++ * ++ * @param {string} format ++ * @param {string} text ++ * @param {Record} options ++ * @returns {Promise<{ source: string }>} ++ */ ++ async #compileToSource(format, text, options) { ++ const flavor = typeof options.flavor === 'string' ? options.flavor : undefined; ++ const fileName = typeof options.fileName === 'string' ? options.fileName : `dynamic.${format}`; ++ const opts = { ...options, fileName, renderToString: true }; ++ ++ const compiler = await this.#getCompiler(format, flavor); ++ const { api, dispose } = this.#createCompileScope(); ++ ++ try { ++ const compiled = await compiler.compile(text, opts, api); ++ ++ if (typeof compiled === 'string') { ++ return { source: compiled }; ++ } ++ ++ if ( ++ compiled !== null && ++ typeof compiled === 'object' && ++ 'source' in compiled && ++ typeof compiled.source === 'string' ++ ) { ++ return { source: compiled.source }; ++ } ++ ++ const shape = ++ compiled !== null && typeof compiled === 'object' ++ ? Object.keys(compiled).join(', ') ++ : typeof compiled; ++ ++ throw new Error( ++ `Compiler for format '${format}' was asked to renderToString but returned ` + ++ `${shape} instead of a source string.` ++ ); ++ } finally { ++ // renderToString never renders, so anything `provideScope` registered ++ // during this compile has no rendered-element lifecycle to attach to. ++ // Release it immediately — the emitted source string was constructed ++ // with the scope inlined as a virtual specifier reference, but the ++ // *value* behind that specifier is only meaningful while this compile ++ // is in flight (e.g. for gmd's nested renderToString sub-compiles to ++ // share helpers with their parent). The caller's bundler resolves the ++ // specifier statically at build time; runtime lookup is never used. ++ dispose(); ++ } ++ } ++ + /** + * @param {string} format + * @param {string} text +@@ -384,7 +535,16 @@ export class Compiler { + this.#log('[compile] compiling'); + + const compiler = await this.#getCompiler(format, opts.flavor); +- const compiled = await compiler.compile(text, opts); ++ const { api, dispose } = this.#createCompileScope(); ++ ++ let compiled; ++ ++ try { ++ compiled = await compiler.compile(text, opts, api); ++ } catch (e) { ++ dispose(); ++ throw e; ++ } + + let compiledText = 'export default "failed to compile"'; + let extras = { compiled: '' }; +@@ -408,20 +568,42 @@ export class Compiler { + extras = compiled; + } + +- return this.#render(compiler, value, { +- ...extras, +- compiled: value, +- }); ++ return this.#render( ++ compiler, ++ value, ++ { ++ ...extras, ++ compiled: value, ++ ...(opts.args ? { args: opts.args } : {}), ++ }, ++ api, ++ dispose ++ ); + } + +- const asBlobUrl = textToBlobUrl(compiledText); ++ let defaultExport; + +- // @ts-ignore +- const { default: defaultExport } = await shimmedImport(/* @vite-ignore */ asBlobUrl); ++ try { ++ const asBlobUrl = textToBlobUrl(compiledText); ++ // @ts-ignore ++ ({ default: defaultExport } = await shimmedImport(/* @vite-ignore */ asBlobUrl)); ++ } catch (e) { ++ dispose(); ++ throw e; ++ } + + this.#log('[compile] preparing to render', defaultExport, extras); + +- return this.#render(compiler, defaultExport, extras); ++ return this.#render( ++ compiler, ++ defaultExport, ++ { ++ ...extras, ++ ...(opts.args ? { args: opts.args } : {}), ++ }, ++ api, ++ dispose ++ ); + } + + #compilerCache = new WeakMap(); +@@ -507,28 +689,42 @@ export class Compiler { + * @param {import('./types.ts').Compiler} compiler + * @param {string} whatToRender + * @param {{ compiled: string } & Record} extras ++ * @param {import('./types.ts').CompileAPI} api ++ * @param {() => void} dispose + * @returns {Promise<{ element: HTMLElement, destroy: () => void }>} + */ +- async #render(compiler, whatToRender, extras) { ++ async #render(compiler, whatToRender, extras, api, dispose) { + this.#announce('info', 'Rendering'); + + const div = this.#createDiv(); + +- assert(`Cannot render falsey values. Did compilation succeed?`, whatToRender); ++ try { ++ assert(`Cannot render falsey values. Did compilation succeed?`, whatToRender); + +- const destroy = await compiler.render(div, whatToRender, extras, this.#nestedPublicAPI); ++ const destroy = await compiler.render(div, whatToRender, extras, api); + +- // Wait for render +- await new Promise((resolve) => requestAnimationFrame(resolve)); ++ // Wait for render ++ await new Promise((resolve) => requestAnimationFrame(resolve)); + +- return { +- element: div, +- destroy: () => { +- if (destroy) { +- return destroy(); +- } +- }, +- }; ++ return { ++ element: div, ++ // The caller's `destroy` releases everything this compile pinned — ++ // first the compiler's own teardown (DOM detach, framework ++ // destructors, …), then any scopes the compiler exposed via ++ // `api.provideScope`. Individual compilers don't (and shouldn't) ++ // know about the scope lifecycle; the Compiler owns it. ++ destroy: () => { ++ try { ++ if (destroy) destroy(); ++ } finally { ++ dispose(); ++ } ++ }, ++ }; ++ } catch (e) { ++ dispose(); ++ throw e; ++ } + } + + /** +@@ -666,6 +862,15 @@ export class Compiler { + * @param {Parameters} args + */ + compile: (...args) => this.compile(...args), ++ /** ++ * Build-time variant of `compile` — returns the compiled JS source as a ++ * string instead of rendering. Exposed on the public API so compilers ++ * (e.g. `gmd`) can recursively ask other compilers to renderToString. ++ * ++ * @param {Parameters} args ++ */ ++ compileToSource: (...args) => this.compileToSource(...args), ++ + /** + * @param {Parameters} args + */ +diff --git a/src/render-to-string.js b/src/render-to-string.js +new file mode 100644 +index 0000000000000000000000000000000000000000..b61f6e7564a788d019e7818ea40bb5ccc3d16ceb +--- /dev/null ++++ b/src/render-to-string.js +@@ -0,0 +1,325 @@ ++/** ++ * Shared helpers for the build-time `renderToString` code path. ++ * ++ * The job of these helpers is purely lexical — given the JS source emitted by ++ * a sub-compiler (e.g. gjs/hbs), split it into top-level imports + body so ++ * the caller (e.g. `gmd`) can merge many such modules into one self-contained ++ * module string that the host app's own bundler will then process. ++ * ++ * Nothing here parses JS into an AST — the input is expected to be the ++ * deterministic output of babel + content-tag, where imports are top-level ++ * and the module ends with a single `export default ;`. ++ */ ++ ++/** ++ * @typedef {object} ModuleParts ++ * @property {string[]} imports - Top-level import statements (each ending in `;`) ++ * @property {string} body - Remaining module body with imports removed and ++ * `export default ;` rewritten to `return ;` ++ */ ++ ++/** ++ * Split a JS module source into its top-level import statements and the rest ++ * of its body, rewriting any trailing `export default ;` to a `return` ++ * so the body is suitable for IIFE-wrapping. ++ * ++ * Multi-line imports (`import {\n a,\n b\n} from 'x';`) are supported by ++ * brace-balanced continuation across lines. ++ * ++ * @param {string} source ++ * @returns {ModuleParts} ++ */ ++export function splitModule(source) { ++ const lines = source.split('\n'); ++ /** @type {string[]} */ ++ const imports = []; ++ /** @type {string[]} */ ++ const bodyLines = []; ++ ++ let i = 0; ++ ++ while (i < lines.length) { ++ const line = /** @type {string} */ (lines[i]); ++ ++ if (!isImportStart(line)) { ++ bodyLines.push(line); ++ i++; ++ continue; ++ } ++ ++ let chunk = line; ++ let depth = braceDelta(line); ++ let parenDepth = parenDelta(line); ++ ++ while ( ++ i + 1 < lines.length && ++ (depth > 0 || parenDepth > 0 || !chunk.trimEnd().endsWith(';')) ++ ) { ++ i++; ++ ++ const next = /** @type {string} */ (lines[i]); ++ ++ chunk += '\n' + next; ++ depth += braceDelta(next); ++ parenDepth += parenDelta(next); ++ } ++ ++ imports.push(chunk); ++ i++; ++ } ++ ++ const body = rewriteDefaultExport(bodyLines.join('\n')); ++ ++ return { imports, body }; ++} ++ ++/** ++ * @param {string} line ++ */ ++function isImportStart(line) { ++ return /^\s*import(\s|\s*['"`{*])/.test(line); ++} ++ ++/** ++ * Count `{` − `}` in a line, ignoring chars inside strings or line comments. ++ * Good enough for the small set of characters babel emits inside an import ++ * statement (no template literals, no regex literals). ++ * ++ * @param {string} line ++ */ ++function braceDelta(line) { ++ return countCharsOutsideStrings(line, '{', '}'); ++} ++ ++/** ++ * @param {string} line ++ */ ++function parenDelta(line) { ++ return countCharsOutsideStrings(line, '(', ')'); ++} ++ ++/** ++ * @param {string} line ++ * @param {string} open ++ * @param {string} close ++ */ ++function countCharsOutsideStrings(line, open, close) { ++ let depth = 0; ++ /** @type {string | null} */ ++ let stringChar = null; ++ ++ for (let i = 0; i < line.length; i++) { ++ const ch = line[i]; ++ ++ if (stringChar) { ++ if (ch === '\\') { ++ i++; ++ continue; ++ } ++ ++ if (ch === stringChar) { ++ stringChar = null; ++ } ++ ++ continue; ++ } ++ ++ if (ch === '"' || ch === "'" || ch === '`') { ++ stringChar = ch; ++ continue; ++ } ++ ++ if (ch === '/' && line[i + 1] === '/') break; ++ ++ if (ch === open) depth++; ++ else if (ch === close) depth--; ++ } ++ ++ return depth; ++} ++ ++/** ++ * Rewrite the final `export default ;` to `return ;` so the ++ * caller can wrap the body in an IIFE. ++ * ++ * If there is no `export default`, the body is returned unchanged. ++ * ++ * @param {string} body ++ * @returns {string} ++ */ ++function rewriteDefaultExport(body) { ++ // Anchor with `/m` so we only match `export default` at the start of a ++ // statement line — never inside line comments, strings, or expressions. ++ // Babel never emits multiple top-level `export default`s, so first-match ++ // semantics are fine. ++ const match = body.match(/^\s*export\s+default\s+/m); ++ ++ if (!match || match.index === undefined) { ++ return body; ++ } ++ ++ const before = body.slice(0, match.index); ++ const after = body.slice(match.index + match[0].length); ++ ++ return `${before}return ${after}`; ++} ++ ++/** ++ * Deduplicate a list of import statements by their exact textual content ++ * (after trimming trailing whitespace). This is intentionally conservative — ++ * we'd rather emit two equivalent-but-different imports than collapse two ++ * imports that bind different things. ++ * ++ * @param {string[][]} groups ++ * @returns {string[]} ++ */ ++export function mergeImports(groups) { ++ const seen = new Set(); ++ /** @type {string[]} */ ++ const out = []; ++ ++ for (const group of groups) { ++ for (const imp of group) { ++ const key = imp.trim(); ++ ++ if (seen.has(key)) continue; ++ ++ seen.add(key); ++ out.push(imp); ++ } ++ } ++ ++ return out; ++} ++ ++/** ++ * Wrap a module body in an IIFE that returns the (rewritten) default export. ++ * ++ * @param {string} body ++ * @param {string} name ++ */ ++export function wrapAsConst(body, name) { ++ return `const ${name} = (() => {\n${indent(body)}\n})();`; ++} ++ ++/** ++ * @param {string} text ++ */ ++function indent(text) { ++ return text ++ .split('\n') ++ .map((line) => (line.length ? ` ${line}` : line)) ++ .join('\n'); ++} ++ ++/** ++ * Inline one or more compiled sub-modules into the surrounding gmd prose, ++ * producing one self-contained ES module string. ++ * ++ * The same function is used by both the *runtime* compile path (the module ++ * gets blob-eval'd and rendered) and the *renderToString* path (the module ++ * gets handed back to the caller's bundler). The only differences are: ++ * ++ * - `templateModule`: which `template` to import. Use ++ * `'@ember/template-compiler/runtime'` when this module will be ++ * evaluated at runtime, or `'@ember/template-compiler'` when a build- ++ * time babel plugin is expected to precompile the `template(...)` call. ++ * ++ * - `scope`: a virtual ES module specifier that the emitted module will ++ * `import * as __scope__ from ''`, and the list of keys to ++ * destructure off that namespace. The runtime path registers the live ++ * scope object behind such a specifier via `api.provide`; the ++ * renderToString path passes `null` because there is no live scope to ++ * bridge. ++ * ++ * The placeholders in `prose` are replaced with `` Glimmer ++ * invocations wrapped in a div that preserves the original placeholder's ++ * `class` attribute (e.g. `repl-sdk__demo`) so existing CSS still applies. ++ * ++ * This is a pure function — the caller is responsible for driving the ++ * sub-compiles and (for the runtime path) for registering the scope value ++ * behind `scope.specifier` before this output is evaluated. ++ * ++ * @param {object} args ++ * @param {string} args.prose ++ * @param {Array<{ name: string, placeholderId: string, source: string }>} args.demos ++ * @param {string} [args.templateModule] ++ * @param {{ specifier: string, keys: string[] } | null} [args.scope] ++ * @returns {string} ++ */ ++export function buildGmdModule({ ++ prose, ++ demos, ++ templateModule = '@ember/template-compiler', ++ scope = null, ++}) { ++ /** @type {string[][]} */ ++ const importGroups = [[`import { template } from '${templateModule}';`]]; ++ ++ if (scope && scope.keys.length) { ++ importGroups.push([`import * as __scope__ from '${scope.specifier}';`]); ++ } ++ ++ /** @type {string[]} */ ++ const bodyDecls = []; ++ /** @type {string[]} */ ++ const scopeIdents = []; ++ ++ let rewrittenProse = prose; ++ ++ for (const demo of demos) { ++ const { imports, body } = splitModule(demo.source); ++ ++ importGroups.push(imports); ++ bodyDecls.push(wrapAsConst(body, demo.name)); ++ scopeIdents.push(demo.name); ++ ++ rewrittenProse = replacePlaceholder(rewrittenProse, demo.placeholderId, demo.name); ++ } ++ ++ const mergedImports = mergeImports(importGroups).join('\n'); ++ ++ /** @type {string[]} */ ++ const preludeLines = []; ++ ++ if (scope && scope.keys.length) { ++ preludeLines.push(`const { ${scope.keys.join(', ')} } = __scope__;`); ++ } ++ ++ const allScopeIdents = scope && scope.keys.length ? [...scope.keys, ...scopeIdents] : scopeIdents; ++ const scopeBody = allScopeIdents.length ? `{ ${allScopeIdents.join(', ')} }` : `{}`; ++ ++ return ( ++ `${mergedImports}\n\n` + ++ (preludeLines.length ? preludeLines.join('\n') + '\n\n' : '') + ++ (bodyDecls.length ? bodyDecls.join('\n\n') + '\n\n' : '') + ++ `const _component = template(${JSON.stringify(rewrittenProse)}, {\n` + ++ ` scope: () => (${scopeBody}),\n` + ++ `});\n` + ++ `export default _component;\n` ++ ); ++} ++ ++/** ++ * Replace the single `
` placeholder emitted ++ * by `liveCodeExtraction` with a Glimmer component invocation. The wrapping ++ * div is preserved (sans `id`) so the `repl-sdk__demo` (or ++ * caller-supplied) class still styles the demo container. ++ * ++ * @param {string} html ++ * @param {string} id ++ * @param {string} name ++ */ ++export function replacePlaceholder(html, id, name) { ++ const escapedId = id.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); ++ const pattern = new RegExp( ++ `]*>\\s*
`, ++ 'g' ++ ); ++ ++ return html.replace(pattern, (_match, _attr, classes) => { ++ const classAttr = classes !== undefined ? ` class="${classes}"` : ''; ++ ++ return `<${name} />`; ++ }); ++} +diff --git a/src/types.ts b/src/types.ts +index 89f8b8b71bcc1717ee81eb220a102946d921d166..3121877815bd873039187cb21f76907aad4c8f0b 100644 +--- a/src/types.ts ++++ b/src/types.ts +@@ -39,9 +39,25 @@ export interface PublicMethods { + options?: { + flavor?: string; + fileName?: string; ++ [key: string]: unknown; + } + ) => Promise<{ element: HTMLElement; destroy: () => void }>; + ++ /** ++ * Build-time variant of {@link PublicMethods.compile}: returns the compiled ++ * source as a string rather than evaluating it and rendering to a DOM ++ * element. ++ * ++ * Useful for SSG / pre-rendering pipelines that want to take a live demo's ++ * compiled output and hand it to their own bundler instead of executing it ++ * in the browser. ++ */ ++ compileToSource: ( ++ format: string, ++ text: string, ++ options?: Record ++ ) => Promise<{ source: string }>; ++ + optionsFor: ( + format: string, + flavor?: string +@@ -76,7 +92,38 @@ type CompileResult = + | { + compiled: string; + [option: string]: unknown; +- }; ++ } ++ /** ++ * Variant returned by compilers when invoked with `renderToString: true` — ++ * the build-time form, where no rendering happens and the caller receives ++ * the compiled JS module source as a string instead of a rendered element. ++ */ ++ | { source: string; [option: string]: unknown }; ++ ++/** ++ * Per-compile API: everything in {@link PublicMethods}, plus the ++ * {@link CompileAPI.provideScope | provideScope} method whose registrations ++ * are scoped to the compile call (and any render it triggers). ++ * ++ * The Compiler hands a fresh `CompileAPI` to every `compile()` / `render()` ++ * invocation and disposes the registry it tracks after the compile's ++ * destroy fires — individual compilers never have to remember to release ++ * what they registered. ++ */ ++export interface CompileAPI extends PublicMethods { ++ /** ++ * Register a JS value behind a Compiler-generated virtual ES module ++ * specifier, and return that specifier. The emitted source for this ++ * compile can then `import * as foo from ''` and the ++ * Compiler's existing `manual:` resolver hands the value back. ++ * ++ * The registration is bound to this compile's lifecycle: when the ++ * compile's `destroy()` is called (or the compile throws before ++ * reaching a render), the Compiler releases the entry automatically. ++ * Compilers do not — and should not — track an unregister callback. ++ */ ++ provideScope: (value: unknown) => { specifier: string }; ++} + + export interface Compiler { + /** +@@ -84,8 +131,17 @@ export interface Compiler { + * This will be loaded as a module and then passed to the render method. + * + * You may return either just a string, or an object with a `compiled` property that is a string -- any additional properties will be passde through to the render function -- which may be useful if there is accompanying CSS. ++ * ++ * The third `api` argument is a per-compile API: same surface as the ++ * factory-time `PublicMethods`, plus `provideScope(value)` whose ++ * registrations are auto-released by the Compiler when the compile's ++ * destroy fires. Existing implementations may ignore it. + */ +- compile: (text: string, options: Record) => Promise; ++ compile: ( ++ text: string, ++ options: Record, ++ api?: CompileAPI ++ ) => Promise; + + /** + * For the root of a node rendered for this compiler, +@@ -118,7 +174,7 @@ export interface Compiler { + element: HTMLElement, + defaultExport: unknown, + extras: { compiled: string } & Record, +- compiler: PublicMethods ++ compiler: CompileAPI + ) => Promise void)>; + + /** diff --git a/patches/vite-ember-ssr.patch b/patches/vite-ember-ssr.patch new file mode 100644 index 00000000..e340d711 --- /dev/null +++ b/patches/vite-ember-ssr.patch @@ -0,0 +1,75 @@ +diff --git a/dist/vite-plugin-9BSJgEL9.js b/dist/vite-plugin-9BSJgEL9.js +index e74e1f1c9537eb0d7239722f39fbbf053b3cb2ff..e1001ed0fca88bd5ec23a105004580b6ed172575 100644 +--- a/dist/vite-plugin-9BSJgEL9.js ++++ b/dist/vite-plugin-9BSJgEL9.js +@@ -352,7 +352,7 @@ function emberSsg(options) { + ssrBundlePath = join(ssrOutDir, `${entryBasename}.js`); + } + const ssrBundleURL = pathToFileURL(ssrBundlePath).href; +- const app = await createEmberApp(ssrBundleURL, { workers: cpus().length }); ++ const app = await createEmberApp(ssrBundleURL, { workers: cpus().length, isolateWorkers: true }); + try { + await Promise.all(routes.map(async (route) => { + const url = route === "index" ? "/" : `/${route}`; +diff --git a/dist/worker.js b/dist/worker.js +index b2d021e7b72b0fddbd96a98179f80055c2404858..20c446db7782ec58cd2c3ba51a2e610e47bcfd99 100644 +--- a/dist/worker.js ++++ b/dist/worker.js +@@ -41,7 +41,15 @@ const BROWSER_GLOBALS = [ + "PointerEvent", + "IntersectionObserver", + "ResizeObserver", +- "CSSStyleSheet" ++ "CSSStyleSheet", ++ "HTMLScriptElement", ++ "HTMLLinkElement", ++ "HTMLStyleElement", ++ "HTMLIFrameElement", ++ "HTMLImageElement", ++ "MessageEvent", ++ "EventTarget", ++ "ShadowRoot" + ]; + function installGlobals(win) { + for (const name of BROWSER_GLOBALS) try { +@@ -65,6 +73,40 @@ const win = new Window({ + } + }); + installGlobals(win); ++// HappyDOM doesn't ship Worker but the SSR bundle's import graph includes ++// code that does `new Worker(...)` at module-top-level (e.g. repl-sdk's ++// tar.js for runtime markdown compilation, then wrapped with comlink). ++// ++// We can't use Node's `worker_threads.Worker` directly because: ++// 1. Its API is EventEmitter (.on('message',...)) not EventTarget ++// (.addEventListener('message',...)), and comlink expects the latter. ++// 2. The bundled URL like `new URL("./tar-worker.js", import.meta.url)` ++// resolves to a path inside .ssg-tmp/assets/ where Vite never emits ++// the worker file (it only lands in the *client* build output), so ++// Node.js's worker thread spawn fails with `Cannot find module`. ++// ++// Since SSG never reaches the runtime markdown compiler (pages are ++// pre-compiled by `kolay/vite`), the Worker is constructed at module-eval ++// but never actually messaged. Provide an inert EventTarget that swallows ++// postMessage/terminate calls — comlink's `wrap()` runs, the parent-side ++// proxy gets created, but no messages ever flow. ++class InertWebWorker extends EventTarget { ++ postMessage() {} ++ terminate() {} ++} ++if (typeof globalThis.Worker === "undefined") globalThis.Worker = InertWebWorker; ++// Worker-only browser globals referenced as bare identifiers (not via ++// `globalThis.x`) in the SSR bundle's `standardScope` object literal — ++// e.g. `var standardScope = { ..., postMessage, ... }` in ember-repl's ++// compile.js. JavaScript identifier resolution walks lexical scope and ++// falls through to the global object, so installing them on globalThis ++// is enough to keep the module from throwing ReferenceError at evaluation. ++// These are no-ops because no code path through SSG actually invokes ++// the runtime markdown compiler that would use them. ++if (typeof globalThis.postMessage === "undefined") ++ globalThis.postMessage = () => {}; ++if (typeof globalThis.isSecureContext === "undefined") ++ globalThis.isSecureContext = false; + const { ssrBundlePath: startupBundlePath } = process.__tinypool_state__.workerData; + const startupMod = await import(startupBundlePath); + if (typeof startupMod.createSsrApp !== "function") throw new Error(`SSR bundle '${startupBundlePath}' does not export a 'createSsrApp' function. Found exports: ${Object.keys(startupMod).join(", ")}`); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78d2289d..b948af2d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,17 @@ overrides: packageExtensionsChecksum: sha256-I4/i17OOf0WifUwpGYE/KCRs9xMfb6hiQlSpKvR/tsk= +patchedDependencies: + ember-repl: + hash: cbea90b2edadff3d670cd183117e59dd4b487b219e581bcb95eec2dc6dee9b7d + path: patches/ember-repl.patch + repl-sdk: + hash: d1fe29012a6c4a614eefd0d05420d1fd33ddbf20f5fae22f803608d981e06891 + path: patches/repl-sdk.patch + vite-ember-ssr: + hash: f4ed313947273cab8416b3974a50ccfc3d9427eb6c90ad8e3725e51e6a68341f + path: patches/vite-ember-ssr.patch + importers: .: @@ -50,7 +61,7 @@ importers: version: 0.53.0(8026817c44d5531aaa70f177099aafb2) ember-repl: specifier: ^8.0.2 - version: 8.0.2(0f15df29688aaf2535d173c404cf8efa) + version: 8.0.2(patch_hash=cbea90b2edadff3d670cd183117e59dd4b487b219e581bcb95eec2dc6dee9b7d)(0f15df29688aaf2535d173c404cf8efa) ember-resources: specifier: ^7.0.4 version: 7.0.7(099a17061adaa39d579f1e156593ef5d) @@ -83,7 +94,7 @@ importers: version: 11.1.2 repl-sdk: specifier: ^1.5.2 - version: 1.5.2(6f4bb085e066dcd1e8cba157a733f3a5) + version: 1.5.2(patch_hash=d1fe29012a6c4a614eefd0d05420d1fd33ddbf20f5fae22f803608d981e06891)(6f4bb085e066dcd1e8cba157a733f3a5) send: specifier: ^1.2.1 version: 1.2.1 @@ -180,7 +191,7 @@ importers: version: 9.6.1 fix-bad-declaration-output: specifier: ^1.1.5 - version: 1.1.5(96503fdd924316efedea1b15928b7aad) + version: 1.1.5(85346fca787e2ba7ee5cc61c0e01fa28) globals: specifier: ^17.0.0 version: 17.3.0 @@ -216,10 +227,10 @@ importers: version: 11.0.5 vite: specifier: ^8.0.14 - version: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + version: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) vitest: specifier: ^4.0.16 - version: 4.0.18(31650c18aea9d2e0cdac92197f228980) + version: 4.0.18(9a336c60f0d483703ff47422c636b3ce) docs-app: dependencies: @@ -244,9 +255,6 @@ importers: ember-mobile-menu: specifier: ^6.0.0 version: 6.0.0(625bcaafed89719dbde04fef5efa7e32) - ember-route-template: - specifier: ^1.0.3 - version: 1.0.3 kolay: specifier: workspace:^ version: link:.. @@ -274,7 +282,7 @@ importers: version: 2.0.0 '@ember/optional-features': specifier: ^3.0.0 - version: 3.0.0(@types/node@25.2.0) + version: 3.0.0(@types/node@25.9.1) '@ember/string': specifier: ^4.0.1 version: 4.0.1 @@ -285,17 +293,17 @@ importers: specifier: ^4.0.0 version: 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/compat': - specifier: ^4.1.13 - version: 4.1.13(e238ad5f7331c9e1f8a49298b282f063) + specifier: ^4.1.18 + version: 4.1.18(08820dd21dc646074fb879906de0104b) '@embroider/core': - specifier: ^4.4.3 - version: 4.4.3(@glint/template@1.7.4) + specifier: ^4.4.7 + version: 4.4.7(@glint/template@1.7.4) '@embroider/macros': - specifier: ^1.19.7 - version: 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + specifier: ^1.20.2 + version: 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@embroider/vite': - specifier: ^1.5.2 - version: 1.5.2(5f41de3fafd619070cf36afcb240fe74) + specifier: ^1.7.3 + version: 1.7.3(03c4728243303ca3ed5a123601c1cc2b) '@glimmer/component': specifier: ^2.0.0 version: 2.0.0 @@ -314,6 +322,9 @@ importers: '@rollup/plugin-babel': specifier: ^6.0.4 version: 6.1.0(@babel/core@7.29.0)(rollup@4.59.0) + '@types/node': + specifier: ^25.9.1 + version: 25.9.1 '@types/qunit': specifier: ^2.19.13 version: 2.19.13 @@ -331,7 +342,7 @@ importers: version: 2.12.0(@glint/template@1.7.4) ember-cli: specifier: ^6.9.1 - version: 6.10.0(aee6f353e28028806c6c60dd4e194896) + version: 6.10.0(03ad83d5106095c4dd77448655e3c54b) ember-eslint: specifier: ^0.6.1 version: 0.6.1(5a7f00948a6312db668ed12e548f3346) @@ -382,7 +393,7 @@ importers: version: 12.0.0 testem: specifier: 3.17.0 - version: 3.17.0(8bcdda22d80c66a2796dcf0ed2360163) + version: 3.17.0(db015b790b423be5302f27310d983004) tracked-built-ins: specifier: ^4.1.0 version: 4.1.0(@babel/core@7.29.0) @@ -391,16 +402,19 @@ importers: version: 5.9.3 unplugin-info: specifier: ^1.2.4 - version: 1.2.4(780e7b315b6d999a5119d944ff478cb6) + version: 1.2.4(ffd467c98beae3b2d8bae0fb29b0db14) vite: specifier: 8.0.14 - version: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + version: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) + vite-ember-ssr: + specifier: ^0.1.0 + version: 0.1.0(patch_hash=f4ed313947273cab8416b3974a50ccfc3d9427eb6c90ad8e3725e51e6a68341f)(1ae42ea40c273aad7303991cb84a8103) vite-plugin-inspect: specifier: ^11.3.3 - version: 11.3.3(b755ca744ca121980f4cb7903c2754d2) + version: 11.3.3(1ae42ea40c273aad7303991cb84a8103) vite-plugin-wasm: specifier: ^3.5.0 - version: 3.5.0(b755ca744ca121980f4cb7903c2754d2) + version: 3.5.0(1ae42ea40c273aad7303991cb84a8103) test-apps/custom-root-url: dependencies: @@ -452,7 +466,7 @@ importers: version: 4.4.3(@glint/template@1.7.4) '@embroider/vite': specifier: 1.5.2 - version: 1.5.2(5f41de3fafd619070cf36afcb240fe74) + version: 1.5.2(202dcc17de8bc9c33c094c94eaf1d368) '@glint/ember-tsc': specifier: 1.1.1 version: 1.1.1(typescript@5.9.3) @@ -506,13 +520,13 @@ importers: version: 1.0.0 testem: specifier: 3.17.0 - version: 3.17.0(8bcdda22d80c66a2796dcf0ed2360163) + version: 3.17.0(db015b790b423be5302f27310d983004) typescript: specifier: 5.9.3 version: 5.9.3 vite: specifier: 8.0.14 - version: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + version: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) test-apps/markdown-and-gjs-md-app: dependencies: @@ -561,7 +575,7 @@ importers: version: 4.4.3(@glint/template@1.7.4) '@embroider/vite': specifier: 1.5.2 - version: 1.5.2(5f41de3fafd619070cf36afcb240fe74) + version: 1.5.2(202dcc17de8bc9c33c094c94eaf1d368) '@glint/ember-tsc': specifier: 1.1.1 version: 1.1.1(typescript@5.9.3) @@ -615,13 +629,13 @@ importers: version: 1.0.0 testem: specifier: 3.17.0 - version: 3.17.0(8bcdda22d80c66a2796dcf0ed2360163) + version: 3.17.0(db015b790b423be5302f27310d983004) typescript: specifier: 5.9.3 version: 5.9.3 vite: specifier: 8.0.14 - version: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + version: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) test-apps/markdown-only: dependencies: @@ -673,7 +687,7 @@ importers: version: 4.4.3(@glint/template@1.7.4) '@embroider/vite': specifier: 1.5.2 - version: 1.5.2(5f41de3fafd619070cf36afcb240fe74) + version: 1.5.2(202dcc17de8bc9c33c094c94eaf1d368) '@glint/ember-tsc': specifier: 1.1.1 version: 1.1.1(typescript@5.9.3) @@ -727,13 +741,13 @@ importers: version: 1.0.0 testem: specifier: 3.17.0 - version: 3.17.0(8bcdda22d80c66a2796dcf0ed2360163) + version: 3.17.0(db015b790b423be5302f27310d983004) typescript: specifier: 5.9.3 version: 5.9.3 vite: specifier: 8.0.14 - version: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + version: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) packages: @@ -760,6 +774,10 @@ packages: resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.29.0': resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} @@ -789,6 +807,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-create-class-features-plugin@7.29.3': + resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-create-regexp-features-plugin@7.28.5': resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} @@ -800,6 +824,11 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} @@ -867,6 +896,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} @@ -885,6 +919,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3': + resolution: {integrity: sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} @@ -1145,6 +1185,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-systemjs@7.29.4': + resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-umd@7.27.1': resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} engines: {node: '>=6.9.0'} @@ -1317,6 +1363,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/preset-env@7.29.5': + resolution: {integrity: sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/preset-flow@7.27.1': resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==} engines: {node: '>=6.9.0'} @@ -1347,6 +1399,10 @@ packages: resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -1543,16 +1599,20 @@ packages: resolution: {integrity: sha512-EfI9cJ5/3QSUJtwm7x1MXrx3TEa2p7RNgSHefy7fvGm8/DP1xUFL25nST1NaHbHcqR1UhMlrTtv5iUIDoVzeQQ==} engines: {node: 12.* || 14.* || >= 16} - '@embroider/compat@4.1.13': - resolution: {integrity: sha512-TUvc1bv95deXBdhbgnuNAISbgky5Muo+2x38H4qaw56B//9ppmwqnqw0LIVTXlezY40qgwrW8/ztLW6qIbsPeg==} + '@embroider/compat@4.1.18': + resolution: {integrity: sha512-g9OWzW7zG55D0KoZgaE0PcCgVE+uc7N4x3zMfENlMepSww+LzMvu1bqCWoJhFkns7pJmTSqozdwwOMjM9aFcwg==} engines: {node: '>= 20.19.*'} peerDependencies: - '@embroider/core': ^4.4.3 + '@embroider/core': ^4.4.7 '@embroider/core@4.4.3': resolution: {integrity: sha512-kj651cfYIRf4V8OUnMhuPy1mo7lF1CpCCXyw7kD77qkeBXdvAzCSQFGKANxwuOVkcTW0kU74l3Dv9gGp2NrHxA==} engines: {node: 12.* || 14.* || >= 16} + '@embroider/core@4.4.7': + resolution: {integrity: sha512-8byUO0RKTI/Y25dTxQt/S9L6Ph57L4obGGJfqquP5cQzlEos5w2CRSWV85RhAYowFAuTxgqMbVfAnJTWatgvpg==} + engines: {node: 12.* || 14.* || >= 16} + '@embroider/legacy-inspector-support@0.1.3': resolution: {integrity: sha512-0VzD1xExkT78a1CUiW8wZ5VZDL4bVyMSc3t8E/RiAW1X6TlyKIA/m6zoQgsQtQIiiTPPxH0/1Tdd0F7b5//etw==} @@ -1574,6 +1634,15 @@ packages: '@glint/template': optional: true + '@embroider/macros@1.20.2': + resolution: {integrity: sha512-WJWSkG9vIL0s93vKwtNFqqAOCOflNkWNpqsC7VAqXeeTKNpCc7wtdOhPkNGJpb52CEt7vlQ5R/zMyCfGAB7MEA==} + engines: {node: 12.* || 14.* || >= 16} + peerDependencies: + '@glint/template': 1.7.4 + peerDependenciesMeta: + '@glint/template': + optional: true + '@embroider/reverse-exports@0.2.0': resolution: {integrity: sha512-WFsw8nQpHZiWGEDYpa/A79KEFfTisqteXbY+jg9eZiww1r1G+LZvsmdszDp86TkotUSCqrMbK/ewn0jR1CJmqg==} engines: {node: 12.* || 14.* || >= 16} @@ -1600,6 +1669,12 @@ packages: '@embroider/core': ^4.4.3 vite: '>= 5.2.0' + '@embroider/vite@1.7.3': + resolution: {integrity: sha512-Q+V5pKvP9kXlZ3snn433QPd/PbIIn5vwRLs/SU/egvN2ys+f8VSoL5tb5OdvwmXtAPgeqBuZ4kBp4hQVspzftQ==} + peerDependencies: + '@embroider/core': ^4.4.7 + vite: '>= 5.2.0' + '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -2861,9 +2936,6 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - '@types/babel__code-frame@7.27.0': resolution: {integrity: sha512-Dwlo+LrxDx/0SpfmJ/BKveHf7QXWvLBLc+x03l5sbzykj3oB9nHygCpSECF1a+s+QIxbghe+KHqC90vGtxLRAA==} @@ -2922,6 +2994,9 @@ packages: '@types/node@25.2.0': resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==} + '@types/node@25.9.1': + resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2947,6 +3022,12 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.54.0': resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3008,7 +3089,6 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@universal-ember/test-support@0.7.0': resolution: {integrity: sha512-YX2wg+xuSgvICZy9Tx27GEsO9KwG0GDDrZL8ADgWG7iTVmPyV/VyEdi920Lwmheh63w8vIqCefccFLpamxw3NA==} @@ -3177,7 +3257,6 @@ packages: '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} - deprecated: this version has critical issues, please update to the latest version abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -3200,6 +3279,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -3477,6 +3561,11 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs3@0.13.0: resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: @@ -3487,11 +3576,21 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-regenerator@0.6.6: resolution: {integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-syntax-dynamic-import@6.18.0: resolution: {integrity: sha512-MioUE+LfjCEz65Wf7Z/Rm4XCP5k2c+TbMd2Z2JKc7U9uwjBhAfNPE48KC4GTGKhppMeYVepwDBNO/nGY6NYHBA==} @@ -3518,6 +3617,11 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + baseline-browser-mapping@2.10.32: + resolution: {integrity: sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==} + engines: {node: '>=6.0.0'} + hasBin: true + baseline-browser-mapping@2.9.19: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true @@ -3590,6 +3694,10 @@ packages: resolution: {integrity: sha512-dFB5ATPwOyV8S2I7a07HxCoutoq23oY//LhM6Mou86cWUTB174rND5aQLR7Fu8FjFFLxoTbkk7y0VPITJ1IQrw==} engines: {node: 10.* || >= 12.*} + broccoli-concat@4.2.7: + resolution: {integrity: sha512-JePfBFwHtZ2FR33PBZQA99/hQ4idIbZ205rH84Jw6vgkuKDRVXWVzZP2gvR2WXugXaQ1fj3+yO04b0QsstNHzQ==} + engines: {node: 10.* || >= 12.*} + broccoli-config-loader@1.0.1: resolution: {integrity: sha512-MDKYQ50rxhn+g17DYdfzfEM9DjTuSGu42Db37A8TQHQe8geYEcUZ4SQqZRgzdAI3aRQNlA1yBHJfOeGmOjhLIg==} @@ -3690,6 +3798,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -3742,6 +3855,9 @@ packages: caniuse-lite@1.0.30001766: resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + capture-exit@2.0.0: resolution: {integrity: sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==} engines: {node: 6.* || 8.* || >= 10.*} @@ -4164,6 +4280,9 @@ packages: content-tag@4.1.0: resolution: {integrity: sha512-On6gUuvI1l5MScHO+Xbwjeq1Pk9H6HOipDWkzqGGUGmKpq6K5TRmQuCl1LGSHbdIo2l+lSsgLKrLgCl5kKYA+A==} + content-tag@4.2.0: + resolution: {integrity: sha512-f/o+F3qSa4gg23I7RWy6cMDxP2nPo99YWusxw2bjne7ZC6Acqqf4uB/+87AekOq1ehTocHH7b7nMd2X4S3NHVw==} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -4188,6 +4307,9 @@ packages: core-js-compat@3.48.0: resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js@2.6.12: resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. @@ -4418,6 +4540,9 @@ packages: electron-to-chromium@1.5.283: resolution: {integrity: sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==} + electron-to-chromium@1.5.361: + resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} + ember-async-data@2.0.1: resolution: {integrity: sha512-yCQlVR3NLgjGA09LMc1NYX0XiAd0tHTM4/AmIHD0wq87QpHGmWcpMxyKGEe/TBczyCwBEJifaZNtAjMDsW1YdA==} peerDependencies: @@ -4625,9 +4750,6 @@ packages: ember-rfc176-data@0.3.18: resolution: {integrity: sha512-JtuLoYGSjay1W3MQAxt3eINWXNYYQliK90tLwtb8aeCuQK8zKGCRbBodVIrkcTqshULMnRuTOS6t1P7oQk3g6Q==} - ember-route-template@1.0.3: - resolution: {integrity: sha512-p//Nk4g4Wu9F8cZdjB69rKxTRi6RRW32a8K5sYsi5cofTcJtPBXRWUXWpQEjJX6qcucgxooQwEm9+7MOy4lwNw==} - ember-router-generator@2.0.0: resolution: {integrity: sha512-89oVHVJwmLDvGvAUWgS87KpBoRhy3aZ6U0Ql6HOmU4TrPkyaa8pM0W81wj9cIwjYprcQtN9EwzZMHnq46+oUyw==} engines: {node: 8.* || 10.* || >= 12} @@ -4709,6 +4831,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -4755,6 +4881,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} @@ -5481,6 +5611,15 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + happy-dom@20.8.9: + resolution: {integrity: sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==} + engines: {node: '>=20.0.0'} + has-ansi@3.0.0: resolution: {integrity: sha512-5JRDTvNq6mVkaMHQVXrGnaCXHD6JfqxwCy8LA/DQSqLLqePR9uaJVm2u3Ek/UziJFQz+d1ul99RtfIhE2aorkQ==} engines: {node: '>=4'} @@ -5515,13 +5654,17 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hash-for-dep@1.5.1: - resolution: {integrity: sha512-/dQ/A2cl7FBPI2pO0CANkvuuVi/IFS5oTyJ0PsOb6jW6WbVW1js5qJXMJTNbWHXBIPdFTWFbabjB+mE0d+gelw==} + hash-for-dep@1.5.2: + resolution: {integrity: sha512-+kJRJpgO+V8x6c3UQuzO+gzHu5euS8HDOIaIUsOPdQrVu7ajNKkMykbSC8O0VX3LuRnUNf4hHE0o/rJ+nB8czw==} hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} @@ -5780,6 +5923,10 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + is-data-view@1.0.2: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} @@ -6100,6 +6247,9 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + jsonify@0.0.1: resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} @@ -6288,6 +6438,9 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@2.2.0: resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} engines: {node: '>=4'} @@ -6780,6 +6933,10 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} + node-watch@0.7.3: resolution: {integrity: sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==} engines: {node: '>=6'} @@ -7432,8 +7589,8 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.13.0: - resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true rehype-autolink-headings@7.1.0: @@ -7536,6 +7693,11 @@ packages: engines: {node: '>= 0.4'} hasBin: true + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + restore-cursor@2.0.0: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} @@ -7720,6 +7882,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -8143,8 +8310,8 @@ packages: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} engines: {node: '>=6.0.0'} - terser@5.46.0: - resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + terser@5.48.0: + resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} engines: {node: '>=10'} hasBin: true @@ -8194,6 +8361,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.0.3: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} @@ -8393,6 +8564,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -8523,7 +8697,6 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true validate-npm-package-license@3.0.4: @@ -8559,6 +8732,12 @@ packages: peerDependencies: vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + vite-ember-ssr@0.1.0: + resolution: {integrity: sha512-5AD9mvznfkNApNxWqMgshnzERGOARrcT6eKSbIKwtmgLV58NBhv2bFSr+P5e3BrbabQRGdWjNU92Wix3k9p5mA==} + engines: {node: '>=22'} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 + vite-hot-client@2.1.0: resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} peerDependencies: @@ -8579,8 +8758,8 @@ packages: peerDependencies: vite: ^2 || ^3 || ^4 || ^5 || ^6 || ^7 - vite@7.3.3: - resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -8805,6 +8984,10 @@ packages: whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} @@ -8903,8 +9086,8 @@ packages: write-file-atomic@2.4.3: resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + ws@7.5.11: + resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 @@ -8939,6 +9122,18 @@ packages: utf-8-validate: optional: true + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.1.0: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} @@ -9025,7 +9220,7 @@ snapshots: cjs-module-lexer: 1.4.3 fflate: 0.8.2 lru-cache: 11.2.5 - semver: 7.7.3 + semver: 7.8.1 typescript: 5.6.1-rc validate-npm-package-name: 5.0.1 @@ -9045,6 +9240,8 @@ snapshots: '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.3': {} + '@babel/core@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -9106,6 +9303,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9120,7 +9330,18 @@ snapshots: '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.11 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.12 transitivePeerDependencies: - supports-color @@ -9203,6 +9424,10 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.3': + dependencies: + '@babel/types': 7.29.0 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9221,6 +9446,14 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9359,7 +9592,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -9507,6 +9740,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9576,7 +9819,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -9585,7 +9828,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -9766,6 +10009,83 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/preset-env@7.29.5(@babel/core@7.29.0)': + dependencies: + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/preset-flow@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9806,6 +10126,8 @@ snapshots: '@babel/runtime@7.28.6': {} + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -10160,10 +10482,10 @@ snapshots: '@ember/library-tsconfig@2.0.0': {} - '@ember/optional-features@3.0.0(@types/node@25.2.0)': + '@ember/optional-features@3.0.0(@types/node@25.9.1)': dependencies: ember-cli-version-checker: 5.1.2 - inquirer: 13.2.2(@types/node@25.2.0) + inquirer: 13.2.2(@types/node@25.9.1) silent-error: 1.1.1 tinyglobby: 0.2.15 transitivePeerDependencies: @@ -10176,7 +10498,7 @@ snapshots: dependencies: '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@simple-dom/interface': 1.4.0 decorator-transforms: 2.3.1(@babel/core@7.29.0) dom-element-descriptors: 0.5.1 @@ -10188,7 +10510,7 @@ snapshots: '@ember/test-waiters@4.1.1(882cf82caec78f226fcbd647b89f3bbe)': dependencies: '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) transitivePeerDependencies: - '@babel/core' - '@glint/template' @@ -10196,7 +10518,7 @@ snapshots: '@embroider/addon-dev@8.3.0(@glint/template@1.7.4)(rollup@4.59.0)': dependencies: - '@embroider/core': 4.4.3(@glint/template@1.7.4) + '@embroider/core': 4.4.7(@glint/template@1.7.4) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) content-tag: 4.1.0 execa: 5.1.1 @@ -10224,7 +10546,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@embroider/compat@4.1.13(e238ad5f7331c9e1f8a49298b282f063)': + '@embroider/compat@4.1.18(08820dd21dc646074fb879906de0104b)': dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 @@ -10232,11 +10554,11 @@ snapshots: '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) - '@babel/preset-env': 7.29.0(@babel/core@7.29.0) - '@babel/runtime': 7.28.6 + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + '@babel/runtime': 7.29.2 '@babel/traverse': 7.29.0 - '@embroider/core': 4.4.3(@glint/template@1.7.4) - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/core': 4.4.7(@glint/template@1.7.4) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@types/babel__code-frame': 7.27.0 assert-never: 1.4.0 babel-import-util: 3.0.1 @@ -10247,7 +10569,7 @@ snapshots: babylon: 6.18.0 bind-decorator: 1.0.11 broccoli: 4.0.0 - broccoli-concat: 4.2.5 + broccoli-concat: 4.2.7 broccoli-file-creator: 2.1.1 broccoli-funnel: 3.0.8 broccoli-merge-trees: 4.2.0 @@ -10260,12 +10582,12 @@ snapshots: fs-extra: 9.1.0 fs-tree-diff: 2.0.1 jsdom: 26.1.0 - lodash: 4.17.23 + lodash: 4.18.1 pkg-up: 3.1.0 - resolve: 1.22.11 + resolve: 1.22.12 resolve-package-path: 4.0.3 resolve.exports: 2.0.3 - semver: 7.7.3 + semver: 7.8.1 symlink-or-copy: 1.3.1 tree-sync: 2.1.0 typescript-memoize: 1.1.1 @@ -10280,7 +10602,7 @@ snapshots: '@embroider/core@4.4.3(@glint/template@1.7.4)': dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.3 '@babel/traverse': 7.29.0 '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) '@embroider/reverse-exports': 0.2.0 @@ -10296,14 +10618,50 @@ snapshots: fast-sourcemap-concat: 2.1.1 fs-extra: 9.1.0 fs-tree-diff: 2.0.1 - handlebars: 4.7.8 + handlebars: 4.7.9 js-string-escape: 1.0.1 jsdom: 25.0.1 - lodash: 4.17.23 - resolve: 1.22.11 + lodash: 4.18.1 + resolve: 1.22.12 resolve-package-path: 4.0.3 resolve.exports: 2.0.3 - semver: 7.7.3 + semver: 7.8.1 + typescript-memoize: 1.1.1 + walk-sync: 3.0.0 + transitivePeerDependencies: + - '@glint/template' + - bufferutil + - canvas + - supports-color + - utf-8-validate + + '@embroider/core@4.4.7(@glint/template@1.7.4)': + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.3 + '@babel/traverse': 7.29.0 + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/reverse-exports': 0.2.0 + '@embroider/shared-internals': 3.0.2 + assert-never: 1.4.0 + babel-plugin-ember-template-compilation: 3.1.0 + broccoli-node-api: 1.7.0 + broccoli-persistent-filter: 3.1.3 + broccoli-plugin: 4.0.7 + broccoli-source: 3.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + fast-sourcemap-concat: 2.1.1 + fs-extra: 9.1.0 + fs-tree-diff: 2.0.1 + handlebars: 4.7.9 + js-string-escape: 1.0.1 + jsdom: 25.0.1 + lodash: 4.18.1 + resolve: 1.22.12 + resolve-package-path: 4.0.3 + resolve.exports: 2.0.3 + semver: 7.8.1 typescript-memoize: 1.1.1 walk-sync: 3.0.0 transitivePeerDependencies: @@ -10326,9 +10684,9 @@ snapshots: babel-import-util: 3.0.1 ember-cli-babel: 7.26.11 find-up: 5.0.0 - lodash: 4.17.23 - resolve: 1.22.11 - semver: 7.7.3 + lodash: 4.18.1 + resolve: 1.22.12 + semver: 7.8.1 optionalDependencies: '@glint/template': 1.7.4 transitivePeerDependencies: @@ -10341,9 +10699,25 @@ snapshots: babel-import-util: 3.0.1 ember-cli-babel: 8.3.1(@babel/core@7.29.0) find-up: 5.0.0 - lodash: 4.17.23 - resolve: 1.22.11 - semver: 7.7.3 + lodash: 4.18.1 + resolve: 1.22.12 + semver: 7.8.1 + optionalDependencies: + '@glint/template': 1.7.4 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@embroider/macros@1.20.2(882cf82caec78f226fcbd647b89f3bbe)': + dependencies: + '@embroider/shared-internals': 3.0.2 + assert-never: 1.4.0 + babel-import-util: 3.0.1 + ember-cli-babel: 8.3.1(@babel/core@7.29.0) + find-up: 5.0.0 + lodash: 4.18.1 + resolve: 1.22.12 + semver: 7.8.1 optionalDependencies: '@glint/template': 1.7.4 transitivePeerDependencies: @@ -10391,7 +10765,7 @@ snapshots: fs-extra: 9.1.0 is-subdir: 1.2.0 js-string-escape: 1.0.1 - lodash: 4.17.23 + lodash: 4.18.1 minimatch: 3.1.2 pkg-entry-points: 1.1.1 resolve-package-path: 4.0.3 @@ -10401,7 +10775,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@embroider/vite@1.5.2(5f41de3fafd619070cf36afcb240fe74)': + '@embroider/vite@1.5.2(202dcc17de8bc9c33c094c94eaf1d368)': dependencies: '@babel/core': 7.29.0 '@embroider/core': 4.4.3(@glint/template@1.7.4) @@ -10409,18 +10783,18 @@ snapshots: '@embroider/reverse-exports': 0.2.0 '@rollup/pluginutils': 5.3.0(rollup@4.59.0) assert-never: 1.4.0 - browserslist: 4.28.1 - browserslist-to-esbuild: 2.1.1(browserslist@4.28.1) + browserslist: 4.28.2 + browserslist-to-esbuild: 2.1.1(browserslist@4.28.2) chalk: 5.6.2 - content-tag: 4.1.0 + content-tag: 4.2.0 debug: 4.4.3 fast-glob: 3.3.3 fs-extra: 10.1.0 jsdom: 25.0.1 send: 0.18.0 source-map-url: 0.4.1 - terser: 5.46.0 - vite: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + terser: 5.48.0 + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) transitivePeerDependencies: - '@glint/template' - bufferutil @@ -10429,6 +10803,32 @@ snapshots: - supports-color - utf-8-validate + '@embroider/vite@1.7.3(03c4728243303ca3ed5a123601c1cc2b)': + dependencies: + '@babel/core': 7.29.0 + '@embroider/core': 4.4.7(@glint/template@1.7.4) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/reverse-exports': 0.2.0 + assert-never: 1.4.0 + browserslist: 4.28.2 + browserslist-to-esbuild: 2.1.1(browserslist@4.28.2) + chalk: 5.6.2 + content-tag: 4.2.0 + debug: 4.4.3 + fast-glob: 3.3.3 + fs-extra: 10.1.0 + jsdom: 25.0.1 + send: 1.2.1 + source-map-url: 0.4.1 + terser: 5.48.0 + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) + transitivePeerDependencies: + - '@glint/template' + - bufferutil + - canvas + - supports-color + - utf-8-validate + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -10808,122 +11208,122 @@ snapshots: '@inquirer/ansi@2.0.3': {} - '@inquirer/checkbox@5.0.4(@types/node@25.2.0)': + '@inquirer/checkbox@5.0.4(@types/node@25.9.1)': dependencies: '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/confirm@6.0.4(@types/node@25.2.0)': + '@inquirer/confirm@6.0.4(@types/node@25.9.1)': dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/core@11.1.1(@types/node@25.2.0)': + '@inquirer/core@11.1.1(@types/node@25.9.1)': dependencies: '@inquirer/ansi': 2.0.3 '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/type': 4.0.3(@types/node@25.9.1) cli-width: 4.1.0 mute-stream: 3.0.0 signal-exit: 4.1.0 wrap-ansi: 9.0.2 optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/editor@5.0.4(@types/node@25.2.0)': + '@inquirer/editor@5.0.4(@types/node@25.9.1)': dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/external-editor': 2.0.3(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/external-editor': 2.0.3(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/expand@5.0.4(@types/node@25.2.0)': + '@inquirer/expand@5.0.4(@types/node@25.9.1)': dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/external-editor@2.0.3(@types/node@25.2.0)': + '@inquirer/external-editor@2.0.3(@types/node@25.9.1)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 '@inquirer/figures@2.0.3': {} - '@inquirer/input@5.0.4(@types/node@25.2.0)': + '@inquirer/input@5.0.4(@types/node@25.9.1)': dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/number@4.0.4(@types/node@25.2.0)': + '@inquirer/number@4.0.4(@types/node@25.9.1)': dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/password@5.0.4(@types/node@25.2.0)': + '@inquirer/password@5.0.4(@types/node@25.9.1)': dependencies: '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 - - '@inquirer/prompts@8.2.0(@types/node@25.2.0)': - dependencies: - '@inquirer/checkbox': 5.0.4(@types/node@25.2.0) - '@inquirer/confirm': 6.0.4(@types/node@25.2.0) - '@inquirer/editor': 5.0.4(@types/node@25.2.0) - '@inquirer/expand': 5.0.4(@types/node@25.2.0) - '@inquirer/input': 5.0.4(@types/node@25.2.0) - '@inquirer/number': 4.0.4(@types/node@25.2.0) - '@inquirer/password': 5.0.4(@types/node@25.2.0) - '@inquirer/rawlist': 5.2.0(@types/node@25.2.0) - '@inquirer/search': 4.1.0(@types/node@25.2.0) - '@inquirer/select': 5.0.4(@types/node@25.2.0) + '@types/node': 25.9.1 + + '@inquirer/prompts@8.2.0(@types/node@25.9.1)': + dependencies: + '@inquirer/checkbox': 5.0.4(@types/node@25.9.1) + '@inquirer/confirm': 6.0.4(@types/node@25.9.1) + '@inquirer/editor': 5.0.4(@types/node@25.9.1) + '@inquirer/expand': 5.0.4(@types/node@25.9.1) + '@inquirer/input': 5.0.4(@types/node@25.9.1) + '@inquirer/number': 4.0.4(@types/node@25.9.1) + '@inquirer/password': 5.0.4(@types/node@25.9.1) + '@inquirer/rawlist': 5.2.0(@types/node@25.9.1) + '@inquirer/search': 4.1.0(@types/node@25.9.1) + '@inquirer/select': 5.0.4(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/rawlist@5.2.0(@types/node@25.2.0)': + '@inquirer/rawlist@5.2.0(@types/node@25.9.1)': dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/search@4.1.0(@types/node@25.2.0)': + '@inquirer/search@4.1.0(@types/node@25.9.1)': dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/select@5.0.4(@types/node@25.2.0)': + '@inquirer/select@5.0.4(@types/node@25.9.1)': dependencies: '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/type': 4.0.3(@types/node@25.9.1) optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 - '@inquirer/type@4.0.3(@types/node@25.2.0)': + '@inquirer/type@4.0.3(@types/node@25.9.1)': optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 '@isaacs/balanced-match@4.0.1': {} @@ -11107,7 +11507,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@tybys/wasm-util': 0.10.1 optional: true '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -11129,7 +11529,7 @@ snapshots: '@npmcli/fs@1.1.1': dependencies: '@gar/promisify': 1.1.3 - semver: 7.7.3 + semver: 7.8.1 '@npmcli/git@6.0.3': dependencies: @@ -11139,7 +11539,7 @@ snapshots: npm-pick-manifest: 10.0.0 proc-log: 5.0.0 promise-retry: 2.0.1 - semver: 7.7.3 + semver: 7.8.1 which: 5.0.0 '@npmcli/move-file@1.1.2': @@ -11154,7 +11554,7 @@ snapshots: hosted-git-info: 8.1.0 json-parse-even-better-errors: 4.0.0 proc-log: 5.0.0 - semver: 7.7.3 + semver: 7.8.1 validate-npm-package-license: 3.0.4 '@npmcli/promise-spawn@8.0.3': @@ -11258,9 +11658,9 @@ snapshots: '@open-rpc/client-js@1.8.1(encoding@0.1.13)': dependencies: isomorphic-fetch: 3.0.0(encoding@0.1.13) - isomorphic-ws: 5.0.0(ws@7.5.10) + isomorphic-ws: 5.0.0(ws@7.5.11) strict-event-emitter-types: 2.0.0 - ws: 7.5.10 + ws: 7.5.11 transitivePeerDependencies: - bufferutil - encoding @@ -11625,11 +12025,6 @@ snapshots: tslib: 2.8.1 optional: true - '@tybys/wasm-util@0.10.2': - dependencies: - tslib: 2.8.1 - optional: true - '@types/babel__code-frame@7.27.0': {} '@types/chai@5.2.3': @@ -11641,7 +12036,7 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 '@types/debug@4.1.12': dependencies: @@ -11658,11 +12053,11 @@ snapshots: '@types/fs-extra@5.1.0': dependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 '@types/fs-extra@8.1.5': dependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 '@types/glob@9.0.0': dependencies: @@ -11690,6 +12085,10 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + '@types/normalize-package-data@2.4.4': {} '@types/parse-path@7.1.0': @@ -11701,7 +12100,7 @@ snapshots: '@types/rimraf@2.0.5': dependencies: '@types/glob': 9.0.0 - '@types/node': 25.2.0 + '@types/node': 25.9.1 '@types/rsvp@4.0.9': {} @@ -11713,6 +12112,12 @@ snapshots: '@types/unist@3.0.3': {} + '@types/whatwg-mimetype@3.0.2': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.9.1 + '@typescript-eslint/eslint-plugin@8.54.0(8cba54b1ce8d4815212ee8a3ae487b8b)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -11781,7 +12186,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3 minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.8.1 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 @@ -11896,13 +12301,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(860304eea6b56489beb687487b25659c)': + '@vitest/mocker@4.0.18(d4eb820b869314410368ca3ef0a4963d)': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.3(c46cd77709f114f3781a3e26c325c401) + vite: 7.3.1(ae46566fde63be75904a2137faf7346d) '@vitest/pretty-format@4.0.18': dependencies: @@ -11995,6 +12400,8 @@ snapshots: acorn@8.15.0: {} + acorn@8.16.0: {} + agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -12199,7 +12606,7 @@ snapshots: async@2.6.4: dependencies: - lodash: 4.17.23 + lodash: 4.18.1 async@3.2.6: {} @@ -12248,7 +12655,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 babel-import-util: 2.1.1 - semver: 7.7.3 + semver: 7.8.1 babel-plugin-ember-data-packages-polyfill@0.1.2: dependencies: @@ -12289,7 +12696,7 @@ snapshots: glob: 7.2.3 pkg-up: 2.0.0 reselect: 3.0.1 - resolve: 1.22.11 + resolve: 1.22.12 babel-plugin-module-resolver@5.0.2: dependencies: @@ -12297,7 +12704,7 @@ snapshots: glob: 9.3.5 pkg-up: 3.1.0 reselect: 4.1.8 - resolve: 1.22.11 + resolve: 1.22.12 babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0): dependencies: @@ -12308,6 +12715,15 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + dependencies: + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 @@ -12324,6 +12740,14 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 @@ -12331,6 +12755,13 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + babel-plugin-syntax-dynamic-import@6.18.0: {} babel-remove-types@1.1.0: @@ -12356,6 +12787,8 @@ snapshots: base64id@2.0.0: {} + baseline-browser-mapping@2.10.32: {} + baseline-browser-mapping@2.9.19: {} basic-auth@2.0.1: @@ -12439,7 +12872,7 @@ snapshots: broccoli-merge-trees: 3.0.2 broccoli-persistent-filter: 2.3.1 clone: 2.1.2 - hash-for-dep: 1.5.1 + hash-for-dep: 1.5.2 heimdalljs: 0.2.6 heimdalljs-logger: 0.1.10 json-stable-stringify: 1.3.0 @@ -12453,7 +12886,7 @@ snapshots: '@babel/core': 7.29.0 broccoli-persistent-filter: 3.1.3 clone: 2.1.2 - hash-for-dep: 1.5.1 + hash-for-dep: 1.5.2 heimdalljs: 0.2.6 heimdalljs-logger: 0.1.10 json-stable-stringify: 1.3.0 @@ -12488,6 +12921,19 @@ snapshots: transitivePeerDependencies: - supports-color + broccoli-concat@4.2.7: + dependencies: + broccoli-debug: 0.6.5 + broccoli-plugin: 4.0.7 + ensure-posix-path: 1.1.1 + fast-sourcemap-concat: 2.1.1 + find-index: 1.1.1 + fs-extra: 8.1.0 + fs-tree-diff: 2.0.1 + lodash: 4.18.1 + transitivePeerDependencies: + - supports-color + broccoli-config-loader@1.0.1: dependencies: broccoli-caching-writer: 3.1.0 @@ -12595,7 +13041,7 @@ snapshots: async-promise-queue: 1.0.5 broccoli-plugin: 1.3.1 fs-tree-diff: 2.0.1 - hash-for-dep: 1.5.1 + hash-for-dep: 1.5.2 heimdalljs: 0.2.6 heimdalljs-logger: 0.1.10 mkdirp: 0.5.6 @@ -12614,7 +13060,7 @@ snapshots: async-promise-queue: 1.0.5 broccoli-plugin: 4.0.7 fs-tree-diff: 2.0.1 - hash-for-dep: 1.5.1 + hash-for-dep: 1.5.2 heimdalljs: 0.2.6 heimdalljs-logger: 0.1.10 promise-map-series: 0.2.3 @@ -12702,9 +13148,9 @@ snapshots: transitivePeerDependencies: - supports-color - browserslist-to-esbuild@2.1.1(browserslist@4.28.1): + browserslist-to-esbuild@2.1.1(browserslist@4.28.2): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 meow: 13.2.0 browserslist@4.28.1: @@ -12715,6 +13161,14 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.32 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.361 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -12783,6 +13237,8 @@ snapshots: caniuse-lite@1.0.30001766: {} + caniuse-lite@1.0.30001793: {} + capture-exit@2.0.0: dependencies: rsvp: 4.8.5 @@ -13075,12 +13531,12 @@ snapshots: ora: 3.4.0 through2: 3.0.2 - consolidate@0.16.0(96859ec0e78b0ee98c3aca0843543c97): + consolidate@0.16.0(7811b97a5e30c09a2a38467656138c3b): dependencies: bluebird: 3.7.2 optionalDependencies: ejs: 3.1.10 - handlebars: 4.7.8 + handlebars: 4.7.9 lodash: 4.17.23 mustache: 4.2.0 underscore: 1.13.7 @@ -13097,6 +13553,8 @@ snapshots: content-tag@4.1.0: {} + content-tag@4.2.0: {} + content-type@1.0.5: {} continuable-cache@0.3.1: {} @@ -13111,7 +13569,11 @@ snapshots: core-js-compat@3.48.0: dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 + + core-js-compat@3.49.0: + dependencies: + browserslist: 4.28.2 core-js@2.6.12: {} @@ -13328,6 +13790,8 @@ snapshots: electron-to-chromium@1.5.283: {} + electron-to-chromium@1.5.361: {} + ember-async-data@2.0.1(4d82d0b9b992fab2689d70080ddde57e): dependencies: '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) @@ -13347,7 +13811,7 @@ snapshots: '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.29.0) '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) '@babel/preset-env': 7.29.0(@babel/core@7.29.0) - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@embroider/reverse-exports': 0.2.0 '@embroider/shared-internals': 2.9.2 babel-loader: 8.4.1(@babel/core@7.29.0) @@ -13395,7 +13859,7 @@ snapshots: ember-cached-decorator-polyfill@1.0.2(4d82d0b9b992fab2689d70080ddde57e): dependencies: - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@glimmer/tracking': 1.1.2 babel-import-util: 1.4.1 ember-cache-primitive-polyfill: 1.0.1(@babel/core@7.29.0) @@ -13490,10 +13954,10 @@ snapshots: ember-cli-babel-plugin-helpers: 1.1.1 ember-cli-version-checker: 5.1.2 fs-tree-diff: 2.0.1 - hash-for-dep: 1.5.1 + hash-for-dep: 1.5.2 heimdalljs-logger: 0.1.10 json-stable-stringify: 1.3.0 - semver: 7.7.3 + semver: 7.8.1 silent-error: 1.1.1 strip-bom: 4.0.0 walk-sync: 2.2.0 @@ -13542,7 +14006,7 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli@6.10.0(aee6f353e28028806c6c60dd4e194896): + ember-cli@6.10.0(03ad83d5106095c4dd77448655e3c54b): dependencies: '@ember-tooling/blueprint-blueprint': 0.2.1 '@ember-tooling/blueprint-model': 0.5.0 @@ -13598,7 +14062,7 @@ snapshots: heimdalljs-logger: 0.1.10 http-proxy: 1.18.1 inflection: 3.0.2 - inquirer: 13.2.2(@types/node@25.2.0) + inquirer: 13.2.2(@types/node@25.9.1) is-git-url: 1.0.0 is-language-code: 5.1.3 isbinaryfile: 6.0.0 @@ -13624,7 +14088,7 @@ snapshots: sort-package-json: 3.6.1 symlink-or-copy: 1.3.1 temp: 0.9.4 - testem: 3.17.0(8bcdda22d80c66a2796dcf0ed2360163) + testem: 3.17.0(db015b790b423be5302f27310d983004) tiny-lr: 2.0.0 tree-sync: 2.1.0 walk-sync: 4.0.1 @@ -13805,7 +14269,7 @@ snapshots: '@babel/runtime': 7.28.6 '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@floating-ui/dom': 1.7.5 '@glimmer/component': 2.0.0 decorator-transforms: 2.3.1(@babel/core@7.29.0) @@ -13832,7 +14296,7 @@ snapshots: '@babel/runtime': 7.28.6 '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@floating-ui/dom': 1.7.5 '@glimmer/component': 2.0.0 decorator-transforms: 2.3.1(@babel/core@7.29.0) @@ -13859,7 +14323,7 @@ snapshots: '@babel/runtime': 7.28.6 '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@floating-ui/dom': 1.7.5 '@glimmer/component': 2.0.0 decorator-transforms: 2.3.1(@babel/core@7.29.0) @@ -13886,7 +14350,7 @@ snapshots: '@babel/runtime': 7.28.6 '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@floating-ui/dom': 1.7.5 '@glimmer/component': 2.0.0 decorator-transforms: 2.3.1(@babel/core@7.29.0) @@ -13912,7 +14376,7 @@ snapshots: dependencies: '@ember/test-helpers': 5.4.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) qunit: 2.25.0 qunit-theme-ember: 1.0.0 transitivePeerDependencies: @@ -13920,7 +14384,7 @@ snapshots: - '@glint/template' - supports-color - ember-repl@8.0.2(0f15df29688aaf2535d173c404cf8efa): + ember-repl@8.0.2(patch_hash=cbea90b2edadff3d670cd183117e59dd4b487b219e581bcb95eec2dc6dee9b7d)(0f15df29688aaf2535d173c404cf8efa): dependencies: '@ember/test-helpers': 5.4.1(882cf82caec78f226fcbd647b89f3bbe) '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) @@ -13932,7 +14396,7 @@ snapshots: ember-resources: 7.0.7(099a17061adaa39d579f1e156593ef5d) ember-source: 6.12.0-alpha.3(@glimmer/component@2.0.0)(rsvp@4.8.5) reactiveweb: 1.9.1(9dcb653de42b381c793197699ab8c1c6) - repl-sdk: 1.5.2(6f4bb085e066dcd1e8cba157a733f3a5) + repl-sdk: 1.5.2(patch_hash=d1fe29012a6c4a614eefd0d05420d1fd33ddbf20f5fae22f803608d981e06891)(6f4bb085e066dcd1e8cba157a733f3a5) optionalDependencies: '@glint/template': 1.7.4 transitivePeerDependencies: @@ -13958,7 +14422,7 @@ snapshots: ember-resources@7.0.7(099a17061adaa39d579f1e156593ef5d): dependencies: '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@glint/template': 1.7.4 ember-source: 6.12.0-alpha.3(@glimmer/component@2.0.0)(rsvp@4.8.5) optionalDependencies: @@ -13970,7 +14434,7 @@ snapshots: ember-resources@7.0.7(c9b2d83ca9f23eabb33653a2e0f9845f): dependencies: '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) '@glint/template': 1.7.4 ember-source: 6.10.1(@glimmer/component@2.0.0)(rsvp@4.8.5) optionalDependencies: @@ -13981,12 +14445,6 @@ snapshots: ember-rfc176-data@0.3.18: {} - ember-route-template@1.0.3: - dependencies: - '@embroider/addon-shim': 1.10.2 - transitivePeerDependencies: - - supports-color - ember-router-generator@2.0.0: dependencies: '@babel/parser': 7.29.0 @@ -14121,7 +14579,7 @@ snapshots: engine.io@6.6.5: dependencies: '@types/cors': 2.8.19 - '@types/node': 25.2.0 + '@types/node': 25.9.1 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -14145,6 +14603,8 @@ snapshots: entities@6.0.1: {} + entities@7.0.1: {} + env-paths@2.2.1: {} environment@1.1.0: {} @@ -14232,12 +14692,16 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 es-shim-unscopables@1.1.0: dependencies: @@ -14291,7 +14755,7 @@ snapshots: eslint-compat-utils@0.5.1(eslint@9.39.2(jiti@2.6.1)): dependencies: eslint: 9.39.2(jiti@2.6.1) - semver: 7.7.3 + semver: 7.8.1 eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): dependencies: @@ -14308,7 +14772,7 @@ snapshots: dependencies: debug: 3.2.7 is-core-module: 2.16.1 - resolve: 1.22.11 + resolve: 1.22.12 transitivePeerDependencies: - supports-color @@ -14858,12 +15322,12 @@ snapshots: lodash.flatten: 3.0.2 minimatch: 3.1.2 - fix-bad-declaration-output@1.1.5(96503fdd924316efedea1b15928b7aad): + fix-bad-declaration-output@1.1.5(85346fca787e2ba7ee5cc61c0e01fa28): dependencies: '@babel/parser': 7.29.0 fs-extra: 11.3.3 globby: 14.1.0 - jscodeshift: 0.15.2(96503fdd924316efedea1b15928b7aad) + jscodeshift: 0.15.2(85346fca787e2ba7ee5cc61c0e01fa28) transitivePeerDependencies: - '@babel/preset-env' - supports-color @@ -14923,7 +15387,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.3 mime-types: 2.1.35 forwarded@0.2.0: {} @@ -14979,7 +15443,7 @@ snapshots: dependencies: at-least-node: 1.0.0 graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-merger@3.2.1: @@ -15067,18 +15531,18 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-stdin@9.0.0: {} @@ -15252,6 +15716,27 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + happy-dom@20.8.9: + dependencies: + '@types/node': 25.9.1 + '@types/whatwg-mimetype': 3.0.2 + '@types/ws': 8.18.1 + entities: 7.0.1 + whatwg-mimetype: 3.0.0 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + has-ansi@3.0.0: dependencies: ansi-regex: 3.0.1 @@ -15278,13 +15763,11 @@ snapshots: has-unicode@2.0.1: {} - hash-for-dep@1.5.1: + hash-for-dep@1.5.2: dependencies: - broccoli-kitchen-sink-helpers: 0.3.1 heimdalljs: 0.2.6 heimdalljs-logger: 0.1.10 - path-root: 0.1.1 - resolve: 1.22.11 + resolve: 1.22.12 resolve-package-path: 1.2.7 transitivePeerDependencies: - supports-color @@ -15293,6 +15776,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + hast-util-from-parse5@8.0.3: dependencies: '@types/hast': 3.0.4 @@ -15547,17 +16034,17 @@ snapshots: ini@5.0.0: {} - inquirer@13.2.2(@types/node@25.2.0): + inquirer@13.2.2(@types/node@25.9.1): dependencies: '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.0) - '@inquirer/prompts': 8.2.0(@types/node@25.2.0) - '@inquirer/type': 4.0.3(@types/node@25.2.0) + '@inquirer/core': 11.1.1(@types/node@25.9.1) + '@inquirer/prompts': 8.2.0(@types/node@25.9.1) + '@inquirer/type': 4.0.3(@types/node@25.9.1) mute-stream: 3.0.0 run-async: 4.0.6 rxjs: 7.8.2 optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 inquirer@6.5.2: dependencies: @@ -15567,7 +16054,7 @@ snapshots: cli-width: 2.2.1 external-editor: 3.1.0 figures: 2.0.0 - lodash: 4.17.23 + lodash: 4.18.1 mute-stream: 0.0.7 run-async: 2.4.1 rxjs: 6.6.7 @@ -15578,7 +16065,7 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 + hasown: 2.0.3 side-channel: 1.1.0 invert-kv@3.0.1: {} @@ -15614,7 +16101,7 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.7.3 + semver: 7.8.1 is-callable@1.2.7: {} @@ -15622,6 +16109,10 @@ snapshots: dependencies: hasown: 2.0.2 + is-core-module@2.16.2: + dependencies: + hasown: 2.0.3 + is-data-view@1.0.2: dependencies: call-bound: 1.0.4 @@ -15790,9 +16281,9 @@ snapshots: transitivePeerDependencies: - encoding - isomorphic-ws@5.0.0(ws@7.5.10): + isomorphic-ws@5.0.0(ws@7.5.11): dependencies: - ws: 7.5.10 + ws: 7.5.11 istextorbinary@2.1.0: dependencies: @@ -15835,10 +16326,10 @@ snapshots: dependencies: argparse: 2.0.1 - jscodeshift@0.15.2(96503fdd924316efedea1b15928b7aad): + jscodeshift@0.15.2(85346fca787e2ba7ee5cc61c0e01fa28): dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.3 '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) @@ -15858,7 +16349,7 @@ snapshots: temp: 0.8.4 write-file-atomic: 2.4.3 optionalDependencies: - '@babel/preset-env': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -15883,7 +16374,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.19.0 + ws: 8.21.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -15910,7 +16401,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.19.0 + ws: 8.21.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -15961,6 +16452,12 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsonify@0.0.1: {} keyborg@2.6.0: {} @@ -16123,6 +16620,8 @@ snapshots: lodash@4.17.23: {} + lodash@4.18.1: {} + log-symbols@2.2.0: dependencies: chalk: 2.4.2 @@ -16768,13 +17267,15 @@ snapshots: dependencies: growly: 1.3.0 is-wsl: 2.2.0 - semver: 7.7.3 + semver: 7.8.1 shellwords: 0.1.1 uuid: 8.3.2 which: 2.0.2 node-releases@2.0.27: {} + node-releases@2.0.46: {} + node-watch@0.7.3: {} nopt@3.0.6: @@ -16784,14 +17285,14 @@ snapshots: normalize-package-data@8.0.0: dependencies: hosted-git-info: 9.0.2 - semver: 7.7.3 + semver: 7.8.1 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} npm-install-checks@7.1.2: dependencies: - semver: 7.7.3 + semver: 7.8.1 npm-normalize-package-bin@4.0.0: {} @@ -16799,7 +17300,7 @@ snapshots: dependencies: hosted-git-info: 8.1.0 proc-log: 5.0.0 - semver: 7.7.3 + semver: 7.8.1 validate-npm-package-name: 6.0.2 npm-package-arg@13.0.2: @@ -16814,7 +17315,7 @@ snapshots: npm-install-checks: 7.1.2 npm-normalize-package-bin: 4.0.0 npm-package-arg: 12.0.2 - semver: 7.7.3 + semver: 7.8.1 npm-run-path@2.0.2: dependencies: @@ -17024,7 +17525,7 @@ snapshots: ky: 1.14.3 registry-auth-token: 5.1.1 registry-url: 6.0.1 - semver: 7.7.3 + semver: 7.8.1 package-manager-detector@1.6.0: {} @@ -17339,7 +17840,7 @@ snapshots: dependencies: '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) decorator-transforms: 2.3.1(@babel/core@7.29.0) ember-resources: 7.0.7(c9b2d83ca9f23eabb33653a2e0f9845f) transitivePeerDependencies: @@ -17353,7 +17854,7 @@ snapshots: dependencies: '@ember/test-waiters': 4.1.1(882cf82caec78f226fcbd647b89f3bbe) '@embroider/addon-shim': 1.10.2 - '@embroider/macros': 1.19.7(882cf82caec78f226fcbd647b89f3bbe) + '@embroider/macros': 1.20.2(882cf82caec78f226fcbd647b89f3bbe) decorator-transforms: 2.3.1(@babel/core@7.29.0) ember-resources: 7.0.7(099a17061adaa39d579f1e156593ef5d) transitivePeerDependencies: @@ -17452,7 +17953,7 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.13.0 + regjsparser: 0.13.1 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.1 @@ -17466,7 +17967,7 @@ snapshots: regjsgen@0.8.0: {} - regjsparser@0.13.0: + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 @@ -17562,7 +18063,7 @@ snapshots: transitivePeerDependencies: - supports-color - repl-sdk@1.5.2(6f4bb085e066dcd1e8cba157a733f3a5): + repl-sdk@1.5.2(patch_hash=d1fe29012a6c4a614eefd0d05420d1fd33ddbf20f5fae22f803608d981e06891)(6f4bb085e066dcd1e8cba157a733f3a5): dependencies: '@codemirror/autocomplete': 6.20.0 '@codemirror/commands': 6.10.2 @@ -17641,17 +18142,17 @@ snapshots: resolve-package-path@1.2.7: dependencies: path-root: 0.1.1 - resolve: 1.22.11 + resolve: 1.22.12 resolve-package-path@2.0.0: dependencies: path-root: 0.1.1 - resolve: 1.22.11 + resolve: 1.22.12 resolve-package-path@3.1.0: dependencies: path-root: 0.1.1 - resolve: 1.22.11 + resolve: 1.22.12 resolve-package-path@4.0.3: dependencies: @@ -17676,6 +18177,13 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@2.0.0: dependencies: onetime: 2.0.1 @@ -17921,6 +18429,8 @@ snapshots: semver@7.7.3: {} + semver@7.8.1: {} + send@0.18.0: dependencies: debug: 2.6.9 @@ -18177,7 +18687,7 @@ snapshots: get-stdin: 9.0.0 git-hooks-list: 3.2.0 is-plain-obj: 4.1.0 - semver: 7.7.3 + semver: 7.8.1 sort-object-keys: 1.1.3 tinyglobby: 0.2.15 @@ -18457,14 +18967,14 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.6.3 - terser@5.46.0: + terser@5.48.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 + acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 - testem@3.17.0(8bcdda22d80c66a2796dcf0ed2360163): + testem@3.17.0(db015b790b423be5302f27310d983004): dependencies: '@xmldom/xmldom': 0.8.11 backbone: 1.6.1 @@ -18472,7 +18982,7 @@ snapshots: charm: 1.0.2 commander: 2.20.3 compression: 1.8.1 - consolidate: 0.16.0(96859ec0e78b0ee98c3aca0843543c97) + consolidate: 0.16.0(7811b97a5e30c09a2a38467656138c3b) execa: 1.0.0 express: 4.22.1 fireworm: 0.7.2 @@ -18597,6 +19107,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinypool@2.1.0: {} + tinyrainbow@3.0.3: {} tldts-core@6.1.86: {} @@ -18786,7 +19298,7 @@ snapshots: typescript-auto-import-cache@0.3.6: dependencies: - semver: 7.7.3 + semver: 7.8.1 typescript-eslint@8.54.0(b1cbe766652296b79becebe56b05ba7c): dependencies: @@ -18826,6 +19338,8 @@ snapshots: undici-types@7.16.0: {} + undici-types@7.24.6: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-emoji-modifier-base@1.0.0: {} @@ -18892,7 +19406,7 @@ snapshots: unpipe@1.0.0: {} - unplugin-info@1.2.4(780e7b315b6d999a5119d944ff478cb6): + unplugin-info@1.2.4(ffd467c98beae3b2d8bae0fb29b0db14): dependencies: ci-info: 4.4.0 git-url-parse: 16.1.0 @@ -18901,7 +19415,7 @@ snapshots: optionalDependencies: esbuild: 0.27.2 rollup: 4.59.0 - vite: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) transitivePeerDependencies: - supports-color @@ -18955,6 +19469,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -18995,17 +19515,26 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-dev-rpc@1.1.0(b755ca744ca121980f4cb7903c2754d2): + vite-dev-rpc@1.1.0(1ae42ea40c273aad7303991cb84a8103): dependencies: birpc: 2.9.0 - vite: 8.0.14(050a6ec760bdf6c8475789235dc875ef) - vite-hot-client: 2.1.0(b755ca744ca121980f4cb7903c2754d2) + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) + vite-hot-client: 2.1.0(1ae42ea40c273aad7303991cb84a8103) - vite-hot-client@2.1.0(b755ca744ca121980f4cb7903c2754d2): + vite-ember-ssr@0.1.0(patch_hash=f4ed313947273cab8416b3974a50ccfc3d9427eb6c90ad8e3725e51e6a68341f)(1ae42ea40c273aad7303991cb84a8103): dependencies: - vite: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + happy-dom: 20.8.9 + tinypool: 2.1.0 + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) + transitivePeerDependencies: + - bufferutil + - utf-8-validate - vite-plugin-inspect@11.3.3(b755ca744ca121980f4cb7903c2754d2): + vite-hot-client@2.1.0(1ae42ea40c273aad7303991cb84a8103): + dependencies: + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) + + vite-plugin-inspect@11.3.3(1ae42ea40c273aad7303991cb84a8103): dependencies: ansis: 4.2.0 debug: 4.4.3 @@ -19015,16 +19544,16 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 8.0.14(050a6ec760bdf6c8475789235dc875ef) - vite-dev-rpc: 1.1.0(b755ca744ca121980f4cb7903c2754d2) + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) + vite-dev-rpc: 1.1.0(1ae42ea40c273aad7303991cb84a8103) transitivePeerDependencies: - supports-color - vite-plugin-wasm@3.5.0(b755ca744ca121980f4cb7903c2754d2): + vite-plugin-wasm@3.5.0(1ae42ea40c273aad7303991cb84a8103): dependencies: - vite: 8.0.14(050a6ec760bdf6c8475789235dc875ef) + vite: 8.0.14(8c0d5e89015afe9a2a4c3da41463a131) - vite@7.3.3(c46cd77709f114f3781a3e26c325c401): + vite@7.3.1(ae46566fde63be75904a2137faf7346d): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -19033,14 +19562,14 @@ snapshots: rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.32.0 - terser: 5.46.0 + terser: 5.48.0 yaml: 2.8.2 - vite@8.0.14(050a6ec760bdf6c8475789235dc875ef): + vite@8.0.14(8c0d5e89015afe9a2a4c3da41463a131): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -19048,17 +19577,17 @@ snapshots: rolldown: 1.0.2 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 esbuild: 0.27.2 fsevents: 2.3.3 jiti: 2.6.1 - terser: 5.46.0 + terser: 5.48.0 yaml: 2.8.2 - vitest@4.0.18(31650c18aea9d2e0cdac92197f228980): + vitest@4.0.18(9a336c60f0d483703ff47422c636b3ce): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(860304eea6b56489beb687487b25659c) + '@vitest/mocker': 4.0.18(d4eb820b869314410368ca3ef0a4963d) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -19075,10 +19604,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.3(c46cd77709f114f3781a3e26c325c401) + vite: 7.3.1(ae46566fde63be75904a2137faf7346d) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.2.0 + '@types/node': 25.9.1 + happy-dom: 20.8.9 jsdom: 26.1.0 transitivePeerDependencies: - jiti @@ -19222,6 +19752,8 @@ snapshots: whatwg-fetch@3.6.20: {} + whatwg-mimetype@3.0.0: {} + whatwg-mimetype@4.0.0: {} whatwg-url@14.2.0: @@ -19348,12 +19880,14 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@7.5.10: {} + ws@7.5.11: {} ws@8.18.3: {} ws@8.19.0: {} + ws@8.21.0: {} + wsl-utils@0.1.0: dependencies: is-wsl: 3.1.0 diff --git a/src/browser/index.ts b/src/browser/index.ts index cf1e80b3..347a1c6c 100644 --- a/src/browser/index.ts +++ b/src/browser/index.ts @@ -12,9 +12,10 @@ export { // Required to use Kolay export { addRoutes, handlePotentialIndexVisit } from './router.ts'; export { typedocLoader } from './services/api-docs.ts'; +export { prewarmTypedocCache } from './typedoc/utils.gts'; export { Compiled } from './services/compiler/reactive.ts'; export { docsManager } from './services/docs.ts'; -export { selected } from './services/selected.ts'; +export { prefetchPage, prewarmTypedocCaches, selected } from './services/selected.ts'; // Utilities export { getIndexPage, isCollection, isIndex } from './utils.ts'; diff --git a/src/browser/services/selected.ts b/src/browser/services/selected.ts index 20322873..bdbeddfa 100644 --- a/src/browser/services/selected.ts +++ b/src/browser/services/selected.ts @@ -27,6 +27,143 @@ export function selected(context: unknown) { type File = { default: string | ComponentLike }; type Loader = () => Promise; +/** + * Normalize a page path into the form used as keys in `kolay/compiled-docs:virtual` + * and the manifest: no trailing slash (except for the root `/`), no `.md` suffix. + * + * Without this, an SSG'd URL like `/usage/setup/` (with the trailing slash that + * static-file servers add when serving `/usage/setup/index.html`) misses the + * `/usage/setup` manifest entry, causing Selected to error and stomp the + * server-rendered DOM during rehydration. + * + * @param path A URL pathname like `/usage/setup/`, `/usage/setup.md`, or `/usage/setup` + */ +function normalizePagePath(path: string): string { + let normalized = path.replace(/\.md$/, ''); + + if (normalized.length > 1 && normalized.endsWith('/')) { + normalized = normalized.slice(0, -1); + } + + return normalized; +} + +/** + * Resolved page modules, keyed by URL path. Module-scoped so it survives + * Selected instance recreation (route transitions, SSR rehydration, etc.). + * + * Why this exists: `loaderFor` previously passed a fresh `wrapper` closure to + * `getPromiseState` on every call. `getPromiseState` caches state by function + * reference, so a new wrapper always started in `isLoading: true` for one + * microtask — long enough for Glimmer to render the `<:pending>` branch on the + * client even though the SSR'd DOM had the resolved `<:success>` content, + * causing a flash of stale content during rehydration. + * + * With this cache, the second-and-onward access for any path resolves + * synchronously. Combined with `prefetchPage` called from the client entry + * before boot, the first client render of an SSR'd page matches the DOM with + * no mismatch. + */ +const moduleCache = new Map(); + +/** + * Eagerly load a page module so the next read of `Selected.prose` returns + * synchronously. Call from your client entry before booting Ember, against the + * current URL, to eliminate the flash-of-pending-state during SSR rehydration. + * + * @example + * ```ts + * import { prefetchPage } from 'kolay'; + * await prefetchPage(window.location.pathname); + * Application.create(config.APP); + * ``` + */ +export async function prefetchPage(path: string): Promise { + const trimmed = normalizePagePath(path); + + if (moduleCache.has(trimmed)) return; + + // The virtual module is provided by kolay's vite/webpack plugin in the + // consumer's build. We let the bundler rewrite this specifier at build + // time — without that, the browser sees the raw `kolay/compiled-docs:virtual` + // specifier at runtime and bails with "Failed to resolve module specifier", + // moduleCache stays empty, and loaderFor falls through to the async + // getPromiseState path, which is the rehydration FOUC the prefetch was + // supposed to prevent in the first place. + const mod = (await import('kolay/compiled-docs:virtual')) as { + pages: Record; + }; + const fn = mod.pages[trimmed] ?? mod.pages[trimmed + '.md']; + + if (!fn) return; + + const file = await fn(); + + // `.md` files need runtime markdown compilation through ember-repl which + // requires an owner; skip prewarming those and let Selected's async path + // handle them on first render. + if (typeof file.default !== 'string') { + moduleCache.set(trimmed, file.default); + } +} + +/** + * Pre-load + deserialize the typedoc JSON for every configured + * `apiDocs({ packages: [...] })` entry, so the first `` / + * `` render on rehydration is synchronous and matches the SSG'd + * `
`. + * + * Without this prewarm, every `` first-renders the + * "Loading api docs..." branch (because the fetch starts in + * `isLoading: true`), unmounting the SSG-rendered typedoc declarations + * for ~the duration of the fetch. With it, `` reads from the + * project cache synchronously. + * + * Safe to call before `Application.create`: it only needs `fetch` to + * be available, not an Ember owner. + * + * @example + * ```ts + * import { prefetchPage, prewarmTypedocCaches } from 'kolay'; + * + * if (shouldRehydrate()) { + * await Promise.all([ + * prefetchPage(window.location.pathname), + * prewarmTypedocCaches(), + * ]); + * } + * ``` + */ +export async function prewarmTypedocCaches(): Promise { + let mod: { + packageNames?: string[]; + loadApiDocs?: Record Promise>; + }; + + try { + mod = (await import('kolay/api-docs:virtual')) as typeof mod; + } catch { + return; + } + + const names = mod.packageNames ?? []; + const loaders = mod.loadApiDocs ?? {}; + + if (names.length === 0) return; + + const { prewarmTypedocCache } = await import('../typedoc/utils.gts'); + + await Promise.all( + names.map((name) => { + const loader = loaders[name]; + + if (!loader) return; + + return prewarmTypedocCache(name, loader); + }) + ); +} + /** * With .gjs.md and .gts.md documents, we have only one promise to deal with. * With .md documents, we have two promises. @@ -38,8 +175,10 @@ type Loader = () => Promise; * 1. the request to get the module * 2. compile */ -function loaderFor(selected: Selected, path: string | undefined) { - if (!path) return; +function loaderFor(selected: Selected, maybePath: string | undefined) { + if (!maybePath) return; + + const path: string = maybePath; const docs = selected.compiledDocs; const owner = getOwner(selected); @@ -49,20 +188,36 @@ function loaderFor(selected: Selected, path: string | undefined) { */ const fn = docs[path] ?? docs[path + '.md']; + // Synchronous fast path: when the module is already cached (mid-session + // re-navigation, or pre-warmed via prefetchPage before rehydration), return + // a resolved state immediately. This avoids the microtask hop through + // getPromiseState's pending state that causes Glimmer to render `<:pending>` + // before flipping to `<:success>` — the source of the rehydration flash. + const cached = moduleCache.get(path); + + if (cached) { + return { isLoading: false, error: null, resolved: cached }; + } + async function wrapper(): Promise { if (!fn) return; assert(`[Bug] Owner is missing`, owner); const module = await fn(); + let resolved: ComponentLike | undefined; if (typeof module.default === 'string') { const state = compileText(owner, module.default); - return state.promise; + resolved = (await state.promise) as ComponentLike | undefined; + } else { + resolved = module.default; } - return module.default; + if (resolved) moduleCache.set(path, resolved); + + return resolved; } const wrapped = getPromiseState(wrapper); @@ -154,7 +309,7 @@ class Selected { return; } - return path?.replace(/\.md$/, ''); + return normalizePagePath(path); } get #matchOrFirstPagePath() { diff --git a/src/browser/typedoc/utils.gts b/src/browser/typedoc/utils.gts index 0e95cfaf..cc7a1ae3 100644 --- a/src/browser/typedoc/utils.gts +++ b/src/browser/typedoc/utils.gts @@ -50,7 +50,71 @@ export const Query: TOC<{ {{/let}} ; -const cache = new Map Promise>(); +const loaderCache = new Map Promise>(); + +/** + * Resolved typedoc projects keyed by package name. Mirrors the moduleCache + * pattern in `services/selected.ts`: once a project is loaded + deserialized + * we hold onto it so subsequent `` invocations (and rehydration of any + * `` block) skip both the `fetch` and the deserialize step entirely. + * + * Without this, every `` on rehydration starts with `request.isLoading + * = true` and the SSG-rendered `
` gets unmounted for the duration + * of the fetch — a visible FOUC of about a second on pages that render typedoc. + * + * Call {@link prewarmTypedocCache} from your client entry, before booting + * Ember, to populate this for the packages a page references. + */ +const projectCache = new Map(); + +/** + * Pre-load and deserialize the typedoc JSON for a given package so the + * `` / `` first render on rehydration is synchronous. + * + * Intended to be called from a client entry like: + * + * ```ts + * import { prewarmTypedocCache } from 'kolay'; + * await Promise.all([ + * prefetchPage(window.location.pathname), + * prewarmTypedocCache('kolay'), + * ]); + * Application.create(config.APP); + * ``` + * + * Safe to call repeatedly: subsequent calls for the same package return the + * cached project synchronously. + * + * @param pkg The package name configured in `apiDocs({ packages: [...] })` + * @param loader A fetcher that returns the typedoc JSON Response. In most + * apps you'll plumb this from `typedocLoader(owner).load`; the helper is + * factored out so it can also be driven from a context that doesn't yet + * have an Ember owner (e.g. client entry before `Application.create`). + */ +export async function prewarmTypedocCache( + pkg: string, + loader: (name: string) => Promise +): Promise { + if (projectCache.has(pkg)) return projectCache.get(pkg); + + try { + const req = await loader(pkg); + const json = await req.json(); + + const logger = new ConsoleLogger(); + const deserializer = new Deserializer(logger); + const project = deserializer.reviveProject('API Docs', json, { + projectRoot: '/', + registry: new FileRegistry(), + }); + + projectCache.set(pkg, project); + + return project; + } catch { + return; + } +} export class Load extends Component<{ Args: { @@ -64,7 +128,30 @@ export class Load extends Component<{ return typedocLoader(this); } - get request() { + /** + * Either a synchronous { resolved } state (when the project was already + * deserialized — common on rehydration thanks to `prewarmTypedocCache`) + * or a reactive promise state that hydrates a fresh fetch. + * + * The branch is important for rehydration: a synchronous resolved state + * lets `
` render on the very first Glimmer pass, matching the + * SSG-emitted DOM. Going through `getPromiseState` always starts in + * `isLoading: true` for at least one microtask, which is enough for + * Glimmer to render the "Loading api docs..." branch instead and unmount + * everything underneath. + */ + get request(): + | { isLoading: boolean; error: unknown; resolved: ProjectReflection | undefined } { + const { package: pkg } = this.args; + + if (pkg) { + const cached = projectCache.get(pkg); + + if (cached) { + return { isLoading: false, error: null, resolved: cached }; + } + } + return getPromiseState(this.#createProject); } @@ -80,13 +167,17 @@ export class Load extends Component<{ throw new Error(`A @package must be specified to load.`); } - const seen = cache.get(pkg); + const seen = loaderCache.get(pkg); if (seen) { return seen; } const loadNew = async (): Promise => { + const cachedProject = projectCache.get(pkg); + + if (cachedProject) return cachedProject; + const req = await this.#apiDocs.load(pkg); const json = await req.json(); @@ -97,10 +188,12 @@ export class Load extends Component<{ registry: new FileRegistry(), }); + projectCache.set(pkg, project); + return project; }; - cache.set(pkg, loadNew); + loaderCache.set(pkg, loadNew); return loadNew; } diff --git a/src/build/plugins/gjs-md.js b/src/build/plugins/gjs-md.js index 3127b68b..bb4d02cd 100644 --- a/src/build/plugins/gjs-md.js +++ b/src/build/plugins/gjs-md.js @@ -1,70 +1,37 @@ -import assert from 'node:assert'; import { readFile } from 'node:fs/promises'; import * as babel from '@babel/core'; import { Preprocessor } from 'content-tag'; import { buildCompiler, parseMarkdown } from 'repl-sdk/markdown/parse'; -import { visit } from 'unist-util-visit'; +import { + mergeImports, + replacePlaceholder, + splitModule, + wrapAsConst, +} from 'repl-sdk/render-to-string'; import { extFilter } from './utils.js'; const processor = new Preprocessor(); -function componentNameFromId(id) { - return id - .split(/[^A-Za-z0-9_]/g) - .filter(Boolean) - .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) - .join('') - .toLowerCase(); -} - -function rehypeInjectComponentInvocation() { - return (tree, file) => { - const liveCode = /** @type {unknown[]} */ (file?.data?.liveCode ?? []); - - if (!Array.isArray(liveCode) || liveCode.length === 0) return; - - const componentNamesById = new Map(); - - for (const block of liveCode) { - const demoId = block?.id ?? block?.placeholderId; - - if (!demoId || typeof demoId !== 'string') continue; - - const componentName = block?.componentName ?? componentNameFromId(demoId); - - componentNamesById.set(demoId, componentName); - } - - if (componentNamesById.size === 0) return; - - visit(tree, 'raw', (node) => { - if (node.tagName === 'code') return 'skip'; - if (node.type !== 'raw') return; - - const id = node.value?.match(/id="([^"]+)"/)?.[1]; - - if (!id || typeof id !== 'string') return; - - const componentName = componentNamesById.get(id); - - if (!componentName) return; - - node.value = node.value.replace(``, `<${componentName} />`); - }); - }; +/** + * Turn a placeholder id like `repl_1` into a Glimmer-invokable PascalCase + * tag name. We keep the per-document numbering stable so debugging is easier. + * + * @param {string} id + * @param {number} nth + */ +function demoName(id, nth) { + return `Demo${nth}_${id.replace(/[^A-Za-z0-9_]/g, '_')}`; } /** - * @porom {Options} options + * @param {Options} options */ export function createCompiler(options) { - const rehypePlugins = [...(options.rehypePlugins ?? []), rehypeInjectComponentInvocation]; - - const compiler = buildCompiler({ + return buildCompiler({ remarkPlugins: options.remarkPlugins, - rehypePlugins, + rehypePlugins: options.rehypePlugins, isLive: (meta) => meta?.includes('live'), isPreview: (meta) => meta?.includes('preview'), isBelow: (meta) => meta.includes('below'), @@ -72,60 +39,127 @@ export function createCompiler(options) { ALLOWED_FORMATS: ['gjs', 'hbs'], getFlavorFromMeta: () => null, }); - - return compiler; } /** - * @param {string} input - * @param {{ compiler: unknown; virtualModulesByMarkdownFile: unknown; id: string; scope?: string }} options - * @return {Promise<{ code: string, map: unknown }>} + * Transform a `.gjs.md` document into a single `.gjs` module that inlines + * every live code fence as a Glimmer component sibling of the prose. + * + * Pipeline: + * + * 1. `parseMarkdown` (from repl-sdk) returns the HTML body + an array of + * `liveCode` blocks, with `
` holes + * where each demo should land. + * 2. For every live block we preprocess its code with `content-tag` so + * `