@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.
- package/dist/{builtins-caRjFvKz.mjs → builtins-B0dptoXq.mjs} +626 -241
- package/dist/builtins-B0dptoXq.mjs.map +1 -0
- package/dist/{builtins-Cb_d-b1S.mjs → builtins-N3mDa6bM.mjs} +695 -273
- package/dist/cli.mjs +9 -9
- package/dist/{config-8bAt-mLl.mjs → config-Bc6ERRTE.mjs} +35 -5
- package/dist/{config-C_LQNClP.mjs → config-CRi3zCxk.mjs} +36 -6
- package/dist/config-CRi3zCxk.mjs.map +1 -0
- package/dist/{generator-extension-CYY-RI21.mjs → generator-extension-C-HwKvFf.mjs} +76 -39
- package/dist/generator-extension-C-HwKvFf.mjs.map +1 -0
- package/dist/index.d.mts +282 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/{plugin-D8K5fG-O.mjs → plugin-DfomEcef.mjs} +4 -4
- package/dist/{plugin-D8K5fG-O.mjs.map → plugin-DfomEcef.mjs.map} +1 -1
- package/dist/{plugin-Bgfg7qMk.mjs → plugin-b7ig7Uxv.mjs} +2 -2
- package/dist/{rolldown-runtime-BM29JyaJ.mjs → rolldown-runtime-CV_zlh2d.mjs} +1 -1
- package/dist/{run-plugins-BXvMFPhJ.mjs → run-plugins-D9abb5Nx.mjs} +2 -2
- package/dist/{typegen-BNz_RQTb.mjs → typegen-B9S81bOx.mjs} +48 -225
- package/dist/typegen-B9S81bOx.mjs.map +1 -0
- package/dist/{typegen-8ZeA1B-X.mjs → typegen-BKUAdp_3.mjs} +47 -228
- package/dist/{types-BBUo1vXh.mjs → types-CU89yUxU.mjs} +2 -2
- package/dist/{types-BBUo1vXh.mjs.map → types-CU89yUxU.mjs.map} +1 -1
- package/package.json +6 -4
- package/dist/builtins-caRjFvKz.mjs.map +0 -1
- package/dist/config-C_LQNClP.mjs.map +0 -1
- package/dist/generator-extension-CYY-RI21.mjs.map +0 -1
- package/dist/typegen-8ZeA1B-X.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-cli v5.1
|
|
2
|
+
* @forinda/kickjs-cli v5.2.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,19 +8,292 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import { A as httpMethodColor, C as intro, D as select, E as outro, F as writeFileSafe, M as severityColor, N as fileExists, O as spinner, P as setDryRun, S as confirm, T as multiSelect, _ as pluralize, a as initProject, b as toKebabCase, c as generateKickJsSkills, d as generateService, f as generateGuard, g as resolveRepoType, h as generateModule, j as pc, k as text, l as generateDto, m as generateAdapter, n as tryDispatchPluginGenerator, o as generateAgents, p as generateMiddleware, s as generateClaude, t as listPluginGenerators, u as generateController, v as pluralizePascal, w as log, x as toPascalCase, y as toCamelCase } from "./generator-extension-
|
|
12
|
-
import { a as resolveModuleConfig, i as loadKickConfig, t as PACKAGE_MANAGERS } from "./config-
|
|
13
|
-
import { n as defineCliPlugin } from "./types-
|
|
14
|
-
import { n as mergeCliPlugins } from "./plugin-
|
|
15
|
-
import { a as discoverAssets, i as TokenCollisionError, o as renderAssetTypes, r as watchTypegen, t as runTypegen$1 } from "./typegen-
|
|
16
|
-
import path, { basename, dirname,
|
|
11
|
+
import { A as httpMethodColor, C as intro, D as select, E as outro, F as writeFileSafe, M as severityColor, N as fileExists, O as spinner, P as setDryRun, S as confirm, T as multiSelect, _ as pluralize, a as initProject, b as toKebabCase, c as generateKickJsSkills, d as generateService, f as generateGuard, g as resolveRepoType, h as generateModule, j as pc, k as text, l as generateDto, m as generateAdapter, n as tryDispatchPluginGenerator, o as generateAgents, p as generateMiddleware, s as generateClaude, t as listPluginGenerators, u as generateController, v as pluralizePascal, w as log, x as toPascalCase, y as toCamelCase } from "./generator-extension-C-HwKvFf.mjs";
|
|
12
|
+
import { a as resolveModuleConfig, i as loadKickConfig, o as resolveTokenScope, t as PACKAGE_MANAGERS } from "./config-CRi3zCxk.mjs";
|
|
13
|
+
import { n as defineCliPlugin } from "./types-CU89yUxU.mjs";
|
|
14
|
+
import { n as mergeCliPlugins } from "./plugin-DfomEcef.mjs";
|
|
15
|
+
import { a as discoverAssets, i as TokenCollisionError, o as renderAssetTypes, r as watchTypegen, s as scanProject, t as runTypegen$1 } from "./typegen-B9S81bOx.mjs";
|
|
16
|
+
import path, { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
17
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
17
18
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
18
19
|
import { execSync, fork, spawn, spawnSync } from "node:child_process";
|
|
19
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { pathToFileURL } from "node:url";
|
|
21
21
|
import { glob } from "glob";
|
|
22
|
+
import { groupAssetKeys } from "@forinda/kickjs";
|
|
22
23
|
import { arch, platform, release } from "node:os";
|
|
23
24
|
import { generate, migrateDown, migrateLatest, migrateRollback, migrateStatus, migrateUp, renderSchemaSource, resolveDbConfig } from "@forinda/kickjs-db";
|
|
25
|
+
//#region src/commands/add.ts
|
|
26
|
+
/** Registry of KickJS packages and their required peer dependencies */
|
|
27
|
+
const PACKAGE_REGISTRY = {
|
|
28
|
+
kickjs: {
|
|
29
|
+
pkg: "@forinda/kickjs",
|
|
30
|
+
peers: ["express"],
|
|
31
|
+
description: "Unified framework: DI, decorators, routing, middleware",
|
|
32
|
+
core: true
|
|
33
|
+
},
|
|
34
|
+
vite: {
|
|
35
|
+
pkg: "@forinda/kickjs-vite",
|
|
36
|
+
peers: ["vite"],
|
|
37
|
+
description: "Vite plugin: dev server, HMR, module discovery",
|
|
38
|
+
dev: true,
|
|
39
|
+
core: true
|
|
40
|
+
},
|
|
41
|
+
cli: {
|
|
42
|
+
pkg: "@forinda/kickjs-cli",
|
|
43
|
+
peers: [],
|
|
44
|
+
description: "CLI tool and code generators",
|
|
45
|
+
dev: true,
|
|
46
|
+
core: true
|
|
47
|
+
},
|
|
48
|
+
swagger: {
|
|
49
|
+
pkg: "@forinda/kickjs-swagger",
|
|
50
|
+
peers: [],
|
|
51
|
+
description: "OpenAPI spec + Swagger UI + ReDoc"
|
|
52
|
+
},
|
|
53
|
+
db: {
|
|
54
|
+
pkg: "@forinda/kickjs-db",
|
|
55
|
+
peers: [],
|
|
56
|
+
description: "kick/db core — schema DSL, migrations, KickDbClient, customType"
|
|
57
|
+
},
|
|
58
|
+
"db-pg": {
|
|
59
|
+
pkg: "@forinda/kickjs-db-pg",
|
|
60
|
+
peers: ["pg"],
|
|
61
|
+
description: "kick/db PostgreSQL dialect + adapter (pgDialect, pgAdapter)"
|
|
62
|
+
},
|
|
63
|
+
drizzle: {
|
|
64
|
+
pkg: "@forinda/kickjs-drizzle",
|
|
65
|
+
peers: ["drizzle-orm"],
|
|
66
|
+
description: "Drizzle ORM adapter + query builder"
|
|
67
|
+
},
|
|
68
|
+
prisma: {
|
|
69
|
+
pkg: "@forinda/kickjs-prisma",
|
|
70
|
+
peers: ["@prisma/client"],
|
|
71
|
+
description: "Prisma adapter + query builder"
|
|
72
|
+
},
|
|
73
|
+
ws: {
|
|
74
|
+
pkg: "@forinda/kickjs-ws",
|
|
75
|
+
peers: ["socket.io"],
|
|
76
|
+
description: "WebSocket with @WsController decorators"
|
|
77
|
+
},
|
|
78
|
+
devtools: {
|
|
79
|
+
pkg: "@forinda/kickjs-devtools",
|
|
80
|
+
peers: [],
|
|
81
|
+
description: "Development dashboard — routes, DI, metrics, health",
|
|
82
|
+
dev: true
|
|
83
|
+
},
|
|
84
|
+
auth: {
|
|
85
|
+
pkg: "@forinda/kickjs-auth",
|
|
86
|
+
peers: ["jsonwebtoken"],
|
|
87
|
+
description: "Authentication — JWT, API key, and custom strategies"
|
|
88
|
+
},
|
|
89
|
+
queue: {
|
|
90
|
+
pkg: "@forinda/kickjs-queue",
|
|
91
|
+
peers: [],
|
|
92
|
+
description: "Queue adapter (BullMQ/RabbitMQ/Kafka)"
|
|
93
|
+
},
|
|
94
|
+
"queue:bullmq": {
|
|
95
|
+
pkg: "@forinda/kickjs-queue",
|
|
96
|
+
peers: ["bullmq", "ioredis"],
|
|
97
|
+
description: "Queue with BullMQ + Redis"
|
|
98
|
+
},
|
|
99
|
+
"queue:rabbitmq": {
|
|
100
|
+
pkg: "@forinda/kickjs-queue",
|
|
101
|
+
peers: ["amqplib"],
|
|
102
|
+
description: "Queue with RabbitMQ"
|
|
103
|
+
},
|
|
104
|
+
"queue:kafka": {
|
|
105
|
+
pkg: "@forinda/kickjs-queue",
|
|
106
|
+
peers: ["kafkajs"],
|
|
107
|
+
description: "Queue with Kafka"
|
|
108
|
+
},
|
|
109
|
+
mcp: {
|
|
110
|
+
pkg: "@forinda/kickjs-mcp",
|
|
111
|
+
peers: ["@modelcontextprotocol/sdk"],
|
|
112
|
+
description: "Model Context Protocol server — expose @Controller endpoints as AI tools"
|
|
113
|
+
},
|
|
114
|
+
testing: {
|
|
115
|
+
pkg: "@forinda/kickjs-testing",
|
|
116
|
+
peers: [],
|
|
117
|
+
description: "Test utilities and TestModule builder",
|
|
118
|
+
dev: true
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Walk up from `fromDir` to filesystem root, returning the first
|
|
123
|
+
* directory that contains `name`. Lets monorepo sub-packages pick up
|
|
124
|
+
* lockfiles and `packageManager` fields living at the workspace root.
|
|
125
|
+
*/
|
|
126
|
+
function findUp(name, fromDir = process.cwd()) {
|
|
127
|
+
let current = fromDir;
|
|
128
|
+
while (true) {
|
|
129
|
+
if (existsSync(resolve(current, name))) return current;
|
|
130
|
+
const parent = dirname(current);
|
|
131
|
+
if (parent === current) return null;
|
|
132
|
+
current = parent;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function detectFromLockfile() {
|
|
136
|
+
if (findUp("pnpm-lock.yaml")) return "pnpm";
|
|
137
|
+
if (findUp("yarn.lock")) return "yarn";
|
|
138
|
+
if (findUp("bun.lockb") || findUp("bun.lock")) return "bun";
|
|
139
|
+
if (findUp("package-lock.json")) return "npm";
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Read `packageManager` from the nearest ancestor `package.json` that
|
|
144
|
+
* declares the field (corepack convention: `"pnpm@10.0.0"`). Climbs so
|
|
145
|
+
* monorepo sub-packages inherit the workspace pm even when their own
|
|
146
|
+
* package.json omits the field.
|
|
147
|
+
*/
|
|
148
|
+
function packageManagerFromPackageJson() {
|
|
149
|
+
let dir = process.cwd();
|
|
150
|
+
while (dir) {
|
|
151
|
+
const pkgPath = resolve(dir, "package.json");
|
|
152
|
+
if (existsSync(pkgPath)) try {
|
|
153
|
+
const field = JSON.parse(readFileSync(pkgPath, "utf-8")).packageManager;
|
|
154
|
+
if (typeof field === "string") {
|
|
155
|
+
const name = field.split("@")[0];
|
|
156
|
+
if (PACKAGE_MANAGERS.includes(name)) return name;
|
|
157
|
+
}
|
|
158
|
+
} catch {}
|
|
159
|
+
const parent = dirname(dir);
|
|
160
|
+
if (parent === dir) return null;
|
|
161
|
+
dir = parent;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve which package manager to use, in priority order:
|
|
167
|
+
* 1. `--pm` CLI flag
|
|
168
|
+
* 2. `packageManager` in kick.config
|
|
169
|
+
* 3. `packageManager` in nearest ancestor package.json (corepack)
|
|
170
|
+
* 4. Nearest ancestor lockfile (pnpm-lock.yaml → yarn.lock → bun.lock → package-lock.json)
|
|
171
|
+
* 5. `'npm'` fallback
|
|
172
|
+
*
|
|
173
|
+
* Returns the chosen pm plus the source for callers that want to log
|
|
174
|
+
* the resolution path.
|
|
175
|
+
*/
|
|
176
|
+
async function resolvePackageManagerWithSource(flagPm) {
|
|
177
|
+
if (flagPm && PACKAGE_MANAGERS.includes(flagPm)) return {
|
|
178
|
+
pm: flagPm,
|
|
179
|
+
source: "flag"
|
|
180
|
+
};
|
|
181
|
+
const config = await loadKickConfig(process.cwd());
|
|
182
|
+
if (config?.packageManager && PACKAGE_MANAGERS.includes(config.packageManager)) return {
|
|
183
|
+
pm: config.packageManager,
|
|
184
|
+
source: "config"
|
|
185
|
+
};
|
|
186
|
+
const fromPkg = packageManagerFromPackageJson();
|
|
187
|
+
if (fromPkg) return {
|
|
188
|
+
pm: fromPkg,
|
|
189
|
+
source: "package.json"
|
|
190
|
+
};
|
|
191
|
+
const fromLock = detectFromLockfile();
|
|
192
|
+
if (fromLock) return {
|
|
193
|
+
pm: fromLock,
|
|
194
|
+
source: "lockfile"
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
pm: "npm",
|
|
198
|
+
source: "default"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/** Convenience wrapper for callers that don't care about the source. */
|
|
202
|
+
async function resolvePackageManager(flagPm) {
|
|
203
|
+
const { pm } = await resolvePackageManagerWithSource(flagPm);
|
|
204
|
+
return pm;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Print the package catalog. By default shows just the three core
|
|
208
|
+
* packages every project always has — the optional list churns
|
|
209
|
+
* (packages added, deprecated, removed) and a long enumeration in CLI
|
|
210
|
+
* output / docs goes stale within a release. Pass `all = true` to dump
|
|
211
|
+
* everything; that's what `kick add --list --all` triggers when an
|
|
212
|
+
* adopter genuinely wants the live catalog.
|
|
213
|
+
*/
|
|
214
|
+
function printPackageList(all = false) {
|
|
215
|
+
const entries = Object.entries(PACKAGE_REGISTRY);
|
|
216
|
+
const maxName = Math.max(...entries.map(([k]) => k.length));
|
|
217
|
+
const core = entries.filter(([, info]) => info.core);
|
|
218
|
+
const optional = entries.filter(([, info]) => !info.core);
|
|
219
|
+
const formatRow = ([name, info]) => {
|
|
220
|
+
const padded = name.padEnd(maxName + 2);
|
|
221
|
+
const peers = info.peers.length ? ` (+ ${info.peers.join(", ")})` : "";
|
|
222
|
+
return ` ${padded} ${info.description}${peers}`;
|
|
223
|
+
};
|
|
224
|
+
console.log("\n Core packages (always installed by `kick new`):\n");
|
|
225
|
+
for (const row of core) console.log(formatRow(row));
|
|
226
|
+
if (all) {
|
|
227
|
+
console.log("\n Optional packages (add as needed):\n");
|
|
228
|
+
for (const row of optional) console.log(formatRow(row));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`\n Plus ${optional.length} optional packages (auth, swagger, db, queue, …).`);
|
|
231
|
+
console.log(" Run `kick add --list --all` for the full catalog.");
|
|
232
|
+
}
|
|
233
|
+
console.log("\n Usage: kick add auth drizzle swagger");
|
|
234
|
+
console.log(" kick add queue:bullmq");
|
|
235
|
+
console.log();
|
|
236
|
+
}
|
|
237
|
+
function registerListCommand(program) {
|
|
238
|
+
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) => {
|
|
239
|
+
printPackageList(Boolean(opts.all));
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function registerAddCommand(program) {
|
|
243
|
+
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) => {
|
|
244
|
+
if (opts.list || packages.length === 0) {
|
|
245
|
+
printPackageList(Boolean(opts.all));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const { pm, source } = await resolvePackageManagerWithSource(opts.pm);
|
|
249
|
+
console.log(`\n Using ${pm} (resolved from ${source})`);
|
|
250
|
+
const forceDevFlag = opts.dev;
|
|
251
|
+
const prodDeps = /* @__PURE__ */ new Set();
|
|
252
|
+
const devDeps = /* @__PURE__ */ new Set();
|
|
253
|
+
const unknown = [];
|
|
254
|
+
for (const name of packages) {
|
|
255
|
+
const entry = PACKAGE_REGISTRY[name];
|
|
256
|
+
if (!entry) {
|
|
257
|
+
unknown.push(name);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const target = forceDevFlag || entry.dev ? devDeps : prodDeps;
|
|
261
|
+
target.add(entry.pkg);
|
|
262
|
+
for (const peer of entry.peers) target.add(peer);
|
|
263
|
+
}
|
|
264
|
+
if (unknown.length > 0) {
|
|
265
|
+
console.log(`\n Unknown packages: ${unknown.join(", ")}`);
|
|
266
|
+
console.log(" Run \"kick add --list\" to see available packages.\n");
|
|
267
|
+
if (prodDeps.size === 0 && devDeps.size === 0) return;
|
|
268
|
+
}
|
|
269
|
+
if (prodDeps.size > 0) {
|
|
270
|
+
const deps = Array.from(prodDeps);
|
|
271
|
+
const cmd = `${pm} add ${deps.join(" ")}`;
|
|
272
|
+
console.log(`\n Installing ${deps.length} dependency(ies):`);
|
|
273
|
+
for (const dep of deps) console.log(` + ${dep}`);
|
|
274
|
+
console.log();
|
|
275
|
+
try {
|
|
276
|
+
execSync(cmd, { stdio: "inherit" });
|
|
277
|
+
} catch {
|
|
278
|
+
console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (devDeps.size > 0) {
|
|
282
|
+
const deps = Array.from(devDeps);
|
|
283
|
+
const cmd = `${pm} add -D ${deps.join(" ")}`;
|
|
284
|
+
console.log(`\n Installing ${deps.length} dev dependency(ies):`);
|
|
285
|
+
for (const dep of deps) console.log(` + ${dep} (dev)`);
|
|
286
|
+
console.log();
|
|
287
|
+
try {
|
|
288
|
+
execSync(cmd, { stdio: "inherit" });
|
|
289
|
+
} catch {
|
|
290
|
+
console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
console.log(" Done!\n");
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
24
297
|
//#region src/commands/init.ts
|
|
25
298
|
/** All optional packages available for selection */
|
|
26
299
|
const OPTIONAL_PACKAGES = [
|
|
@@ -51,9 +324,11 @@ const OPTIONAL_PACKAGES = [
|
|
|
51
324
|
}
|
|
52
325
|
];
|
|
53
326
|
function registerInitCommand(program) {
|
|
54
|
-
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) => {
|
|
327
|
+
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) => {
|
|
55
328
|
intro("KickJS — Create a new project");
|
|
56
|
-
|
|
329
|
+
const yes = Boolean(opts.yes || opts.nonInteractive);
|
|
330
|
+
if (!name) if (yes) name = "my-api";
|
|
331
|
+
else name = await text({
|
|
57
332
|
message: "Project name",
|
|
58
333
|
placeholder: "my-api",
|
|
59
334
|
defaultValue: "my-api"
|
|
@@ -67,7 +342,11 @@ function registerInitCommand(program) {
|
|
|
67
342
|
const entries = readdirSync(directory);
|
|
68
343
|
if (entries.length > 0) {
|
|
69
344
|
if (opts.force) log.warn(`Clearing existing files in ${directory}`);
|
|
70
|
-
else {
|
|
345
|
+
else if (yes) {
|
|
346
|
+
log.warn(`Directory "${name}" is not empty. Pass --force to clear it.`);
|
|
347
|
+
outro("Aborted.");
|
|
348
|
+
return;
|
|
349
|
+
} else {
|
|
71
350
|
log.warn(`Directory "${name}" is not empty:`);
|
|
72
351
|
const shown = entries.slice(0, 5);
|
|
73
352
|
for (const entry of shown) log.message(` - ${entry}`);
|
|
@@ -87,7 +366,8 @@ function registerInitCommand(program) {
|
|
|
87
366
|
}
|
|
88
367
|
}
|
|
89
368
|
let template = opts.template;
|
|
90
|
-
if (!template) template =
|
|
369
|
+
if (!template) if (yes) template = "minimal";
|
|
370
|
+
else template = await select({
|
|
91
371
|
message: "Project template",
|
|
92
372
|
options: [
|
|
93
373
|
{
|
|
@@ -113,7 +393,8 @@ function registerInitCommand(program) {
|
|
|
113
393
|
]
|
|
114
394
|
});
|
|
115
395
|
let packageManager = opts.pm;
|
|
116
|
-
if (!packageManager) packageManager = await
|
|
396
|
+
if (!packageManager) if (yes) packageManager = await resolvePackageManager(void 0);
|
|
397
|
+
else packageManager = await select({
|
|
117
398
|
message: "Package manager",
|
|
118
399
|
options: [
|
|
119
400
|
{
|
|
@@ -135,7 +416,8 @@ function registerInitCommand(program) {
|
|
|
135
416
|
]
|
|
136
417
|
});
|
|
137
418
|
let defaultRepo = opts.repo;
|
|
138
|
-
if (!defaultRepo)
|
|
419
|
+
if (!defaultRepo) if (yes) defaultRepo = "inmemory";
|
|
420
|
+
else {
|
|
139
421
|
defaultRepo = await select({
|
|
140
422
|
message: "Default repository/ORM",
|
|
141
423
|
options: [
|
|
@@ -168,19 +450,20 @@ function registerInitCommand(program) {
|
|
|
168
450
|
const raw = opts.packages.trim().toLowerCase();
|
|
169
451
|
if (raw === "" || raw === "none" || raw === "false") selectedPackages = [];
|
|
170
452
|
else selectedPackages = opts.packages.split(",").map((p) => p.trim()).filter(Boolean);
|
|
171
|
-
} else selectedPackages =
|
|
453
|
+
} else if (yes) selectedPackages = [];
|
|
454
|
+
else selectedPackages = await multiSelect({
|
|
172
455
|
message: "Select packages to include",
|
|
173
456
|
options: [...OPTIONAL_PACKAGES],
|
|
174
457
|
required: false
|
|
175
458
|
});
|
|
176
459
|
let initGit;
|
|
177
|
-
if (opts.git === void 0) initGit = await confirm({
|
|
460
|
+
if (opts.git === void 0) initGit = yes ? true : await confirm({
|
|
178
461
|
message: "Initialize git repository?",
|
|
179
462
|
initialValue: true
|
|
180
463
|
});
|
|
181
464
|
else initGit = opts.git;
|
|
182
465
|
let installDeps;
|
|
183
|
-
if (opts.install === void 0) installDeps = await confirm({
|
|
466
|
+
if (opts.install === void 0) installDeps = yes ? true : await confirm({
|
|
184
467
|
message: "Install dependencies?",
|
|
185
468
|
initialValue: true
|
|
186
469
|
});
|
|
@@ -428,7 +711,7 @@ function detectName(outDir, override) {
|
|
|
428
711
|
const pkg = JSON.parse(readFileSync(join(outDir, "package.json"), "utf-8"));
|
|
429
712
|
if (pkg.name) return pkg.name.replace(/^@[^/]+\//, "");
|
|
430
713
|
} catch {}
|
|
431
|
-
return outDir.split("/").
|
|
714
|
+
return outDir.split("/").findLast(Boolean) ?? "app";
|
|
432
715
|
}
|
|
433
716
|
function detectPm(outDir, override) {
|
|
434
717
|
if (override) return override;
|
|
@@ -892,7 +1175,7 @@ function parseFields(raw) {
|
|
|
892
1175
|
});
|
|
893
1176
|
}
|
|
894
1177
|
async function generateScaffold(options) {
|
|
895
|
-
const { name, fields, modulesDir, noEntity, noTests, repo = "inmemory" } = options;
|
|
1178
|
+
const { name, fields, modulesDir, noEntity, noTests: _noTests, repo = "inmemory", tokenScope = "app" } = options;
|
|
896
1179
|
const shouldPluralize = options.pluralize !== false;
|
|
897
1180
|
const kebab = toKebabCase(name);
|
|
898
1181
|
const pascal = toPascalCase(name);
|
|
@@ -914,7 +1197,7 @@ async function generateScaffold(options) {
|
|
|
914
1197
|
await write(`application/dtos/${kebab}-response.dto.ts`, genResponseDTO(pascal, fields));
|
|
915
1198
|
const useCases = genUseCases(pascal, kebab, plural, pluralPascal);
|
|
916
1199
|
for (const uc of useCases) await write(`application/use-cases/${uc.file}`, uc.content);
|
|
917
|
-
await write(`domain/repositories/${kebab}.repository.ts`, genRepositoryInterface(pascal, kebab));
|
|
1200
|
+
await write(`domain/repositories/${kebab}.repository.ts`, genRepositoryInterface(pascal, kebab, tokenScope));
|
|
918
1201
|
await write(`domain/services/${kebab}-domain.service.ts`, genDomainService(pascal, kebab));
|
|
919
1202
|
if (repo === "inmemory") await write(`infrastructure/repositories/in-memory-${kebab}.repository.ts`, genInMemoryRepository(pascal, kebab, fields));
|
|
920
1203
|
if (!noEntity) {
|
|
@@ -1195,7 +1478,7 @@ export class ${pascal}Controller {
|
|
|
1195
1478
|
}
|
|
1196
1479
|
`;
|
|
1197
1480
|
}
|
|
1198
|
-
function genRepositoryInterface(pascal, kebab) {
|
|
1481
|
+
function genRepositoryInterface(pascal, kebab, tokenScope) {
|
|
1199
1482
|
return `import { createToken } from '@forinda/kickjs'
|
|
1200
1483
|
import type { ${pascal}ResponseDTO } from '../../application/dtos/${kebab}-response.dto'
|
|
1201
1484
|
import type { Create${pascal}DTO } from '../../application/dtos/create-${kebab}.dto'
|
|
@@ -1216,8 +1499,12 @@ export interface I${pascal}Repository {
|
|
|
1216
1499
|
* \`container.resolve(${pascal.toUpperCase()}_REPOSITORY)\` and
|
|
1217
1500
|
* \`@Inject(${pascal.toUpperCase()}_REPOSITORY)\` both return the typed
|
|
1218
1501
|
* interface — no manual generic, no \`any\` cast.
|
|
1502
|
+
*
|
|
1503
|
+
* The \`'${tokenScope}/'\` prefix matches the project scope so
|
|
1504
|
+
* \`kick-lint\`'s \`token-reserved-prefix\` rule never fires —
|
|
1505
|
+
* adopters must NOT use the reserved \`'kick/'\` namespace.
|
|
1219
1506
|
*/
|
|
1220
|
-
export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('
|
|
1507
|
+
export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('${tokenScope}/${kebab}/repository')
|
|
1221
1508
|
`;
|
|
1222
1509
|
}
|
|
1223
1510
|
function genDomainService(pascal, kebab) {
|
|
@@ -1497,6 +1784,7 @@ async function runModuleGeneration(names, opts, dryRun) {
|
|
|
1497
1784
|
const repo = opts.repo ?? resolveRepoType(mc.repo);
|
|
1498
1785
|
const pattern = opts.pattern ?? config?.pattern ?? "ddd";
|
|
1499
1786
|
const shouldPluralize = opts.pluralize === false ? false : mc.pluralize ?? true;
|
|
1787
|
+
const tokenScope = resolveTokenScope(config, process.cwd());
|
|
1500
1788
|
const allFiles = [];
|
|
1501
1789
|
for (const name of names) {
|
|
1502
1790
|
const files = await generateModule({
|
|
@@ -1510,7 +1798,8 @@ async function runModuleGeneration(names, opts, dryRun) {
|
|
|
1510
1798
|
pattern,
|
|
1511
1799
|
dryRun,
|
|
1512
1800
|
pluralize: shouldPluralize,
|
|
1513
|
-
prismaClientPath: mc.prismaClientPath
|
|
1801
|
+
prismaClientPath: mc.prismaClientPath,
|
|
1802
|
+
tokenScope
|
|
1514
1803
|
});
|
|
1515
1804
|
allFiles.push(...files);
|
|
1516
1805
|
}
|
|
@@ -1673,16 +1962,19 @@ function registerGenerateCommand(program) {
|
|
|
1673
1962
|
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");
|
|
1674
1963
|
process.exit(1);
|
|
1675
1964
|
}
|
|
1676
|
-
const
|
|
1965
|
+
const config = await loadKickConfig(process.cwd());
|
|
1966
|
+
const mc = resolveModuleConfig(config);
|
|
1677
1967
|
const modulesDir = opts.modulesDir ?? mc.dir ?? "src/modules";
|
|
1678
1968
|
const fields = parseFields(rawFields);
|
|
1969
|
+
const tokenScope = resolveTokenScope(config, process.cwd());
|
|
1679
1970
|
const files = await generateScaffold({
|
|
1680
1971
|
name,
|
|
1681
1972
|
fields,
|
|
1682
1973
|
modulesDir: resolve(modulesDir),
|
|
1683
1974
|
noEntity: opts.entity === false,
|
|
1684
1975
|
noTests: opts.tests === false,
|
|
1685
|
-
pluralize: opts.pluralize === false ? false : mc.pluralize ?? true
|
|
1976
|
+
pluralize: opts.pluralize === false ? false : mc.pluralize ?? true,
|
|
1977
|
+
tokenScope
|
|
1686
1978
|
});
|
|
1687
1979
|
console.log(`\n Scaffolded ${name} with ${fields.length} field(s):`);
|
|
1688
1980
|
for (const f of fields) console.log(` ${f.name}: ${f.type}${f.optional ? " (optional)" : ""}`);
|
|
@@ -1771,6 +2063,17 @@ const BANNER_PREFIX = "/* AUTO-GENERATED by kick typegen — do not edit. Plugin
|
|
|
1771
2063
|
async function runTypegen(opts) {
|
|
1772
2064
|
const typesDirAbs = path.resolve(opts.cwd, TYPES_DIR);
|
|
1773
2065
|
await mkdir(typesDirAbs, { recursive: true });
|
|
2066
|
+
const scanCache = /* @__PURE__ */ new Map();
|
|
2067
|
+
const scanFn = opts.scan ?? scanProject;
|
|
2068
|
+
const getScanResult = (scanOpts) => {
|
|
2069
|
+
const key = stableScanKey(scanOpts);
|
|
2070
|
+
let pending = scanCache.get(key);
|
|
2071
|
+
if (!pending) {
|
|
2072
|
+
pending = scanFn(scanOpts);
|
|
2073
|
+
scanCache.set(key, pending);
|
|
2074
|
+
}
|
|
2075
|
+
return pending;
|
|
2076
|
+
};
|
|
1774
2077
|
const ctx = {
|
|
1775
2078
|
cwd: opts.cwd,
|
|
1776
2079
|
config: opts.config,
|
|
@@ -1782,6 +2085,7 @@ async function runTypegen(opts) {
|
|
|
1782
2085
|
await mkdir(path.dirname(abs), { recursive: true });
|
|
1783
2086
|
await writeFile(abs, contents, "utf8");
|
|
1784
2087
|
},
|
|
2088
|
+
getScanResult,
|
|
1785
2089
|
log: console
|
|
1786
2090
|
};
|
|
1787
2091
|
const results = [];
|
|
@@ -1794,7 +2098,8 @@ async function runTypegen(opts) {
|
|
|
1794
2098
|
});
|
|
1795
2099
|
continue;
|
|
1796
2100
|
}
|
|
1797
|
-
const
|
|
2101
|
+
const ext = plugin.outExtension ?? ".d.ts";
|
|
2102
|
+
const file = path.join(typesDirAbs, `${plugin.id.replace(/\//g, "__")}${ext}`);
|
|
1798
2103
|
const next = `${BANNER_PREFIX}${plugin.id} */\n\n` + out + "\n";
|
|
1799
2104
|
let prev = "";
|
|
1800
2105
|
if (existsSync(file)) prev = await readFile(file, "utf8");
|
|
@@ -1816,6 +2121,24 @@ async function runTypegen(opts) {
|
|
|
1816
2121
|
}
|
|
1817
2122
|
return results;
|
|
1818
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Order-independent cache key for `ScanOptions`. Builds the key from
|
|
2126
|
+
* the known fields in a fixed order so semantically equal options
|
|
2127
|
+
* always produce the same key regardless of how the caller built the
|
|
2128
|
+
* object literal. Arrays (extensions / exclude) are sorted before
|
|
2129
|
+
* joining so `['.ts', '.tsx']` and `['.tsx', '.ts']` collide.
|
|
2130
|
+
*/
|
|
2131
|
+
function stableScanKey(opts) {
|
|
2132
|
+
const extensions = (opts.extensions ?? []).slice().toSorted().join(",");
|
|
2133
|
+
const exclude = (opts.exclude ?? []).slice().toSorted().join(",");
|
|
2134
|
+
return [
|
|
2135
|
+
`root=${opts.root}`,
|
|
2136
|
+
`cwd=${opts.cwd}`,
|
|
2137
|
+
`extensions=${extensions}`,
|
|
2138
|
+
`exclude=${exclude}`,
|
|
2139
|
+
`envFile=${opts.envFile ?? ""}`
|
|
2140
|
+
].join("|");
|
|
2141
|
+
}
|
|
1819
2142
|
//#endregion
|
|
1820
2143
|
//#region src/typegen/disable-filter.ts
|
|
1821
2144
|
/**
|
|
@@ -1937,18 +2260,15 @@ async function processEntry(namespace, entry, cwd, distAbs) {
|
|
|
1937
2260
|
});
|
|
1938
2261
|
mkdirSync(destAbs, { recursive: true });
|
|
1939
2262
|
const manifestSlice = {};
|
|
1940
|
-
const
|
|
1941
|
-
for (const relPath of
|
|
2263
|
+
const { pairs, collisionGroupsResolved } = groupAssetKeys(namespace, [...matches].toSorted(), { strategy: entry.keys ?? "auto" });
|
|
2264
|
+
for (const { rel: relPath, key } of pairs) {
|
|
1942
2265
|
const srcFile = join(srcAbs, relPath);
|
|
1943
2266
|
const destFile = join(destAbs, relPath);
|
|
1944
2267
|
mkdirSync(dirname(destFile), { recursive: true });
|
|
1945
2268
|
cpSync(srcFile, destFile);
|
|
1946
|
-
|
|
1947
|
-
const previous = keyOwner.get(logicalKey);
|
|
1948
|
-
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.`);
|
|
1949
|
-
keyOwner.set(logicalKey, relPath);
|
|
1950
|
-
manifestSlice[logicalKey] = toManifestRelative(distAbs, destFile);
|
|
2269
|
+
manifestSlice[key] = toManifestRelative(distAbs, destFile);
|
|
1951
2270
|
}
|
|
2271
|
+
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).`);
|
|
1952
2272
|
return {
|
|
1953
2273
|
entrySummary: {
|
|
1954
2274
|
namespace,
|
|
@@ -1959,11 +2279,6 @@ async function processEntry(namespace, entry, cwd, distAbs) {
|
|
|
1959
2279
|
manifestSlice
|
|
1960
2280
|
};
|
|
1961
2281
|
}
|
|
1962
|
-
/** Strip the final extension from a file path (`mails/welcome.ejs` → `mails/welcome`). */
|
|
1963
|
-
function stripExt(path) {
|
|
1964
|
-
const ext = extname(path);
|
|
1965
|
-
return ext ? path.slice(0, -ext.length) : path;
|
|
1966
|
-
}
|
|
1967
2282
|
/**
|
|
1968
2283
|
* Make `destFile` relative to the manifest's directory + force POSIX
|
|
1969
2284
|
* separators so the manifest is byte-stable across platforms.
|
|
@@ -2005,8 +2320,21 @@ function isDirectorySync(path) {
|
|
|
2005
2320
|
* This function just creates the Vite server, listens, and handles shutdown.
|
|
2006
2321
|
* Vite owns the HTTP port — Express runs as post-middleware on Vite's server.
|
|
2007
2322
|
*/
|
|
2008
|
-
|
|
2323
|
+
/**
|
|
2324
|
+
* Resolve whether the dev server's chokidar should poll instead of
|
|
2325
|
+
* relying on `fs.watch` events. CLI flag wins over env var; default
|
|
2326
|
+
* is event-based (faster, lower CPU). Polling is the right choice in
|
|
2327
|
+
* Docker bind mounts, WSL crossing the WSL/Windows boundary, NFS,
|
|
2328
|
+
* and some old Linux kernels where new-file events get dropped.
|
|
2329
|
+
*/
|
|
2330
|
+
function resolvePolling(flag) {
|
|
2331
|
+
if (typeof flag === "boolean") return flag;
|
|
2332
|
+
const env = process.env.KICKJS_WATCH_POLLING;
|
|
2333
|
+
return env === "1" || env === "true";
|
|
2334
|
+
}
|
|
2335
|
+
async function startDevServer(_entry, port, opts = {}) {
|
|
2009
2336
|
if (port) process.env.PORT = port;
|
|
2337
|
+
const polling = resolvePolling(opts.polling);
|
|
2010
2338
|
const cwd = process.cwd();
|
|
2011
2339
|
const devConfig = await loadKickConfig(cwd);
|
|
2012
2340
|
const schemaValidator = devConfig?.typegen?.schemaValidator ?? "zod";
|
|
@@ -2019,7 +2347,8 @@ async function startDevServer(_entry, port) {
|
|
|
2019
2347
|
envFile,
|
|
2020
2348
|
srcDir: devConfig?.typegen?.srcDir,
|
|
2021
2349
|
outDir: devConfig?.typegen?.outDir,
|
|
2022
|
-
assetMap: devConfig?.assetMap
|
|
2350
|
+
assetMap: devConfig?.assetMap,
|
|
2351
|
+
runPlugins: false
|
|
2023
2352
|
});
|
|
2024
2353
|
} catch (err) {
|
|
2025
2354
|
console.warn(` kick typegen: skipped (${err?.message ?? err})`);
|
|
@@ -2032,7 +2361,13 @@ async function startDevServer(_entry, port) {
|
|
|
2032
2361
|
const { createServer } = await import(pathToFileURL(createRequire(resolve("package.json")).resolve("vite")).href);
|
|
2033
2362
|
const server = await createServer({
|
|
2034
2363
|
configFile: resolve("vite.config.ts"),
|
|
2035
|
-
server: {
|
|
2364
|
+
server: {
|
|
2365
|
+
port: port ? parseInt(port, 10) : void 0,
|
|
2366
|
+
...polling ? { watch: {
|
|
2367
|
+
usePolling: true,
|
|
2368
|
+
interval: 100
|
|
2369
|
+
} } : {}
|
|
2370
|
+
}
|
|
2036
2371
|
});
|
|
2037
2372
|
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)) : [];
|
|
2038
2373
|
const isAssetFile = (file) => assetSrcRoots.some((root) => file === root || file.startsWith(`${root}/`));
|
|
@@ -2053,7 +2388,8 @@ async function startDevServer(_entry, port) {
|
|
|
2053
2388
|
envFile,
|
|
2054
2389
|
srcDir: devConfig?.typegen?.srcDir,
|
|
2055
2390
|
outDir: devConfig?.typegen?.outDir,
|
|
2056
|
-
assetMap: devConfig?.assetMap
|
|
2391
|
+
assetMap: devConfig?.assetMap,
|
|
2392
|
+
runPlugins: false
|
|
2057
2393
|
}).catch(() => {});
|
|
2058
2394
|
runAllPluginTypegens({
|
|
2059
2395
|
cwd,
|
|
@@ -2078,9 +2414,9 @@ async function startDevServer(_entry, port) {
|
|
|
2078
2414
|
process.on("SIGTERM", shutdown);
|
|
2079
2415
|
}
|
|
2080
2416
|
function registerRunCommands(program) {
|
|
2081
|
-
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) => {
|
|
2417
|
+
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) => {
|
|
2082
2418
|
try {
|
|
2083
|
-
await startDevServer(opts.entry, opts.port);
|
|
2419
|
+
await startDevServer(opts.entry, opts.port, { polling: opts.polling });
|
|
2084
2420
|
} catch (err) {
|
|
2085
2421
|
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");
|
|
2086
2422
|
else console.error("\n Dev server failed:", err.message ?? err);
|
|
@@ -2173,7 +2509,7 @@ function registerInfoCommand(program) {
|
|
|
2173
2509
|
}
|
|
2174
2510
|
//#endregion
|
|
2175
2511
|
//#region src/commands/inspect.ts
|
|
2176
|
-
const { bold, dim, green, red, yellow,
|
|
2512
|
+
const { bold, dim, green, red, yellow, blue } = pc;
|
|
2177
2513
|
function formatUptime(seconds) {
|
|
2178
2514
|
const d = Math.floor(seconds / 86400);
|
|
2179
2515
|
const h = Math.floor(seconds % 86400 / 3600);
|
|
@@ -2282,198 +2618,6 @@ function registerInspectCommand(program) {
|
|
|
2282
2618
|
});
|
|
2283
2619
|
}
|
|
2284
2620
|
//#endregion
|
|
2285
|
-
//#region src/commands/add.ts
|
|
2286
|
-
/** Registry of KickJS packages and their required peer dependencies */
|
|
2287
|
-
const PACKAGE_REGISTRY = {
|
|
2288
|
-
kickjs: {
|
|
2289
|
-
pkg: "@forinda/kickjs",
|
|
2290
|
-
peers: ["express"],
|
|
2291
|
-
description: "Unified framework: DI, decorators, routing, middleware"
|
|
2292
|
-
},
|
|
2293
|
-
vite: {
|
|
2294
|
-
pkg: "@forinda/kickjs-vite",
|
|
2295
|
-
peers: ["vite"],
|
|
2296
|
-
description: "Vite plugin: dev server, HMR, module discovery",
|
|
2297
|
-
dev: true
|
|
2298
|
-
},
|
|
2299
|
-
config: {
|
|
2300
|
-
pkg: "dotenv",
|
|
2301
|
-
peers: [],
|
|
2302
|
-
description: "Optional .env file loader (kickjs ConfigService now ships in @forinda/kickjs)"
|
|
2303
|
-
},
|
|
2304
|
-
cli: {
|
|
2305
|
-
pkg: "@forinda/kickjs-cli",
|
|
2306
|
-
peers: [],
|
|
2307
|
-
description: "CLI tool and code generators",
|
|
2308
|
-
dev: true
|
|
2309
|
-
},
|
|
2310
|
-
swagger: {
|
|
2311
|
-
pkg: "@forinda/kickjs-swagger",
|
|
2312
|
-
peers: [],
|
|
2313
|
-
description: "OpenAPI spec + Swagger UI + ReDoc"
|
|
2314
|
-
},
|
|
2315
|
-
drizzle: {
|
|
2316
|
-
pkg: "@forinda/kickjs-drizzle",
|
|
2317
|
-
peers: ["drizzle-orm"],
|
|
2318
|
-
description: "Drizzle ORM adapter + query builder"
|
|
2319
|
-
},
|
|
2320
|
-
prisma: {
|
|
2321
|
-
pkg: "@forinda/kickjs-prisma",
|
|
2322
|
-
peers: ["@prisma/client"],
|
|
2323
|
-
description: "Prisma adapter + query builder"
|
|
2324
|
-
},
|
|
2325
|
-
ws: {
|
|
2326
|
-
pkg: "@forinda/kickjs-ws",
|
|
2327
|
-
peers: ["socket.io"],
|
|
2328
|
-
description: "WebSocket with @WsController decorators"
|
|
2329
|
-
},
|
|
2330
|
-
devtools: {
|
|
2331
|
-
pkg: "@forinda/kickjs-devtools",
|
|
2332
|
-
peers: [],
|
|
2333
|
-
description: "Development dashboard — routes, DI, metrics, health",
|
|
2334
|
-
dev: true
|
|
2335
|
-
},
|
|
2336
|
-
auth: {
|
|
2337
|
-
pkg: "@forinda/kickjs-auth",
|
|
2338
|
-
peers: ["jsonwebtoken"],
|
|
2339
|
-
description: "Authentication — JWT, API key, and custom strategies"
|
|
2340
|
-
},
|
|
2341
|
-
queue: {
|
|
2342
|
-
pkg: "@forinda/kickjs-queue",
|
|
2343
|
-
peers: [],
|
|
2344
|
-
description: "Queue adapter (BullMQ/RabbitMQ/Kafka)"
|
|
2345
|
-
},
|
|
2346
|
-
"queue:bullmq": {
|
|
2347
|
-
pkg: "@forinda/kickjs-queue",
|
|
2348
|
-
peers: ["bullmq", "ioredis"],
|
|
2349
|
-
description: "Queue with BullMQ + Redis"
|
|
2350
|
-
},
|
|
2351
|
-
"queue:rabbitmq": {
|
|
2352
|
-
pkg: "@forinda/kickjs-queue",
|
|
2353
|
-
peers: ["amqplib"],
|
|
2354
|
-
description: "Queue with RabbitMQ"
|
|
2355
|
-
},
|
|
2356
|
-
"queue:kafka": {
|
|
2357
|
-
pkg: "@forinda/kickjs-queue",
|
|
2358
|
-
peers: ["kafkajs"],
|
|
2359
|
-
description: "Queue with Kafka"
|
|
2360
|
-
},
|
|
2361
|
-
mcp: {
|
|
2362
|
-
pkg: "@forinda/kickjs-mcp",
|
|
2363
|
-
peers: ["@modelcontextprotocol/sdk"],
|
|
2364
|
-
description: "Model Context Protocol server — expose @Controller endpoints as AI tools"
|
|
2365
|
-
},
|
|
2366
|
-
testing: {
|
|
2367
|
-
pkg: "@forinda/kickjs-testing",
|
|
2368
|
-
peers: [],
|
|
2369
|
-
description: "Test utilities and TestModule builder",
|
|
2370
|
-
dev: true
|
|
2371
|
-
}
|
|
2372
|
-
};
|
|
2373
|
-
function detectPackageManager() {
|
|
2374
|
-
if (existsSync(resolve("pnpm-lock.yaml"))) return "pnpm";
|
|
2375
|
-
if (existsSync(resolve("yarn.lock"))) return "yarn";
|
|
2376
|
-
if (existsSync(resolve("bun.lockb")) || existsSync(resolve("bun.lock"))) return "bun";
|
|
2377
|
-
return "npm";
|
|
2378
|
-
}
|
|
2379
|
-
/** Read `packageManager` from package.json (corepack convention: "pnpm@10.0.0") */
|
|
2380
|
-
function packageManagerFromPackageJson() {
|
|
2381
|
-
try {
|
|
2382
|
-
const field = JSON.parse(readFileSync(resolve("package.json"), "utf-8")).packageManager;
|
|
2383
|
-
if (typeof field !== "string") return null;
|
|
2384
|
-
const name = field.split("@")[0];
|
|
2385
|
-
return PACKAGE_MANAGERS.includes(name) ? name : null;
|
|
2386
|
-
} catch {
|
|
2387
|
-
return null;
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
/**
|
|
2391
|
-
* Resolve which package manager to use, in priority order:
|
|
2392
|
-
* 1. `--pm` CLI flag
|
|
2393
|
-
* 2. `packageManager` in kick.config
|
|
2394
|
-
* 3. `packageManager` in package.json (corepack)
|
|
2395
|
-
* 4. Lockfile detection
|
|
2396
|
-
* 5. `'npm'`
|
|
2397
|
-
*/
|
|
2398
|
-
async function resolvePackageManager(flagPm) {
|
|
2399
|
-
if (flagPm && PACKAGE_MANAGERS.includes(flagPm)) return flagPm;
|
|
2400
|
-
const config = await loadKickConfig(process.cwd());
|
|
2401
|
-
if (config?.packageManager && PACKAGE_MANAGERS.includes(config.packageManager)) return config.packageManager;
|
|
2402
|
-
const fromPkg = packageManagerFromPackageJson();
|
|
2403
|
-
if (fromPkg) return fromPkg;
|
|
2404
|
-
return detectPackageManager();
|
|
2405
|
-
}
|
|
2406
|
-
function printPackageList() {
|
|
2407
|
-
console.log("\n Available KickJS packages:\n");
|
|
2408
|
-
const maxName = Math.max(...Object.keys(PACKAGE_REGISTRY).map((k) => k.length));
|
|
2409
|
-
for (const [name, info] of Object.entries(PACKAGE_REGISTRY)) {
|
|
2410
|
-
const padded = name.padEnd(maxName + 2);
|
|
2411
|
-
const peers = info.peers.length ? ` (+ ${info.peers.join(", ")})` : "";
|
|
2412
|
-
console.log(` ${padded} ${info.description}${peers}`);
|
|
2413
|
-
}
|
|
2414
|
-
console.log("\n Usage: kick add auth drizzle swagger");
|
|
2415
|
-
console.log(" kick add queue:bullmq");
|
|
2416
|
-
console.log();
|
|
2417
|
-
}
|
|
2418
|
-
function registerListCommand(program) {
|
|
2419
|
-
program.command("list").alias("ls").description("List all available KickJS packages").action(() => {
|
|
2420
|
-
printPackageList();
|
|
2421
|
-
});
|
|
2422
|
-
}
|
|
2423
|
-
function registerAddCommand(program) {
|
|
2424
|
-
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) => {
|
|
2425
|
-
if (opts.list || packages.length === 0) {
|
|
2426
|
-
printPackageList();
|
|
2427
|
-
return;
|
|
2428
|
-
}
|
|
2429
|
-
const pm = await resolvePackageManager(opts.pm);
|
|
2430
|
-
const forceDevFlag = opts.dev;
|
|
2431
|
-
const prodDeps = /* @__PURE__ */ new Set();
|
|
2432
|
-
const devDeps = /* @__PURE__ */ new Set();
|
|
2433
|
-
const unknown = [];
|
|
2434
|
-
for (const name of packages) {
|
|
2435
|
-
const entry = PACKAGE_REGISTRY[name];
|
|
2436
|
-
if (!entry) {
|
|
2437
|
-
unknown.push(name);
|
|
2438
|
-
continue;
|
|
2439
|
-
}
|
|
2440
|
-
const target = forceDevFlag || entry.dev ? devDeps : prodDeps;
|
|
2441
|
-
target.add(entry.pkg);
|
|
2442
|
-
for (const peer of entry.peers) target.add(peer);
|
|
2443
|
-
}
|
|
2444
|
-
if (unknown.length > 0) {
|
|
2445
|
-
console.log(`\n Unknown packages: ${unknown.join(", ")}`);
|
|
2446
|
-
console.log(" Run \"kick add --list\" to see available packages.\n");
|
|
2447
|
-
if (prodDeps.size === 0 && devDeps.size === 0) return;
|
|
2448
|
-
}
|
|
2449
|
-
if (prodDeps.size > 0) {
|
|
2450
|
-
const deps = Array.from(prodDeps);
|
|
2451
|
-
const cmd = `${pm} add ${deps.join(" ")}`;
|
|
2452
|
-
console.log(`\n Installing ${deps.length} dependency(ies):`);
|
|
2453
|
-
for (const dep of deps) console.log(` + ${dep}`);
|
|
2454
|
-
console.log();
|
|
2455
|
-
try {
|
|
2456
|
-
execSync(cmd, { stdio: "inherit" });
|
|
2457
|
-
} catch {
|
|
2458
|
-
console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
if (devDeps.size > 0) {
|
|
2462
|
-
const deps = Array.from(devDeps);
|
|
2463
|
-
const cmd = `${pm} add -D ${deps.join(" ")}`;
|
|
2464
|
-
console.log(`\n Installing ${deps.length} dev dependency(ies):`);
|
|
2465
|
-
for (const dep of deps) console.log(` + ${dep} (dev)`);
|
|
2466
|
-
console.log();
|
|
2467
|
-
try {
|
|
2468
|
-
execSync(cmd, { stdio: "inherit" });
|
|
2469
|
-
} catch {
|
|
2470
|
-
console.log(`\n Installation failed. Run manually:\n ${cmd}\n`);
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
console.log(" Done!\n");
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
//#endregion
|
|
2477
2621
|
//#region src/explain/known-issues.ts
|
|
2478
2622
|
function includesAll(haystack, needles) {
|
|
2479
2623
|
const lower = haystack.toLowerCase();
|
|
@@ -3280,8 +3424,8 @@ function registerTypegenCommand(program) {
|
|
|
3280
3424
|
const cwd = process.cwd();
|
|
3281
3425
|
const config = await loadKickConfig(cwd);
|
|
3282
3426
|
if (opts.list) {
|
|
3283
|
-
const { mergeCliPlugins } = await import("./plugin-
|
|
3284
|
-
const { builtinCliPlugins } = await import("./builtins-
|
|
3427
|
+
const { mergeCliPlugins } = await import("./plugin-DfomEcef.mjs").then((n) => n.t);
|
|
3428
|
+
const { builtinCliPlugins } = await import("./builtins-B0dptoXq.mjs");
|
|
3285
3429
|
const merged = mergeCliPlugins([...builtinCliPlugins, ...config?.plugins ?? []], config?.commands ?? []);
|
|
3286
3430
|
const disabled = new Set(config?.typegen?.disable ?? []);
|
|
3287
3431
|
if (merged.typegens.length === 0) {
|
|
@@ -3307,7 +3451,8 @@ function registerTypegenCommand(program) {
|
|
|
3307
3451
|
allowDuplicates: opts.allowDuplicates,
|
|
3308
3452
|
schemaValidator,
|
|
3309
3453
|
envFile,
|
|
3310
|
-
assetMap: config?.assetMap
|
|
3454
|
+
assetMap: config?.assetMap,
|
|
3455
|
+
runPlugins: false
|
|
3311
3456
|
};
|
|
3312
3457
|
try {
|
|
3313
3458
|
if (opts.watch) {
|
|
@@ -3727,6 +3872,238 @@ const kickAssetsTypegen = () => ({
|
|
|
3727
3872
|
}
|
|
3728
3873
|
});
|
|
3729
3874
|
//#endregion
|
|
3875
|
+
//#region src/typegen/render/routes.ts
|
|
3876
|
+
const ROUTES_HEADER = `/* eslint-disable */
|
|
3877
|
+
// AUTO-GENERATED by \`kick typegen\`. DO NOT EDIT.
|
|
3878
|
+
// Re-run with \`kick typegen\` or rely on \`kick dev\` to refresh.
|
|
3879
|
+
`;
|
|
3880
|
+
/**
|
|
3881
|
+
* Render the `KickRoutes` global namespace augmentation. Each interface
|
|
3882
|
+
* inside corresponds to a controller class; each property is a single
|
|
3883
|
+
* route method on that controller, conforming to `RouteShape`.
|
|
3884
|
+
*
|
|
3885
|
+
* Fills `params` from URL patterns, `query` from `@ApiQueryParams`, and
|
|
3886
|
+
* `body`/`query`/`params` (when schema-validated) from the configured
|
|
3887
|
+
* schema validator. `response` is emitted as `unknown`.
|
|
3888
|
+
*/
|
|
3889
|
+
function renderRoutes(routes, routesOutFile, schemaValidator) {
|
|
3890
|
+
if (routes.length === 0) return `${ROUTES_HEADER}
|
|
3891
|
+
// (no routes discovered yet — annotate a controller method with
|
|
3892
|
+
// @Get/@Post/@Put/@Delete/@Patch and re-run \`kick typegen\`)
|
|
3893
|
+
declare global {
|
|
3894
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
3895
|
+
namespace KickRoutes {}
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
export {}
|
|
3899
|
+
`;
|
|
3900
|
+
const byController = /* @__PURE__ */ new Map();
|
|
3901
|
+
for (const r of routes) {
|
|
3902
|
+
const arr = byController.get(r.controller) ?? [];
|
|
3903
|
+
arr.push(r);
|
|
3904
|
+
byController.set(r.controller, arr);
|
|
3905
|
+
}
|
|
3906
|
+
const schemaImports = /* @__PURE__ */ new Map();
|
|
3907
|
+
const renderField = (schema, routeFilePath) => {
|
|
3908
|
+
const alias = planSchemaImport(schema, routeFilePath, routesOutFile, schemaValidator, schemaImports);
|
|
3909
|
+
return alias ? `import('zod').infer<typeof ${alias}>` : null;
|
|
3910
|
+
};
|
|
3911
|
+
const interfaces = [];
|
|
3912
|
+
for (const [controller, methods] of byController) {
|
|
3913
|
+
const lines = [` interface ${controller} {`];
|
|
3914
|
+
for (const m of methods) {
|
|
3915
|
+
const urlParamsType = m.pathParams.length > 0 ? `{ ${m.pathParams.map((p) => `${p}: string`).join("; ")} }` : "{}";
|
|
3916
|
+
const bodySchemaType = renderField(m.bodySchema, m.filePath);
|
|
3917
|
+
const querySchemaType = renderField(m.querySchema, m.filePath);
|
|
3918
|
+
const paramsType = renderField(m.paramsSchema, m.filePath) ?? urlParamsType;
|
|
3919
|
+
const bodyType = bodySchemaType ?? "unknown";
|
|
3920
|
+
const queryType = querySchemaType ?? renderQueryShape(m);
|
|
3921
|
+
const docLines = renderQueryDocLines(m);
|
|
3922
|
+
lines.push(` /**`, ` * ${m.httpMethod} ${m.path}`, ...docLines.map((d) => ` * ${d}`), ` */`, ` ${m.method}: {`, ` params: ${paramsType}`, ` body: ${bodyType}`, ` query: ${queryType}`, ` response: unknown`, ` }`);
|
|
3923
|
+
}
|
|
3924
|
+
lines.push(" }");
|
|
3925
|
+
interfaces.push(lines.join("\n"));
|
|
3926
|
+
}
|
|
3927
|
+
return `${ROUTES_HEADER}${renderSchemaImports(schemaImports)}
|
|
3928
|
+
declare global {
|
|
3929
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
3930
|
+
namespace KickRoutes {
|
|
3931
|
+
${interfaces.join("\n")}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
export {}
|
|
3936
|
+
`;
|
|
3937
|
+
}
|
|
3938
|
+
function renderQueryShape(m) {
|
|
3939
|
+
if (m.queryFilterable === null) return "unknown";
|
|
3940
|
+
const sortable = m.querySortable ?? [];
|
|
3941
|
+
return `{ filter?: string | string[]; sort?: ${sortable.length > 0 ? sortable.flatMap((f) => [`'${f}'`, `'-${f}'`]).join(" | ") : "string"}; q?: string; page?: string; limit?: string }`;
|
|
3942
|
+
}
|
|
3943
|
+
function renderQueryDocLines(m) {
|
|
3944
|
+
const lines = [];
|
|
3945
|
+
if (m.queryFilterable && m.queryFilterable.length > 0) lines.push(`Filterable: ${m.queryFilterable.join(", ")}`);
|
|
3946
|
+
if (m.querySortable && m.querySortable.length > 0) lines.push(`Sortable: ${m.querySortable.join(", ")}`);
|
|
3947
|
+
if (m.querySearchable && m.querySearchable.length > 0) lines.push(`Searchable: ${m.querySearchable.join(", ")}`);
|
|
3948
|
+
return lines;
|
|
3949
|
+
}
|
|
3950
|
+
/**
|
|
3951
|
+
* Plan a schema import for hoisting at the top of `routes.ts`. Returns
|
|
3952
|
+
* the alias the in-namespace code should use, or `null` if the schema
|
|
3953
|
+
* cannot be referenced (no validator configured, or source unresolvable).
|
|
3954
|
+
*/
|
|
3955
|
+
function planSchemaImport(schema, routeFilePath, routesOutFile, schemaValidator, imports) {
|
|
3956
|
+
if (!schema || schemaValidator !== "zod") return null;
|
|
3957
|
+
if (schema.source === null) return null;
|
|
3958
|
+
const specifier = resolveSchemaImportSpecifier(schema.source, routeFilePath, routesOutFile);
|
|
3959
|
+
if (specifier === "unknown") return null;
|
|
3960
|
+
const key = `${specifier}::${schema.identifier}`;
|
|
3961
|
+
let alias = imports.get(key)?.specifier;
|
|
3962
|
+
if (!alias) {
|
|
3963
|
+
alias = `_S${imports.size}`;
|
|
3964
|
+
imports.set(key, {
|
|
3965
|
+
identifier: schema.identifier,
|
|
3966
|
+
specifier: alias
|
|
3967
|
+
});
|
|
3968
|
+
} else alias = imports.get(key).specifier;
|
|
3969
|
+
return alias;
|
|
3970
|
+
}
|
|
3971
|
+
function renderSchemaImports(imports) {
|
|
3972
|
+
if (imports.size === 0) return "";
|
|
3973
|
+
const lines = [];
|
|
3974
|
+
for (const [key, value] of imports) {
|
|
3975
|
+
const [path] = key.split("::");
|
|
3976
|
+
lines.push(`import type { ${value.identifier} as ${value.specifier} } from '${path}'`);
|
|
3977
|
+
}
|
|
3978
|
+
return lines.join("\n") + "\n";
|
|
3979
|
+
}
|
|
3980
|
+
/**
|
|
3981
|
+
* Compute the import specifier the generated `routes.d.ts` should use
|
|
3982
|
+
* to reach a schema declared either in the controller file (empty
|
|
3983
|
+
* string) or imported from elsewhere (relative path or bare module).
|
|
3984
|
+
*/
|
|
3985
|
+
function resolveSchemaImportSpecifier(source, routeFilePath, routesOutFile) {
|
|
3986
|
+
if (source === null) return "unknown";
|
|
3987
|
+
const routesDir = dirname(routesOutFile);
|
|
3988
|
+
if (source === "") {
|
|
3989
|
+
let rel = relative(routesDir, routeFilePath).split(sep).join("/");
|
|
3990
|
+
rel = rel.replace(/\.(ts|tsx|mts|cts)$/i, "");
|
|
3991
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3992
|
+
return rel;
|
|
3993
|
+
}
|
|
3994
|
+
if (!source.startsWith(".") && !source.startsWith("/")) return source;
|
|
3995
|
+
let rel = relative(routesDir, resolve(dirname(routeFilePath), source)).split(sep).join("/");
|
|
3996
|
+
rel = rel.replace(/\.(ts|tsx|mts|cts)$/i, "");
|
|
3997
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
3998
|
+
return rel;
|
|
3999
|
+
}
|
|
4000
|
+
//#endregion
|
|
4001
|
+
//#region src/typegen/builtin/routes.ts
|
|
4002
|
+
const kickRoutesTypegen = () => ({
|
|
4003
|
+
id: "kick/routes",
|
|
4004
|
+
outExtension: ".ts",
|
|
4005
|
+
inputs: ["src/**/*.controller.ts", "src/**/*.module.ts"],
|
|
4006
|
+
async generate(ctx) {
|
|
4007
|
+
const scan = await ctx.getScanResult({
|
|
4008
|
+
root: resolveSrcDir$1(ctx),
|
|
4009
|
+
cwd: ctx.cwd,
|
|
4010
|
+
envFile: resolveEnvFile$1(ctx)
|
|
4011
|
+
});
|
|
4012
|
+
const schemaValidator = ctx.config?.typegen?.schemaValidator ?? "zod";
|
|
4013
|
+
const outFile = path.resolve(ctx.cwd, ".kickjs/types/kick__routes.ts");
|
|
4014
|
+
return renderRoutes(scan.routes, outFile, schemaValidator);
|
|
4015
|
+
}
|
|
4016
|
+
});
|
|
4017
|
+
function resolveSrcDir$1(ctx) {
|
|
4018
|
+
return path.resolve(ctx.cwd, ctx.config?.typegen?.srcDir ?? "src");
|
|
4019
|
+
}
|
|
4020
|
+
function resolveEnvFile$1(ctx) {
|
|
4021
|
+
const cfg = ctx.config?.typegen?.envFile;
|
|
4022
|
+
if (cfg === false) return void 0;
|
|
4023
|
+
return cfg;
|
|
4024
|
+
}
|
|
4025
|
+
//#endregion
|
|
4026
|
+
//#region src/typegen/render/env.ts
|
|
4027
|
+
const ENV_HEADER = `/* eslint-disable */
|
|
4028
|
+
// AUTO-GENERATED by \`kick typegen\`. DO NOT EDIT.
|
|
4029
|
+
// Re-run with \`kick typegen\` or rely on \`kick dev\` to refresh.
|
|
4030
|
+
`;
|
|
4031
|
+
/**
|
|
4032
|
+
* Render the `KickEnv` + `NodeJS.ProcessEnv` augmentation file from a
|
|
4033
|
+
* detected env schema. Returns `null` when no env file was discovered,
|
|
4034
|
+
* so the caller can skip emission entirely (rather than emitting an
|
|
4035
|
+
* empty augmentation that would shadow `KickEnv` to a useless `{}`).
|
|
4036
|
+
*/
|
|
4037
|
+
function renderEnv(env, envOutFile) {
|
|
4038
|
+
if (!env) return null;
|
|
4039
|
+
let rel = relative(dirname(envOutFile), env.filePath).split(sep).join("/");
|
|
4040
|
+
rel = rel.replace(/\.(ts|tsx|mts|cts)$/i, "");
|
|
4041
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
4042
|
+
return `${ENV_HEADER}
|
|
4043
|
+
// Importing the schema as a type lets us infer its shape without
|
|
4044
|
+
// pulling in any runtime code. \`Awaited<>\` strips an accidental
|
|
4045
|
+
// Promise wrap on dynamic-imported defaults.
|
|
4046
|
+
import type _envSchema from '${rel}'
|
|
4047
|
+
|
|
4048
|
+
// Local type alias — interfaces can only \`extend\` an identifier,
|
|
4049
|
+
// not an inline import expression, so we resolve the schema's
|
|
4050
|
+
// inferred shape into a named type first.
|
|
4051
|
+
type _KickEnvShape = import('zod').infer<typeof _envSchema>
|
|
4052
|
+
|
|
4053
|
+
declare global {
|
|
4054
|
+
/**
|
|
4055
|
+
* Typed environment registry. Augmented from \`${env.relativePath}\`
|
|
4056
|
+
* so \`@Value('PORT')\`, \`Env<'PORT'>\`, and \`process.env.PORT\` are
|
|
4057
|
+
* all type-safe and autocomplete.
|
|
4058
|
+
*/
|
|
4059
|
+
interface KickEnv extends _KickEnvShape {}
|
|
4060
|
+
|
|
4061
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
4062
|
+
namespace NodeJS {
|
|
4063
|
+
/**
|
|
4064
|
+
* Narrow \`process.env\` so known keys exist as \`string\` (the raw
|
|
4065
|
+
* pre-Zod-coercion form). \`@Value\` and the \`ConfigService\` apply
|
|
4066
|
+
* the schema's transforms internally; access \`process.env\` directly
|
|
4067
|
+
* only when you need the raw string. Unknown keys still resolve to
|
|
4068
|
+
* \`string | undefined\` via the base @types/node declaration.
|
|
4069
|
+
*/
|
|
4070
|
+
interface ProcessEnv extends Record<keyof KickEnv, string> {}
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
export {}
|
|
4075
|
+
`;
|
|
4076
|
+
}
|
|
4077
|
+
//#endregion
|
|
4078
|
+
//#region src/typegen/builtin/env.ts
|
|
4079
|
+
const kickEnvTypegen = () => ({
|
|
4080
|
+
id: "kick/env",
|
|
4081
|
+
outExtension: ".ts",
|
|
4082
|
+
inputs: [
|
|
4083
|
+
"src/env.ts",
|
|
4084
|
+
"src/**/env.ts",
|
|
4085
|
+
"src/**/*.env.ts"
|
|
4086
|
+
],
|
|
4087
|
+
async generate(ctx) {
|
|
4088
|
+
const envFile = resolveEnvFile(ctx);
|
|
4089
|
+
if (envFile === false) return null;
|
|
4090
|
+
const scan = await ctx.getScanResult({
|
|
4091
|
+
root: resolveSrcDir(ctx),
|
|
4092
|
+
cwd: ctx.cwd,
|
|
4093
|
+
envFile
|
|
4094
|
+
});
|
|
4095
|
+
if (!scan.env) return null;
|
|
4096
|
+
const outFile = path.resolve(ctx.cwd, ".kickjs/types/kick__env.ts");
|
|
4097
|
+
return renderEnv(scan.env, outFile);
|
|
4098
|
+
}
|
|
4099
|
+
});
|
|
4100
|
+
function resolveSrcDir(ctx) {
|
|
4101
|
+
return path.resolve(ctx.cwd, ctx.config?.typegen?.srcDir ?? "src");
|
|
4102
|
+
}
|
|
4103
|
+
function resolveEnvFile(ctx) {
|
|
4104
|
+
return ctx.config?.typegen?.envFile;
|
|
4105
|
+
}
|
|
4106
|
+
//#endregion
|
|
3730
4107
|
//#region src/plugin/builtins.ts
|
|
3731
4108
|
const builtinCliPlugins = [
|
|
3732
4109
|
defineCliPlugin({
|
|
@@ -3789,9 +4166,17 @@ const builtinCliPlugins = [
|
|
|
3789
4166
|
defineCliPlugin({
|
|
3790
4167
|
name: "kick/assets",
|
|
3791
4168
|
typegens: [kickAssetsTypegen()]
|
|
4169
|
+
}),
|
|
4170
|
+
defineCliPlugin({
|
|
4171
|
+
name: "kick/routes",
|
|
4172
|
+
typegens: [kickRoutesTypegen()]
|
|
4173
|
+
}),
|
|
4174
|
+
defineCliPlugin({
|
|
4175
|
+
name: "kick/env",
|
|
4176
|
+
typegens: [kickEnvTypegen()]
|
|
3792
4177
|
})
|
|
3793
4178
|
];
|
|
3794
4179
|
//#endregion
|
|
3795
4180
|
export { builtinCliPlugins, applyDisableFilter as n, runAllPluginTypegens as t };
|
|
3796
4181
|
|
|
3797
|
-
//# sourceMappingURL=builtins-
|
|
4182
|
+
//# sourceMappingURL=builtins-B0dptoXq.mjs.map
|