@forinda/kickjs-cli 5.1.0 → 5.2.1

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.
Files changed (27) hide show
  1. package/dist/{builtins-caRjFvKz.mjs → builtins-B0dptoXq.mjs} +626 -241
  2. package/dist/builtins-B0dptoXq.mjs.map +1 -0
  3. package/dist/{builtins-Cb_d-b1S.mjs → builtins-N3mDa6bM.mjs} +695 -273
  4. package/dist/cli.mjs +9 -9
  5. package/dist/{config-8bAt-mLl.mjs → config-Bc6ERRTE.mjs} +35 -5
  6. package/dist/{config-C_LQNClP.mjs → config-CRi3zCxk.mjs} +36 -6
  7. package/dist/config-CRi3zCxk.mjs.map +1 -0
  8. package/dist/{generator-extension-CYY-RI21.mjs → generator-extension-C-HwKvFf.mjs} +76 -39
  9. package/dist/generator-extension-C-HwKvFf.mjs.map +1 -0
  10. package/dist/index.d.mts +282 -1
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +4 -4
  13. package/dist/{plugin-D8K5fG-O.mjs → plugin-DfomEcef.mjs} +4 -4
  14. package/dist/{plugin-D8K5fG-O.mjs.map → plugin-DfomEcef.mjs.map} +1 -1
  15. package/dist/{plugin-Bgfg7qMk.mjs → plugin-b7ig7Uxv.mjs} +2 -2
  16. package/dist/{rolldown-runtime-BM29JyaJ.mjs → rolldown-runtime-CV_zlh2d.mjs} +1 -1
  17. package/dist/{run-plugins-BXvMFPhJ.mjs → run-plugins-D9abb5Nx.mjs} +2 -2
  18. package/dist/{typegen-BNz_RQTb.mjs → typegen-B9S81bOx.mjs} +48 -225
  19. package/dist/typegen-B9S81bOx.mjs.map +1 -0
  20. package/dist/{typegen-8ZeA1B-X.mjs → typegen-BKUAdp_3.mjs} +47 -228
  21. package/dist/{types-BBUo1vXh.mjs → types-CU89yUxU.mjs} +2 -2
  22. package/dist/{types-BBUo1vXh.mjs.map → types-CU89yUxU.mjs.map} +1 -1
  23. package/package.json +6 -4
  24. package/dist/builtins-caRjFvKz.mjs.map +0 -1
  25. package/dist/config-C_LQNClP.mjs.map +0 -1
  26. package/dist/generator-extension-CYY-RI21.mjs.map +0 -1
  27. package/dist/typegen-8ZeA1B-X.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v5.1.0
2
+ * @forinda/kickjs-cli v5.2.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,13 +8,13 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { t as __exportAll } from "./rolldown-runtime-BM29JyaJ.mjs";
12
- import { i as resolveModuleConfig, r as loadKickConfig, t as PACKAGE_MANAGERS } from "./config-8bAt-mLl.mjs";
13
- import { n as mergeCliPlugins, r as defineCliPlugin } from "./plugin-Bgfg7qMk.mjs";
14
- import { a as discoverAssets, i as TokenCollisionError, o as renderAssetTypes, r as watchTypegen, t as runTypegen$1 } from "./typegen-BNz_RQTb.mjs";
11
+ import { t as __exportAll } from "./rolldown-runtime-CV_zlh2d.mjs";
12
+ import { a as resolveTokenScope, i as resolveModuleConfig, r as loadKickConfig, t as PACKAGE_MANAGERS } from "./config-Bc6ERRTE.mjs";
13
+ import { n as mergeCliPlugins, r as defineCliPlugin } from "./plugin-b7ig7Uxv.mjs";
14
+ import { a as discoverAssets, i as TokenCollisionError, o as renderAssetTypes, r as watchTypegen, s as scanProject, t as runTypegen$1 } from "./typegen-BKUAdp_3.mjs";
15
15
  import { createRequire } from "node:module";
