@forinda/kickjs-vite 7.0.1 → 8.0.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 +0 -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 +10 -11
package/dist/index.d.mts
CHANGED
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/typegen-plugin.ts","../src/devtools-flag-plugin.ts","../src/devtools-strip-plugin.ts","../src/babel-strip-devtools.ts","../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/env-watch-plugin.ts","../src/typegen-plugin.ts","../src/devtools-flag-plugin.ts","../src/devtools-strip-plugin.ts","../src/babel-strip-devtools.ts","../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UAuBiB;;;;;;;;;;;;;;EAcf;;EAGA,MAAM;;;;;;;;;;;;;;;;;EAkBN,WAAW;;;;;;;UAQI;;EAEf;;EAEA;;;;;;;;;;;;UAae;;EAEf;;EAEA;;;;;;;;;;UAWe;;;;;;EAMf;;;;;;;;;;;;;;;;;EAkBA,kBAAkB,SAAS;;;;;;UAOZ;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;;;;iBC9Gc,kBAAkB;;;;UCIjB;EACf,eAAe,cAAc;EAC7B,wBAAwB;IACtB;IACA;IACA,cAAc;;IAEd,iBAAiB,kDAAkD;IACnE;IACA;IACA;;;UAkEa;;EAEf,WAAW,iBAAiB,QAAQ;;iBAGtB,oBAAoB,OAAM,uBAA4B;;;UC7ErD;;;;;;;;EAQf;;;;;;EAMA;;;;;;iBAOc,oBAAoB,OAAM,qBAA0B,MAAM;iBAY1D,mBAAmB,OAAM,sBAA2B;;;UC9CnD;;;;;;;EAOf;;;;;;;EAOA,UAAU;;;;;;;iBAUI,oBAAoB,OAAM,uBAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCGrD;;;;;;;;;EASf;;UAGe;;EAEf;;EAEA;;;;;;;iBAQc,kBACd,gBACA,kBACA,OAAM,uBACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCiCa,iBAAiB,UAAS,sBAA2B"}
|
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/warm.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/typegen-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 // ━━━ Eagerly bootstrap the app once the server is listening ━━━\n // The post-middleware below only evaluates the entry on the FIRST\n // request (lazy SSR). That means `bootstrap()`, adapter\n // `afterStart`, and the app's startup logs don't run until someone\n // hits the URL — the app looks \"not started\" until then. Warm the\n // module once the HTTP server is listening so startup behaves like\n // `node`/`tsx` (logs + adapters + the in-dev shutdown hook ready\n // immediately). Errors surface with a fixed stacktrace.\n const warm = () => {\n viteServer.ssrLoadModule(VIRTUAL_APP).catch((err: unknown) => {\n if (err instanceof Error) viteServer.ssrFixStacktrace(err)\n viteServer.config.logger.error(\n `[kickjs] failed to bootstrap app on startup: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n })\n }\n const httpServer = viteServer.httpServer\n if (httpServer) {\n if (httpServer.listening) warm()\n else httpServer.once('listening', warm)\n }\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 * Eager app re-warm after an invalidation.\n *\n * The dev server evaluates `virtual:kickjs/app` lazily — after a save\n * invalidates it, re-evaluation waits for the NEXT HTTP request. A\n * broken file (syntax error, failed import, boot throw) was therefore\n * silent until something hit the server, even though startup surfaced\n * the same class of error eagerly. Re-warming right after invalidation\n * pushes transform/bootstrap errors into the dev console the moment\n * the save lands.\n *\n * Errors are logged (with fixed stacktraces) but never thrown — the\n * dev loop must survive a mid-edit broken state; the next successful\n * save heals it.\n */\nimport type { ViteDevServer } from 'vite'\n\nimport { VIRTUAL_APP } from './virtual-modules'\n\nexport function rewarmApp(server: ViteDevServer, reason: string): void {\n server.ssrLoadModule(VIRTUAL_APP).catch((err: unknown) => {\n if (err instanceof Error) server.ssrFixStacktrace(err)\n server.config.logger.error(\n `[kickjs] app failed to reload after ${reason}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n { timestamp: true },\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'\nimport { rewarmApp } from './warm'\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 // Re-warm immediately so a broken module file surfaces its error on\n // save instead of on the next HTTP request (see warm.ts).\n rewarmApp(server, 'module discovery change')\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'\nimport { rewarmApp } from './warm'\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 * Shape of the reactive-container handle the KickJS runtime publishes on\n * `globalThis` for dev-mode HMR. Optional all the way down — the container\n * only exists after the app has bootstrapped at least once, and older\n * runtime versions may not expose `invalidate`.\n */\ninterface KickHmrGlobal {\n __kickjs_container?: {\n invalidate?: (token: string) => void\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 typeof globalThis & KickHmrGlobal).__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 // 2b. Re-warm immediately — without this, re-evaluation waits for the\n // next HTTP request, so a broken save (syntax error, boot throw)\n // stays SILENT until something hits the server. Mirrors the eager\n // startup warm in dev-server.ts.\n rewarmApp(server, `HMR invalidation (${batch.length} token${batch.length === 1 ? '' : 's'})`)\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","/**\n * kickjs:typegen — typegen-on-save for plain `vite` boots.\n *\n * `kick dev` owns the typegen watcher when it boots Vite (it claims\n * ownership on `globalThis` before `createServer`). But adopters who\n * run bare `vite` (the pre-fix scaffold default, or any Vite-embedding\n * tool) historically got working HMR with silently frozen\n * `.kickjs/types` — new routes lost their typing until a manual\n * `kick typegen`.\n *\n * This plugin closes that gap: when nothing has claimed ownership, it\n * dynamically loads the PROJECT's `@forinda/kickjs-cli` (optional peer\n * — resolved from the project root, never bundled) and wires the same\n * `createTypegenDevWatcher` engine `kick dev` uses, plus one startup\n * catch-up pass. No CLI installed → quiet no-op with a one-line notice.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\n/** Mirror of the CLI's TYPEGEN_OWNER_KEY — string-duplicated so this\n * module never needs the CLI at import time. */\nconst OWNER_KEY = '__kickjs_typegen_owner'\n\n/** Structural slice of the CLI surface this plugin consumes. */\nexport interface TypegenCliModule {\n loadKickConfig(cwd: string): Promise<unknown>\n createTypegenDevWatcher(opts: {\n cwd: string\n config: unknown\n emitWarning: (message: string) => void\n }): {\n handleWatchEvent(event: 'add' | 'change' | 'unlink' | 'unlinkDir', file: string): void\n runOnce(): void\n assetSrcRoots: readonly string[]\n dispose(): void\n }\n}\n\n/**\n * Resolve `@forinda/kickjs-cli` from the PROJECT root (not from this\n * package — under pnpm's strict layout the vite plugin can't see the\n * CLI through its own node_modules). Walks `node_modules` upward and\n * reads the manifest directly with fs: the CLI is ESM-only (no\n * `require` condition in its exports map), so `createRequire().resolve`\n * throws ERR_PACKAGE_PATH_NOT_EXPORTED and can't be used here.\n * Returns null when not installed or too old to export the watcher\n * engine.\n */\nfunction resolveCliEntry(root: string): string | null {\n let dir = root\n for (;;) {\n const pkgDir = join(dir, 'node_modules', '@forinda', 'kickjs-cli')\n const manifestPath = join(pkgDir, 'package.json')\n if (existsSync(manifestPath)) {\n try {\n const pkg = JSON.parse(readFileSync(manifestPath, 'utf8')) as {\n exports?: Record<string, unknown>\n module?: string\n main?: string\n }\n const dot = pkg.exports?.['.'] as string | Record<string, unknown> | undefined\n const fromExports =\n typeof dot === 'string'\n ? dot\n : typeof dot?.import === 'string'\n ? dot.import\n : typeof (dot?.import as Record<string, unknown> | undefined)?.default === 'string'\n ? ((dot!.import as Record<string, unknown>).default as string)\n : typeof dot?.default === 'string'\n ? (dot.default as string)\n : undefined\n const entryRel = fromExports ?? pkg.module ?? pkg.main\n if (typeof entryRel === 'string') return join(pkgDir, entryRel)\n } catch {\n // Unreadable manifest — keep walking up.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\nasync function loadCliFromProject(root: string): Promise<TypegenCliModule | null> {\n try {\n const entry = resolveCliEntry(root)\n if (!entry || !existsSync(entry)) return null\n const mod = (await import(pathToFileURL(entry).href)) as Partial<TypegenCliModule>\n if (\n typeof mod.createTypegenDevWatcher === 'function' &&\n typeof mod.loadKickConfig === 'function'\n ) {\n return mod as TypegenCliModule\n }\n return null\n } catch {\n return null\n }\n}\n\nexport interface TypegenPluginOptions {\n /** Test seam — defaults to project-root resolution of the real CLI. */\n loadCli?: (root: string) => Promise<TypegenCliModule | null>\n}\n\nexport function kickjsTypegenPlugin(opts: TypegenPluginOptions = {}): Plugin {\n const loadCli = opts.loadCli ?? loadCliFromProject\n return {\n name: 'kickjs:typegen',\n apply: 'serve',\n\n async configureServer(server: ViteDevServer) {\n // `kick dev` boots Vite in-process and runs its own watcher —\n // stand down so the pipeline never double-runs.\n if ((globalThis as Record<string, unknown>)[OWNER_KEY]) return\n\n const root = server.config.root\n const cli = await loadCli(root)\n if (!cli) {\n server.config.logger.info(\n '[kickjs] typegen-on-save disabled — @forinda/kickjs-cli not resolvable from the ' +\n 'project (install it, or run `kick dev`).',\n )\n return\n }\n\n const config = await cli.loadKickConfig(root).catch(() => null)\n const watcher = cli.createTypegenDevWatcher({\n cwd: root,\n config,\n emitWarning: (message) => {\n server.config.logger.warn(message)\n server.hot.send({\n type: 'custom',\n event: 'kickjs:typegen-error',\n data: { message, timestamp: Date.now() },\n })\n },\n })\n\n // Startup catch-up — bare `vite` has no pre-server typegen pass\n // (kick dev runs one before createServer), so types may be stale\n // from edits made while no dev server was running.\n watcher.runOnce()\n\n server.watcher.on('add', (f: string) => watcher.handleWatchEvent('add', f))\n server.watcher.on('change', (f: string) => watcher.handleWatchEvent('change', f))\n server.watcher.on('unlink', (f: string) => watcher.handleWatchEvent('unlink', f))\n server.watcher.on('unlinkDir', (d: string) => watcher.handleWatchEvent('unlinkDir', d))\n if (watcher.assetSrcRoots.length > 0) {\n server.watcher.add([...watcher.assetSrcRoots])\n }\n if (server.httpServer) {\n server.httpServer.once('close', () => watcher.dispose())\n } else {\n // Middleware mode — no httpServer; tie disposal to the chokidar\n // watcher's own close so the debounce timer can't leak.\n server.watcher.once('close', () => watcher.dispose())\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'\nimport { devtoolsStripPlugin } from './devtools-strip-plugin'\nimport { kickjsTypegenPlugin } from './typegen-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 // Typegen-on-save for bare `vite` boots — no-op under `kick dev`\n // (which claims ownership) or when @forinda/kickjs-cli is absent.\n kickjsTypegenPlugin(),\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 kickjsTypegenPlugin,\n type TypegenPluginOptions,\n type TypegenCliModule,\n} from './typegen-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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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/core-plugin.ts","../src/virtual-modules.ts","../src/dev-server.ts","../src/warm.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/typegen-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 // ━━━ Eagerly bootstrap the app once the server is listening ━━━\n // The post-middleware below only evaluates the entry on the FIRST\n // request (lazy SSR). That means `bootstrap()`, adapter\n // `afterStart`, and the app's startup logs don't run until someone\n // hits the URL — the app looks \"not started\" until then. Warm the\n // module once the HTTP server is listening so startup behaves like\n // `node`/`tsx` (logs + adapters + the in-dev shutdown hook ready\n // immediately). Errors surface with a fixed stacktrace.\n const warm = () => {\n viteServer.ssrLoadModule(VIRTUAL_APP).catch((err: unknown) => {\n if (err instanceof Error) viteServer.ssrFixStacktrace(err)\n viteServer.config.logger.error(\n `[kickjs] failed to bootstrap app on startup: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n })\n }\n const httpServer = viteServer.httpServer\n if (httpServer) {\n if (httpServer.listening) warm()\n else httpServer.once('listening', warm)\n }\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 * Eager app re-warm after an invalidation.\n *\n * The dev server evaluates `virtual:kickjs/app` lazily — after a save\n * invalidates it, re-evaluation waits for the NEXT HTTP request. A\n * broken file (syntax error, failed import, boot throw) was therefore\n * silent until something hit the server, even though startup surfaced\n * the same class of error eagerly. Re-warming right after invalidation\n * pushes transform/bootstrap errors into the dev console the moment\n * the save lands.\n *\n * Errors are logged (with fixed stacktraces) but never thrown — the\n * dev loop must survive a mid-edit broken state; the next successful\n * save heals it.\n */\nimport type { ViteDevServer } from 'vite'\n\nimport { VIRTUAL_APP } from './virtual-modules'\n\nexport function rewarmApp(server: ViteDevServer, reason: string): void {\n server.ssrLoadModule(VIRTUAL_APP).catch((err: unknown) => {\n if (err instanceof Error) server.ssrFixStacktrace(err)\n server.config.logger.error(\n `[kickjs] app failed to reload after ${reason}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n { timestamp: true },\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'\nimport { rewarmApp } from './warm'\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 // Re-warm immediately so a broken module file surfaces its error on\n // save instead of on the next HTTP request (see warm.ts).\n rewarmApp(server, 'module discovery change')\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'\nimport { rewarmApp } from './warm'\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 * Shape of the reactive-container handle the KickJS runtime publishes on\n * `globalThis` for dev-mode HMR. Optional all the way down — the container\n * only exists after the app has bootstrapped at least once, and older\n * runtime versions may not expose `invalidate`.\n */\ninterface KickHmrGlobal {\n __kickjs_container?: {\n invalidate?: (token: string) => void\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 typeof globalThis & KickHmrGlobal).__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 // 2b. Re-warm immediately — without this, re-evaluation waits for the\n // next HTTP request, so a broken save (syntax error, boot throw)\n // stays SILENT until something hits the server. Mirrors the eager\n // startup warm in dev-server.ts.\n rewarmApp(server, `HMR invalidation (${batch.length} token${batch.length === 1 ? '' : 's'})`)\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\n// Namespace import, not a default import: Babel 8 ships as native ESM and\n// `@babel/core` exposes only named exports — `import babel from '@babel/core'`\n// fails at load with \"does not provide an export named 'default'\".\nimport * as 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.PluginObject {\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","/**\n * kickjs:typegen — typegen-on-save for plain `vite` boots.\n *\n * `kick dev` owns the typegen watcher when it boots Vite (it claims\n * ownership on `globalThis` before `createServer`). But adopters who\n * run bare `vite` (the pre-fix scaffold default, or any Vite-embedding\n * tool) historically got working HMR with silently frozen\n * `.kickjs/types` — new routes lost their typing until a manual\n * `kick typegen`.\n *\n * This plugin closes that gap: when nothing has claimed ownership, it\n * dynamically loads the PROJECT's `@forinda/kickjs-cli` (optional peer\n * — resolved from the project root, never bundled) and wires the same\n * `createTypegenDevWatcher` engine `kick dev` uses, plus one startup\n * catch-up pass. No CLI installed → quiet no-op with a one-line notice.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\n/** Mirror of the CLI's TYPEGEN_OWNER_KEY — string-duplicated so this\n * module never needs the CLI at import time. */\nconst OWNER_KEY = '__kickjs_typegen_owner'\n\n/** Structural slice of the CLI surface this plugin consumes. */\nexport interface TypegenCliModule {\n loadKickConfig(cwd: string): Promise<unknown>\n createTypegenDevWatcher(opts: {\n cwd: string\n config: unknown\n emitWarning: (message: string) => void\n }): {\n handleWatchEvent(event: 'add' | 'change' | 'unlink' | 'unlinkDir', file: string): void\n runOnce(): void\n assetSrcRoots: readonly string[]\n dispose(): void\n }\n}\n\n/**\n * Resolve `@forinda/kickjs-cli` from the PROJECT root (not from this\n * package — under pnpm's strict layout the vite plugin can't see the\n * CLI through its own node_modules). Walks `node_modules` upward and\n * reads the manifest directly with fs: the CLI is ESM-only (no\n * `require` condition in its exports map), so `createRequire().resolve`\n * throws ERR_PACKAGE_PATH_NOT_EXPORTED and can't be used here.\n * Returns null when not installed or too old to export the watcher\n * engine.\n */\nfunction resolveCliEntry(root: string): string | null {\n let dir = root\n for (;;) {\n const pkgDir = join(dir, 'node_modules', '@forinda', 'kickjs-cli')\n const manifestPath = join(pkgDir, 'package.json')\n if (existsSync(manifestPath)) {\n try {\n const pkg = JSON.parse(readFileSync(manifestPath, 'utf8')) as {\n exports?: Record<string, unknown>\n module?: string\n main?: string\n }\n const dot = pkg.exports?.['.'] as string | Record<string, unknown> | undefined\n const fromExports =\n typeof dot === 'string'\n ? dot\n : typeof dot?.import === 'string'\n ? dot.import\n : typeof (dot?.import as Record<string, unknown> | undefined)?.default === 'string'\n ? ((dot!.import as Record<string, unknown>).default as string)\n : typeof dot?.default === 'string'\n ? (dot.default as string)\n : undefined\n const entryRel = fromExports ?? pkg.module ?? pkg.main\n if (typeof entryRel === 'string') return join(pkgDir, entryRel)\n } catch {\n // Unreadable manifest — keep walking up.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\nasync function loadCliFromProject(root: string): Promise<TypegenCliModule | null> {\n try {\n const entry = resolveCliEntry(root)\n if (!entry || !existsSync(entry)) return null\n const mod = (await import(pathToFileURL(entry).href)) as Partial<TypegenCliModule>\n if (\n typeof mod.createTypegenDevWatcher === 'function' &&\n typeof mod.loadKickConfig === 'function'\n ) {\n return mod as TypegenCliModule\n }\n return null\n } catch {\n return null\n }\n}\n\nexport interface TypegenPluginOptions {\n /** Test seam — defaults to project-root resolution of the real CLI. */\n loadCli?: (root: string) => Promise<TypegenCliModule | null>\n}\n\nexport function kickjsTypegenPlugin(opts: TypegenPluginOptions = {}): Plugin {\n const loadCli = opts.loadCli ?? loadCliFromProject\n return {\n name: 'kickjs:typegen',\n apply: 'serve',\n\n async configureServer(server: ViteDevServer) {\n // `kick dev` boots Vite in-process and runs its own watcher —\n // stand down so the pipeline never double-runs.\n if ((globalThis as Record<string, unknown>)[OWNER_KEY]) return\n\n const root = server.config.root\n const cli = await loadCli(root)\n if (!cli) {\n server.config.logger.info(\n '[kickjs] typegen-on-save disabled — @forinda/kickjs-cli not resolvable from the ' +\n 'project (install it, or run `kick dev`).',\n )\n return\n }\n\n const config = await cli.loadKickConfig(root).catch(() => null)\n const watcher = cli.createTypegenDevWatcher({\n cwd: root,\n config,\n emitWarning: (message) => {\n server.config.logger.warn(message)\n server.hot.send({\n type: 'custom',\n event: 'kickjs:typegen-error',\n data: { message, timestamp: Date.now() },\n })\n },\n })\n\n // Startup catch-up — bare `vite` has no pre-server typegen pass\n // (kick dev runs one before createServer), so types may be stale\n // from edits made while no dev server was running.\n watcher.runOnce()\n\n server.watcher.on('add', (f: string) => watcher.handleWatchEvent('add', f))\n server.watcher.on('change', (f: string) => watcher.handleWatchEvent('change', f))\n server.watcher.on('unlink', (f: string) => watcher.handleWatchEvent('unlink', f))\n server.watcher.on('unlinkDir', (d: string) => watcher.handleWatchEvent('unlinkDir', d))\n if (watcher.assetSrcRoots.length > 0) {\n server.watcher.add([...watcher.assetSrcRoots])\n }\n if (server.httpServer) {\n server.httpServer.once('close', () => watcher.dispose())\n } else {\n // Middleware mode — no httpServer; tie disposal to the chokidar\n // watcher's own close so the debounce timer can't leak.\n server.watcher.once('close', () => watcher.dispose())\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'\nimport { devtoolsStripPlugin } from './devtools-strip-plugin'\nimport { kickjsTypegenPlugin } from './typegen-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 // Typegen-on-save for bare `vite` boots — no-op under `kick dev`\n // (which claims ownership) or when @forinda/kickjs-cli is absent.\n kickjsTypegenPlugin(),\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 kickjsTypegenPlugin,\n type TypegenPluginOptions,\n type TypegenCliModule,\n} from './typegen-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": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Vite plugin for KickJS — dev server integration, HMR, virtual modules, httpServer piping",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kickjs",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"globals.d.ts"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@babel/core": "^
|
|
35
|
+
"@babel/core": "^8.0.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"vite": ">=6.0.0",
|
|
@@ -41,15 +41,14 @@
|
|
|
41
41
|
"@forinda/kickjs-cli": ">=6.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@types/babel__core": "^7.20.5",
|
|
45
44
|
"@types/express": "^5.0.6",
|
|
46
|
-
"@types/node": "^
|
|
45
|
+
"@types/node": "^26.1.0",
|
|
47
46
|
"express": "^5.1.0",
|
|
48
|
-
"typescript": "^
|
|
49
|
-
"vite": "^8.
|
|
50
|
-
"vitest": "^4.1.
|
|
51
|
-
"@forinda/kickjs": "6.
|
|
52
|
-
"@forinda/kickjs-cli": "6.
|
|
47
|
+
"typescript": "^7.0.2",
|
|
48
|
+
"vite": "^8.1.3",
|
|
49
|
+
"vitest": "^4.1.9",
|
|
50
|
+
"@forinda/kickjs": "6.5.0",
|
|
51
|
+
"@forinda/kickjs-cli": "6.9.0"
|
|
53
52
|
},
|
|
54
53
|
"publishConfig": {
|
|
55
54
|
"access": "public"
|
|
@@ -57,7 +56,7 @@
|
|
|
57
56
|
"license": "MIT",
|
|
58
57
|
"author": "Felix Orinda",
|
|
59
58
|
"engines": {
|
|
60
|
-
"node": ">=
|
|
59
|
+
"node": "^22.18.0 || >=24.11.0"
|
|
61
60
|
},
|
|
62
61
|
"homepage": "https://kickjs.app/",
|
|
63
62
|
"repository": {
|
|
@@ -77,7 +76,7 @@
|
|
|
77
76
|
"build": "tsdown",
|
|
78
77
|
"dev": "tsdown --watch",
|
|
79
78
|
"test": "vitest run --passWithNoTests",
|
|
80
|
-
"typecheck": "
|
|
79
|
+
"typecheck": "tsc --noEmit",
|
|
81
80
|
"clean": "rm -rf dist .wireit"
|
|
82
81
|
}
|
|
83
82
|
}
|