@forinda/kickjs-vite 5.2.1 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +86 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +0 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.d.mts
CHANGED
|
@@ -175,6 +175,91 @@ interface DevtoolsFlagOptions {
|
|
|
175
175
|
declare function resolveDevtoolsFlag(opts?: DevtoolsFlagOptions, env?: ConfigEnv): boolean;
|
|
176
176
|
declare function devtoolsFlagPlugin(opts?: DevtoolsFlagOptions): Plugin;
|
|
177
177
|
//#endregion
|
|
178
|
+
//#region src/devtools-strip-plugin.d.ts
|
|
179
|
+
interface DevtoolsStripOptions {
|
|
180
|
+
/**
|
|
181
|
+
* Force enable / disable. Default: enabled when `command === 'build'`.
|
|
182
|
+
* Adopters running a debug-prod build (`kick build` with the flag
|
|
183
|
+
* forced on) can pass `false` to keep devtools-kit code in the
|
|
184
|
+
* bundle.
|
|
185
|
+
*/
|
|
186
|
+
enabled?: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Glob-like include pattern. Default: any `.ts` / `.tsx` /
|
|
189
|
+
* `.mts` / `.cts` file under the project root. The transform is
|
|
190
|
+
* a no-op on files that don't import devtools-kit, so this is
|
|
191
|
+
* normally fine to leave at the default.
|
|
192
|
+
*/
|
|
193
|
+
include?: RegExp;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Strips devtools-kit imports + their top-level call sites from
|
|
197
|
+
* production bundles. See `babel-strip-devtools.ts` for the exact
|
|
198
|
+
* rule set.
|
|
199
|
+
*/
|
|
200
|
+
declare function devtoolsStripPlugin(opts?: DevtoolsStripOptions): Plugin;
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/babel-strip-devtools.d.ts
|
|
203
|
+
/**
|
|
204
|
+
* Babel-based devtools stripper for production builds.
|
|
205
|
+
*
|
|
206
|
+
* Built around a single rule: anything sourced from
|
|
207
|
+
* `@forinda/kickjs-devtools-kit` (or any of its sub-paths) is dev-
|
|
208
|
+
* only and must not ship in the prod bundle. The transform walks
|
|
209
|
+
* each module and removes:
|
|
210
|
+
*
|
|
211
|
+
* 1. `import ... from '@forinda/kickjs-devtools-kit'` declarations
|
|
212
|
+
* (named, default, namespace, side-effect — all forms).
|
|
213
|
+
* 2. Top-level `ExpressionStatement`s whose call/expression root is
|
|
214
|
+
* a binding imported from devtools-kit
|
|
215
|
+
* (e.g., `defineDevtoolsRenderTab({...})`).
|
|
216
|
+
* 3. Side-effect imports whose path ends in `/devtools-events`
|
|
217
|
+
* (with any extension) — type-augmentation modules shipped by
|
|
218
|
+
* adapter packages. Already side-effect-only, safe to drop in
|
|
219
|
+
* prod.
|
|
220
|
+
*
|
|
221
|
+
* The transform is intentionally conservative. It will not:
|
|
222
|
+
*
|
|
223
|
+
* - Remove identifier *references* outside of the rules above. If
|
|
224
|
+
* your code calls `defineDevtoolsRenderTab(...)` inside a regular
|
|
225
|
+
* function body (i.e. not a top-level `ExpressionStatement`),
|
|
226
|
+
* the reference stays. After we drop the import the build will
|
|
227
|
+
* fail loud — that is the signal to gate the call behind
|
|
228
|
+
* `__KICKJS_DEVTOOLS__` (see `devtools-flag-plugin.ts`).
|
|
229
|
+
* - Touch files that don't import from devtools-kit at all.
|
|
230
|
+
* - Touch files in `node_modules` (Vite's plugin chain handles
|
|
231
|
+
* that already, but the transform short-circuits on a quick
|
|
232
|
+
* string check too).
|
|
233
|
+
*
|
|
234
|
+
* The dev path is unchanged: this transform only runs when Vite's
|
|
235
|
+
* `command === 'build'`. In dev, devtools-kit imports stay live.
|
|
236
|
+
*
|
|
237
|
+
* Spec: docs/db/m3-plan.md §M3.C.
|
|
238
|
+
*/
|
|
239
|
+
interface StripDevtoolsOptions {
|
|
240
|
+
/**
|
|
241
|
+
* When `false`, skips files that don't import devtools-kit. The
|
|
242
|
+
* default short-circuit is a substring check on the source text;
|
|
243
|
+
* disable it only for tests where you want the visitor to run
|
|
244
|
+
* unconditionally.
|
|
245
|
+
*
|
|
246
|
+
* @default true
|
|
247
|
+
*/
|
|
248
|
+
fastReject?: boolean;
|
|
249
|
+
}
|
|
250
|
+
interface StripResult {
|
|
251
|
+
/** Transformed source. Returns the original `code` when nothing was stripped. */
|
|
252
|
+
code: string;
|
|
253
|
+
/** `true` if the visitor removed at least one node. */
|
|
254
|
+
changed: boolean;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Strip devtools-kit imports and their dependent top-level calls
|
|
258
|
+
* from a single TypeScript module. Pure — no I/O, no Vite, no
|
|
259
|
+
* filesystem.
|
|
260
|
+
*/
|
|
261
|
+
declare function stripDevtoolsCode(source: string, filename: string, opts?: StripDevtoolsOptions): StripResult;
|
|
262
|
+
//#endregion
|
|
178
263
|
//#region src/index.d.ts
|
|
179
264
|
/**
|
|
180
265
|
* Create the KickJS Vite plugin array.
|
|
@@ -204,5 +289,5 @@ declare function devtoolsFlagPlugin(opts?: DevtoolsFlagOptions): Plugin;
|
|
|
204
289
|
*/
|
|
205
290
|
declare function kickjsVitePlugin(options?: KickJSPluginOptions): Plugin[];
|
|
206
291
|
//#endregion
|
|
207
|
-
export { type DevtoolsFlagOptions, type DevtoolsOptions, type HmrInvalidationContext, type HmrOptions, type KickJSPluginOptions, type PluginContext, devtoolsFlagPlugin, envWatchPlugin, kickjsVitePlugin, resolveDevtoolsFlag };
|
|
292
|
+
export { type DevtoolsFlagOptions, type DevtoolsOptions, type DevtoolsStripOptions, type HmrInvalidationContext, type HmrOptions, type KickJSPluginOptions, type PluginContext, type StripDevtoolsOptions, type StripResult, devtoolsFlagPlugin, devtoolsStripPlugin, envWatchPlugin, kickjsVitePlugin, resolveDevtoolsFlag, stripDevtoolsCode };
|
|
208
293
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/env-watch-plugin.ts","../src/devtools-flag-plugin.ts","../src/index.ts"],"mappings":";;;;;;;AAuBA;;;;;;;;;;;AA2CA;;;;;AAiBA;;;UA5DiB,mBAAA;EAgEN;AAWX;;;;;;;;;;AA+BA;;EA5FE,KAAA;EA8FA;EA3FA,GAAA,GAAM,UAAA;;;;ACjBR;;;;;;;;ACQA;;;;;EF2BE,QAAA,GAAW,eAAA;AAAA;;;;;;UAQI,eAAA;EEdkE;EFgBjF,OAAA;EEJc;EFMd,QAAA;AAAA;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/env-watch-plugin.ts","../src/devtools-flag-plugin.ts","../src/devtools-strip-plugin.ts","../src/babel-strip-devtools.ts","../src/index.ts"],"mappings":";;;;;;;AAuBA;;;;;;;;;;;AA2CA;;;;;AAiBA;;;UA5DiB,mBAAA;EAgEN;AAWX;;;;;;;;;;AA+BA;;EA5FE,KAAA;EA8FA;EA3FA,GAAA,GAAM,UAAA;;;;ACjBR;;;;;;;;ACQA;;;;;EF2BE,QAAA,GAAW,eAAA;AAAA;;;;;;UAQI,eAAA;EEdkE;EFgBjF,OAAA;EEJc;EFMd,QAAA;AAAA;;;;;;;;;;AGpDF;UHiEiB,sBAAA;;EAEf,MAAA;EG5DA;EH8DA,SAAA;AAAA;;;AG7CF;;;;;;UHwDiB,UAAA;EGxD2D;;;;;EH8D1E,MAAA;EI9DmC;;;;AAYrC;;;;;AAYA;;;;;;;EJwDE,cAAA,IAAkB,OAAA,EAAS,sBAAA;AAAA;;;;;UAOZ,aAAA;EKxBD;EL0Bd,KAAA;;EAEA,IAAA;AAAA;;;;;AA9GF;;;;;;;;;;;AA2CA;;;;;iBC3CgB,cAAA,CAAA,GAAkB,MAAA;;;UCQjB,mBAAA;;AFRjB;;;;;;EEgBE,OAAA;EFmBA;;;;AAQF;EErBE,QAAA;AAAA;;;AFsCF;;iBE/BgB,mBAAA,CAAoB,IAAA,GAAM,mBAAA,EAA0B,GAAA,GAAM,SAAA;AAAA,iBAY1D,kBAAA,CAAmB,IAAA,GAAM,mBAAA,GAA2B,MAAA;;;UC9CnD,oBAAA;;;;AHiEjB;;;EG1DE,OAAA;EH8DS;AAWX;;;;;EGlEE,OAAA,GAAU,MAAA;AAAA;;;;AHiGZ;;iBGvFgB,mBAAA,CAAoB,IAAA,GAAM,oBAAA,GAA4B,MAAA;;;;;;AHnBtE;;;;;;;;;;;AA2CA;;;;;AAiBA;;;;;AAeA;;;;;;;;;;AA+BA;;UIvFiB,oBAAA;EJyFf;;;;;AC5GF;;;EG4BE,UAAA;AAAA;AAAA,UAGe,WAAA;;EAEf,IAAA;EFzBe;EE2Bf,OAAA;AAAA;;;AFNF;;;iBEcgB,iBAAA,CACd,MAAA,UACA,QAAA,UACA,IAAA,GAAM,oBAAA,GACL,WAAA;;;;;;;;;;;ADpDH;;;;;;;;;AAwBA;;;;;;;;;iBE+DgB,gBAAA,CAAiB,OAAA,GAAS,mBAAA,GAA2B,MAAA"}
|
package/dist/index.mjs
CHANGED
|
Binary file
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/core-plugin.ts","../src/virtual-modules.ts","../src/dev-server.ts","../src/module-discovery.ts","../src/hmr-plugin.ts","../src/devtools-flag-plugin.ts","../src/env-watch-plugin.ts","../src/index.ts"],"sourcesContent":["/**\n * Core Vite plugin for KickJS — configures the Vite server for backend\n * framework use: custom app type, SSR environment, and Node.js target.\n *\n * This plugin runs first in the array and sets up the environment that\n * the other sub-plugins (dev-server, virtual-modules, hmr) depend on.\n *\n * @module @forinda/kickjs-vite/core-plugin\n */\n\nimport type { Plugin } from 'vite'\nimport type { PluginContext } from './types'\n\n/**\n * Creates the core configuration plugin.\n *\n * Responsibilities:\n * - Sets `appType: 'custom'` so Vite doesn't serve index.html\n * - Configures the SSR environment for Node.js backend code\n * - Prevents Vite from clearing the terminal (KickJS logs route tables on startup)\n * - Externalizes Node.js built-ins and framework packages from the bundle\n *\n * @param ctx - Shared plugin context (entry file, root directory)\n * @returns Vite plugin\n */\nexport function kickjsCorePlugin(ctx: PluginContext): Plugin {\n return {\n name: 'kickjs:core',\n\n /**\n * Vite config hook — runs before config is resolved.\n * Sets the foundational configuration for a backend Node.js framework.\n */\n config(userConfig, _env) {\n // Respect explicit server.port from user's vite.config.ts.\n // Only inject .env PORT when the user hasn't set one.\n const explicitPort = userConfig.server?.port\n const envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined\n const port = explicitPort ?? envPort ?? 3000\n\n return {\n server: {\n port,\n },\n\n // 'custom' tells Vite this is not a SPA or MPA — we handle all requests\n appType: 'custom' as const,\n\n // Don't clear the terminal — KickJS logs route tables on startup\n clearScreen: false,\n\n // SSR environment configuration for backend code\n environments: {\n ssr: {\n // Warm up the entry file for faster first request\n dev: {\n warmup: [ctx.entry],\n },\n },\n },\n\n // Disable dep optimization discovery — KickJS is SSR-only, not a client app\n optimizeDeps: {\n noDiscovery: true,\n },\n\n // SSR-specific settings\n ssr: {\n // Externalize Node.js built-ins and framework packages\n // These should NOT be bundled by Vite — they run natively in Node\n external: ['@forinda/kickjs', 'express', 'reflect-metadata'],\n },\n }\n },\n }\n}\n","/**\n * Virtual module plugin for KickJS — generates the `virtual:kickjs/app`\n * module that bridges Vite and the KickJS application.\n *\n * Virtual modules are Vite's mechanism for generating code at build/serve\n * time that doesn't exist on disk. When the dev server calls\n * `ssrLoadModule('virtual:kickjs/app')`, this plugin generates the code\n * that imports and re-exports the user's application entry.\n *\n * In the future, this will also support auto-discovery of `@Module` classes\n * (Step 4 in v3/plan.md) and container manifest generation.\n *\n * @see https://vite.dev/guide/api-plugin.html#virtual-modules-convention\n *\n * @module @forinda/kickjs-vite/virtual-modules\n */\n\nimport { normalizePath, type Plugin } from 'vite'\nimport type { PluginContext } from './types'\n\n/** Prefix for resolved virtual module IDs (Vite convention) */\nconst VIRTUAL_PREFIX = '\\0'\n\n/** Virtual module ID for the KickJS application entry */\nconst VIRTUAL_APP = 'virtual:kickjs/app'\n\n/** Resolved virtual module ID (with \\0 prefix to mark as virtual) */\nconst RESOLVED_APP = `${VIRTUAL_PREFIX}${VIRTUAL_APP}`\n\n/**\n * Creates the virtual modules plugin.\n *\n * Currently provides one virtual module:\n * - `virtual:kickjs/app` — re-exports the user's application entry\n *\n * The dev server plugin imports this on every request via `ssrLoadModule()`.\n * When any source file changes, Vite invalidates the module graph and the\n * next import returns fresh code — this is how server-side HMR works.\n *\n * Future virtual modules (planned for Steps 4-5):\n * - `virtual:kickjs/modules` — auto-discovered `@Module` classes\n * - `virtual:kickjs/manifest` — container metadata for DevTools\n *\n * @param ctx - Shared plugin context (entry file, root directory)\n * @returns Vite plugin\n */\nexport function kickjsVirtualModulesPlugin(ctx: PluginContext): Plugin {\n return {\n name: 'kickjs:virtual-modules',\n\n /**\n * Resolve virtual module IDs.\n * Tells Vite that `virtual:kickjs/app` is a valid module ID\n * and maps it to our internal resolved ID.\n */\n resolveId(id: string) {\n if (id === VIRTUAL_APP) {\n return RESOLVED_APP\n }\n },\n\n /**\n * Load virtual module content.\n * Generates the code for `virtual:kickjs/app` that imports the user's\n * entry file and re-exports the Express app.\n *\n * The generated code:\n * 1. Imports the user's entry file (which calls `bootstrap()`)\n * 2. Re-exports everything from it (including the `app` export)\n *\n * The dev server plugin then does:\n * `const { app } = await ssrLoadModule('virtual:kickjs/app')`\n * and uses `app.handle(req, res, next)` to route requests through Express.\n */\n load(id: string) {\n if (id !== RESOLVED_APP) return\n\n // Generate code that imports and re-exports the user's entry\n // The entry file should: export const app = bootstrap({ modules: [...] })\n const normalizedEntry = normalizePath(ctx.entry)\n return [\n `// Auto-generated by @forinda/kickjs-vite`,\n `// Imports the user's application entry and re-exports the Express app.`,\n `// Vite's module graph tracks this — file changes trigger re-evaluation.`,\n `export * from '${normalizedEntry}'`,\n ].join('\\n')\n },\n }\n}\n\nexport { VIRTUAL_APP, RESOLVED_APP }\n","/**\n * Dev server plugin for KickJS — mounts the Express application on Vite's\n * HTTP server using the `configureServer` hook.\n *\n * ## Architecture\n *\n * In development, Vite owns the HTTP port and creates the `http.Server`.\n * This plugin:\n *\n * 1. **Stores `viteServer.httpServer` on `globalThis`** so KickJS adapters\n * (WsAdapter, Socket.IO, GraphQL subscriptions) can attach to the real\n * server via `server.on('upgrade', ...)`. This is the key to supporting\n * any library that needs the raw `http.Server`.\n *\n * 2. **Registers a post-middleware** that loads the KickJS app through\n * Vite's SSR transform pipeline on every request. When source files\n * change, Vite invalidates the module graph and the next request\n * gets fresh code — no process restart needed.\n *\n * 3. **Stores the Vite server reference** on `globalThis` so SSR\n * renderers can use `createViteRuntime()`.\n *\n * ## Why NOT middlewareMode\n *\n * The initial KickJS Vite plugin attempt used `middlewareMode: true`, which\n * makes `viteServer.httpServer = null`. This broke WsAdapter and any library\n * needing `server.on('upgrade', ...)`. By letting Vite create the server,\n * `httpServer` is the real `http.Server` — adapters attach to it seamlessly.\n *\n * ## Request Flow\n *\n * ```\n * HTTP Request → Vite static middleware (HMR, assets)\n * → If Vite handles it → done\n * → If not → KickJS post-middleware\n * → ssrLoadModule('virtual:kickjs/app') → fresh Express app\n * → Express handles request (routes, middleware, controllers)\n * ```\n *\n * @see v3/architecture.md Section 2.6 for the httpServer piping design\n * @see bench-mark/react-router-analysis.md for the pattern origin\n *\n * @module @forinda/kickjs-vite/dev-server\n */\n\nimport type { Plugin, ViteDevServer } from 'vite'\nimport type { PluginContext } from './types'\nimport { VIRTUAL_APP } from './virtual-modules'\n\n/**\n * Declare the globalThis properties used for cross-module communication.\n * These persist across Vite's `ssrLoadModule()` re-evaluations.\n *\n * `__kickjs_httpServer` uses the same `HttpServer` type that Vite uses\n * internally (union of http.Server | https.Server | Http2SecureServer).\n * KickJS adapters receive it as `http.Server` via AdapterContext — the\n * cast is safe because Express only works with HTTP/1.1 servers.\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __kickjs_httpServer: any\n // eslint-disable-next-line no-var\n var __kickjs_viteServer: ViteDevServer | null\n}\n\n/**\n * Creates the dev server plugin.\n *\n * This is the critical integration point between Vite and KickJS:\n * - Pipes `viteServer.httpServer` to adapters via `globalThis`\n * - Loads fresh Express app on every request via `ssrLoadModule()`\n * - Fixes stack traces for SSR errors with `ssrFixStacktrace()`\n *\n * @param ctx - Shared plugin context (entry file, root directory)\n * @returns Vite plugin\n */\nexport function kickjsDevServerPlugin(_ctx: PluginContext): Plugin {\n return {\n name: 'kickjs:dev-server',\n\n /**\n * Configure the Vite dev server.\n *\n * The `configureServer` hook runs after Vite creates the HTTP server.\n * We use it to:\n * 1. Store the httpServer on globalThis (for WsAdapter, Socket.IO, etc.)\n * 2. Store the Vite server reference (for SSR rendering)\n * 3. Return a post-middleware that loads the KickJS app on each request\n *\n * Returning a function from `configureServer` registers it as a\n * **post-middleware** — it runs AFTER Vite's own middleware (static\n * files, HMR client, etc.), so Vite-handled requests never hit Express.\n */\n configureServer(viteServer: ViteDevServer) {\n // ━━━ Store the REAL http.Server on globalThis ━━━\n // This is the key to supporting Socket.IO, WsAdapter, GraphQL WS,\n // and any library that needs `server.on('upgrade', ...)`.\n //\n // Application.start() detects this and skips creating its own server:\n // if (globalThis.__kickjs_httpServer) → reuse Vite's server\n // else → create own http.Server (production mode)\n //\n // Adapters receive the server in afterStart({ server }) — same API\n // in both dev and prod. Zero adapter code changes needed.\n globalThis.__kickjs_httpServer = viteServer.httpServer ?? null\n globalThis.__kickjs_viteServer = viteServer\n\n // Return post-middleware — runs after Vite's static/HMR middleware\n return () => {\n viteServer.middlewares.use(async (req, res, next) => {\n try {\n // Load the KickJS app through Vite's SSR transform pipeline.\n // On first request: evaluates the entry file and all imports.\n // On subsequent requests: returns cached modules (or re-evaluates\n // if source files changed since last request).\n //\n // This is the same pattern React Router and TanStack Start use.\n const mod = await viteServer.ssrLoadModule(VIRTUAL_APP)\n\n // The entry file exports: export const app = bootstrap({ modules })\n // `app` is the configured Express instance (not yet listening).\n const expressApp = mod.app\n\n if (!expressApp?.handle) {\n // Entry file doesn't export an Express app — fall through to Vite's 404\n return next()\n }\n\n // Let Express handle the request.\n // If Express doesn't match any route, it calls next() and\n // Vite's default 404 handler takes over.\n expressApp.handle(req, res, (err?: any) => {\n if (err) {\n // Fix stack traces to point to original source (not compiled)\n if (err instanceof Error) {\n viteServer.ssrFixStacktrace(err)\n }\n return next(err)\n }\n next()\n })\n } catch (err) {\n // SSR load/evaluation error (syntax error, missing import, etc.)\n if (err instanceof Error) {\n viteServer.ssrFixStacktrace(err)\n }\n next(err)\n }\n })\n }\n },\n }\n}\n","/**\n * Module discovery plugin for KickJS — automatically detects `@Module` classes\n * by scanning source files during Vite's `transform()` hook.\n *\n * ## How It Works\n *\n * 1. Vite calls `transform(code, id)` for every file it processes\n * 2. This plugin checks if the file matches `*.module.ts` (or `.js`)\n * 3. If the file contains a class exported with `@Module` or extends `AppModule`,\n * it extracts the class name and records the file path\n * 4. The virtual module `virtual:kickjs/app` is invalidated so the next request\n * picks up the newly discovered module\n *\n * ## Why This Matters\n *\n * Without auto-discovery, users must manually maintain a barrel file\n * (`src/modules/index.ts`) that imports and re-exports all modules.\n * When `kick g module` creates a new module, it has to edit this barrel.\n * With auto-discovery, modules are detected automatically — no barrel needed.\n *\n * ## Interaction with HMR\n *\n * When a module file is added, renamed, or deleted:\n * - `handleHotUpdate()` detects the change\n * - The virtual module is invalidated\n * - The next `ssrLoadModule()` call regenerates the virtual module\n * with the updated module list\n *\n * @module @forinda/kickjs-vite/module-discovery\n */\n\nimport type { Plugin, ViteDevServer } from 'vite'\nimport type { PluginContext } from './types'\nimport { RESOLVED_APP } from './virtual-modules'\n\n/** Regex to detect exported module classes in source files */\nconst MODULE_CLASS_REGEX =\n /export\\s+(?:default\\s+)?class\\s+(\\w+(?:Module))\\s*(?:extends|implements|{)/\n\n/** File extensions to scan for modules */\nconst MODULE_FILE_PATTERN = /\\.module\\.[tj]sx?$/\n\n/**\n * Discovered module entry — maps a file path to its exported class name.\n *\n * @example\n * ```\n * {\n * filePath: '/src/modules/users/user.module.ts',\n * className: 'UserModule'\n * }\n * ```\n */\nexport interface DiscoveredModule {\n /** Absolute file path */\n filePath: string\n /** Exported class name (e.g., 'UserModule') */\n className: string\n}\n\n/**\n * Creates the module auto-discovery plugin.\n *\n * Scans `*.module.ts` files during Vite's transform phase to build a\n * registry of `@Module` classes. The registry is used by the virtual\n * modules plugin to generate auto-imports.\n *\n * @param ctx - Shared plugin context\n * @returns Vite plugin\n */\nexport function kickjsModuleDiscoveryPlugin(ctx: PluginContext): Plugin {\n /**\n * Registry of discovered modules.\n * Keyed by absolute file path to handle renames and deletions.\n * Stored on the plugin context so the virtual modules plugin can read it.\n */\n const discovered = new Map<string, DiscoveredModule>()\n let server: ViteDevServer | null = null\n\n // Expose discovered modules on the context for virtual-modules plugin\n ;(ctx as any).discoveredModules = discovered\n\n return {\n name: 'kickjs:module-discovery',\n\n configureServer(viteServer) {\n server = viteServer\n\n // Watch for new/deleted files in the modules directory\n viteServer.watcher.on('add', (filePath) => {\n if (MODULE_FILE_PATTERN.test(filePath)) {\n // File added — it will be processed in transform() when first imported.\n // Invalidate virtual module so it picks up the new file on next request.\n invalidateVirtualApp(viteServer)\n }\n })\n\n viteServer.watcher.on('unlink', (filePath) => {\n if (discovered.has(filePath)) {\n discovered.delete(filePath)\n invalidateVirtualApp(viteServer)\n }\n })\n },\n\n /**\n * Transform hook — called for every file Vite processes.\n *\n * We don't actually transform the code (return null), but we use this\n * hook to observe which files contain module classes. This is cheaper\n * than AST parsing — a regex match on the source is sufficient because\n * module files follow a consistent naming pattern (`*.module.ts`).\n */\n transform(code: string, id: string) {\n // Only scan files matching the module pattern\n if (!MODULE_FILE_PATTERN.test(id)) return null\n // Skip node_modules\n if (id.includes('node_modules')) return null\n\n const match = code.match(MODULE_CLASS_REGEX)\n if (match) {\n const className = match[1]\n const wasNew = !discovered.has(id)\n discovered.set(id, { filePath: id, className })\n\n if (wasNew && server) {\n // New module discovered — invalidate virtual module\n invalidateVirtualApp(server)\n }\n } else if (discovered.has(id)) {\n // File no longer exports a module class (user removed it)\n discovered.delete(id)\n if (server) {\n invalidateVirtualApp(server)\n }\n }\n\n // Return null — we don't transform the code, just observe it\n return null\n },\n\n /**\n * HMR hook — called when a file changes.\n *\n * If a module file changes, we re-scan it and invalidate the virtual\n * module so the next request picks up the changes.\n */\n handleHotUpdate({ file, server: viteServer }) {\n if (!MODULE_FILE_PATTERN.test(file)) return\n\n // The file will be re-processed in transform() on next import.\n // Invalidate the virtual module so it regenerates with fresh imports.\n invalidateVirtualApp(viteServer)\n },\n }\n}\n\n/**\n * Invalidate the virtual:kickjs/app module in Vite's module graph.\n * This forces the next `ssrLoadModule()` call to re-evaluate the\n * virtual module, picking up any new/removed modules.\n */\nfunction invalidateVirtualApp(server: ViteDevServer): void {\n const mod = server.moduleGraph.getModuleById(RESOLVED_APP)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n}\n\n/**\n * Get the current list of discovered modules.\n * Used by the virtual modules plugin to generate auto-imports.\n */\nexport function getDiscoveredModules(ctx: PluginContext): DiscoveredModule[] {\n const map = (ctx as any).discoveredModules as Map<string, DiscoveredModule> | undefined\n return map ? [...map.values()] : []\n}\n","/**\n * HMR plugin for KickJS — selective container invalidation when source\n * files change, with debounced batching for bulk operations.\n *\n * ## How It Works\n *\n * 1. During `transform()`, the plugin scans for decorator patterns\n * (`@Service`, `@Controller`, etc.) and maps file paths to DI token names\n * 2. When a file changes (`handleHotUpdate()`), the plugin:\n * - Looks up which DI tokens are defined in that file\n * - Calls `container.invalidate(token)` for each (which walks the dep graph)\n * - Invalidates the virtual module so `ssrLoadModule()` returns fresh code\n * - Sends a custom HMR event (`kickjs:hmr`) so DevTools/Swagger can react\n * 3. Changes are **debounced** (150ms) so `kick g module` creating 10+ files\n * emits ONE invalidation batch, not 10 separate events\n *\n * ## Interaction with Reactive Container\n *\n * `container.invalidate(token)` (from Step 3) does:\n * - Clears the cached instance for that token\n * - Walks the dependency graph to find dependents\n * - Invalidates all dependents recursively\n * - Emits batched `onChange()` events to all subscribers (Swagger, DevTools, etc.)\n *\n * ## Detection Strategy\n *\n * Uses regex patterns (not AST) for speed. This is the same approach\n * TanStack Start uses (`KindDetectionPatterns` in their compiler).\n * It's fast because:\n * - Only runs on files that actually contain decorator patterns\n * - No Babel/SWC parsing needed\n * - False positives are harmless (extra invalidation is safe)\n *\n * @see bench-mark/tanstack-router-analysis.md — Detection pattern origin\n * @see v3/plan.md Step 5 — Design rationale\n *\n * @module @forinda/kickjs-vite/hmr-plugin\n */\n\nimport type { Plugin, ViteDevServer } from 'vite'\nimport type { HmrOptions, PluginContext } from './types'\nimport { RESOLVED_APP } from './virtual-modules'\n\n/**\n * Test whether a watcher event refers to a file we should react to.\n *\n * Vite's chokidar fires for everything under the project root. We only\n * want code that the running Express app could realistically depend on:\n *\n * - **Source extensions only.** `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`,\n * `.cts`, `.mjs`, `.cjs`. JSON / lockfiles / images / typegen\n * declaration files are noise.\n * - **Skip `.d.ts` declaration files.** They're regenerated by typegen\n * on every save and would create an invalidation loop with the\n * typegen watcher in `kick dev`.\n * - **Skip well-known noise directories.** `node_modules`, `dist`, the\n * `.kickjs` typegen output, `.git`, `coverage`, OS swap files.\n *\n * Errs on the inclusive side: a file that is genuinely unused but still\n * lives under `src/` causes a cheap virtual-app invalidation, no worse\n * than `kick dev`'s typegen sweep already does on the same event.\n */\nfunction isProjectSource(file: string): boolean {\n if (!/\\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/.test(file)) return false\n if (file.endsWith('.d.ts') || file.endsWith('.d.mts') || file.endsWith('.d.cts')) return false\n if (\n file.includes('/node_modules/') ||\n file.includes('/.kickjs/') ||\n file.includes('/dist/') ||\n file.includes('/.git/') ||\n file.includes('/coverage/')\n ) {\n return false\n }\n return true\n}\n\n/** Strip directory components for a friendlier dev-console label. */\nfunction basename(file: string): string {\n const i = file.lastIndexOf('/')\n return i === -1 ? file : file.slice(i + 1)\n}\n\n/**\n * Regex patterns that detect KickJS decorator usage in source files.\n * Matches `@Service()`, `@Controller()`, `@Repository()`, etc.\n * followed by an exported class declaration.\n *\n * Pattern: decorator → optional whitespace → export? class ClassName\n */\nconst DECORATOR_CLASS_REGEX =\n /@(?:Service|Controller|Repository|Injectable|Component)\\s*\\([^)]*\\)\\s*\\n?\\s*(?:export\\s+)?class\\s+(\\w+)/g\n\n/**\n * Regex patterns that detect the v4 factory-style declarations:\n * `defineAdapter`, `definePlugin`, `defineContextDecorator`. These\n * produce values rather than decorated classes, so the class regex\n * above misses them and changes to plugin/adapter/contributor files\n * silently bypass kickjs HMR.\n *\n * Pattern: `export const Name = defineAdapter|definePlugin|defineContextDecorator(`\n * — captures the constant's name so it can be surfaced in the dev\n * console alongside the rest of the invalidation batch.\n */\nconst FACTORY_DECL_REGEX =\n /(?:export\\s+)?const\\s+(\\w+)\\s*(?::[^=]+)?=\\s*(defineAdapter|definePlugin|defineContextDecorator)\\s*[<(]/g\n\n/** Substrings that bail out the transform scanner before regex work. */\nconst FACTORY_KEYWORDS = ['defineAdapter', 'definePlugin', 'defineContextDecorator'] as const\nconst DECORATOR_KEYWORDS = [\n '@Service',\n '@Controller',\n '@Repository',\n '@Injectable',\n '@Component',\n] as const\n\n/** Debounce time for batching multiple file changes into one invalidation */\nconst DEBOUNCE_MS = 150\n\n/**\n * Creates the HMR selective invalidation plugin.\n *\n * Tracks which DI tokens are defined in which files, then selectively\n * invalidates only the affected tokens (and their dependents) when\n * those files change. Much faster than a full Container.reset().\n *\n * @param ctx - Shared plugin context\n * @returns Vite plugin\n */\nexport function kickjsHmrPlugin(ctx: PluginContext, hmrOptions: HmrOptions = {}): Plugin {\n /**\n * Maps file paths to the DI token names defined in them.\n * Built up during transform(), read during handleHotUpdate().\n *\n * @example\n * ```\n * '/src/modules/users/user.service.ts' → ['UserService']\n * '/src/modules/users/user.controller.ts' → ['UserController']\n * ```\n */\n const fileTokenMap = new Map<string, string[]>()\n\n /** Pending tokens to invalidate (accumulated during debounce window) */\n let pendingTokens = new Set<string>()\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n return {\n name: 'kickjs:hmr',\n\n /**\n * Transform hook — scans source files for decorator patterns.\n *\n * Doesn't modify the code (returns null), just records which\n * DI tokens are defined in each file. This mapping is used by\n * `handleHotUpdate()` to determine what to invalidate.\n */\n transform(code: string, id: string) {\n // Skip non-TypeScript/JavaScript files and node_modules\n if (!/\\.[tj]sx?$/.test(id)) return null\n if (id.includes('node_modules')) return null\n\n const hasDecorator = DECORATOR_KEYWORDS.some((kw) => code.includes(kw))\n const hasFactory = FACTORY_KEYWORDS.some((kw) => code.includes(kw))\n\n // Quick bail-out: nothing kickjs-relevant in the file\n if (!hasDecorator && !hasFactory) {\n if (fileTokenMap.has(id)) fileTokenMap.delete(id)\n return null\n }\n\n const tokens: string[] = []\n\n if (hasDecorator) {\n for (const m of code.matchAll(DECORATOR_CLASS_REGEX)) {\n tokens.push(m[1])\n }\n }\n\n if (hasFactory) {\n // Tag each entry with the factory kind so the HMR log line\n // distinguishes a controller-class invalidation from an adapter\n // / plugin / contributor reload — the second kind never maps\n // back to a DI token, but it still needs the virtual app\n // re-evaluated so bootstrap re-imports the new value.\n for (const m of code.matchAll(FACTORY_DECL_REGEX)) {\n tokens.push(`${m[1]} (${m[2]})`)\n }\n }\n\n if (tokens.length > 0) {\n fileTokenMap.set(id, tokens)\n } else if (fileTokenMap.has(id)) {\n fileTokenMap.delete(id)\n }\n\n return null\n },\n\n /**\n * HMR hook — called when a file changes in dev mode.\n *\n * If the changed file contains KickJS decorated classes:\n * 1. Accumulate the affected tokens in the debounce buffer\n * 2. After 150ms of quiet, flush: invalidate all tokens at once\n * 3. Invalidate the virtual module for fresh `ssrLoadModule()` response\n * 4. Send ONE `kickjs:hmr` event to the client (DevTools, Swagger UI)\n *\n * The 150ms debounce is critical for `kick g module` which creates\n * 10+ files in rapid succession. Without debouncing, each file would\n * trigger a separate invalidation cycle.\n *\n * Returns an empty array to tell Vite we handled the update\n * (prevents Vite's default full-page reload for these files).\n */\n handleHotUpdate({ file, server }) {\n // Reject anything outside the project's source surface — chokidar\n // also fires for `.kickjs/` typegen output, `dist/`, and editor\n // swap files that have nothing to do with the running app.\n if (!isProjectSource(file)) return\n\n const tokens = fileTokenMap.get(file)\n\n // Files with kickjs decorators / factories: accumulate their token\n // names so the dev console can name what changed. Files without a\n // tracked token (plain helpers, pure utilities, side-effect modules\n // like `hello.ts`) still trigger an invalidation — just labelled\n // by the file's basename so the user sees something happened.\n if (tokens && tokens.length > 0) {\n for (const t of tokens) pendingTokens.add(t)\n } else {\n pendingTokens.add(basename(file))\n }\n\n // Debounce: flush after 150ms of quiet\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n flushInvalidation(server, pendingTokens, hmrOptions)\n pendingTokens = new Set()\n }, DEBOUNCE_MS)\n\n // Tell Vite we handled it — don't do default full-page reload.\n // `flushInvalidation` always invalidates the virtual app module,\n // so the next ssrLoadModule re-imports the changed file via the\n // dev-server's middleware shim.\n return []\n },\n }\n}\n\n/**\n * Flush accumulated token invalidations.\n *\n * Called after the debounce window closes. Performs:\n * 1. Container invalidation (clears instances + walks dependency graph)\n * 2. Virtual module invalidation (forces ssrLoadModule to re-evaluate)\n * 3. HMR event broadcast (notifies DevTools, Swagger UI, etc.)\n *\n * @param server - Vite dev server\n * @param tokens - Set of DI token names to invalidate\n */\nfunction flushInvalidation(\n server: ViteDevServer,\n tokens: Set<string>,\n hmrOptions: HmrOptions,\n): void {\n const batch = [...tokens]\n if (batch.length === 0) return\n const timestamp = Date.now()\n\n // 1. Invalidate tokens in the reactive container (if available)\n // container.invalidate() walks the dependency graph and notifies subscribers\n const container = (globalThis as any).__kickjs_container\n if (container && typeof container.invalidate === 'function') {\n for (const token of batch) {\n container.invalidate(token)\n }\n }\n\n // 2. Invalidate the virtual module so next ssrLoadModule() re-evaluates\n const vmod = server.moduleGraph.getModuleById(RESOLVED_APP)\n if (vmod) {\n server.moduleGraph.invalidateModule(vmod)\n }\n\n // 3. Send ONE custom HMR event with the full batch\n // DevTools dashboard, Swagger UI, and other dev tools can listen:\n // import.meta.hot?.on('kickjs:hmr', (data) => { ... })\n server.hot.send({\n type: 'custom',\n event: 'kickjs:hmr',\n data: { tokens: batch, timestamp },\n })\n\n // 4. Dev-console log — three modes, in priority order:\n // a. `silent: true` → no log\n // b. `onInvalidation` provided → custom function controls the line\n // (returning a string emits one console.log, void/undefined =\n // suppresses, useful when piping to a structured logger or\n // overlay)\n // c. neither set → default `HMR invalidated N tokens: …` line\n if (hmrOptions.silent) return\n\n if (hmrOptions.onInvalidation) {\n const out = hmrOptions.onInvalidation({ tokens: batch, timestamp })\n if (typeof out === 'string') console.log(out)\n return\n }\n\n const names = batch.join(', ')\n const label = batch.length === 1 ? '1 token' : `${batch.length} tokens`\n console.log(` ${green('HMR')} invalidated ${label}: ${names}`)\n}\n\n/** ANSI green for terminal output */\nfunction green(text: string): string {\n return `\\x1b[32m${text}\\x1b[0m`\n}\n","// `devtoolsFlagPlugin()` — exposes `__KICKJS_DEVTOOLS__` as a build-time\n// constant adopters guard their devtools imports behind. Vite/Rollup's\n// existing dead-code-elimination strips the gated branch from production\n// bundles entirely (including any dynamic `import('@forinda/kickjs-devtools')`\n// inside the gate), giving \"devtools-in-dev, zero overhead in prod\"\n// without a babel pass.\n//\n// Adopter usage:\n//\n// const adapters = [/* prod adapters… */]\n// if (__KICKJS_DEVTOOLS__) {\n// const { DevToolsAdapter } = await import('@forinda/kickjs-devtools')\n// adapters.push(DevToolsAdapter({ basePath: '/_debug' }))\n// }\n//\n// bootstrap({ adapters, modules: [...] })\n//\n// Resolution order for the flag's value:\n// 1. explicit `enabled` option on the plugin → wins\n// 2. `KICKJS_DEVTOOLS=0|1` env var → operator-side override\n// 3. Vite `command` — `'serve'` (dev) → true, `'build'` → false.\n// `command` is cleaner than NODE_ENV: Vite passes it explicitly\n// to plugins regardless of how the user set their environment.\n//\n// The plugin returns a Vite `config` hook that adds the literal under\n// `define`. Vite's `define` is a verbatim-substitution map; stringifying\n// with JSON.stringify keeps the value a true / false token (no quotes\n// would break the parser).\n\nimport type { ConfigEnv, Plugin } from 'vite'\n\nexport interface DevtoolsFlagOptions {\n /**\n * Force the flag value. When set, environment-based detection is\n * bypassed entirely. Useful for explicit feature gates per build\n * profile.\n *\n * @default `vite command === 'serve'` (true in dev, false in build)\n */\n enabled?: boolean\n /**\n * Override the global name. Default `__KICKJS_DEVTOOLS__`.\n * Useful for adopters who want a project-specific flag name to\n * avoid colliding with another tooling's own globals.\n */\n flagName?: string\n}\n\n/**\n * Resolve the build-time devtools flag. Exposed for unit tests +\n * adopters who want to compute the same answer the plugin would.\n */\nexport function resolveDevtoolsFlag(opts: DevtoolsFlagOptions = {}, env?: ConfigEnv): boolean {\n if (typeof opts.enabled === 'boolean') return opts.enabled\n const envOverride = process.env.KICKJS_DEVTOOLS\n if (envOverride === '1' || envOverride === 'true') return true\n if (envOverride === '0' || envOverride === 'false') return false\n // env may be undefined when callers compute the flag outside a\n // Vite plugin invocation (tests). Fall back to NODE_ENV as a last\n // resort so the resolver still gives a sensible answer.\n if (env) return env.command === 'serve'\n return process.env.NODE_ENV !== 'production'\n}\n\nexport function devtoolsFlagPlugin(opts: DevtoolsFlagOptions = {}): Plugin {\n const flagName = opts.flagName ?? '__KICKJS_DEVTOOLS__'\n return {\n name: 'kickjs:devtools-flag',\n config(_userConfig, env) {\n const enabled = resolveDevtoolsFlag(opts, env)\n return {\n define: {\n [flagName]: JSON.stringify(enabled),\n },\n }\n },\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { reloadEnv } from '@forinda/kickjs'\nimport type { Plugin } from 'vite'\n\n/**\n * Vite plugin that watches `.env` files and triggers a full reload\n * when they change. This ensures the dev server picks up environment\n * variable changes without a manual restart.\n *\n * Lives in `@forinda/kickjs-vite` so all Vite-only concerns are in one\n * place.\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { envWatchPlugin } from '@forinda/kickjs-vite'\n *\n * export default defineConfig({\n * plugins: [swc.vite(), envWatchPlugin()],\n * })\n * ```\n */\nexport function envWatchPlugin(): Plugin {\n const envFiles = ['.env', '.env.local', '.env.development', '.env.production', '.env.test']\n\n return {\n name: 'kickjs-env-watch',\n\n configureServer(server) {\n const root = server.config.root\n\n for (const file of envFiles) {\n const filePath = path.resolve(root, file)\n if (fs.existsSync(filePath)) {\n server.watcher.add(filePath)\n }\n }\n\n server.watcher.on('change', (changedPath) => {\n const basename = path.basename(changedPath)\n if (envFiles.includes(basename)) {\n server.config.logger.info(` .env changed (${basename}), triggering reload...`, {\n timestamp: true,\n })\n\n // Re-read the .env file into process.env *before* invalidating\n // modules. Without this, the next SSR evaluation parses the\n // (stale) cached env snapshot — Zod throws on the missing key\n // and the user has to hard-restart the dev server.\n try {\n reloadEnv()\n } catch (err: any) {\n server.config.logger.warn(\n ` env reload failed: ${err?.message ?? err} — restart may be required`,\n { timestamp: true },\n )\n }\n\n // Invalidate all modules to trigger full HMR rebuild\n const mods = server.moduleGraph.getModulesByFile(path.resolve(root, 'src/index.ts'))\n if (mods) {\n for (const mod of mods) {\n server.moduleGraph.invalidateModule(mod)\n }\n }\n\n server.ws.send({ type: 'full-reload' })\n }\n })\n },\n }\n}\n","/**\n * @forinda/kickjs-vite — Vite plugin for the KickJS framework.\n *\n * Provides first-class Vite integration for KickJS backend applications:\n * - **Dev server**: Mounts Express on Vite's HTTP server (single port)\n * - **HMR**: Fresh server code on every request via `ssrLoadModule()`\n * - **httpServer piping**: Real `http.Server` available to all adapters\n * - **Virtual modules**: Auto-generated application entry\n *\n * ## Quick Start\n *\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite'\n * import { kickjsVitePlugin } from '@forinda/kickjs-vite'\n * import swc from 'unplugin-swc'\n *\n * export default defineConfig({\n * plugins: [\n * swc.vite({ tsconfigFile: 'tsconfig.json' }),\n * kickjsVitePlugin({ entry: 'src/index.ts' }),\n * ],\n * })\n * ```\n *\n * ```ts\n * // src/index.ts\n * import { bootstrap } from '@forinda/kickjs'\n * import { UserModule } from './modules/users/user.module'\n *\n * // Export the Express app — Vite serves it in dev, you start it in prod\n * export const app = bootstrap({\n * modules: [UserModule],\n * middleware: [express.json()],\n * })\n *\n * // Production: start the server directly\n * if (process.env.NODE_ENV === 'production') {\n * app.start()\n * }\n * ```\n *\n * ## Architecture\n *\n * The plugin returns an array of focused sub-plugins (React Router pattern):\n *\n * | Plugin | Responsibility |\n * |--------|---------------|\n * | `kickjs:core` | Vite config: appType, SSR environment, externals |\n * | `kickjs:module-discovery` | Auto-discover `@Module` classes via `transform()` |\n * | `kickjs:hmr` | Selective container invalidation via `handleHotUpdate()` |\n * | `kickjs:virtual-modules` | `virtual:kickjs/app` resolution and generation |\n * | `kickjs:dev-server` | `configureServer()` — mounts Express, pipes httpServer |\n *\n * ## httpServer Piping\n *\n * Vite creates the `http.Server` — this plugin stores it on\n * `globalThis.__kickjs_httpServer`. KickJS adapters (WsAdapter, Socket.IO,\n * GraphQL subscriptions) attach to this server via the standard\n * `afterStart({ server })` hook. Zero adapter code changes needed.\n *\n * @see v3/architecture.md — Full architecture documentation\n * @see v3/plan.md — Implementation plan and rationale\n * @see bench-mark/react-router-analysis.md — Pattern origin\n *\n * @module @forinda/kickjs-vite\n */\n\nimport { resolve } from 'node:path'\nimport type { Plugin } from 'vite'\nimport type { KickJSPluginOptions, PluginContext } from './types'\nimport { kickjsCorePlugin } from './core-plugin'\nimport { kickjsVirtualModulesPlugin } from './virtual-modules'\nimport { kickjsDevServerPlugin } from './dev-server'\nimport { kickjsModuleDiscoveryPlugin } from './module-discovery'\nimport { kickjsHmrPlugin } from './hmr-plugin'\nimport { devtoolsFlagPlugin } from './devtools-flag-plugin'\n\n/**\n * Create the KickJS Vite plugin array.\n *\n * Returns an array of focused sub-plugins that together provide full\n * Vite integration for KickJS backend applications. Each sub-plugin\n * has a single responsibility and runs at the appropriate Vite lifecycle stage.\n *\n * @param options - Plugin configuration\n * @param options.entry - Path to app entry file (default: 'src/index.ts')\n * @returns Array of Vite plugins\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite'\n * import { kickjsVitePlugin } from '@forinda/kickjs-vite'\n * import swc from 'unplugin-swc'\n *\n * export default defineConfig({\n * plugins: [\n * swc.vite({ tsconfigFile: 'tsconfig.json' }),\n * kickjsVitePlugin(),\n * ],\n * })\n * ```\n */\nexport function kickjsVitePlugin(options: KickJSPluginOptions = {}): Plugin[] {\n const entry = options.entry ?? 'src/index.ts'\n\n // Create shared context — resolved lazily in config hook since we\n // don't have the root directory until Vite resolves its config.\n const ctx: PluginContext = {\n entry,\n root: process.cwd(),\n }\n\n // Resolve root and entry as early as possible so sub-plugin config() hooks\n // (e.g., warmup) see the correct absolute entry path.\n const rootResolver: Plugin = {\n name: 'kickjs:root-resolver',\n config(config) {\n const root = config.root ?? process.cwd()\n ctx.root = root\n ctx.entry = resolve(root, entry)\n },\n }\n\n const plugins: Plugin[] = [\n rootResolver,\n kickjsCorePlugin(ctx),\n kickjsModuleDiscoveryPlugin(ctx),\n kickjsHmrPlugin(ctx, options.hmr),\n kickjsVirtualModulesPlugin(ctx),\n kickjsDevServerPlugin(ctx),\n ]\n // Register the devtools flag plugin by default. Adopters who don't\n // gate their devtools imports get a harmless no-op; adopters who do\n // get tree-shaking for free. Pass `devtools: false` to skip.\n if (options.devtools !== false) {\n plugins.push(devtoolsFlagPlugin(options.devtools ?? {}))\n }\n return plugins\n}\n\n// Re-export types for consumers\nexport type {\n KickJSPluginOptions,\n PluginContext,\n HmrOptions,\n HmrInvalidationContext,\n DevtoolsOptions,\n} from './types'\n\n// Standalone plugins users can compose alongside `kickjsVitePlugin()`\nexport { envWatchPlugin } from './env-watch-plugin'\nexport {\n devtoolsFlagPlugin,\n resolveDevtoolsFlag,\n type DevtoolsFlagOptions,\n} from './devtools-flag-plugin'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/core-plugin.ts","../src/virtual-modules.ts","../src/dev-server.ts","../src/module-discovery.ts","../src/hmr-plugin.ts","../src/devtools-flag-plugin.ts","../src/babel-strip-devtools.ts","../src/devtools-strip-plugin.ts","../src/env-watch-plugin.ts","../src/index.ts"],"sourcesContent":["/**\n * Core Vite plugin for KickJS — configures the Vite server for backend\n * framework use: custom app type, SSR environment, and Node.js target.\n *\n * This plugin runs first in the array and sets up the environment that\n * the other sub-plugins (dev-server, virtual-modules, hmr) depend on.\n *\n * @module @forinda/kickjs-vite/core-plugin\n */\n\nimport type { Plugin } from 'vite'\nimport type { PluginContext } from './types'\n\n/**\n * Creates the core configuration plugin.\n *\n * Responsibilities:\n * - Sets `appType: 'custom'` so Vite doesn't serve index.html\n * - Configures the SSR environment for Node.js backend code\n * - Prevents Vite from clearing the terminal (KickJS logs route tables on startup)\n * - Externalizes Node.js built-ins and framework packages from the bundle\n *\n * @param ctx - Shared plugin context (entry file, root directory)\n * @returns Vite plugin\n */\nexport function kickjsCorePlugin(ctx: PluginContext): Plugin {\n return {\n name: 'kickjs:core',\n\n /**\n * Vite config hook — runs before config is resolved.\n * Sets the foundational configuration for a backend Node.js framework.\n */\n config(userConfig, _env) {\n // Respect explicit server.port from user's vite.config.ts.\n // Only inject .env PORT when the user hasn't set one.\n const explicitPort = userConfig.server?.port\n const envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined\n const port = explicitPort ?? envPort ?? 3000\n\n return {\n server: {\n port,\n },\n\n // 'custom' tells Vite this is not a SPA or MPA — we handle all requests\n appType: 'custom' as const,\n\n // Don't clear the terminal — KickJS logs route tables on startup\n clearScreen: false,\n\n // SSR environment configuration for backend code\n environments: {\n ssr: {\n // Warm up the entry file for faster first request\n dev: {\n warmup: [ctx.entry],\n },\n },\n },\n\n // Disable dep optimization discovery — KickJS is SSR-only, not a client app\n optimizeDeps: {\n noDiscovery: true,\n },\n\n // SSR-specific settings\n ssr: {\n // Externalize Node.js built-ins and framework packages\n // These should NOT be bundled by Vite — they run natively in Node\n external: ['@forinda/kickjs', 'express', 'reflect-metadata'],\n },\n }\n },\n }\n}\n","/**\n * Virtual module plugin for KickJS — generates the `virtual:kickjs/app`\n * module that bridges Vite and the KickJS application.\n *\n * Virtual modules are Vite's mechanism for generating code at build/serve\n * time that doesn't exist on disk. When the dev server calls\n * `ssrLoadModule('virtual:kickjs/app')`, this plugin generates the code\n * that imports and re-exports the user's application entry.\n *\n * In the future, this will also support auto-discovery of `@Module` classes\n * (Step 4 in v3/plan.md) and container manifest generation.\n *\n * @see https://vite.dev/guide/api-plugin.html#virtual-modules-convention\n *\n * @module @forinda/kickjs-vite/virtual-modules\n */\n\nimport { normalizePath, type Plugin } from 'vite'\nimport type { PluginContext } from './types'\n\n/** Prefix for resolved virtual module IDs (Vite convention) */\nconst VIRTUAL_PREFIX = '\\0'\n\n/** Virtual module ID for the KickJS application entry */\nconst VIRTUAL_APP = 'virtual:kickjs/app'\n\n/** Resolved virtual module ID (with \\0 prefix to mark as virtual) */\nconst RESOLVED_APP = `${VIRTUAL_PREFIX}${VIRTUAL_APP}`\n\n/**\n * Creates the virtual modules plugin.\n *\n * Currently provides one virtual module:\n * - `virtual:kickjs/app` — re-exports the user's application entry\n *\n * The dev server plugin imports this on every request via `ssrLoadModule()`.\n * When any source file changes, Vite invalidates the module graph and the\n * next import returns fresh code — this is how server-side HMR works.\n *\n * Future virtual modules (planned for Steps 4-5):\n * - `virtual:kickjs/modules` — auto-discovered `@Module` classes\n * - `virtual:kickjs/manifest` — container metadata for DevTools\n *\n * @param ctx - Shared plugin context (entry file, root directory)\n * @returns Vite plugin\n */\nexport function kickjsVirtualModulesPlugin(ctx: PluginContext): Plugin {\n return {\n name: 'kickjs:virtual-modules',\n\n /**\n * Resolve virtual module IDs.\n * Tells Vite that `virtual:kickjs/app` is a valid module ID\n * and maps it to our internal resolved ID.\n */\n resolveId(id: string) {\n if (id === VIRTUAL_APP) {\n return RESOLVED_APP\n }\n },\n\n /**\n * Load virtual module content.\n * Generates the code for `virtual:kickjs/app` that imports the user's\n * entry file and re-exports the Express app.\n *\n * The generated code:\n * 1. Imports the user's entry file (which calls `bootstrap()`)\n * 2. Re-exports everything from it (including the `app` export)\n *\n * The dev server plugin then does:\n * `const { app } = await ssrLoadModule('virtual:kickjs/app')`\n * and uses `app.handle(req, res, next)` to route requests through Express.\n */\n load(id: string) {\n if (id !== RESOLVED_APP) return\n\n // Generate code that imports and re-exports the user's entry\n // The entry file should: export const app = bootstrap({ modules: [...] })\n const normalizedEntry = normalizePath(ctx.entry)\n return [\n `// Auto-generated by @forinda/kickjs-vite`,\n `// Imports the user's application entry and re-exports the Express app.`,\n `// Vite's module graph tracks this — file changes trigger re-evaluation.`,\n `export * from '${normalizedEntry}'`,\n ].join('\\n')\n },\n }\n}\n\nexport { VIRTUAL_APP, RESOLVED_APP }\n","/**\n * Dev server plugin for KickJS — mounts the Express application on Vite's\n * HTTP server using the `configureServer` hook.\n *\n * ## Architecture\n *\n * In development, Vite owns the HTTP port and creates the `http.Server`.\n * This plugin:\n *\n * 1. **Stores `viteServer.httpServer` on `globalThis`** so KickJS adapters\n * (WsAdapter, Socket.IO, GraphQL subscriptions) can attach to the real\n * server via `server.on('upgrade', ...)`. This is the key to supporting\n * any library that needs the raw `http.Server`.\n *\n * 2. **Registers a post-middleware** that loads the KickJS app through\n * Vite's SSR transform pipeline on every request. When source files\n * change, Vite invalidates the module graph and the next request\n * gets fresh code — no process restart needed.\n *\n * 3. **Stores the Vite server reference** on `globalThis` so SSR\n * renderers can use `createViteRuntime()`.\n *\n * ## Why NOT middlewareMode\n *\n * The initial KickJS Vite plugin attempt used `middlewareMode: true`, which\n * makes `viteServer.httpServer = null`. This broke WsAdapter and any library\n * needing `server.on('upgrade', ...)`. By letting Vite create the server,\n * `httpServer` is the real `http.Server` — adapters attach to it seamlessly.\n *\n * ## Request Flow\n *\n * ```\n * HTTP Request → Vite static middleware (HMR, assets)\n * → If Vite handles it → done\n * → If not → KickJS post-middleware\n * → ssrLoadModule('virtual:kickjs/app') → fresh Express app\n * → Express handles request (routes, middleware, controllers)\n * ```\n *\n * @see v3/architecture.md Section 2.6 for the httpServer piping design\n * @see bench-mark/react-router-analysis.md for the pattern origin\n *\n * @module @forinda/kickjs-vite/dev-server\n */\n\nimport type { Plugin, ViteDevServer } from 'vite'\nimport type { PluginContext } from './types'\nimport { VIRTUAL_APP } from './virtual-modules'\n\n/**\n * Declare the globalThis properties used for cross-module communication.\n * These persist across Vite's `ssrLoadModule()` re-evaluations.\n *\n * `__kickjs_httpServer` uses the same `HttpServer` type that Vite uses\n * internally (union of http.Server | https.Server | Http2SecureServer).\n * KickJS adapters receive it as `http.Server` via AdapterContext — the\n * cast is safe because Express only works with HTTP/1.1 servers.\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __kickjs_httpServer: any\n // eslint-disable-next-line no-var\n var __kickjs_viteServer: ViteDevServer | null\n}\n\n/**\n * Creates the dev server plugin.\n *\n * This is the critical integration point between Vite and KickJS:\n * - Pipes `viteServer.httpServer` to adapters via `globalThis`\n * - Loads fresh Express app on every request via `ssrLoadModule()`\n * - Fixes stack traces for SSR errors with `ssrFixStacktrace()`\n *\n * @param ctx - Shared plugin context (entry file, root directory)\n * @returns Vite plugin\n */\nexport function kickjsDevServerPlugin(_ctx: PluginContext): Plugin {\n return {\n name: 'kickjs:dev-server',\n\n /**\n * Configure the Vite dev server.\n *\n * The `configureServer` hook runs after Vite creates the HTTP server.\n * We use it to:\n * 1. Store the httpServer on globalThis (for WsAdapter, Socket.IO, etc.)\n * 2. Store the Vite server reference (for SSR rendering)\n * 3. Return a post-middleware that loads the KickJS app on each request\n *\n * Returning a function from `configureServer` registers it as a\n * **post-middleware** — it runs AFTER Vite's own middleware (static\n * files, HMR client, etc.), so Vite-handled requests never hit Express.\n */\n configureServer(viteServer: ViteDevServer) {\n // ━━━ Store the REAL http.Server on globalThis ━━━\n // This is the key to supporting Socket.IO, WsAdapter, GraphQL WS,\n // and any library that needs `server.on('upgrade', ...)`.\n //\n // Application.start() detects this and skips creating its own server:\n // if (globalThis.__kickjs_httpServer) → reuse Vite's server\n // else → create own http.Server (production mode)\n //\n // Adapters receive the server in afterStart({ server }) — same API\n // in both dev and prod. Zero adapter code changes needed.\n globalThis.__kickjs_httpServer = viteServer.httpServer ?? null\n globalThis.__kickjs_viteServer = viteServer\n\n // Return post-middleware — runs after Vite's static/HMR middleware\n return () => {\n viteServer.middlewares.use(async (req, res, next) => {\n try {\n // Load the KickJS app through Vite's SSR transform pipeline.\n // On first request: evaluates the entry file and all imports.\n // On subsequent requests: returns cached modules (or re-evaluates\n // if source files changed since last request).\n //\n // This is the same pattern React Router and TanStack Start use.\n const mod = await viteServer.ssrLoadModule(VIRTUAL_APP)\n\n // The entry file exports: export const app = bootstrap({ modules })\n // `app` is the configured Express instance (not yet listening).\n const expressApp = mod.app\n\n if (!expressApp?.handle) {\n // Entry file doesn't export an Express app — fall through to Vite's 404\n return next()\n }\n\n // Let Express handle the request.\n // If Express doesn't match any route, it calls next() and\n // Vite's default 404 handler takes over.\n expressApp.handle(req, res, (err?: any) => {\n if (err) {\n // Fix stack traces to point to original source (not compiled)\n if (err instanceof Error) {\n viteServer.ssrFixStacktrace(err)\n }\n return next(err)\n }\n next()\n })\n } catch (err) {\n // SSR load/evaluation error (syntax error, missing import, etc.)\n if (err instanceof Error) {\n viteServer.ssrFixStacktrace(err)\n }\n next(err)\n }\n })\n }\n },\n }\n}\n","/**\n * Module discovery plugin for KickJS — automatically detects `@Module` classes\n * by scanning source files during Vite's `transform()` hook.\n *\n * ## How It Works\n *\n * 1. Vite calls `transform(code, id)` for every file it processes\n * 2. This plugin checks if the file matches `*.module.ts` (or `.js`)\n * 3. If the file contains a class exported with `@Module` or extends `AppModule`,\n * it extracts the class name and records the file path\n * 4. The virtual module `virtual:kickjs/app` is invalidated so the next request\n * picks up the newly discovered module\n *\n * ## Why This Matters\n *\n * Without auto-discovery, users must manually maintain a barrel file\n * (`src/modules/index.ts`) that imports and re-exports all modules.\n * When `kick g module` creates a new module, it has to edit this barrel.\n * With auto-discovery, modules are detected automatically — no barrel needed.\n *\n * ## Interaction with HMR\n *\n * When a module file is added, renamed, or deleted:\n * - `handleHotUpdate()` detects the change\n * - The virtual module is invalidated\n * - The next `ssrLoadModule()` call regenerates the virtual module\n * with the updated module list\n *\n * @module @forinda/kickjs-vite/module-discovery\n */\n\nimport type { Plugin, ViteDevServer } from 'vite'\nimport type { PluginContext } from './types'\nimport { RESOLVED_APP } from './virtual-modules'\n\n/** Regex to detect exported module classes in source files */\nconst MODULE_CLASS_REGEX =\n /export\\s+(?:default\\s+)?class\\s+(\\w+(?:Module))\\s*(?:extends|implements|{)/\n\n/** File extensions to scan for modules */\nconst MODULE_FILE_PATTERN = /\\.module\\.[tj]sx?$/\n\n/**\n * Discovered module entry — maps a file path to its exported class name.\n *\n * @example\n * ```\n * {\n * filePath: '/src/modules/users/user.module.ts',\n * className: 'UserModule'\n * }\n * ```\n */\nexport interface DiscoveredModule {\n /** Absolute file path */\n filePath: string\n /** Exported class name (e.g., 'UserModule') */\n className: string\n}\n\n/**\n * Creates the module auto-discovery plugin.\n *\n * Scans `*.module.ts` files during Vite's transform phase to build a\n * registry of `@Module` classes. The registry is used by the virtual\n * modules plugin to generate auto-imports.\n *\n * @param ctx - Shared plugin context\n * @returns Vite plugin\n */\nexport function kickjsModuleDiscoveryPlugin(ctx: PluginContext): Plugin {\n /**\n * Registry of discovered modules.\n * Keyed by absolute file path to handle renames and deletions.\n * Stored on the plugin context so the virtual modules plugin can read it.\n */\n const discovered = new Map<string, DiscoveredModule>()\n let server: ViteDevServer | null = null\n\n // Expose discovered modules on the context for virtual-modules plugin\n ;(ctx as any).discoveredModules = discovered\n\n return {\n name: 'kickjs:module-discovery',\n\n configureServer(viteServer) {\n server = viteServer\n\n // Watch for new/deleted files in the modules directory\n viteServer.watcher.on('add', (filePath) => {\n if (MODULE_FILE_PATTERN.test(filePath)) {\n // File added — it will be processed in transform() when first imported.\n // Invalidate virtual module so it picks up the new file on next request.\n invalidateVirtualApp(viteServer)\n }\n })\n\n viteServer.watcher.on('unlink', (filePath) => {\n if (discovered.has(filePath)) {\n discovered.delete(filePath)\n invalidateVirtualApp(viteServer)\n }\n })\n },\n\n /**\n * Transform hook — called for every file Vite processes.\n *\n * We don't actually transform the code (return null), but we use this\n * hook to observe which files contain module classes. This is cheaper\n * than AST parsing — a regex match on the source is sufficient because\n * module files follow a consistent naming pattern (`*.module.ts`).\n */\n transform(code: string, id: string) {\n // Only scan files matching the module pattern\n if (!MODULE_FILE_PATTERN.test(id)) return null\n // Skip node_modules\n if (id.includes('node_modules')) return null\n\n const match = code.match(MODULE_CLASS_REGEX)\n if (match) {\n const className = match[1]\n const wasNew = !discovered.has(id)\n discovered.set(id, { filePath: id, className })\n\n if (wasNew && server) {\n // New module discovered — invalidate virtual module\n invalidateVirtualApp(server)\n }\n } else if (discovered.has(id)) {\n // File no longer exports a module class (user removed it)\n discovered.delete(id)\n if (server) {\n invalidateVirtualApp(server)\n }\n }\n\n // Return null — we don't transform the code, just observe it\n return null\n },\n\n /**\n * HMR hook — called when a file changes.\n *\n * If a module file changes, we re-scan it and invalidate the virtual\n * module so the next request picks up the changes.\n */\n handleHotUpdate({ file, server: viteServer }) {\n if (!MODULE_FILE_PATTERN.test(file)) return\n\n // The file will be re-processed in transform() on next import.\n // Invalidate the virtual module so it regenerates with fresh imports.\n invalidateVirtualApp(viteServer)\n },\n }\n}\n\n/**\n * Invalidate the virtual:kickjs/app module in Vite's module graph.\n * This forces the next `ssrLoadModule()` call to re-evaluate the\n * virtual module, picking up any new/removed modules.\n */\nfunction invalidateVirtualApp(server: ViteDevServer): void {\n const mod = server.moduleGraph.getModuleById(RESOLVED_APP)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n}\n\n/**\n * Get the current list of discovered modules.\n * Used by the virtual modules plugin to generate auto-imports.\n */\nexport function getDiscoveredModules(ctx: PluginContext): DiscoveredModule[] {\n const map = (ctx as any).discoveredModules as Map<string, DiscoveredModule> | undefined\n return map ? [...map.values()] : []\n}\n","/**\n * HMR plugin for KickJS — selective container invalidation when source\n * files change, with debounced batching for bulk operations.\n *\n * ## How It Works\n *\n * 1. During `transform()`, the plugin scans for decorator patterns\n * (`@Service`, `@Controller`, etc.) and maps file paths to DI token names\n * 2. When a file changes (`handleHotUpdate()`), the plugin:\n * - Looks up which DI tokens are defined in that file\n * - Calls `container.invalidate(token)` for each (which walks the dep graph)\n * - Invalidates the virtual module so `ssrLoadModule()` returns fresh code\n * - Sends a custom HMR event (`kickjs:hmr`) so DevTools/Swagger can react\n * 3. Changes are **debounced** (150ms) so `kick g module` creating 10+ files\n * emits ONE invalidation batch, not 10 separate events\n *\n * ## Interaction with Reactive Container\n *\n * `container.invalidate(token)` (from Step 3) does:\n * - Clears the cached instance for that token\n * - Walks the dependency graph to find dependents\n * - Invalidates all dependents recursively\n * - Emits batched `onChange()` events to all subscribers (Swagger, DevTools, etc.)\n *\n * ## Detection Strategy\n *\n * Uses regex patterns (not AST) for speed. This is the same approach\n * TanStack Start uses (`KindDetectionPatterns` in their compiler).\n * It's fast because:\n * - Only runs on files that actually contain decorator patterns\n * - No Babel/SWC parsing needed\n * - False positives are harmless (extra invalidation is safe)\n *\n * @see bench-mark/tanstack-router-analysis.md — Detection pattern origin\n * @see v3/plan.md Step 5 — Design rationale\n *\n * @module @forinda/kickjs-vite/hmr-plugin\n */\n\nimport type { Plugin, ViteDevServer } from 'vite'\nimport type { HmrOptions, PluginContext } from './types'\nimport { RESOLVED_APP } from './virtual-modules'\n\n/**\n * Test whether a watcher event refers to a file we should react to.\n *\n * Vite's chokidar fires for everything under the project root. We only\n * want code that the running Express app could realistically depend on:\n *\n * - **Source extensions only.** `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`,\n * `.cts`, `.mjs`, `.cjs`. JSON / lockfiles / images / typegen\n * declaration files are noise.\n * - **Skip `.d.ts` declaration files.** They're regenerated by typegen\n * on every save and would create an invalidation loop with the\n * typegen watcher in `kick dev`.\n * - **Skip well-known noise directories.** `node_modules`, `dist`, the\n * `.kickjs` typegen output, `.git`, `coverage`, OS swap files.\n *\n * Errs on the inclusive side: a file that is genuinely unused but still\n * lives under `src/` causes a cheap virtual-app invalidation, no worse\n * than `kick dev`'s typegen sweep already does on the same event.\n */\nfunction isProjectSource(file: string): boolean {\n if (!/\\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/.test(file)) return false\n if (file.endsWith('.d.ts') || file.endsWith('.d.mts') || file.endsWith('.d.cts')) return false\n if (\n file.includes('/node_modules/') ||\n file.includes('/.kickjs/') ||\n file.includes('/dist/') ||\n file.includes('/.git/') ||\n file.includes('/coverage/')\n ) {\n return false\n }\n return true\n}\n\n/** Strip directory components for a friendlier dev-console label. */\nfunction basename(file: string): string {\n const i = file.lastIndexOf('/')\n return i === -1 ? file : file.slice(i + 1)\n}\n\n/**\n * Regex patterns that detect KickJS decorator usage in source files.\n * Matches `@Service()`, `@Controller()`, `@Repository()`, etc.\n * followed by an exported class declaration.\n *\n * Pattern: decorator → optional whitespace → export? class ClassName\n */\nconst DECORATOR_CLASS_REGEX =\n /@(?:Service|Controller|Repository|Injectable|Component)\\s*\\([^)]*\\)\\s*\\n?\\s*(?:export\\s+)?class\\s+(\\w+)/g\n\n/**\n * Regex patterns that detect the v4 factory-style declarations:\n * `defineAdapter`, `definePlugin`, `defineContextDecorator`. These\n * produce values rather than decorated classes, so the class regex\n * above misses them and changes to plugin/adapter/contributor files\n * silently bypass kickjs HMR.\n *\n * Pattern: `export const Name = defineAdapter|definePlugin|defineContextDecorator(`\n * — captures the constant's name so it can be surfaced in the dev\n * console alongside the rest of the invalidation batch.\n */\nconst FACTORY_DECL_REGEX =\n /(?:export\\s+)?const\\s+(\\w+)\\s*(?::[^=]+)?=\\s*(defineAdapter|definePlugin|defineContextDecorator)\\s*[<(]/g\n\n/** Substrings that bail out the transform scanner before regex work. */\nconst FACTORY_KEYWORDS = ['defineAdapter', 'definePlugin', 'defineContextDecorator'] as const\nconst DECORATOR_KEYWORDS = [\n '@Service',\n '@Controller',\n '@Repository',\n '@Injectable',\n '@Component',\n] as const\n\n/** Debounce time for batching multiple file changes into one invalidation */\nconst DEBOUNCE_MS = 150\n\n/**\n * Creates the HMR selective invalidation plugin.\n *\n * Tracks which DI tokens are defined in which files, then selectively\n * invalidates only the affected tokens (and their dependents) when\n * those files change. Much faster than a full Container.reset().\n *\n * @param ctx - Shared plugin context\n * @returns Vite plugin\n */\nexport function kickjsHmrPlugin(ctx: PluginContext, hmrOptions: HmrOptions = {}): Plugin {\n /**\n * Maps file paths to the DI token names defined in them.\n * Built up during transform(), read during handleHotUpdate().\n *\n * @example\n * ```\n * '/src/modules/users/user.service.ts' → ['UserService']\n * '/src/modules/users/user.controller.ts' → ['UserController']\n * ```\n */\n const fileTokenMap = new Map<string, string[]>()\n\n /** Pending tokens to invalidate (accumulated during debounce window) */\n let pendingTokens = new Set<string>()\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n return {\n name: 'kickjs:hmr',\n\n /**\n * Transform hook — scans source files for decorator patterns.\n *\n * Doesn't modify the code (returns null), just records which\n * DI tokens are defined in each file. This mapping is used by\n * `handleHotUpdate()` to determine what to invalidate.\n */\n transform(code: string, id: string) {\n // Skip non-TypeScript/JavaScript files and node_modules\n if (!/\\.[tj]sx?$/.test(id)) return null\n if (id.includes('node_modules')) return null\n\n const hasDecorator = DECORATOR_KEYWORDS.some((kw) => code.includes(kw))\n const hasFactory = FACTORY_KEYWORDS.some((kw) => code.includes(kw))\n\n // Quick bail-out: nothing kickjs-relevant in the file\n if (!hasDecorator && !hasFactory) {\n if (fileTokenMap.has(id)) fileTokenMap.delete(id)\n return null\n }\n\n const tokens: string[] = []\n\n if (hasDecorator) {\n for (const m of code.matchAll(DECORATOR_CLASS_REGEX)) {\n tokens.push(m[1])\n }\n }\n\n if (hasFactory) {\n // Tag each entry with the factory kind so the HMR log line\n // distinguishes a controller-class invalidation from an adapter\n // / plugin / contributor reload — the second kind never maps\n // back to a DI token, but it still needs the virtual app\n // re-evaluated so bootstrap re-imports the new value.\n for (const m of code.matchAll(FACTORY_DECL_REGEX)) {\n tokens.push(`${m[1]} (${m[2]})`)\n }\n }\n\n if (tokens.length > 0) {\n fileTokenMap.set(id, tokens)\n } else if (fileTokenMap.has(id)) {\n fileTokenMap.delete(id)\n }\n\n return null\n },\n\n /**\n * HMR hook — called when a file changes in dev mode.\n *\n * If the changed file contains KickJS decorated classes:\n * 1. Accumulate the affected tokens in the debounce buffer\n * 2. After 150ms of quiet, flush: invalidate all tokens at once\n * 3. Invalidate the virtual module for fresh `ssrLoadModule()` response\n * 4. Send ONE `kickjs:hmr` event to the client (DevTools, Swagger UI)\n *\n * The 150ms debounce is critical for `kick g module` which creates\n * 10+ files in rapid succession. Without debouncing, each file would\n * trigger a separate invalidation cycle.\n *\n * Returns an empty array to tell Vite we handled the update\n * (prevents Vite's default full-page reload for these files).\n */\n handleHotUpdate({ file, server }) {\n // Reject anything outside the project's source surface — chokidar\n // also fires for `.kickjs/` typegen output, `dist/`, and editor\n // swap files that have nothing to do with the running app.\n if (!isProjectSource(file)) return\n\n const tokens = fileTokenMap.get(file)\n\n // Files with kickjs decorators / factories: accumulate their token\n // names so the dev console can name what changed. Files without a\n // tracked token (plain helpers, pure utilities, side-effect modules\n // like `hello.ts`) still trigger an invalidation — just labelled\n // by the file's basename so the user sees something happened.\n if (tokens && tokens.length > 0) {\n for (const t of tokens) pendingTokens.add(t)\n } else {\n pendingTokens.add(basename(file))\n }\n\n // Debounce: flush after 150ms of quiet\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n flushInvalidation(server, pendingTokens, hmrOptions)\n pendingTokens = new Set()\n }, DEBOUNCE_MS)\n\n // Tell Vite we handled it — don't do default full-page reload.\n // `flushInvalidation` always invalidates the virtual app module,\n // so the next ssrLoadModule re-imports the changed file via the\n // dev-server's middleware shim.\n return []\n },\n }\n}\n\n/**\n * Flush accumulated token invalidations.\n *\n * Called after the debounce window closes. Performs:\n * 1. Container invalidation (clears instances + walks dependency graph)\n * 2. Virtual module invalidation (forces ssrLoadModule to re-evaluate)\n * 3. HMR event broadcast (notifies DevTools, Swagger UI, etc.)\n *\n * @param server - Vite dev server\n * @param tokens - Set of DI token names to invalidate\n */\nfunction flushInvalidation(\n server: ViteDevServer,\n tokens: Set<string>,\n hmrOptions: HmrOptions,\n): void {\n const batch = [...tokens]\n if (batch.length === 0) return\n const timestamp = Date.now()\n\n // 1. Invalidate tokens in the reactive container (if available)\n // container.invalidate() walks the dependency graph and notifies subscribers\n const container = (globalThis as any).__kickjs_container\n if (container && typeof container.invalidate === 'function') {\n for (const token of batch) {\n container.invalidate(token)\n }\n }\n\n // 2. Invalidate the virtual module so next ssrLoadModule() re-evaluates\n const vmod = server.moduleGraph.getModuleById(RESOLVED_APP)\n if (vmod) {\n server.moduleGraph.invalidateModule(vmod)\n }\n\n // 3. Send ONE custom HMR event with the full batch\n // DevTools dashboard, Swagger UI, and other dev tools can listen:\n // import.meta.hot?.on('kickjs:hmr', (data) => { ... })\n server.hot.send({\n type: 'custom',\n event: 'kickjs:hmr',\n data: { tokens: batch, timestamp },\n })\n\n // 4. Dev-console log — three modes, in priority order:\n // a. `silent: true` → no log\n // b. `onInvalidation` provided → custom function controls the line\n // (returning a string emits one console.log, void/undefined =\n // suppresses, useful when piping to a structured logger or\n // overlay)\n // c. neither set → default `HMR invalidated N tokens: …` line\n if (hmrOptions.silent) return\n\n if (hmrOptions.onInvalidation) {\n const out = hmrOptions.onInvalidation({ tokens: batch, timestamp })\n if (typeof out === 'string') console.log(out)\n return\n }\n\n const names = batch.join(', ')\n const label = batch.length === 1 ? '1 token' : `${batch.length} tokens`\n console.log(` ${green('HMR')} invalidated ${label}: ${names}`)\n}\n\n/** ANSI green for terminal output */\nfunction green(text: string): string {\n return `\\x1b[32m${text}\\x1b[0m`\n}\n","// `devtoolsFlagPlugin()` — exposes `__KICKJS_DEVTOOLS__` as a build-time\n// constant adopters guard their devtools imports behind. Vite/Rollup's\n// existing dead-code-elimination strips the gated branch from production\n// bundles entirely (including any dynamic `import('@forinda/kickjs-devtools')`\n// inside the gate), giving \"devtools-in-dev, zero overhead in prod\"\n// without a babel pass.\n//\n// Adopter usage:\n//\n// const adapters = [/* prod adapters… */]\n// if (__KICKJS_DEVTOOLS__) {\n// const { DevToolsAdapter } = await import('@forinda/kickjs-devtools')\n// adapters.push(DevToolsAdapter({ basePath: '/_debug' }))\n// }\n//\n// bootstrap({ adapters, modules: [...] })\n//\n// Resolution order for the flag's value:\n// 1. explicit `enabled` option on the plugin → wins\n// 2. `KICKJS_DEVTOOLS=0|1` env var → operator-side override\n// 3. Vite `command` — `'serve'` (dev) → true, `'build'` → false.\n// `command` is cleaner than NODE_ENV: Vite passes it explicitly\n// to plugins regardless of how the user set their environment.\n//\n// The plugin returns a Vite `config` hook that adds the literal under\n// `define`. Vite's `define` is a verbatim-substitution map; stringifying\n// with JSON.stringify keeps the value a true / false token (no quotes\n// would break the parser).\n\nimport type { ConfigEnv, Plugin } from 'vite'\n\nexport interface DevtoolsFlagOptions {\n /**\n * Force the flag value. When set, environment-based detection is\n * bypassed entirely. Useful for explicit feature gates per build\n * profile.\n *\n * @default `vite command === 'serve'` (true in dev, false in build)\n */\n enabled?: boolean\n /**\n * Override the global name. Default `__KICKJS_DEVTOOLS__`.\n * Useful for adopters who want a project-specific flag name to\n * avoid colliding with another tooling's own globals.\n */\n flagName?: string\n}\n\n/**\n * Resolve the build-time devtools flag. Exposed for unit tests +\n * adopters who want to compute the same answer the plugin would.\n */\nexport function resolveDevtoolsFlag(opts: DevtoolsFlagOptions = {}, env?: ConfigEnv): boolean {\n if (typeof opts.enabled === 'boolean') return opts.enabled\n const envOverride = process.env.KICKJS_DEVTOOLS\n if (envOverride === '1' || envOverride === 'true') return true\n if (envOverride === '0' || envOverride === 'false') return false\n // env may be undefined when callers compute the flag outside a\n // Vite plugin invocation (tests). Fall back to NODE_ENV as a last\n // resort so the resolver still gives a sensible answer.\n if (env) return env.command === 'serve'\n return process.env.NODE_ENV !== 'production'\n}\n\nexport function devtoolsFlagPlugin(opts: DevtoolsFlagOptions = {}): Plugin {\n const flagName = opts.flagName ?? '__KICKJS_DEVTOOLS__'\n return {\n name: 'kickjs:devtools-flag',\n config(_userConfig, env) {\n const enabled = resolveDevtoolsFlag(opts, env)\n return {\n define: {\n [flagName]: JSON.stringify(enabled),\n },\n }\n },\n }\n}\n","/**\n * Babel-based devtools stripper for production builds.\n *\n * Built around a single rule: anything sourced from\n * `@forinda/kickjs-devtools-kit` (or any of its sub-paths) is dev-\n * only and must not ship in the prod bundle. The transform walks\n * each module and removes:\n *\n * 1. `import ... from '@forinda/kickjs-devtools-kit'` declarations\n * (named, default, namespace, side-effect — all forms).\n * 2. Top-level `ExpressionStatement`s whose call/expression root is\n * a binding imported from devtools-kit\n * (e.g., `defineDevtoolsRenderTab({...})`).\n * 3. Side-effect imports whose path ends in `/devtools-events`\n * (with any extension) — type-augmentation modules shipped by\n * adapter packages. Already side-effect-only, safe to drop in\n * prod.\n *\n * The transform is intentionally conservative. It will not:\n *\n * - Remove identifier *references* outside of the rules above. If\n * your code calls `defineDevtoolsRenderTab(...)` inside a regular\n * function body (i.e. not a top-level `ExpressionStatement`),\n * the reference stays. After we drop the import the build will\n * fail loud — that is the signal to gate the call behind\n * `__KICKJS_DEVTOOLS__` (see `devtools-flag-plugin.ts`).\n * - Touch files that don't import from devtools-kit at all.\n * - Touch files in `node_modules` (Vite's plugin chain handles\n * that already, but the transform short-circuits on a quick\n * string check too).\n *\n * The dev path is unchanged: this transform only runs when Vite's\n * `command === 'build'`. In dev, devtools-kit imports stay live.\n *\n * Spec: docs/db/m3-plan.md §M3.C.\n */\n\nimport babel from '@babel/core'\n\nconst DEVTOOLS_KIT_RE = /^@forinda\\/kickjs-devtools-kit(\\/.*)?$/\nconst DEVTOOLS_EVENTS_RE = /(^|\\/)devtools-events(\\.[a-z]+)?$/\n\nexport interface StripDevtoolsOptions {\n /**\n * When `false`, skips files that don't import devtools-kit. The\n * default short-circuit is a substring check on the source text;\n * disable it only for tests where you want the visitor to run\n * unconditionally.\n *\n * @default true\n */\n fastReject?: boolean\n}\n\nexport interface StripResult {\n /** Transformed source. Returns the original `code` when nothing was stripped. */\n code: string\n /** `true` if the visitor removed at least one node. */\n changed: boolean\n}\n\n/**\n * Strip devtools-kit imports and their dependent top-level calls\n * from a single TypeScript module. Pure — no I/O, no Vite, no\n * filesystem.\n */\nexport function stripDevtoolsCode(\n source: string,\n filename: string,\n opts: StripDevtoolsOptions = {},\n): StripResult {\n if (opts.fastReject !== false) {\n if (!source.includes('@forinda/kickjs-devtools-kit') && !source.includes('devtools-events')) {\n return { code: source, changed: false }\n }\n }\n\n let changed = false\n\n // Add the `jsx` plugin only for files that may contain JSX —\n // `.tsx` / `.jsx`. Mixing `jsx` + `typescript` on a `.ts` file\n // breaks the angle-bracket type-assertion syntax (`<T>x`); kept\n // off by default. `.tsx` files would otherwise fail to parse on\n // any embedded JSX.\n const isJsx = /\\.(?:tsx|jsx)$/i.test(filename)\n const parserPlugins: string[] = ['typescript', 'decorators-legacy', 'classProperties']\n if (isJsx) parserPlugins.push('jsx')\n\n const result = babel.transformSync(source, {\n filename,\n babelrc: false,\n configFile: false,\n sourceType: 'module',\n parserOpts: {\n plugins: parserPlugins as never,\n },\n generatorOpts: {\n retainLines: true,\n },\n plugins: [\n function devtoolsStripPlugin(): babel.PluginObj {\n return {\n name: 'kickjs-strip-devtools',\n visitor: {\n Program(path) {\n const devtoolsBindings = new Set<string>()\n\n // Pass 1 — drop devtools-kit imports + collect bound names.\n for (const stmt of path.get('body')) {\n if (!stmt.isImportDeclaration()) continue\n const src = stmt.node.source.value\n if (!DEVTOOLS_KIT_RE.test(src) && !DEVTOOLS_EVENTS_RE.test(src)) continue\n\n for (const spec of stmt.node.specifiers) {\n if (spec.local?.name) devtoolsBindings.add(spec.local.name)\n }\n stmt.remove()\n changed = true\n }\n\n if (devtoolsBindings.size === 0) return\n\n // Pass 2 — drop top-level expression statements rooted\n // in a stripped binding.\n for (const stmt of path.get('body')) {\n if (!stmt.isExpressionStatement()) continue\n const root = rootIdentifier(stmt.node.expression)\n if (root && devtoolsBindings.has(root)) {\n stmt.remove()\n changed = true\n }\n }\n },\n },\n }\n },\n ],\n })\n\n // Return the original source verbatim when nothing was stripped —\n // Babel's generator otherwise normalises whitespace + adds\n // semicolons, which would invalidate Vite's cache for files that\n // shouldn't have changed at all.\n if (!result || result.code == null || !changed) {\n return { code: source, changed: false }\n }\n return { code: result.code, changed: true }\n}\n\n/**\n * Walk a call/member expression tree to its root identifier. Returns\n * `null` for expressions we don't recognize (literals, sequence\n * expressions, etc.) — the caller skips those rather than guessing.\n */\nfunction rootIdentifier(expr: babel.types.Expression): string | null {\n let node: babel.types.Node = expr\n while (true) {\n if (node.type === 'Identifier') return node.name\n if (node.type === 'CallExpression') {\n node = node.callee\n continue\n }\n if (node.type === 'MemberExpression') {\n node = node.object\n continue\n }\n if (node.type === 'TSAsExpression' || node.type === 'TSNonNullExpression') {\n node = node.expression\n continue\n }\n return null\n }\n}\n","/**\n * Vite plugin wrapping `stripDevtoolsCode` for production builds.\n *\n * Runs only when Vite's `command === 'build'`. In dev the plugin is\n * a no-op so the devtools UI keeps working under `kick dev`.\n *\n * Pairs with `devtoolsFlagPlugin()` — that one constant-folds\n * `if (__KICKJS_DEVTOOLS__)` branches via Vite's existing DCE; this\n * one strips top-level devtools-kit imports + their `defineDevtoolsRenderTab(...)` /\n * `defineDevtoolsTab(...)` call sites without requiring adopters to\n * wrap them in the flag.\n *\n * Spec: docs/db/m3-plan.md §M3.C.\n */\n\nimport type { Plugin } from 'vite'\nimport { stripDevtoolsCode } from './babel-strip-devtools'\n\nexport interface DevtoolsStripOptions {\n /**\n * Force enable / disable. Default: enabled when `command === 'build'`.\n * Adopters running a debug-prod build (`kick build` with the flag\n * forced on) can pass `false` to keep devtools-kit code in the\n * bundle.\n */\n enabled?: boolean\n /**\n * Glob-like include pattern. Default: any `.ts` / `.tsx` /\n * `.mts` / `.cts` file under the project root. The transform is\n * a no-op on files that don't import devtools-kit, so this is\n * normally fine to leave at the default.\n */\n include?: RegExp\n}\n\nconst DEFAULT_INCLUDE = /\\.(?:m|c)?[jt]sx?$/\n\n/**\n * Strips devtools-kit imports + their top-level call sites from\n * production bundles. See `babel-strip-devtools.ts` for the exact\n * rule set.\n */\nexport function devtoolsStripPlugin(opts: DevtoolsStripOptions = {}): Plugin {\n let active = false\n const include = opts.include ?? DEFAULT_INCLUDE\n\n return {\n name: 'kickjs:devtools-strip',\n enforce: 'pre',\n apply: 'build',\n config(_userConfig, env) {\n active = opts.enabled ?? env.command === 'build'\n },\n transform(code, id) {\n if (!active) return null\n if (id.includes('node_modules')) return null\n if (!include.test(id)) return null\n const result = stripDevtoolsCode(code, id)\n if (!result.changed) return null\n return { code: result.code, map: null }\n },\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { reloadEnv } from '@forinda/kickjs'\nimport type { Plugin } from 'vite'\n\n/**\n * Vite plugin that watches `.env` files and triggers a full reload\n * when they change. This ensures the dev server picks up environment\n * variable changes without a manual restart.\n *\n * Lives in `@forinda/kickjs-vite` so all Vite-only concerns are in one\n * place.\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { envWatchPlugin } from '@forinda/kickjs-vite'\n *\n * export default defineConfig({\n * plugins: [swc.vite(), envWatchPlugin()],\n * })\n * ```\n */\nexport function envWatchPlugin(): Plugin {\n const envFiles = ['.env', '.env.local', '.env.development', '.env.production', '.env.test']\n\n return {\n name: 'kickjs-env-watch',\n\n configureServer(server) {\n const root = server.config.root\n\n for (const file of envFiles) {\n const filePath = path.resolve(root, file)\n if (fs.existsSync(filePath)) {\n server.watcher.add(filePath)\n }\n }\n\n server.watcher.on('change', (changedPath) => {\n const basename = path.basename(changedPath)\n if (envFiles.includes(basename)) {\n server.config.logger.info(` .env changed (${basename}), triggering reload...`, {\n timestamp: true,\n })\n\n // Re-read the .env file into process.env *before* invalidating\n // modules. Without this, the next SSR evaluation parses the\n // (stale) cached env snapshot — Zod throws on the missing key\n // and the user has to hard-restart the dev server.\n try {\n reloadEnv()\n } catch (err: any) {\n server.config.logger.warn(\n ` env reload failed: ${err?.message ?? err} — restart may be required`,\n { timestamp: true },\n )\n }\n\n // Invalidate all modules to trigger full HMR rebuild\n const mods = server.moduleGraph.getModulesByFile(path.resolve(root, 'src/index.ts'))\n if (mods) {\n for (const mod of mods) {\n server.moduleGraph.invalidateModule(mod)\n }\n }\n\n server.ws.send({ type: 'full-reload' })\n }\n })\n },\n }\n}\n","/**\n * @forinda/kickjs-vite — Vite plugin for the KickJS framework.\n *\n * Provides first-class Vite integration for KickJS backend applications:\n * - **Dev server**: Mounts Express on Vite's HTTP server (single port)\n * - **HMR**: Fresh server code on every request via `ssrLoadModule()`\n * - **httpServer piping**: Real `http.Server` available to all adapters\n * - **Virtual modules**: Auto-generated application entry\n *\n * ## Quick Start\n *\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite'\n * import { kickjsVitePlugin } from '@forinda/kickjs-vite'\n * import swc from 'unplugin-swc'\n *\n * export default defineConfig({\n * plugins: [\n * swc.vite({ tsconfigFile: 'tsconfig.json' }),\n * kickjsVitePlugin({ entry: 'src/index.ts' }),\n * ],\n * })\n * ```\n *\n * ```ts\n * // src/index.ts\n * import { bootstrap } from '@forinda/kickjs'\n * import { UserModule } from './modules/users/user.module'\n *\n * // Export the Express app — Vite serves it in dev, you start it in prod\n * export const app = bootstrap({\n * modules: [UserModule],\n * middleware: [express.json()],\n * })\n *\n * // Production: start the server directly\n * if (process.env.NODE_ENV === 'production') {\n * app.start()\n * }\n * ```\n *\n * ## Architecture\n *\n * The plugin returns an array of focused sub-plugins (React Router pattern):\n *\n * | Plugin | Responsibility |\n * |--------|---------------|\n * | `kickjs:core` | Vite config: appType, SSR environment, externals |\n * | `kickjs:module-discovery` | Auto-discover `@Module` classes via `transform()` |\n * | `kickjs:hmr` | Selective container invalidation via `handleHotUpdate()` |\n * | `kickjs:virtual-modules` | `virtual:kickjs/app` resolution and generation |\n * | `kickjs:dev-server` | `configureServer()` — mounts Express, pipes httpServer |\n *\n * ## httpServer Piping\n *\n * Vite creates the `http.Server` — this plugin stores it on\n * `globalThis.__kickjs_httpServer`. KickJS adapters (WsAdapter, Socket.IO,\n * GraphQL subscriptions) attach to this server via the standard\n * `afterStart({ server })` hook. Zero adapter code changes needed.\n *\n * @see v3/architecture.md — Full architecture documentation\n * @see v3/plan.md — Implementation plan and rationale\n * @see bench-mark/react-router-analysis.md — Pattern origin\n *\n * @module @forinda/kickjs-vite\n */\n\nimport { resolve } from 'node:path'\nimport type { Plugin } from 'vite'\nimport type { KickJSPluginOptions, PluginContext } from './types'\nimport { kickjsCorePlugin } from './core-plugin'\nimport { kickjsVirtualModulesPlugin } from './virtual-modules'\nimport { kickjsDevServerPlugin } from './dev-server'\nimport { kickjsModuleDiscoveryPlugin } from './module-discovery'\nimport { kickjsHmrPlugin } from './hmr-plugin'\nimport { devtoolsFlagPlugin } from './devtools-flag-plugin'\nimport { devtoolsStripPlugin } from './devtools-strip-plugin'\n\n/**\n * Create the KickJS Vite plugin array.\n *\n * Returns an array of focused sub-plugins that together provide full\n * Vite integration for KickJS backend applications. Each sub-plugin\n * has a single responsibility and runs at the appropriate Vite lifecycle stage.\n *\n * @param options - Plugin configuration\n * @param options.entry - Path to app entry file (default: 'src/index.ts')\n * @returns Array of Vite plugins\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite'\n * import { kickjsVitePlugin } from '@forinda/kickjs-vite'\n * import swc from 'unplugin-swc'\n *\n * export default defineConfig({\n * plugins: [\n * swc.vite({ tsconfigFile: 'tsconfig.json' }),\n * kickjsVitePlugin(),\n * ],\n * })\n * ```\n */\nexport function kickjsVitePlugin(options: KickJSPluginOptions = {}): Plugin[] {\n const entry = options.entry ?? 'src/index.ts'\n\n // Create shared context — resolved lazily in config hook since we\n // don't have the root directory until Vite resolves its config.\n const ctx: PluginContext = {\n entry,\n root: process.cwd(),\n }\n\n // Resolve root and entry as early as possible so sub-plugin config() hooks\n // (e.g., warmup) see the correct absolute entry path.\n const rootResolver: Plugin = {\n name: 'kickjs:root-resolver',\n config(config) {\n const root = config.root ?? process.cwd()\n ctx.root = root\n ctx.entry = resolve(root, entry)\n },\n }\n\n const plugins: Plugin[] = [\n rootResolver,\n kickjsCorePlugin(ctx),\n kickjsModuleDiscoveryPlugin(ctx),\n kickjsHmrPlugin(ctx, options.hmr),\n kickjsVirtualModulesPlugin(ctx),\n kickjsDevServerPlugin(ctx),\n ]\n // Register the devtools flag plugin by default. Adopters who don't\n // gate their devtools imports get a harmless no-op; adopters who do\n // get tree-shaking for free. Pass `devtools: false` to skip.\n if (options.devtools !== false) {\n plugins.push(devtoolsFlagPlugin(options.devtools ?? {}))\n // Babel-based strip — runs only on `vite build`, removes\n // devtools-kit imports + their top-level call sites without\n // requiring adopters to gate them behind the flag. See\n // `babel-strip-devtools.ts` for the rule set.\n plugins.push(devtoolsStripPlugin())\n }\n return plugins\n}\n\n// Re-export types for consumers\nexport type {\n KickJSPluginOptions,\n PluginContext,\n HmrOptions,\n HmrInvalidationContext,\n DevtoolsOptions,\n} from './types'\n\n// Standalone plugins users can compose alongside `kickjsVitePlugin()`\nexport { envWatchPlugin } from './env-watch-plugin'\nexport {\n devtoolsFlagPlugin,\n resolveDevtoolsFlag,\n type DevtoolsFlagOptions,\n} from './devtools-flag-plugin'\nexport { devtoolsStripPlugin, type DevtoolsStripOptions } from './devtools-strip-plugin'\nexport {\n stripDevtoolsCode,\n type StripDevtoolsOptions,\n type StripResult,\n} from './babel-strip-devtools'\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forinda/kickjs-vite",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Vite plugin for KickJS — dev server integration, HMR, virtual modules, httpServer piping",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kickjs",
|
|
@@ -62,13 +62,16 @@
|
|
|
62
62
|
]
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
-
"dependencies": {
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@babel/core": "^7.29.0"
|
|
67
|
+
},
|
|
66
68
|
"peerDependencies": {
|
|
67
69
|
"vite": ">=6.0.0",
|
|
68
70
|
"express": "^5.1.0",
|
|
69
71
|
"@forinda/kickjs": ">=2.3.0"
|
|
70
72
|
},
|
|
71
73
|
"devDependencies": {
|
|
74
|
+
"@types/babel__core": "^7.20.5",
|
|
72
75
|
"@types/express": "^5.0.6",
|
|
73
76
|
"@types/node": "^25.6.0",
|
|
74
77
|
"express": "^5.1.0",
|