16
16
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
17
- import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
17
+ import path, { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
18
18
  import { fileURLToPath, pathToFileURL } from "node:url";
19
19
  import { execSync, fork, spawn, spawnSync } from "node:child_process";
20
20
  import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
@@ -22,6 +22,7 @@ import * as clack from "@clack/prompts";
22
22
  import pc from "picocolors";
23
23
  import pkg from "pluralize";
24
24
  import { glob } from "glob";
25
+ import { groupAssetKeys } from "@forinda/kickjs";
25
26
  import { arch, platform, release } from "node:os";
26
27
  import { generate, migrateDown, migrateLatest, migrateRollback, migrateStatus, migrateUp, renderSchemaSource, resolveDbConfig } from "@forinda/kickjs-db";
27
28
  //#region src/utils/shell.ts
@@ -69,7 +70,7 @@ let _dryRun = false;
69
70
  function setDryRun(enabled) {
70
71
  _dryRun = enabled;
71
72
  }
72
- /** Extensions prettier can format. Anything else is written verbatim. */
73
+ /** Extensions oxfmt can format. Anything else is written verbatim. */
73
74
  const FORMATTABLE = new Set([
74
75
  ".ts",
75
76
  ".tsx",
@@ -83,15 +84,14 @@ const FORMATTABLE = new Set([
83
84
  /**
84
85
  * Write a file, creating parent directories if needed.
85
86
  *
86
- * After write, runs prettier against the file when:
87
+ * After write, runs oxfmt against the file when:
87
88
  * - format-on-write is enabled (default)
88
89
  * - the extension is in {@link FORMATTABLE}
89
- * - prettier resolves from the user's project (or our own cwd)
90
+ * - oxfmt resolves from the user's project (or our own cwd)
90
91
  *
91
- * Failures (missing prettier, unparseable source, prettier crash) are
92
+ * Failures (missing oxfmt, unparseable source, formatter crash) are
92
93
  * swallowed silently — formatting is a polish step, not a correctness
93
- * gate. The pre-existing pre-commit hook still catches anything we
94
- * couldn't format.
94
+ * gate. The pre-commit hook still catches anything we couldn't format.
95
95
  *
96
96
  * Skips writing entirely in dry run mode.
97
97
  */
@@ -101,28 +101,58 @@ async function writeFileSafe(filePath, content) {
101
101
  await writeFile(filePath, content, "utf-8");
102
102
  if (FORMATTABLE.has(extname(filePath))) await formatFile(filePath, content).catch(() => {});
103
103
  }
104
- let _prettier = void 0;
105
- /** Resolve prettier from the user's project; cache the result (or null) for the process. */
106
- function resolvePrettier(cwd) {
107
- if (_prettier !== void 0) return _prettier;
104
+ let _oxfmt = void 0;
105
+ /** Resolve oxfmt from the user's project; cache the result (or null) for the process. */
106
+ async function resolveOxfmt(cwd) {
107
+ if (_oxfmt !== void 0) return _oxfmt;
108
108
  try {
109
- _prettier = createRequire(join(cwd, "package.json"))("prettier");
109
+ _oxfmt = await import(createRequire(join(cwd, "package.json")).resolve("oxfmt"));
110
110
  } catch {
111
- _prettier = null;
111
+ _oxfmt = null;
112
112
  }
113
- return _prettier;
113
+ return _oxfmt;
114
114
  }
115
115
  async function formatFile(filePath, content) {
116
- const prettier = resolvePrettier(process.cwd());
117
- if (!prettier) return;
118
- if ((await prettier.getFileInfo(filePath, { resolveConfig: true })).ignored) return;
119
- const config = await prettier.resolveConfig(filePath) ?? {};
120
- const formatted = await prettier.format(content, {
121
- ...config,
122
- filepath: filePath
123
- });
124
- if (formatted === content) return;
125
- await writeFile(filePath, formatted, "utf-8");
116
+ const oxfmt = await resolveOxfmt(process.cwd());
117
+ if (!oxfmt) return;
118
+ const options = await loadOxfmtConfig(filePath);
119
+ if (options === null) return;
120
+ const result = await oxfmt.format(filePath, content, options);
121
+ if (result.code === content) return;
122
+ await writeFile(filePath, result.code, "utf-8");
123
+ }
124
+ const _oxfmtConfigCache = /* @__PURE__ */ new Map();
125
+ /**
126
+ * Walk up from `filePath`'s directory looking for `.oxfmtrc.json`.
127
+ * Returns `null` when no config is found anywhere on the path —
128
+ * generators then leave the raw template alone (which already
129
+ * follows project conventions). Cached per starting directory so
130
+ * the walk is one-shot per generator run.
131
+ */
132
+ async function loadOxfmtConfig(filePath) {
133
+ let dir = dirname(filePath);
134
+ const startDir = dir;
135
+ if (_oxfmtConfigCache.has(startDir)) return _oxfmtConfigCache.get(startDir);
136
+ while (true) {
137
+ const configPath = join(dir, ".oxfmtrc.json");
138
+ if (existsSync(configPath)) try {
139
+ const raw = await readFile(configPath, "utf-8");
140
+ const parsed = JSON.parse(raw);
141
+ delete parsed["$schema"];
142
+ delete parsed.ignorePatterns;
143
+ _oxfmtConfigCache.set(startDir, parsed);
144
+ return parsed;
145
+ } catch {
146
+ _oxfmtConfigCache.set(startDir, null);
147
+ return null;
148
+ }
149
+ const parent = dirname(dir);
150
+ if (parent === dir) {
151
+ _oxfmtConfigCache.set(startDir, null);
152
+ return null;
153
+ }
154
+ dir = parent;
155
+ }
126
156
  }
127
157
  /** Check if a file exists */
128
158
  async function fileExists(filePath) {
@@ -1606,7 +1636,7 @@ async function initProject(options) {
1606
1636
  }
1607
1637
  }
1608
1638
  try {
1609
- const { runTypegen } = await import("./typegen-BNz_RQTb.mjs").then((n) => n.n);
1639
+ const { runTypegen } = await import("./typegen-BKUAdp_3.mjs").then((n) => n.n);
1610
1640
  await runTypegen({
1611
1641
  cwd: dir,
1612
1642
  allowDuplicates: true,
@@ -1743,6 +1773,278 @@ function spinner() {
1743
1773
  /** Log utilities for styled messages inside clack flow */
1744
1774
  const log = clack.log;
1745
1775
  //#endregion
1776
+ //#region src/commands/add.ts
1777
+ /** Registry of KickJS packages and their required peer dependencies */
1778
+ const PACKAGE_REGISTRY = {
1779
+ kickjs: {
1780
+ pkg: "@forinda/kickjs",
1781
+ peers: ["express"],
1782
+ description: "Unified framework: DI, decorators, routing, middleware",
1783
+ core: true
1784
+ },
1785
+ vite: {
1786
+ pkg: "@forinda/kickjs-vite",
1787
+ peers: ["vite"],
1788
+ description: "Vite plugin: dev server, HMR, module discovery",
1789
+ dev: true,
1790
+ core: true
1791
+ },
1792
+ cli: {
1793
+ pkg: "@forinda/kickjs-cli",
1794
+ peers: [],
1795
+ description: "CLI tool and code generators",
1796
+ dev: true,
1797
+ core: true
1798
+ },
1799
+ swagger: {
1800
+ pkg: "@forinda/kickjs-swagger",
1801
+ peers: [],
1802
+ description: "OpenAPI spec + Swagger UI + ReDoc"
1803
+ },
1804
+ db: {
1805
+ pkg: "@forinda/kickjs-db",
1806
+ peers: [],
1807
+ description: "kick/db core — schema DSL, migrations, KickDbClient, customType"
1808
+ },
1809
+ "db-pg": {
1810
+ pkg: "@forinda/kickjs-db-pg",
1811
+ peers: ["pg"],
1812
+ description: "kick/db PostgreSQL dialect + adapter (pgDialect, pgAdapter)"
1813
+ },
1814
+ drizzle: {
1815
+ pkg: "@forinda/kickjs-drizzle",
1816
+ peers: ["drizzle-orm"],
1817
+ description: "Drizzle ORM adapter + query builder"
1818
+ },
1819
+ prisma: {
1820
+ pkg: "@forinda/kickjs-prisma",
1821
+ peers: ["@prisma/client"],
1822
+ description: "Prisma adapter + query builder"
1823
+ },
1824
+ ws: {
1825
+ pkg: "@forinda/kickjs-ws",
1826
+ peers: ["socket.io"],
1827
+ description: "WebSocket with @WsController decorators"
1828
+ },
1829
+ devtools: {
1830
+ pkg: "@forinda/kickjs-devtools",
1831
+ peers: [],
1832
+ description: "Development dashboard — routes, DI, metrics, health",
1833
+ dev: true
1834
+ },
1835
+ auth: {
1836
+ pkg: "@forinda/kickjs-auth",
1837
+ peers: ["jsonwebtoken"],
1838
+ description: "Authentication — JWT, API key, and custom strategies"
1839
+ },
1840
+ queue: {
1841
+ pkg: "@forinda/kickjs-queue",
1842
+ peers: [],
1843
+ description: "Queue adapter (BullMQ/RabbitMQ/Kafka)"
1844
+ },
1845
+ "queue:bullmq": {
1846
+ pkg: "@forinda/kickjs-queue",
1847
+ peers: ["bullmq", "ioredis"],
1848
+ description: "Queue with BullMQ + Redis"
1849
+ },
1850
+ "queue:rabbitmq": {
1851
+ pkg: "@forinda/kickjs-queue",
1852
+ peers: ["amqplib"],
1853
+ description: "Queue with RabbitMQ"
1854
+ },
1855
+ "queue:kafka": {
1856
+ pkg: "@forinda/kickjs-queue",
1857
+ peers: ["kafkajs"],
1858
+ description: "Queue with Kafka"
1859
+ },
1860
+ mcp: {
1861
+ pkg: "@forinda/kickjs-mcp",
1862
+ peers: ["@modelcontextprotocol/sdk"],
1863
+ description: "Model Context Protocol server — expose @Controller endpoints as AI tools"
1864
+ },
1865
+ testing: {
1866
+ pkg: "@forinda/kickjs-testing",
1867
+ peers: [],
1868
+ description: "Test utilities and TestModule builder",
1869
+ dev: true
1870
+ }
1871
+ };
1872
+ /**
1873
+ * Walk up from `fromDir` to filesystem root, returning the first
1874
+ * directory that contains `name`. Lets monorepo sub-packages pick up
1875
+ * lockfiles and `packageManager` fields living at the workspace root.
1876
+ */
1877
+ function findUp(name, fromDir = process.cwd()) {
1878
+ let current = fromDir;
1879
+ while (true) {
1880
+ if (existsSync(resolve(current, name))) return current;
1881
+ const parent = dirname(current);
1882
+ if (parent === current) return null;
1883
+ current = parent;
1884
+ }
1885
+ }
1886
+ function detectFromLockfile() {
1887
+ if (findUp("pnpm-lock.yaml")) return "pnpm";
1888
+ if (findUp("yarn.lock")) return "yarn";
1889
+ if (findUp("bun.lockb") || findUp("bun.lock")) return "bun";
1890
+ if (findUp("package-lock.json")) return "npm";
1891
+ return null;
1892
+ }
1893
+ /**
1894
+ * Read `packageManager` from the nearest ancestor `package.json` that
1895
+ * declares the field (corepack convention: `"pnpm@10.0.0"`). Climbs so
1896
+ * monorepo sub-packages inherit the workspace pm even when their own
1897
+ * package.json omits the field.
1898
+ */
1899
+ function packageManagerFromPackageJson() {
1900
+ let dir = process.cwd();
1901
+ while (dir) {
1902
+ const pkgPath = resolve(dir, "package.json");
1903
+ if (existsSync(pkgPath)) try {
1904
+ const field = JSON.parse(readFileSync(pkgPath, "utf-8")).packageManager;
1905
+ if (typeof field === "string") {
1906
+ const name = field.split("@")[0];
1907
+ if (PACKAGE_MANAGERS.includes(name)) return name;
1908
+ }
1909
+ } catch {}
1910
+ const parent = dirname(dir);
1911
+ if (parent === dir) return null;
1912
+ dir = parent;
1913
+ }
1914
+ return null;
1915
+ }
1916
+ /**
1917
+ * Resolve which package manager to use, in priority order:
1918
+ * 1. `--pm` CLI flag
1919
+ * 2. `packageManager` in kick.config
1920
+ * 3. `packageManager` in nearest ancestor package.json (corepack)
1921
+ * 4. Nearest ancestor lockfile (pnpm-lock.yaml → yarn.lock → bun.lock → package-lock.json)
1922
+ * 5. `'npm'` fallback
1923
+ *
1924
+ * Returns the chosen pm plus the source for callers that want to log
1925
+ * the resolution path.
1926
+ */
1927
+ async function resolvePackageManagerWithSource(flagPm) {
1928
+ if (flagPm && PACKAGE_MANAGERS.includes(flagPm)) return {
1929
+ pm: flagPm,
1930
+ source: "flag"
1931
+ };
1932
+ const config = await loadKickConfig(process.cwd());
1933
+ if (config?.packageManager && PACKAGE_MANAGERS.includes(config.packageManager)) return {
1934
+ pm: config.packageManager,
1935
+ source: "config"
1936
+ };
1937
+ const fromPkg = packageManagerFromPackageJson();
1938
+ if (fromPkg) return {
1939
+ pm: fromPkg,
1940
+ source: "package.json"
1941
+ };
1942
+ const fromLock = detectFromLockfile();
1943
+ if (fromLock) return {
1944
+ pm: fromLock,
1945
+ source: "lockfile"
1946
+ };
1947
+ return {
1948
+ pm: "npm",
1949
+ source: "default"
1950
+ };
1951
+ }
1952
+ /** Convenience wrapper for callers that don't care about the source. */
1953
+ async function resolvePackageManager(flagPm) {
1954
+ const { pm } = await resolvePackageManagerWithSource(flagPm);
1955
+ return pm;
1956
+ }
1957
+ /**
1958
+ * Print the package catalog. By default shows just the three core
1959
+ * packages every project always has — the optional list churns
1960
+ * (packages added, deprecated, removed) and a long enumeration in CLI
1961
+ * output / docs goes stale within a release. Pass `all = true` to dump
1962
+ * everything; that's what `kick add --list --all` triggers when an
1963
+ * adopter genuinely wants the live catalog.
1964
+ */
1965
+ function printPackageList(all = false) {
1966
+ const entries = Object.entries(PACKAGE_REGISTRY);
1967
+ const maxName = Math.max(...entries.map(([k]) => k.length));
1968
+ const core = entries.filter(([, info]) => info.core);
1969
+ const optional = entries.filter(([, info]) => !info.core);
1970
+ const formatRow = ([name, info]) => {
1971
+ const padded = name.padEnd(maxName + 2);
1972
+ const peers = info.peers.length ? ` (+ ${info.peers.join(", ")})` : "";
1973
+ return ` ${padded} ${info.description}${peers}`;
1974
+ };
1975
+ console.log("\n Core packages (always installed by `kick new`):\n");
1976
+ for (const row of core) console.log(formatRow(row));
1977
+ if (all) {
1978
+ console.log("\n Optional packages (add as needed):\n");
1979
+ for (const row of optional) console.log(formatRow(row));
1980
+ } else {
1981
+ console.log(`\n Plus ${optional.length} optional packages (auth, swagger, db, queue, …).`);
1982
+ console.log(" Run `kick add --list --all` for the full catalog.");
1983
+ }
1984
+ console.log("\n Usage: kick add auth drizzle swagger");
1985
+ console.log(" kick add queue:bullmq");
1986
+ console.log();
1987
+ }
1988
+ function registerListCommand(program) {
1989
+ program.command("list").alias("ls").description("List KickJS packages (core only; pair with --all for the full catalog)").option("--all", "Include the full optional catalog").action((opts) => {
1990
+ printPackageList(Boolean(opts.all));
1991
+ });
1992
+ }
1993
+ function registerAddCommand(program) {
1994
+ program.command("add [packages...]").description("Add KickJS packages with their required dependencies").option("--pm <manager>", "Package manager override").option("-D, --dev", "Install as dev dependency").option("--list", "List packages (core only by default; pair with --all)").option("--all", "When listing, include the full optional catalog").action(async (packages, opts) => {
1995
+ if (opts.list || packages.length === 0) {
1996
+ printPackageList(Boolean(opts.all));
1997
+ return;
1998
+ }
1999
+ const { pm, source } = await resolvePackageManagerWithSource(opts.pm);
2000
+ console.log(`\n Using ${pm} (resolved from ${source})`);
2001
+ const forceDevFlag = opts.dev;
2002
+ const prodDeps = /* @__PURE__ */ new Set();
2003
+ const devDeps = /* @__PURE__ */ new Set();
2004
+ const unknown = [];
2005
+ for (const name of packages) {
2006
+ const entry = PACKAGE_REGISTRY[name];
2007
+ if (!entry) {
2008
+ unknown.push(name);
2009
+ continue;
2010
+ }
2011
+ const target = forceDevFlag || entry.dev ? devDeps : prodDeps;
2012
+ target.add(entry.pkg);
2013
+ for (const peer of entry.peers) target.add(peer);
2014
+ }
2015
+ if (unknown.length > 0) {
2016
+ console.log(`\n Unknown packages: ${unknown.join(", ")}`);
2017
+ console.log(" Run \"kick add --list\" to see available packages.\n");
2018
+ if (prodDeps.size === 0 && devDeps.size === 0) return;
2019
+ }
2020
+ if (prodDeps.size > 0) {
2021
+ const deps = Array.from(prodDeps);
2022
+ const cmd = `${pm} add ${deps.join(" ")}`;
2023
+ console.log(`\n Installing ${deps.length} dependency(ies):`);
2024
+ for (const dep of deps) console.log(` + ${dep}`);
2025
+ console.log();
2026
+ try {
2027
+ execSync(cmd, { stdio: "inherit" });
2028
+ } catch {
2029
+ console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
2030
+ }
2031
+ }
2032
+ if (devDeps.size > 0) {
2033
+ const deps = Array.from(devDeps);
2034
+ const cmd = `${pm} add -D ${deps.join(" ")}`;
2035
+ console.log(`\n Installing ${deps.length} dev dependency(ies):`);
2036
+ for (const dep of deps) console.log(` + ${dep} (dev)`);
2037
+ console.log();
2038
+ try {
2039
+ execSync(cmd, { stdio: "inherit" });
2040
+ } catch {
2041
+ console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
2042
+ }
2043
+ }
2044
+ console.log(" Done!\n");
2045
+ });
2046
+ }
2047
+ //#endregion
1746
2048
  //#region src/commands/init.ts
1747
2049
  /** All optional packages available for selection */
1748
2050
  const OPTIONAL_PACKAGES = [
@@ -1773,9 +2075,11 @@ const OPTIONAL_PACKAGES = [
1773
2075
  }
1774
2076
  ];
1775
2077
  function registerInitCommand(program) {
1776
- program.command("new [name]").alias("init").description("Create a new KickJS project (use \".\" for current directory)").option("-d, --directory <dir>", "Target directory (defaults to project name)").option("--pm <manager>", "Package manager: pnpm | npm | yarn | bun").option("--git", "Initialize git repository").option("--no-git", "Skip git initialization").option("--install", "Install dependencies after scaffolding").option("--no-install", "Skip dependency installation").option("-f, --force", "Remove existing files without prompting").option("-t, --template <type>", "Project template: rest | ddd | cqrs | minimal").option("-r, --repo <type>", "Default repository: prisma | drizzle | inmemory | custom").option("--packages <packages>", "Comma-separated packages to include (e.g. auth,swagger,ws,queue)").action(async (name, opts) => {
2078
+ program.command("new [name]").alias("init").description("Create a new KickJS project (use \".\" for current directory)").option("-d, --directory <dir>", "Target directory (defaults to project name)").option("--pm <manager>", "Package manager: pnpm | npm | yarn | bun").option("--git", "Initialize git repository").option("--no-git", "Skip git initialization").option("--install", "Install dependencies after scaffolding").option("--no-install", "Skip dependency installation").option("-f, --force", "Remove existing files without prompting").option("-t, --template <type>", "Project template: rest | ddd | cqrs | minimal").option("-r, --repo <type>", "Default repository: prisma | drizzle | inmemory | custom").option("--packages <packages>", "Comma-separated packages to include (e.g. auth,swagger,ws,queue)").option("-y, --yes", "Pick safe defaults for every prompt (template=minimal, repo=inmemory, no extras, git+install on)").option("--non-interactive", "alias for --yes").action(async (name, opts) => {
1777
2079
  intro("KickJS — Create a new project");
1778
- if (!name) name = await text({
2080
+ const yes = Boolean(opts.yes || opts.nonInteractive);
2081
+ if (!name) if (yes) name = "my-api";
2082
+ else name = await text({
1779
2083
  message: "Project name",
1780
2084
  placeholder: "my-api",
1781
2085
  defaultValue: "my-api"
@@ -1789,7 +2093,11 @@ function registerInitCommand(program) {
1789
2093
  const entries = readdirSync(directory);
1790
2094
  if (entries.length > 0) {
1791
2095
  if (opts.force) log.warn(`Clearing existing files in ${directory}`);
1792
- else {
2096
+ else if (yes) {
2097
+ log.warn(`Directory "${name}" is not empty. Pass --force to clear it.`);
2098
+ outro("Aborted.");
2099
+ return;
2100
+ } else {
1793
2101
  log.warn(`Directory "${name}" is not empty:`);
1794
2102
  const shown = entries.slice(0, 5);
1795
2103
  for (const entry of shown) log.message(` - ${entry}`);
@@ -1809,7 +2117,8 @@ function registerInitCommand(program) {
1809
2117
  }
1810
2118
  }
1811
2119
  let template = opts.template;
1812
- if (!template) template = await select({
2120
+ if (!template) if (yes) template = "minimal";
2121
+ else template = await select({
1813
2122
  message: "Project template",
1814
2123
  options: [
1815
2124
  {
@@ -1835,7 +2144,8 @@ function registerInitCommand(program) {
1835
2144
  ]
1836
2145
  });
1837
2146
  let packageManager = opts.pm;
1838
- if (!packageManager) packageManager = await select({
2147
+ if (!packageManager) if (yes) packageManager = await resolvePackageManager(void 0);
2148
+ else packageManager = await select({
1839
2149
  message: "Package manager",
1840
2150
  options: [
1841
2151
  {
@@ -1857,7 +2167,8 @@ function registerInitCommand(program) {
1857
2167
  ]
1858
2168
  });
1859
2169
  let defaultRepo = opts.repo;
1860
- if (!defaultRepo) {
2170
+ if (!defaultRepo) if (yes) defaultRepo = "inmemory";
2171
+ else {
1861
2172
  defaultRepo = await select({
1862
2173
  message: "Default repository/ORM",
1863
2174
  options: [
@@ -1890,19 +2201,20 @@ function registerInitCommand(program) {
1890
2201
  const raw = opts.packages.trim().toLowerCase();
1891
2202
  if (raw === "" || raw === "none" || raw === "false") selectedPackages = [];
1892
2203
  else selectedPackages = opts.packages.split(",").map((p) => p.trim()).filter(Boolean);
1893
- } else selectedPackages = await multiSelect({
2204
+ } else if (yes) selectedPackages = [];
2205
+ else selectedPackages = await multiSelect({
1894
2206
  message: "Select packages to include",
1895
2207
  options: [...OPTIONAL_PACKAGES],
1896
2208
  required: false
1897
2209
  });
1898
2210
  let initGit;
1899
- if (opts.git === void 0) initGit = await confirm({
2211
+ if (opts.git === void 0) initGit = yes ? true : await confirm({
1900
2212
  message: "Initialize git repository?",
1901
2213
  initialValue: true
1902
2214
  });
1903
2215
  else initGit = opts.git;
1904
2216
  let installDeps;
1905
- if (opts.install === void 0) installDeps = await confirm({
2217
+ if (opts.install === void 0) installDeps = yes ? true : await confirm({
1906
2218
  message: "Install dependencies?",
1907
2219
  initialValue: true
1908
2220
  });
@@ -2485,7 +2797,7 @@ export const ${pascal.toUpperCase()}_QUERY_CONFIG: QueryParamsConfig = {
2485
2797
  //#endregion
2486
2798
  //#region src/generators/templates/dtos.ts
2487
2799
  function generateCreateDTO(ctx) {
2488
- const { pascal, kebab } = ctx;
2800
+ const { pascal } = ctx;
2489
2801
  return `import { z } from 'zod'
2490
2802
 
2491
2803
  /**
@@ -2505,7 +2817,7 @@ export type Create${pascal}DTO = z.infer<typeof create${pascal}Schema>
2505
2817
  `;
2506
2818
  }
2507
2819
  function generateUpdateDTO(ctx) {
2508
- const { pascal, kebab } = ctx;
2820
+ const { pascal } = ctx;
2509
2821
  return `import { z } from 'zod'
2510
2822
 
2511
2823
  export const update${pascal}Schema = z.object({
@@ -2516,7 +2828,7 @@ export type Update${pascal}DTO = z.infer<typeof update${pascal}Schema>
2516
2828
  `;
2517
2829
  }
2518
2830
  function generateResponseDTO(ctx) {
2519
- const { pascal, kebab } = ctx;
2831
+ const { pascal } = ctx;
2520
2832
  return `export interface ${pascal}ResponseDTO {
2521
2833
  id: string
2522
2834
  name: string
@@ -2633,7 +2945,7 @@ export class Delete${pascal}UseCase {
2633
2945
  //#endregion
2634
2946
  //#region src/generators/templates/repository.ts
2635
2947
  function generateRepositoryInterface(ctx) {
2636
- const { pascal, kebab, dtoPrefix = "../../application/dtos" } = ctx;
2948
+ const { pascal, kebab, dtoPrefix = "../../application/dtos", tokenScope = "app" } = ctx;
2637
2949
  return `/**
2638
2950
  * ${pascal} Repository Interface
2639
2951
  *
@@ -2663,8 +2975,12 @@ export interface I${pascal}Repository {
2663
2975
  * \`container.resolve(${pascal.toUpperCase()}_REPOSITORY)\` and
2664
2976
  * \`@Inject(${pascal.toUpperCase()}_REPOSITORY)\` both return the typed
2665
2977
  * interface — no manual generic, no \`any\` cast.
2978
+ *
2979
+ * The \`'${tokenScope}/'\` prefix matches the project scope so
2980
+ * \`kick-lint\`'s \`token-reserved-prefix\` rule never fires —
2981
+ * adopters must NOT use the reserved \`'kick/'\` namespace.
2666
2982
  */
2667
- export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('app/${kebab}/repository')
2983
+ export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('${tokenScope}/${kebab}/repository')
2668
2984
  `;
2669
2985
  }
2670
2986
  function generateInMemoryRepository(ctx) {
@@ -2908,7 +3224,7 @@ export class ${pascal} {
2908
3224
  `;
2909
3225
  }
2910
3226
  function generateValueObject(ctx) {
2911
- const { pascal, kebab } = ctx;
3227
+ const { pascal } = ctx;
2912
3228
  return `/**
2913
3229
  * ${pascal} ID Value Object
2914
3230
  *
@@ -3666,7 +3982,7 @@ export class ${pascal}Controller {
3666
3982
  //#endregion
3667
3983
  //#region src/generators/patterns/rest.ts
3668
3984
  async function generateRestFiles(ctx) {
3669
- const { pascal, kebab, plural, pluralPascal, repo, noTests, prismaClientPath, write } = ctx;
3985
+ const { pascal, kebab, plural, pluralPascal, repo, noTests, prismaClientPath, tokenScope, write } = ctx;
3670
3986
  await write(`${kebab}.module.ts`, generateRestModuleIndex({
3671
3987
  pascal,
3672
3988
  kebab,
@@ -3702,7 +4018,8 @@ async function generateRestFiles(ctx) {
3702
4018
  await write(`${kebab}.repository.ts`, generateRepositoryInterface({
3703
4019
  pascal,
3704
4020
  kebab,
3705
- dtoPrefix: "./dtos"
4021
+ dtoPrefix: "./dtos",
4022
+ tokenScope
3706
4023
  }));
3707
4024
  const builtinRepoFileMap = {
3708
4025
  inmemory: `in-memory-${kebab}`,
@@ -3762,7 +4079,7 @@ async function generateRestFiles(ctx) {
3762
4079
  //#endregion
3763
4080
  //#region src/generators/patterns/cqrs.ts
3764
4081
  async function generateCqrsFiles(ctx) {
3765
- const { pascal, kebab, plural, pluralPascal, repo, noTests, prismaClientPath, write } = ctx;
4082
+ const { pascal, kebab, plural, pluralPascal, repo, noTests, prismaClientPath, tokenScope, write } = ctx;
3766
4083
  await write(`${kebab}.module.ts`, generateCqrsModuleIndex({
3767
4084
  pascal,
3768
4085
  kebab,
@@ -3811,7 +4128,8 @@ async function generateCqrsFiles(ctx) {
3811
4128
  await write(`${kebab}.repository.ts`, generateRepositoryInterface({
3812
4129
  pascal,
3813
4130
  kebab,
3814
- dtoPrefix: "./dtos"
4131
+ dtoPrefix: "./dtos",
4132
+ tokenScope
3815
4133
  }));
3816
4134
  const builtinRepoFileMap = {
3817
4135
  inmemory: `in-memory-${kebab}`,
@@ -3871,7 +4189,7 @@ async function generateCqrsFiles(ctx) {
3871
4189
  //#endregion
3872
4190
  //#region src/generators/patterns/ddd.ts
3873
4191
  async function generateDddFiles(ctx) {
3874
- const { pascal, kebab, plural, pluralPascal, repo, noEntity, noTests, prismaClientPath, write } = ctx;
4192
+ const { pascal, kebab, plural, pluralPascal, repo, noEntity, noTests, prismaClientPath, tokenScope, write } = ctx;
3875
4193
  await write(`${kebab}.module.ts`, generateModuleIndex({
3876
4194
  pascal,
3877
4195
  kebab,
@@ -3912,7 +4230,8 @@ async function generateDddFiles(ctx) {
3912
4230
  for (const uc of useCases) await write(`application/use-cases/${uc.file}`, uc.content);
3913
4231
  await write(`domain/repositories/${kebab}.repository.ts`, generateRepositoryInterface({
3914
4232
  pascal,
3915
- kebab
4233
+ kebab,
4234
+ tokenScope
3916
4235
  }));
3917
4236
  await write(`domain/services/${kebab}-domain.service.ts`, generateDomainService({
3918
4237
  pascal,
@@ -4029,6 +4348,7 @@ async function generateModule(options) {
4029
4348
  noEntity: noEntity ?? false,
4030
4349
  noTests: noTests ?? false,
4031
4350
  prismaClientPath: options.prismaClientPath ?? "@prisma/client",
4351
+ tokenScope: options.tokenScope ?? "app",
4032
4352
  write,
4033
4353
  files
4034
4354
  };
@@ -4763,7 +5083,7 @@ function detectName(outDir, override) {
4763
5083
  const pkg = JSON.parse(readFileSync(join(outDir, "package.json"), "utf-8"));
4764
5084
  if (pkg.name) return pkg.name.replace(/^@[^/]+\//, "");
4765
5085
  } catch {}
4766
- return outDir.split("/").filter(Boolean).pop() ?? "app";
5086
+ return outDir.split("/").findLast(Boolean) ?? "app";
4767
5087
  }
4768
5088
  function detectPm(outDir, override) {
4769
5089
  if (override) return override;
@@ -5227,7 +5547,7 @@ function parseFields(raw) {
5227
5547
  });
5228
5548
  }
5229
5549
  async function generateScaffold(options) {
5230
- const { name, fields, modulesDir, noEntity, noTests, repo = "inmemory" } = options;
5550
+ const { name, fields, modulesDir, noEntity, noTests: _noTests, repo = "inmemory", tokenScope = "app" } = options;
5231
5551
  const shouldPluralize = options.pluralize !== false;
5232
5552
  const kebab = toKebabCase(name);
5233
5553
  const pascal = toPascalCase(name);
@@ -5249,7 +5569,7 @@ async function generateScaffold(options) {
5249
5569
  await write(`application/dtos/${kebab}-response.dto.ts`, genResponseDTO(pascal, fields));
5250
5570
  const useCases = genUseCases(pascal, kebab, plural, pluralPascal);
5251
5571
  for (const uc of useCases) await write(`application/use-cases/${uc.file}`, uc.content);
5252
- await write(`domain/repositories/${kebab}.repository.ts`, genRepositoryInterface(pascal, kebab));
5572
+ await write(`domain/repositories/${kebab}.repository.ts`, genRepositoryInterface(pascal, kebab, tokenScope));
5253
5573
  await write(`domain/services/${kebab}-domain.service.ts`, genDomainService(pascal, kebab));
5254
5574
  if (repo === "inmemory") await write(`infrastructure/repositories/in-memory-${kebab}.repository.ts`, genInMemoryRepository(pascal, kebab, fields));
5255
5575
  if (!noEntity) {
@@ -5530,7 +5850,7 @@ export class ${pascal}Controller {
5530
5850
  }
5531
5851
  `;
5532
5852
  }
5533
- function genRepositoryInterface(pascal, kebab) {
5853
+ function genRepositoryInterface(pascal, kebab, tokenScope) {
5534
5854
  return `import { createToken } from '@forinda/kickjs'
5535
5855
  import type { ${pascal}ResponseDTO } from '../../application/dtos/${kebab}-response.dto'
5536
5856
  import type { Create${pascal}DTO } from '../../application/dtos/create-${kebab}.dto'
@@ -5551,8 +5871,12 @@ export interface I${pascal}Repository {
5551
5871
  * \`container.resolve(${pascal.toUpperCase()}_REPOSITORY)\` and
5552
5872
  * \`@Inject(${pascal.toUpperCase()}_REPOSITORY)\` both return the typed
5553
5873
  * interface — no manual generic, no \`any\` cast.
5874
+ *
5875
+ * The \`'${tokenScope}/'\` prefix matches the project scope so
5876
+ * \`kick-lint\`'s \`token-reserved-prefix\` rule never fires —
5877
+ * adopters must NOT use the reserved \`'kick/'\` namespace.
5554
5878
  */
5555
- export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('app/${kebab}/repository')
5879
+ export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('${tokenScope}/${kebab}/repository')
5556
5880
  `;
5557
5881
  }
5558
5882
  function genDomainService(pascal, kebab) {
@@ -5832,6 +6156,7 @@ async function runModuleGeneration(names, opts, dryRun) {
5832
6156
  const repo = opts.repo ?? resolveRepoType(mc.repo);
5833
6157
  const pattern = opts.pattern ?? config?.pattern ?? "ddd";
5834
6158
  const shouldPluralize = opts.pluralize === false ? false : mc.pluralize ?? true;
6159
+ const tokenScope = resolveTokenScope(config, process.cwd());
5835
6160
  const allFiles = [];
5836
6161
  for (const name of names) {
5837
6162
  const files = await generateModule({
@@ -5845,7 +6170,8 @@ async function runModuleGeneration(names, opts, dryRun) {
5845
6170
  pattern,
5846
6171
  dryRun,
5847
6172
  pluralize: shouldPluralize,
5848
- prismaClientPath: mc.prismaClientPath
6173
+ prismaClientPath: mc.prismaClientPath,
6174
+ tokenScope
5849
6175
  });
5850
6176
  allFiles.push(...files);
5851
6177
  }
@@ -6008,16 +6334,19 @@ function registerGenerateCommand(program) {
6008
6334
  console.error("\n Error: At least one field is required.\n Usage: kick g scaffold <name> <field:type> [field:type...]\n Example: kick g scaffold Post title:string body:text:optional published:boolean:optional\n Optional: append :optional (shell-safe, no quoting needed)\n");
6009
6335
  process.exit(1);
6010
6336
  }
6011
- const mc = resolveModuleConfig(await loadKickConfig(process.cwd()));
6337
+ const config = await loadKickConfig(process.cwd());
6338
+ const mc = resolveModuleConfig(config);
6012
6339
  const modulesDir = opts.modulesDir ?? mc.dir ?? "src/modules";
6013
6340
  const fields = parseFields(rawFields);
6341
+ const tokenScope = resolveTokenScope(config, process.cwd());
6014
6342
  const files = await generateScaffold({
6015
6343
  name,
6016
6344
  fields,
6017
6345
  modulesDir: resolve(modulesDir),
6018
6346
  noEntity: opts.entity === false,
6019
6347
  noTests: opts.tests === false,
6020
- pluralize: opts.pluralize === false ? false : mc.pluralize ?? true
6348
+ pluralize: opts.pluralize === false ? false : mc.pluralize ?? true,
6349
+ tokenScope
6021
6350
  });
6022
6351
  console.log(`\n Scaffolded ${name} with ${fields.length} field(s):`);
6023
6352
  for (const f of fields) console.log(` ${f.name}: ${f.type}${f.optional ? " (optional)" : ""}`);
@@ -6087,6 +6416,17 @@ const BANNER_PREFIX = "/* AUTO-GENERATED by kick typegen — do not edit. Plugin
6087
6416
  async function runTypegen(opts) {
6088
6417
  const typesDirAbs = path.resolve(opts.cwd, TYPES_DIR);
6089
6418
  await mkdir(typesDirAbs, { recursive: true });
6419
+ const scanCache = /* @__PURE__ */ new Map();
6420
+ const scanFn = opts.scan ?? scanProject;
6421
+ const getScanResult = (scanOpts) => {
6422
+ const key = stableScanKey(scanOpts);
6423
+ let pending = scanCache.get(key);
6424
+ if (!pending) {
6425
+ pending = scanFn(scanOpts);
6426
+ scanCache.set(key, pending);
6427
+ }
6428
+ return pending;
6429
+ };
6090
6430
  const ctx = {
6091
6431
  cwd: opts.cwd,
6092
6432
  config: opts.config,
@@ -6098,6 +6438,7 @@ async function runTypegen(opts) {
6098
6438
  await mkdir(path.dirname(abs), { recursive: true });
6099
6439
  await writeFile(abs, contents, "utf8");
6100
6440
  },
6441
+ getScanResult,
6101
6442
  log: console
6102
6443
  };
6103
6444
  const results = [];
@@ -6110,7 +6451,8 @@ async function runTypegen(opts) {
6110
6451
  });
6111
6452
  continue;
6112
6453
  }
6113
- const file = path.join(typesDirAbs, `${plugin.id.replace(/\//g, "__")}.d.ts`);
6454
+ const ext = plugin.outExtension ?? ".d.ts";
6455
+ const file = path.join(typesDirAbs, `${plugin.id.replace(/\//g, "__")}${ext}`);
6114
6456
  const next = `${BANNER_PREFIX}${plugin.id} */\n\n` + out + "\n";
6115
6457
  let prev = "";
6116
6458
  if (existsSync(file)) prev = await readFile(file, "utf8");
@@ -6132,6 +6474,24 @@ async function runTypegen(opts) {
6132
6474
  }
6133
6475
  return results;
6134
6476
  }
6477
+ /**
6478
+ * Order-independent cache key for `ScanOptions`. Builds the key from
6479
+ * the known fields in a fixed order so semantically equal options
6480
+ * always produce the same key regardless of how the caller built the
6481
+ * object literal. Arrays (extensions / exclude) are sorted before
6482
+ * joining so `['.ts', '.tsx']` and `['.tsx', '.ts']` collide.
6483
+ */
6484
+ function stableScanKey(opts) {
6485
+ const extensions = (opts.extensions ?? []).slice().toSorted().join(",");
6486
+ const exclude = (opts.exclude ?? []).slice().toSorted().join(",");
6487
+ return [
6488
+ `root=${opts.root}`,
6489
+ `cwd=${opts.cwd}`,
6490
+ `extensions=${extensions}`,
6491
+ `exclude=${exclude}`,
6492
+ `envFile=${opts.envFile ?? ""}`
6493
+ ].join("|");
6494
+ }
6135
6495
  //#endregion
6136
6496
  //#region src/typegen/disable-filter.ts
6137
6497
  /**
@@ -6257,18 +6617,15 @@ async function processEntry(namespace, entry, cwd, distAbs) {
6257
6617
  });
6258
6618
  mkdirSync(destAbs, { recursive: true });
6259
6619
  const manifestSlice = {};
6260
- const keyOwner = /* @__PURE__ */ new Map();
6261
- for (const relPath of matches.sort()) {
6620
+ const { pairs, collisionGroupsResolved } = groupAssetKeys(namespace, [...matches].toSorted(), { strategy: entry.keys ?? "auto" });
6621
+ for (const { rel: relPath, key } of pairs) {
6262
6622
  const srcFile = join(srcAbs, relPath);
6263
6623
  const destFile = join(destAbs, relPath);
6264
6624
  mkdirSync(dirname(destFile), { recursive: true });
6265
6625
  cpSync(srcFile, destFile);
6266
- const logicalKey = `${namespace}/${stripExt(relPath)}`;
6267
- const previous = keyOwner.get(logicalKey);
6268
- if (previous) console.warn(` ⚠ assetMap collision in '${namespace}': '${previous}' and '${relPath}' both flatten to key '${logicalKey}'. Last-alphabetical wins ('${relPath}'). Rename one of them or set assetMap.${namespace}.glob to filter by extension.`);
6269
- keyOwner.set(logicalKey, relPath);
6270
- manifestSlice[logicalKey] = toManifestRelative(distAbs, destFile);
6626
+ manifestSlice[key] = toManifestRelative(distAbs, destFile);
6271
6627
  }
6628
+ if (collisionGroupsResolved > 0) console.log(` ℹ assetMap.${namespace}: auto-resolved ${collisionGroupsResolved} basename collision(s) by keeping extensions (set 'keys: "strip"' to opt back into legacy last-write-wins behaviour, or 'keys: "with-extension"' to keep all keys verbose).`);
6272
6629
  return {
6273
6630
  entrySummary: {
6274
6631
  namespace,
@@ -6279,11 +6636,6 @@ async function processEntry(namespace, entry, cwd, distAbs) {
6279
6636
  manifestSlice
6280
6637
  };
6281
6638
  }
6282
- /** Strip the final extension from a file path (`mails/welcome.ejs` → `mails/welcome`). */
6283
- function stripExt(path) {
6284
- const ext = extname(path);
6285
- return ext ? path.slice(0, -ext.length) : path;
6286
- }
6287
6639
  /**
6288
6640
  * Make `destFile` relative to the manifest's directory + force POSIX
6289
6641
  * separators so the manifest is byte-stable across platforms.
@@ -6325,8 +6677,21 @@ function isDirectorySync(path) {
6325
6677
  * This function just creates the Vite server, listens, and handles shutdown.
6326
6678
  * Vite owns the HTTP port — Express runs as post-middleware on Vite's server.
6327
6679
  */
6328
- async function startDevServer(_entry, port) {
6680
+ /**
6681
+ * Resolve whether the dev server's chokidar should poll instead of
6682
+ * relying on `fs.watch` events. CLI flag wins over env var; default
6683
+ * is event-based (faster, lower CPU). Polling is the right choice in
6684
+ * Docker bind mounts, WSL crossing the WSL/Windows boundary, NFS,
6685
+ * and some old Linux kernels where new-file events get dropped.
6686
+ */
6687
+ function resolvePolling(flag) {
6688
+ if (typeof flag === "boolean") return flag;
6689
+ const env = process.env.KICKJS_WATCH_POLLING;
6690
+ return env === "1" || env === "true";
6691
+ }
6692
+ async function startDevServer(_entry, port, opts = {}) {
6329
6693
  if (port) process.env.PORT = port;
6694
+ const polling = resolvePolling(opts.polling);
6330
6695
  const cwd = process.cwd();
6331
6696
  const devConfig = await loadKickConfig(cwd);
6332
6697
  const schemaValidator = devConfig?.typegen?.schemaValidator ?? "zod";
@@ -6339,7 +6704,8 @@ async function startDevServer(_entry, port) {
6339
6704
  envFile,
6340
6705
  srcDir: devConfig?.typegen?.srcDir,
6341
6706
  outDir: devConfig?.typegen?.outDir,
6342
- assetMap: devConfig?.assetMap
6707
+ assetMap: devConfig?.assetMap,
6708
+ runPlugins: false
6343
6709
  });
6344
6710
  } catch (err) {
6345
6711
  console.warn(` kick typegen: skipped (${err?.message ?? err})`);
@@ -6352,7 +6718,13 @@ async function startDevServer(_entry, port) {
6352
6718
  const { createServer } = await import(pathToFileURL(createRequire(resolve("package.json")).resolve("vite")).href);
6353
6719
  const server = await createServer({
6354
6720
  configFile: resolve("vite.config.ts"),
6355
- server: { port: port ? parseInt(port, 10) : void 0 }
6721
+ server: {
6722
+ port: port ? parseInt(port, 10) : void 0,
6723
+ ...polling ? { watch: {
6724
+ usePolling: true,
6725
+ interval: 100
6726
+ } } : {}
6727
+ }
6356
6728
  });
6357
6729
  const assetSrcRoots = devConfig?.assetMap ? Object.values(devConfig.assetMap).map((entry) => entry?.src).filter((src) => typeof src === "string" && src.length > 0).map((src) => resolve(cwd, src)) : [];
6358
6730
  const isAssetFile = (file) => assetSrcRoots.some((root) => file === root || file.startsWith(`${root}/`));
@@ -6373,7 +6745,8 @@ async function startDevServer(_entry, port) {
6373
6745
  envFile,
6374
6746
  srcDir: devConfig?.typegen?.srcDir,
6375
6747
  outDir: devConfig?.typegen?.outDir,
6376
- assetMap: devConfig?.assetMap
6748
+ assetMap: devConfig?.assetMap,
6749
+ runPlugins: false
6377
6750
  }).catch(() => {});
6378
6751
  runAllPluginTypegens({
6379
6752
  cwd,
@@ -6398,9 +6771,9 @@ async function startDevServer(_entry, port) {
6398
6771
  process.on("SIGTERM", shutdown);
6399
6772
  }
6400
6773
  function registerRunCommands(program) {
6401
- program.command("dev").description("Start development server with Vite HMR (zero-downtime reload)").option("-e, --entry <file>", "Entry file", "src/index.ts").option("-p, --port <port>", "Port number").action(async (opts) => {
6774
+ program.command("dev").description("Start development server with Vite HMR (zero-downtime reload)").option("-e, --entry <file>", "Entry file", "src/index.ts").option("-p, --port <port>", "Port number").option("--polling", "Force chokidar to poll for file changes (Docker / WSL / NFS / older kernels)").action(async (opts) => {
6402
6775
  try {
6403
- await startDevServer(opts.entry, opts.port);
6776
+ await startDevServer(opts.entry, opts.port, { polling: opts.polling });
6404
6777
  } catch (err) {
6405
6778
  if (err.code === "ERR_MODULE_NOT_FOUND" && err.message?.includes("vite")) console.error("\n Error: vite is not installed.\n Run: pnpm add -D vite unplugin-swc\n");
6406
6779
  else console.error("\n Dev server failed:", err.message ?? err);
@@ -6493,7 +6866,7 @@ function registerInfoCommand(program) {
6493
6866
  }
6494
6867
  //#endregion
6495
6868
  //#region src/commands/inspect.ts
6496
- const { bold, dim, green, red, yellow, cyan, blue } = pc;
6869
+ const { bold, dim, green, red, yellow, blue } = pc;
6497
6870
  function formatUptime(seconds) {
6498
6871
  const d = Math.floor(seconds / 86400);
6499
6872
  const h = Math.floor(seconds % 86400 / 3600);
@@ -6602,198 +6975,6 @@ function registerInspectCommand(program) {
6602
6975
  });
6603
6976
  }
6604
6977
  //#endregion
6605
- //#region src/commands/add.ts
6606
- /** Registry of KickJS packages and their required peer dependencies */
6607
- const PACKAGE_REGISTRY = {
6608
- kickjs: {
6609
- pkg: "@forinda/kickjs",
6610
- peers: ["express"],
6611
- description: "Unified framework: DI, decorators, routing, middleware"
6612
- },
6613
- vite: {
6614
- pkg: "@forinda/kickjs-vite",
6615
- peers: ["vite"],
6616
- description: "Vite plugin: dev server, HMR, module discovery",
6617
- dev: true
6618
- },
6619
- config: {
6620
- pkg: "dotenv",
6621
- peers: [],
6622
- description: "Optional .env file loader (kickjs ConfigService now ships in @forinda/kickjs)"
6623
- },
6624
- cli: {
6625
- pkg: "@forinda/kickjs-cli",
6626
- peers: [],
6627
- description: "CLI tool and code generators",
6628
- dev: true
6629
- },
6630
- swagger: {
6631
- pkg: "@forinda/kickjs-swagger",
6632
- peers: [],
6633
- description: "OpenAPI spec + Swagger UI + ReDoc"
6634
- },
6635
- drizzle: {
6636
- pkg: "@forinda/kickjs-drizzle",
6637
- peers: ["drizzle-orm"],
6638
- description: "Drizzle ORM adapter + query builder"
6639
- },
6640
- prisma: {
6641
- pkg: "@forinda/kickjs-prisma",
6642
- peers: ["@prisma/client"],
6643
- description: "Prisma adapter + query builder"
6644
- },
6645
- ws: {
6646
- pkg: "@forinda/kickjs-ws",
6647
- peers: ["socket.io"],
6648
- description: "WebSocket with @WsController decorators"
6649
- },
6650
- devtools: {
6651
- pkg: "@forinda/kickjs-devtools",
6652
- peers: [],
6653
- description: "Development dashboard — routes, DI, metrics, health",
6654
- dev: true
6655
- },
6656
- auth: {
6657
- pkg: "@forinda/kickjs-auth",
6658
- peers: ["jsonwebtoken"],
6659
- description: "Authentication — JWT, API key, and custom strategies"
6660
- },
6661
- queue: {
6662
- pkg: "@forinda/kickjs-queue",
6663
- peers: [],
6664
- description: "Queue adapter (BullMQ/RabbitMQ/Kafka)"
6665
- },
6666
- "queue:bullmq": {
6667
- pkg: "@forinda/kickjs-queue",
6668
- peers: ["bullmq", "ioredis"],
6669
- description: "Queue with BullMQ + Redis"
6670
- },
6671
- "queue:rabbitmq": {
6672
- pkg: "@forinda/kickjs-queue",
6673
- peers: ["amqplib"],
6674
- description: "Queue with RabbitMQ"
6675
- },
6676
- "queue:kafka": {
6677
- pkg: "@forinda/kickjs-queue",
6678
- peers: ["kafkajs"],
6679
- description: "Queue with Kafka"
6680
- },
6681
- mcp: {
6682
- pkg: "@forinda/kickjs-mcp",
6683
- peers: ["@modelcontextprotocol/sdk"],
6684
- description: "Model Context Protocol server — expose @Controller endpoints as AI tools"
6685
- },
6686
- testing: {
6687
- pkg: "@forinda/kickjs-testing",
6688
- peers: [],
6689
- description: "Test utilities and TestModule builder",
6690
- dev: true
6691
- }
6692
- };
6693
- function detectPackageManager() {
6694
- if (existsSync(resolve("pnpm-lock.yaml"))) return "pnpm";
6695
- if (existsSync(resolve("yarn.lock"))) return "yarn";
6696
- if (existsSync(resolve("bun.lockb")) || existsSync(resolve("bun.lock"))) return "bun";
6697
- return "npm";
6698
- }
6699
- /** Read `packageManager` from package.json (corepack convention: "pnpm@10.0.0") */
6700
- function packageManagerFromPackageJson() {
6701
- try {
6702
- const field = JSON.parse(readFileSync(resolve("package.json"), "utf-8")).packageManager;
6703
- if (typeof field !== "string") return null;
6704
- const name = field.split("@")[0];
6705
- return PACKAGE_MANAGERS.includes(name) ? name : null;
6706
- } catch {
6707
- return null;
6708
- }
6709
- }
6710
- /**
6711
- * Resolve which package manager to use, in priority order:
6712
- * 1. `--pm` CLI flag
6713
- * 2. `packageManager` in kick.config
6714
- * 3. `packageManager` in package.json (corepack)
6715
- * 4. Lockfile detection
6716
- * 5. `'npm'`
6717
- */
6718
- async function resolvePackageManager(flagPm) {
6719
- if (flagPm && PACKAGE_MANAGERS.includes(flagPm)) return flagPm;
6720
- const config = await loadKickConfig(process.cwd());
6721
- if (config?.packageManager && PACKAGE_MANAGERS.includes(config.packageManager)) return config.packageManager;
6722
- const fromPkg = packageManagerFromPackageJson();
6723
- if (fromPkg) return fromPkg;
6724
- return detectPackageManager();
6725
- }
6726
- function printPackageList() {
6727
- console.log("\n Available KickJS packages:\n");
6728
- const maxName = Math.max(...Object.keys(PACKAGE_REGISTRY).map((k) => k.length));
6729
- for (const [name, info] of Object.entries(PACKAGE_REGISTRY)) {
6730
- const padded = name.padEnd(maxName + 2);
6731
- const peers = info.peers.length ? ` (+ ${info.peers.join(", ")})` : "";
6732
- console.log(` ${padded} ${info.description}${peers}`);
6733
- }
6734
- console.log("\n Usage: kick add auth drizzle swagger");
6735
- console.log(" kick add queue:bullmq");
6736
- console.log();
6737
- }
6738
- function registerListCommand(program) {
6739
- program.command("list").alias("ls").description("List all available KickJS packages").action(() => {
6740
- printPackageList();
6741
- });
6742
- }
6743
- function registerAddCommand(program) {
6744
- program.command("add [packages...]").description("Add KickJS packages with their required dependencies").option("--pm <manager>", "Package manager override").option("-D, --dev", "Install as dev dependency").option("--list", "List all available packages").action(async (packages, opts) => {
6745
- if (opts.list || packages.length === 0) {
6746
- printPackageList();
6747
- return;
6748
- }
6749
- const pm = await resolvePackageManager(opts.pm);
6750
- const forceDevFlag = opts.dev;
6751
- const prodDeps = /* @__PURE__ */ new Set();
6752
- const devDeps = /* @__PURE__ */ new Set();
6753
- const unknown = [];
6754
- for (const name of packages) {
6755
- const entry = PACKAGE_REGISTRY[name];
6756
- if (!entry) {
6757
- unknown.push(name);
6758
- continue;
6759
- }
6760
- const target = forceDevFlag || entry.dev ? devDeps : prodDeps;
6761
- target.add(entry.pkg);
6762
- for (const peer of entry.peers) target.add(peer);
6763
- }
6764
- if (unknown.length > 0) {
6765
- console.log(`\n Unknown packages: ${unknown.join(", ")}`);
6766
- console.log(" Run \"kick add --list\" to see available packages.\n");
6767
- if (prodDeps.size === 0 && devDeps.size === 0) return;
6768
- }
6769
- if (prodDeps.size > 0) {
6770
- const deps = Array.from(prodDeps);
6771
- const cmd = `${pm} add ${deps.join(" ")}`;
6772
- console.log(`\n Installing ${deps.length} dependency(ies):`);
6773
- for (const dep of deps) console.log(` + ${dep}`);
6774
- console.log();
6775
- try {
6776
- execSync(cmd, { stdio: "inherit" });
6777
- } catch {
6778
- console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
6779
- }
6780
- }
6781
- if (devDeps.size > 0) {
6782
- const deps = Array.from(devDeps);
6783
- const cmd = `${pm} add -D ${deps.join(" ")}`;
6784
- console.log(`\n Installing ${deps.length} dev dependency(ies):`);
6785
- for (const dep of deps) console.log(` + ${dep} (dev)`);
6786
- console.log();
6787
- try {
6788
- execSync(cmd, { stdio: "inherit" });
6789
- } catch {
6790
- console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
6791
- }
6792
- }
6793
- console.log(" Done!\n");
6794
- });
6795
- }
6796
- //#endregion
6797
6978
  //#region src/explain/known-issues.ts
6798
6979
  function includesAll(haystack, needles) {
6799
6980
  const lower = haystack.toLowerCase();
@@ -7600,7 +7781,7 @@ function registerTypegenCommand(program) {
7600
7781
  const cwd = process.cwd();
7601
7782
  const config = await loadKickConfig(cwd);
7602
7783
  if (opts.list) {
7603
- const { mergeCliPlugins } = await import("./plugin-Bgfg7qMk.mjs").then((n) => n.t);
7784
+ const { mergeCliPlugins } = await import("./plugin-b7ig7Uxv.mjs").then((n) => n.t);
7604
7785
  const { builtinCliPlugins } = await Promise.resolve().then(() => builtins_exports);
7605
7786
  const merged = mergeCliPlugins([...builtinCliPlugins, ...config?.plugins ?? []], config?.commands ?? []);
7606
7787
  const disabled = new Set(config?.typegen?.disable ?? []);
@@ -7627,7 +7808,8 @@ function registerTypegenCommand(program) {
7627
7808
  allowDuplicates: opts.allowDuplicates,
7628
7809
  schemaValidator,
7629
7810
  envFile,
7630
- assetMap: config?.assetMap
7811
+ assetMap: config?.assetMap,
7812
+ runPlugins: false
7631
7813
  };
7632
7814
  try {
7633
7815
  if (opts.watch) {
@@ -8047,6 +8229,238 @@ const kickAssetsTypegen = () => ({
8047
8229
  }
8048
8230
  });
8049
8231
  //#endregion
8232
+ //#region src/typegen/render/routes.ts
8233
+ const ROUTES_HEADER = `/* eslint-disable */
8234
+ // AUTO-GENERATED by \`kick typegen\`. DO NOT EDIT.
8235
+ // Re-run with \`kick typegen\` or rely on \`kick dev\` to refresh.
8236
+ `;
8237
+ /**
8238
+ * Render the `KickRoutes` global namespace augmentation. Each interface
8239
+ * inside corresponds to a controller class; each property is a single
8240
+ * route method on that controller, conforming to `RouteShape`.
8241
+ *
8242
+ * Fills `params` from URL patterns, `query` from `@ApiQueryParams`, and
8243
+ * `body`/`query`/`params` (when schema-validated) from the configured
8244
+ * schema validator. `response` is emitted as `unknown`.
8245
+ */
8246
+ function renderRoutes(routes, routesOutFile, schemaValidator) {
8247
+ if (routes.length === 0) return `${ROUTES_HEADER}
8248
+ // (no routes discovered yet — annotate a controller method with
8249
+ // @Get/@Post/@Put/@Delete/@Patch and re-run \`kick typegen\`)
8250
+ declare global {
8251
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8252
+ namespace KickRoutes {}
8253
+ }
8254
+
8255
+ export {}
8256
+ `;
8257
+ const byController = /* @__PURE__ */ new Map();
8258
+ for (const r of routes) {
8259
+ const arr = byController.get(r.controller) ?? [];
8260
+ arr.push(r);
8261
+ byController.set(r.controller, arr);
8262
+ }
8263
+ const schemaImports = /* @__PURE__ */ new Map();
8264
+ const renderField = (schema, routeFilePath) => {
8265
+ const alias = planSchemaImport(schema, routeFilePath, routesOutFile, schemaValidator, schemaImports);
8266
+ return alias ? `import('zod').infer<typeof ${alias}>` : null;
8267
+ };
8268
+ const interfaces = [];
8269
+ for (const [controller, methods] of byController) {
8270
+ const lines = [` interface ${controller} {`];
8271
+ for (const m of methods) {
8272
+ const urlParamsType = m.pathParams.length > 0 ? `{ ${m.pathParams.map((p) => `${p}: string`).join("; ")} }` : "{}";
8273
+ const bodySchemaType = renderField(m.bodySchema, m.filePath);
8274
+ const querySchemaType = renderField(m.querySchema, m.filePath);
8275
+ const paramsType = renderField(m.paramsSchema, m.filePath) ?? urlParamsType;
8276
+ const bodyType = bodySchemaType ?? "unknown";
8277
+ const queryType = querySchemaType ?? renderQueryShape(m);
8278
+ const docLines = renderQueryDocLines(m);
8279
+ lines.push(` /**`, ` * ${m.httpMethod} ${m.path}`, ...docLines.map((d) => ` * ${d}`), ` */`, ` ${m.method}: {`, ` params: ${paramsType}`, ` body: ${bodyType}`, ` query: ${queryType}`, ` response: unknown`, ` }`);
8280
+ }
8281
+ lines.push(" }");
8282
+ interfaces.push(lines.join("\n"));
8283
+ }
8284
+ return `${ROUTES_HEADER}${renderSchemaImports(schemaImports)}
8285
+ declare global {
8286
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8287
+ namespace KickRoutes {
8288
+ ${interfaces.join("\n")}
8289
+ }
8290
+ }
8291
+
8292
+ export {}
8293
+ `;
8294
+ }
8295
+ function renderQueryShape(m) {
8296
+ if (m.queryFilterable === null) return "unknown";
8297
+ const sortable = m.querySortable ?? [];
8298
+ return `{ filter?: string | string[]; sort?: ${sortable.length > 0 ? sortable.flatMap((f) => [`'${f}'`, `'-${f}'`]).join(" | ") : "string"}; q?: string; page?: string; limit?: string }`;
8299
+ }
8300
+ function renderQueryDocLines(m) {
8301
+ const lines = [];
8302
+ if (m.queryFilterable && m.queryFilterable.length > 0) lines.push(`Filterable: ${m.queryFilterable.join(", ")}`);
8303
+ if (m.querySortable && m.querySortable.length > 0) lines.push(`Sortable: ${m.querySortable.join(", ")}`);
8304
+ if (m.querySearchable && m.querySearchable.length > 0) lines.push(`Searchable: ${m.querySearchable.join(", ")}`);
8305
+ return lines;
8306
+ }
8307
+ /**
8308
+ * Plan a schema import for hoisting at the top of `routes.ts`. Returns
8309
+ * the alias the in-namespace code should use, or `null` if the schema
8310
+ * cannot be referenced (no validator configured, or source unresolvable).
8311
+ */
8312
+ function planSchemaImport(schema, routeFilePath, routesOutFile, schemaValidator, imports) {
8313
+ if (!schema || schemaValidator !== "zod") return null;
8314
+ if (schema.source === null) return null;
8315
+ const specifier = resolveSchemaImportSpecifier(schema.source, routeFilePath, routesOutFile);
8316
+ if (specifier === "unknown") return null;
8317
+ const key = `${specifier}::${schema.identifier}`;
8318
+ let alias = imports.get(key)?.specifier;
8319
+ if (!alias) {
8320
+ alias = `_S${imports.size}`;
8321
+ imports.set(key, {
8322
+ identifier: schema.identifier,
8323
+ specifier: alias
8324
+ });
8325
+ } else alias = imports.get(key).specifier;
8326
+ return alias;
8327
+ }
8328
+ function renderSchemaImports(imports) {
8329
+ if (imports.size === 0) return "";
8330
+ const lines = [];
8331
+ for (const [key, value] of imports) {
8332
+ const [path] = key.split("::");
8333
+ lines.push(`import type { ${value.identifier} as ${value.specifier} } from '${path}'`);
8334
+ }
8335
+ return lines.join("\n") + "\n";
8336
+ }
8337
+ /**
8338
+ * Compute the import specifier the generated `routes.d.ts` should use
8339
+ * to reach a schema declared either in the controller file (empty
8340
+ * string) or imported from elsewhere (relative path or bare module).
8341
+ */
8342
+ function resolveSchemaImportSpecifier(source, routeFilePath, routesOutFile) {
8343
+ if (source === null) return "unknown";
8344
+ const routesDir = dirname(routesOutFile);
8345
+ if (source === "") {
8346
+ let rel = relative(routesDir, routeFilePath).split(sep).join("/");
8347
+ rel = rel.replace(/\.(ts|tsx|mts|cts)$/i, "");
8348
+ if (!rel.startsWith(".")) rel = "./" + rel;
8349
+ return rel;
8350
+ }
8351
+ if (!source.startsWith(".") && !source.startsWith("/")) return source;
8352
+ let rel = relative(routesDir, resolve(dirname(routeFilePath), source)).split(sep).join("/");
8353
+ rel = rel.replace(/\.(ts|tsx|mts|cts)$/i, "");
8354
+ if (!rel.startsWith(".")) rel = "./" + rel;
8355
+ return rel;
8356
+ }
8357
+ //#endregion
8358
+ //#region src/typegen/builtin/routes.ts
8359
+ const kickRoutesTypegen = () => ({
8360
+ id: "kick/routes",
8361
+ outExtension: ".ts",
8362
+ inputs: ["src/**/*.controller.ts", "src/**/*.module.ts"],
8363
+ async generate(ctx) {
8364
+ const scan = await ctx.getScanResult({
8365
+ root: resolveSrcDir$1(ctx),
8366
+ cwd: ctx.cwd,
8367
+ envFile: resolveEnvFile$1(ctx)
8368
+ });
8369
+ const schemaValidator = ctx.config?.typegen?.schemaValidator ?? "zod";
8370
+ const outFile = path.resolve(ctx.cwd, ".kickjs/types/kick__routes.ts");
8371
+ return renderRoutes(scan.routes, outFile, schemaValidator);
8372
+ }
8373
+ });
8374
+ function resolveSrcDir$1(ctx) {
8375
+ return path.resolve(ctx.cwd, ctx.config?.typegen?.srcDir ?? "src");
8376
+ }
8377
+ function resolveEnvFile$1(ctx) {
8378
+ const cfg = ctx.config?.typegen?.envFile;
8379
+ if (cfg === false) return void 0;
8380
+ return cfg;
8381
+ }
8382
+ //#endregion
8383
+ //#region src/typegen/render/env.ts
8384
+ const ENV_HEADER = `/* eslint-disable */
8385
+ // AUTO-GENERATED by \`kick typegen\`. DO NOT EDIT.
8386
+ // Re-run with \`kick typegen\` or rely on \`kick dev\` to refresh.
8387
+ `;
8388
+ /**
8389
+ * Render the `KickEnv` + `NodeJS.ProcessEnv` augmentation file from a
8390
+ * detected env schema. Returns `null` when no env file was discovered,
8391
+ * so the caller can skip emission entirely (rather than emitting an
8392
+ * empty augmentation that would shadow `KickEnv` to a useless `{}`).
8393
+ */
8394
+ function renderEnv(env, envOutFile) {
8395
+ if (!env) return null;
8396
+ let rel = relative(dirname(envOutFile), env.filePath).split(sep).join("/");
8397
+ rel = rel.replace(/\.(ts|tsx|mts|cts)$/i, "");
8398
+ if (!rel.startsWith(".")) rel = "./" + rel;
8399
+ return `${ENV_HEADER}
8400
+ // Importing the schema as a type lets us infer its shape without
8401
+ // pulling in any runtime code. \`Awaited<>\` strips an accidental
8402
+ // Promise wrap on dynamic-imported defaults.
8403
+ import type _envSchema from '${rel}'
8404
+
8405
+ // Local type alias — interfaces can only \`extend\` an identifier,
8406
+ // not an inline import expression, so we resolve the schema's
8407
+ // inferred shape into a named type first.
8408
+ type _KickEnvShape = import('zod').infer<typeof _envSchema>
8409
+
8410
+ declare global {
8411
+ /**
8412
+ * Typed environment registry. Augmented from \`${env.relativePath}\`
8413
+ * so \`@Value('PORT')\`, \`Env<'PORT'>\`, and \`process.env.PORT\` are
8414
+ * all type-safe and autocomplete.
8415
+ */
8416
+ interface KickEnv extends _KickEnvShape {}
8417
+
8418
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8419
+ namespace NodeJS {
8420
+ /**
8421
+ * Narrow \`process.env\` so known keys exist as \`string\` (the raw
8422
+ * pre-Zod-coercion form). \`@Value\` and the \`ConfigService\` apply
8423
+ * the schema's transforms internally; access \`process.env\` directly
8424
+ * only when you need the raw string. Unknown keys still resolve to
8425
+ * \`string | undefined\` via the base @types/node declaration.
8426
+ */
8427
+ interface ProcessEnv extends Record<keyof KickEnv, string> {}
8428
+ }
8429
+ }
8430
+
8431
+ export {}
8432
+ `;
8433
+ }
8434
+ //#endregion
8435
+ //#region src/typegen/builtin/env.ts
8436
+ const kickEnvTypegen = () => ({
8437
+ id: "kick/env",
8438
+ outExtension: ".ts",
8439
+ inputs: [
8440
+ "src/env.ts",
8441
+ "src/**/env.ts",
8442
+ "src/**/*.env.ts"
8443
+ ],
8444
+ async generate(ctx) {
8445
+ const envFile = resolveEnvFile(ctx);
8446
+ if (envFile === false) return null;
8447
+ const scan = await ctx.getScanResult({
8448
+ root: resolveSrcDir(ctx),
8449
+ cwd: ctx.cwd,
8450
+ envFile
8451
+ });
8452
+ if (!scan.env) return null;
8453
+ const outFile = path.resolve(ctx.cwd, ".kickjs/types/kick__env.ts");
8454
+ return renderEnv(scan.env, outFile);
8455
+ }
8456
+ });
8457
+ function resolveSrcDir(ctx) {
8458
+ return path.resolve(ctx.cwd, ctx.config?.typegen?.srcDir ?? "src");
8459
+ }
8460
+ function resolveEnvFile(ctx) {
8461
+ return ctx.config?.typegen?.envFile;
8462
+ }
8463
+ //#endregion
8050
8464
  //#region src/plugin/builtins.ts
8051
8465
  var builtins_exports = /* @__PURE__ */ __exportAll({ builtinCliPlugins: () => builtinCliPlugins });
8052
8466
  const builtinCliPlugins = [
@@ -8110,6 +8524,14 @@ const builtinCliPlugins = [
8110
8524
  defineCliPlugin({
8111
8525
  name: "kick/assets",
8112
8526
  typegens: [kickAssetsTypegen()]
8527
+ }),
8528
+ defineCliPlugin({
8529
+ name: "kick/routes",
8530
+ typegens: [kickRoutesTypegen()]
8531
+ }),
8532
+ defineCliPlugin({
8533
+ name: "kick/env",
8534
+ typegens: [kickEnvTypegen()]
8113
8535
  })
8114
8536
  ];
8115
8537
  //#endregion