diff --git a/knip.jsonc b/knip.jsonc index 4c66d4e7..c6d1d6f3 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -156,6 +156,24 @@ "src/{client,node,rpc,inject,engine,registry}/index.ts" ] }, + "plugins/inspect": { + // The lockstep assets package is referenced only as a runtime string + // (`${pkg.name}--assets` in `cli.distDir`), never imported, so knip + // can't see the dev-only workspace link that makes it resolvable in + // the monorepo. Repeat the `plugins/*` entry glob (a workspace config + // replaces, not merges, it). + "entry": [ + "src/{index,cli,vite,constants,types}.ts", + "src/{client,node,rpc,inject,engine,registry}/index.ts" + ], + "ignoreDependencies": ["@devframes/plugin-inspect--assets"] + }, + "plugins/inspect/assets-pkg": { + // Assets-only package: no source, just a prebuilt `dist` produced by + // the sibling plugin's Vite build. + "entry": [], + "project": [] + }, "plugins/a11y": { // `storybook-solidjs-vite` (not an official `@storybook/*` framework // package) doesn't match knip's Storybook plugin trigger, so it never diff --git a/plugins/inspect/assets-pkg/package.json b/plugins/inspect/assets-pkg/package.json new file mode 100644 index 00000000..e66f551a --- /dev/null +++ b/plugins/inspect/assets-pkg/package.json @@ -0,0 +1,30 @@ +{ + "name": "@devframes/plugin-inspect--assets", + "type": "module", + "version": "0.9.0-beta.4", + "description": "Prebuilt browser assets (SPA) for @devframes/plugin-inspect, served on demand through devframe's remote-assets back-proxy.", + "author": "Anthony Fu ", + "license": "MIT", + "homepage": "https://github.com/devframes/devframe#readme", + "repository": { + "directory": "plugins/inspect/assets-pkg", + "type": "git", + "url": "git+https://github.com/devframes/devframe.git" + }, + "bugs": "https://github.com/devframes/devframe/issues", + "keywords": [ + "devframe", + "devframe-plugin", + "devtools", + "client-assets" + ], + "exports": { + "./package.json": "./package.json" + }, + "files": [ + "dist" + ], + "scripts": { + "prepack": "pnpm --filter @devframes/plugin-inspect run build" + } +} diff --git a/plugins/inspect/package.json b/plugins/inspect/package.json index 89075325..9269a4d3 100644 --- a/plugins/inspect/package.json +++ b/plugins/inspect/package.json @@ -63,6 +63,7 @@ "devDependencies": { "@antfu/design": "catalog:frontend", "@devframes/hub": "workspace:*", + "@devframes/plugin-inspect--assets": "workspace:*", "@iconify-json/ph": "catalog:frontend", "@standard-schema/spec": "catalog:deps", "@storybook/addon-docs": "catalog:storybook", diff --git a/plugins/inspect/src/index.ts b/plugins/inspect/src/index.ts index 78553b7c..c8afcda1 100644 --- a/plugins/inspect/src/index.ts +++ b/plugins/inspect/src/index.ts @@ -1,6 +1,4 @@ -import type { DevframeDefinition } from 'devframe' -import { existsSync } from 'node:fs' -import { fileURLToPath } from 'node:url' +import type { DevframeDefinition, RemoteAssets } from 'devframe' import { defineDevframe } from 'devframe' import pkg from '../package.json' with { type: 'json' } import { setupInspect } from './node/index' @@ -8,10 +6,16 @@ import { setupInspect } from './node/index' /** Default devframe id — drives the hosted mount path `/__/`. */ const DEFAULT_ID = 'devframes_plugin_inspect' -// The Vue SPA is built (by Vite) into `dist/spa`. From both the source -// entry (`src/index.ts`, via the workspace alias) and the published -// entry (`dist/index.mjs`), `../dist/spa` resolves to `/dist/spa`. -const distDir = fileURLToPath(new URL('../dist/spa', import.meta.url)) +// The Vue SPA ships in the lockstep-versioned `@devframes/plugin-inspect--assets` +// package rather than inside this (slim) node package. `resolveFrom` lets a +// locally installed copy (a workspace link in this monorepo, or an explicit +// `npm install` for air-gapped setups) be served with zero network; otherwise +// the assets stream on demand through devframe's caching CDN back-proxy. +const distDir: RemoteAssets = { + package: `${pkg.name}--assets`, + version: pkg.version, + resolveFrom: import.meta.url, +} export interface InspectDevframeOptions { /** Override the devframe id (and default CLI command / mount path). */ @@ -58,7 +62,7 @@ export function createInspectDevframe(options: InspectDevframeOptions = {}): Dev cli: { command: id, port: options.port ?? 9012, - distDir: existsSync(distDir) ? distDir : undefined, + distDir, // Gate the standalone server by default; `maybeOpenBrowser` folds the // current OTP into the `--open` URL so the tab lands already trusted. // Hosted adapters (Vite/hub) supply their own auth layer and ignore this. diff --git a/plugins/inspect/src/spa/vite.config.ts b/plugins/inspect/src/spa/vite.config.ts index d83059b1..061f32ca 100644 --- a/plugins/inspect/src/spa/vite.config.ts +++ b/plugins/inspect/src/spa/vite.config.ts @@ -22,7 +22,10 @@ export default defineConfig({ // SFCs instead of esbuild pre-bundling them. optimizeDeps: { exclude: ['@antfu/design'] }, build: { - outDir: fileURLToPath(new URL('../../dist/spa', import.meta.url)), + // Emit into the sibling `@devframes/plugin-inspect--assets` package, which + // ships these assets to npm; the node package stays slim and serves them + // on demand through devframe's remote-assets back-proxy. + outDir: fileURLToPath(new URL('../../assets-pkg/dist', import.meta.url)), emptyOutDir: true, }, }) diff --git a/plugins/inspect/test/_utils.ts b/plugins/inspect/test/_utils.ts index 04f01cd9..db5cbd3d 100644 --- a/plugins/inspect/test/_utils.ts +++ b/plugins/inspect/test/_utils.ts @@ -2,6 +2,7 @@ import type { DevframeHubContext } from '@devframes/hub/node' import type { DevframeNodeContext } from 'devframe' import type { StartedServer } from 'devframe/internal' import { existsSync } from 'node:fs' +import os from 'node:os' import path from 'node:path' import process from 'node:process' import { createHubContext } from '@devframes/hub/node' @@ -10,22 +11,38 @@ import { DEVFRAME_CONNECTION_META_FILENAME } from 'devframe/constants' import { createH3DevframeHost } from 'devframe/internal' import { createHostContext } from 'devframe/node' import { resolveBasePath } from 'devframe/node/hub-internals' +import { resolveStaticAssetsSource } from 'devframe/utils/remote-assets' import { mountStaticHandler } from 'devframe/utils/serve-static' import { getPort } from 'get-port-please' import { H3 } from 'h3' import { serveTestContext } from '../../../tests/helpers/serve-test-context' -const SPA_DIST = inspectDevframe.cli!.distDir! +/** + * Resolve the inspector's SPA to a local directory. Its `distDir` is a + * remote-assets declaration; in this monorepo the lockstep + * `@devframes/plugin-inspect--assets` package is workspace-linked, so + * resolution short-circuits to its built `dist`. A store (rather than a + * string) means that build hasn't run. + */ +function localSpaDir(): string { + const resolved = resolveStaticAssetsSource(inspectDevframe.cli!.distDir!, path.join(os.tmpdir(), 'devframes_plugin_inspect-test')) + if (typeof resolved !== 'string') { + throw new TypeError( + '[devframes_plugin_inspect] client SPA missing — run `pnpm -C plugins/inspect run build` first.', + ) + } + return resolved +} /** * Assert the Vue SPA has been built. The dev-server and static-build - * tests mount / copy `dist/spa`; a missing build produces a loud, fixable - * failure rather than an opaque 404. + * tests mount / copy the client SPA; a missing build produces a loud, + * fixable failure rather than an opaque 404. */ export function assertSpaBuilt(): void { - if (!existsSync(path.join(SPA_DIST, 'index.html'))) { + if (!existsSync(path.join(localSpaDir(), 'index.html'))) { throw new Error( - '[devframes_plugin_inspect] dist/spa missing — run `pnpm -C plugins/inspect run build` first.', + '[devframes_plugin_inspect] client SPA missing — run `pnpm -C plugins/inspect run build` first.', ) } } @@ -51,7 +68,7 @@ interface BootOptions { * context exercises the no-hub path (empty list, thrown diagnostic). */ async function boot(options: BootOptions): Promise { - const distDir = inspectDevframe.cli!.distDir! + const distDir = localSpaDir() const basePath = resolveBasePath(inspectDevframe, 'standalone') const host = '127.0.0.1' const port = await getPort({ host, random: true }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cdba12c..0d4e6586 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1883,6 +1883,9 @@ importers: '@devframes/hub': specifier: workspace:* version: link:../../packages/hub + '@devframes/plugin-inspect--assets': + specifier: workspace:* + version: link:assets-pkg '@iconify-json/ph': specifier: catalog:frontend version: 1.2.2 @@ -1941,6 +1944,8 @@ importers: specifier: catalog:deps version: 8.21.3 + plugins/inspect/assets-pkg: {} + plugins/messages: dependencies: '@devframes/vite': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0c3e3496..92b92c6f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -26,6 +26,7 @@ trustPolicyExclude: packages: - packages/* - plugins/* + - plugins/*/assets-pkg - examples/* - storybook - docs diff --git a/turbo.json b/turbo.json index aac4ed6b..908e87f9 100644 --- a/turbo.json +++ b/turbo.json @@ -65,7 +65,7 @@ "@devframes/plugin-inspect#build": { "outputLogs": "new-only", "dependsOn": ["devframe#build", "@devframes/vite#build"], - "outputs": ["dist/**"] + "outputs": ["dist/**", "assets-pkg/dist/**"] }, "@devframes/plugin-og#build": { "outputLogs": "new-only",