@forinda/kickjs-cli 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +989 -734
- package/dist/index.d.mts +11 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +612 -468
- package/dist/index.mjs.map +1 -1
- package/dist/{typegen-vI1eqGLK.mjs → typegen-C-H8pg-y.mjs} +8 -4
- package/dist/{typegen-vI1eqGLK.mjs.map → typegen-C-H8pg-y.mjs.map} +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-cli v4.
|
|
2
|
+
* @forinda/kickjs-cli v4.1.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
12
13
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
14
|
import * as clack from "@clack/prompts";
|
|
14
15
|
import pc from "picocolors";
|
|
@@ -16,10 +17,59 @@ import pkg from "pluralize";
|
|
|
16
17
|
import { execSync } from "node:child_process";
|
|
17
18
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
19
|
import { fileURLToPath } from "node:url";
|
|
19
|
-
/**
|
|
20
|
+
/** Extensions prettier can format. Anything else is written verbatim. */
|
|
21
|
+
const FORMATTABLE = new Set([
|
|
22
|
+
".ts",
|
|
23
|
+
".tsx",
|
|
24
|
+
".js",
|
|
25
|
+
".jsx",
|
|
26
|
+
".mjs",
|
|
27
|
+
".cjs",
|
|
28
|
+
".json",
|
|
29
|
+
".md"
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Write a file, creating parent directories if needed.
|
|
33
|
+
*
|
|
34
|
+
* After write, runs prettier against the file when:
|
|
35
|
+
* - format-on-write is enabled (default)
|
|
36
|
+
* - the extension is in {@link FORMATTABLE}
|
|
37
|
+
* - prettier resolves from the user's project (or our own cwd)
|
|
38
|
+
*
|
|
39
|
+
* Failures (missing prettier, unparseable source, prettier crash) are
|
|
40
|
+
* swallowed silently — formatting is a polish step, not a correctness
|
|
41
|
+
* gate. The pre-existing pre-commit hook still catches anything we
|
|
42
|
+
* couldn't format.
|
|
43
|
+
*
|
|
44
|
+
* Skips writing entirely in dry run mode.
|
|
45
|
+
*/
|
|
20
46
|
async function writeFileSafe(filePath, content) {
|
|
21
47
|
await mkdir(dirname(filePath), { recursive: true });
|
|
22
48
|
await writeFile(filePath, content, "utf-8");
|
|
49
|
+
if (FORMATTABLE.has(extname(filePath))) await formatFile(filePath, content).catch(() => {});
|
|
50
|
+
}
|
|
51
|
+
let _prettier = void 0;
|
|
52
|
+
/** Resolve prettier from the user's project; cache the result (or null) for the process. */
|
|
53
|
+
function resolvePrettier(cwd) {
|
|
54
|
+
if (_prettier !== void 0) return _prettier;
|
|
55
|
+
try {
|
|
56
|
+
_prettier = createRequire(join(cwd, "package.json"))("prettier");
|
|
57
|
+
} catch {
|
|
58
|
+
_prettier = null;
|
|
59
|
+
}
|
|
60
|
+
return _prettier;
|
|
61
|
+
}
|
|
62
|
+
async function formatFile(filePath, content) {
|
|
63
|
+
const prettier = resolvePrettier(process.cwd());
|
|
64
|
+
if (!prettier) return;
|
|
65
|
+
if ((await prettier.getFileInfo(filePath, { resolveConfig: true })).ignored) return;
|
|
66
|
+
const config = await prettier.resolveConfig(filePath) ?? {};
|
|
67
|
+
const formatted = await prettier.format(content, {
|
|
68
|
+
...config,
|
|
69
|
+
filepath: filePath
|
|
70
|
+
});
|
|
71
|
+
if (formatted === content) return;
|
|
72
|
+
await writeFile(filePath, formatted, "utf-8");
|
|
23
73
|
}
|
|
24
74
|
/** Check if a file exists */
|
|
25
75
|
async function fileExists(filePath) {
|
|
@@ -551,7 +601,7 @@ export interface I${pascal}Repository {
|
|
|
551
601
|
* \`@Inject(${pascal.toUpperCase()}_REPOSITORY)\` both return the typed
|
|
552
602
|
* interface — no manual generic, no \`any\` cast.
|
|
553
603
|
*/
|
|
554
|
-
export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('
|
|
604
|
+
export const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('app/${kebab}/repository')
|
|
555
605
|
`;
|
|
556
606
|
}
|
|
557
607
|
function generateInMemoryRepository(ctx) {
|
|
@@ -2311,97 +2361,114 @@ export const modules: AppModuleClass[] = [${pascal}Module]
|
|
|
2311
2361
|
}
|
|
2312
2362
|
//#endregion
|
|
2313
2363
|
//#region src/generators/adapter.ts
|
|
2364
|
+
/**
|
|
2365
|
+
* Scaffold a `defineAdapter()` factory under `src/adapters/<name>.adapter.ts`.
|
|
2366
|
+
*
|
|
2367
|
+
* v4 dropped the `class implements AppAdapter` pattern in favour of the
|
|
2368
|
+
* `defineAdapter()` factory (architecture.md §21.3.4). The generated
|
|
2369
|
+
* template uses the new factory shape so adopters get a working
|
|
2370
|
+
* adapter with all four lifecycle hooks (beforeMount, beforeStart,
|
|
2371
|
+
* afterStart, shutdown), a typed config object with defaults, and the
|
|
2372
|
+
* factory's call / `.scoped()` / `.async()` surfaces — without
|
|
2373
|
+
* writing a single class.
|
|
2374
|
+
*/
|
|
2314
2375
|
async function generateAdapter(options) {
|
|
2315
2376
|
const { name, outDir } = options;
|
|
2316
2377
|
const kebab = toKebabCase(name);
|
|
2317
2378
|
const pascal = toPascalCase(name);
|
|
2318
2379
|
const files = [];
|
|
2319
2380
|
const filePath = join(outDir, `${kebab}.adapter.ts`);
|
|
2320
|
-
await writeFileSafe(filePath, `import
|
|
2381
|
+
await writeFileSafe(filePath, `import { defineAdapter, type AdapterContext, type AdapterMiddleware } from '@forinda/kickjs'
|
|
2321
2382
|
|
|
2322
|
-
|
|
2323
|
-
|
|
2383
|
+
/**
|
|
2384
|
+
* Configuration for the ${pascal} adapter.
|
|
2385
|
+
*
|
|
2386
|
+
* Adapters typically take a small config object so callers can tune
|
|
2387
|
+
* behaviour at bootstrap time. Keep the shape narrow — anything
|
|
2388
|
+
* derived from the environment should be read inside the build
|
|
2389
|
+
* function via getEnv(), not forced onto the caller.
|
|
2390
|
+
*/
|
|
2391
|
+
export interface ${pascal}AdapterConfig {
|
|
2392
|
+
// Add your adapter configuration here, e.g.:
|
|
2393
|
+
// enabled?: boolean
|
|
2394
|
+
// apiKey?: string
|
|
2324
2395
|
}
|
|
2325
2396
|
|
|
2326
2397
|
/**
|
|
2327
|
-
* ${pascal} adapter
|
|
2398
|
+
* ${pascal} adapter — built via \`defineAdapter()\` so callers get the
|
|
2399
|
+
* factory's call / \`.scoped()\` / \`.async()\` surfaces for free.
|
|
2328
2400
|
*
|
|
2329
2401
|
* Hooks into the Application lifecycle to add middleware, routes,
|
|
2330
2402
|
* or external service connections.
|
|
2331
2403
|
*
|
|
2332
|
-
*
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2404
|
+
* @example
|
|
2405
|
+
* \`\`\`ts
|
|
2406
|
+
* import { bootstrap } from '@forinda/kickjs'
|
|
2407
|
+
* import { ${pascal}Adapter } from './adapters/${kebab}.adapter'
|
|
2408
|
+
*
|
|
2409
|
+
* bootstrap({
|
|
2410
|
+
* modules,
|
|
2411
|
+
* adapters: [${pascal}Adapter({ /* config overrides *\\/ })],
|
|
2412
|
+
* })
|
|
2413
|
+
* \`\`\`
|
|
2336
2414
|
*/
|
|
2337
|
-
export
|
|
2338
|
-
name
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
// path: '/api/v1/admin',
|
|
2361
|
-
// handler: myAdminMiddleware(),
|
|
2362
|
-
// },
|
|
2363
|
-
]
|
|
2364
|
-
}
|
|
2415
|
+
export const ${pascal}Adapter = defineAdapter<${pascal}AdapterConfig>({
|
|
2416
|
+
name: '${pascal}Adapter',
|
|
2417
|
+
defaults: {
|
|
2418
|
+
// Default config values go here
|
|
2419
|
+
},
|
|
2420
|
+
build: (_config, { name: _name }) => ({
|
|
2421
|
+
/**
|
|
2422
|
+
* Return middleware entries that the Application will mount.
|
|
2423
|
+
* \`phase\` controls where in the pipeline they run:
|
|
2424
|
+
* 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.
|
|
2425
|
+
*/
|
|
2426
|
+
middleware(): AdapterMiddleware[] {
|
|
2427
|
+
return [
|
|
2428
|
+
// Example: add a custom header to all responses
|
|
2429
|
+
// {
|
|
2430
|
+
// phase: 'beforeGlobal',
|
|
2431
|
+
// handler: (_req, res, next) => {
|
|
2432
|
+
// res.setHeader('X-${pascal}', 'true')
|
|
2433
|
+
// next()
|
|
2434
|
+
// },
|
|
2435
|
+
// },
|
|
2436
|
+
]
|
|
2437
|
+
},
|
|
2365
2438
|
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
// })
|
|
2376
|
-
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Called before global middleware. Use this to mount routes that
|
|
2441
|
+
* bypass the middleware stack (health checks, docs UI, static
|
|
2442
|
+
* assets).
|
|
2443
|
+
*/
|
|
2444
|
+
beforeMount(_ctx: AdapterContext): void {
|
|
2445
|
+
// Example:
|
|
2446
|
+
// _ctx.app.get('/${kebab}/status', (_req, res) => res.json({ status: 'ok' }))
|
|
2447
|
+
},
|
|
2377
2448
|
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2449
|
+
/**
|
|
2450
|
+
* Called after modules and routes are registered, before the
|
|
2451
|
+
* server starts. Use this for late-stage DI registrations or
|
|
2452
|
+
* config validation.
|
|
2453
|
+
*/
|
|
2454
|
+
beforeStart(_ctx: AdapterContext): void {
|
|
2455
|
+
// Example: _ctx.container.bindToken(MY_TOKEN, new MyService(_config))
|
|
2456
|
+
},
|
|
2386
2457
|
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
// container.registerInstance(SOCKET_IO, io)
|
|
2395
|
-
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Called after the HTTP server is listening. Use this to attach
|
|
2460
|
+
* to the raw http.Server (Socket.IO, gRPC, etc).
|
|
2461
|
+
*/
|
|
2462
|
+
afterStart(_ctx: AdapterContext): void {
|
|
2463
|
+
// Example: const io = new Server(_ctx.server)
|
|
2464
|
+
},
|
|
2396
2465
|
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2466
|
+
/** Called on graceful shutdown. Clean up connections. */
|
|
2467
|
+
async shutdown(): Promise<void> {
|
|
2468
|
+
// Example: await this.pool.end()
|
|
2469
|
+
},
|
|
2470
|
+
}),
|
|
2471
|
+
})
|
|
2405
2472
|
`);
|
|
2406
2473
|
files.push(filePath);
|
|
2407
2474
|
return files;
|
|
@@ -2730,7 +2797,7 @@ function generatePackageJson(name, template, kickjsVersion, packages = []) {
|
|
|
2730
2797
|
"unplugin-swc": "^1.5.9",
|
|
2731
2798
|
vite: "^8.0.3",
|
|
2732
2799
|
vitest: "^4.1.2",
|
|
2733
|
-
typescript: "^
|
|
2800
|
+
typescript: "^6.0.3",
|
|
2734
2801
|
prettier: "^3.8.1"
|
|
2735
2802
|
}
|
|
2736
2803
|
}, null, 2);
|
|
@@ -2976,404 +3043,223 @@ Copy \`.env.example\` to \`.env\` and configure:
|
|
|
2976
3043
|
- [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
|
|
2977
3044
|
`;
|
|
2978
3045
|
}
|
|
2979
|
-
/**
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
- Type check with: \`${pm} run typecheck\`
|
|
3029
|
-
|
|
3030
|
-
## Key Patterns
|
|
3031
|
-
|
|
3032
|
-
### Controllers
|
|
3033
|
-
|
|
3034
|
-
Use decorators to define routes. Annotate \`ctx\` with \`Ctx<KickRoutes.X['method']>\`
|
|
3035
|
-
to get fully-typed \`ctx.params\`, \`ctx.body\`, and \`ctx.query\` from the
|
|
3036
|
-
generated \`KickRoutes\` namespace (refreshed on \`kick dev\` and \`kick typegen\`).
|
|
3037
|
-
|
|
3038
|
-
\`\`\`ts
|
|
3039
|
-
import { Controller, Get, Post, type Ctx } from '@forinda/kickjs'
|
|
3040
|
-
|
|
3041
|
-
@Controller()
|
|
3042
|
-
export class UserController {
|
|
3043
|
-
@Get('/')
|
|
3044
|
-
async findAll(ctx: Ctx<KickRoutes.UserController['findAll']>) {
|
|
3045
|
-
return ctx.json({ users: [] })
|
|
3046
|
-
}
|
|
3047
|
-
|
|
3048
|
-
@Post('/')
|
|
3049
|
-
async create(ctx: Ctx<KickRoutes.UserController['create']>) {
|
|
3050
|
-
const data = ctx.body
|
|
3051
|
-
return ctx.created({ user: data })
|
|
3052
|
-
}
|
|
3053
|
-
}
|
|
3054
|
-
\`\`\`
|
|
3055
|
-
|
|
3056
|
-
### Services
|
|
3057
|
-
|
|
3058
|
-
Inject dependencies with \`@Service()\` and \`@Autowired()\`:
|
|
3059
|
-
|
|
3060
|
-
\`\`\`ts
|
|
3061
|
-
import { Service, Autowired } from '@forinda/kickjs'
|
|
3062
|
-
|
|
3063
|
-
@Service()
|
|
3064
|
-
export class UserService {
|
|
3065
|
-
@Autowired()
|
|
3066
|
-
private userRepository!: UserRepository
|
|
3067
|
-
|
|
3068
|
-
async findAll() {
|
|
3069
|
-
return this.userRepository.findAll()
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
\`\`\`
|
|
3073
|
-
|
|
3074
|
-
### Modules
|
|
3075
|
-
|
|
3076
|
-
Modules implement \`AppModule\` and wire controllers via \`buildRoutes()\`.
|
|
3077
|
-
|
|
3078
|
-
> **Naming matters.** Module files **must** be named \`<name>.module.ts\` and live under \`src/modules/\`. The Vite plugin auto-discovers files matching \`*.module.[tj]sx?\` for HMR — a misnamed file (e.g., \`projects.ts\`) won't trigger a graceful module rebuild on save and will require a full server restart. The CLI generator (\`kick g module <name>\`) follows this convention automatically.
|
|
3079
|
-
|
|
3080
|
-
\`\`\`ts
|
|
3081
|
-
// src/modules/users/users.module.ts (named <feature>.module.ts)
|
|
3082
|
-
import { type AppModule, type ModuleRoutes, buildRoutes } from '@forinda/kickjs'
|
|
3083
|
-
import { UserController } from './user.controller'
|
|
3084
|
-
|
|
3085
|
-
export class UserModule implements AppModule {
|
|
3086
|
-
routes(): ModuleRoutes {
|
|
3087
|
-
return {
|
|
3088
|
-
path: '/users',
|
|
3089
|
-
router: buildRoutes(UserController),
|
|
3090
|
-
controller: UserController,
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
\`\`\`
|
|
3095
|
-
|
|
3096
|
-
Register all modules in \`src/modules/index.ts\`:
|
|
3097
|
-
|
|
3098
|
-
\`\`\`ts
|
|
3099
|
-
import type { AppModuleClass } from '@forinda/kickjs'
|
|
3100
|
-
import { UserModule } from './user/user.module'
|
|
3101
|
-
|
|
3102
|
-
export const modules: AppModuleClass[] = [UserModule]
|
|
3103
|
-
\`\`\`
|
|
3104
|
-
|
|
3105
|
-
### RequestContext
|
|
3106
|
-
|
|
3107
|
-
Every controller method receives a \`ctx\` (alias \`Ctx<TRoute>\` or the
|
|
3108
|
-
loose \`RequestContext\`):
|
|
3109
|
-
|
|
3110
|
-
\`\`\`ts
|
|
3111
|
-
ctx.body // Request body (parsed JSON)
|
|
3112
|
-
ctx.params // Route params
|
|
3113
|
-
ctx.query // Query string
|
|
3114
|
-
ctx.headers // Request headers
|
|
3115
|
-
ctx.requestId // Auto-generated request ID
|
|
3116
|
-
ctx.session // Session data (if session middleware enabled)
|
|
3117
|
-
ctx.file // Uploaded file (single)
|
|
3118
|
-
ctx.files // Uploaded files (multiple)
|
|
3119
|
-
|
|
3120
|
-
// Pagination helpers
|
|
3121
|
-
ctx.qs(config) // Parse query with filters/sort/pagination
|
|
3122
|
-
ctx.paginate(handler) // Auto-paginated response
|
|
3123
|
-
|
|
3124
|
-
// Response helpers
|
|
3125
|
-
ctx.json(data) // 200 OK with JSON
|
|
3126
|
-
ctx.created(data) // 201 Created
|
|
3127
|
-
ctx.noContent() // 204 No Content
|
|
3128
|
-
ctx.notFound() // 404 Not Found
|
|
3129
|
-
ctx.badRequest(msg) // 400 Bad Request
|
|
3130
|
-
\`\`\`
|
|
3131
|
-
|
|
3132
|
-
> **Context decorators** — when a middleware's only job is to populate \`ctx.set/get\` for the handler to read, prefer \`defineContextDecorator()\` over \`@Middleware()\`. Typed via \`ContextMeta\`, supports \`dependsOn\` ordering, validates the pipeline at boot. Full pattern reference in \`AGENTS.md\` and at <https://forinda.github.io/kick-js/guide/context-decorators>.
|
|
3133
|
-
|
|
3134
|
-
## CLI Generators
|
|
3135
|
-
|
|
3136
|
-
Generate code with the \`kick\` CLI:
|
|
3137
|
-
|
|
3138
|
-
\`\`\`bash
|
|
3139
|
-
kick g module <name> # Full module (controller, service, DTOs, repo)
|
|
3140
|
-
kick g scaffold <name> <fields> # CRUD module from field definitions
|
|
3141
|
-
kick g controller <name> # Standalone controller
|
|
3142
|
-
kick g service <name> # Service class
|
|
3143
|
-
kick g middleware <name> # Express middleware
|
|
3144
|
-
kick g guard <name> # Route guard (auth, roles)
|
|
3145
|
-
kick g adapter <name> # AppAdapter with lifecycle hooks
|
|
3146
|
-
kick g dto <name> # Zod DTO schema
|
|
3147
|
-
${template === "graphql" ? "kick g resolver <name> # GraphQL resolver\n" : ""}${template === "cqrs" ? "kick g job <name> # Queue job processor\n" : ""}\`\`\`
|
|
3148
|
-
|
|
3149
|
-
## Adding Packages
|
|
3046
|
+
/**
|
|
3047
|
+
* Generate CLAUDE.md.
|
|
3048
|
+
*
|
|
3049
|
+
* v4 update: this file is intentionally thin. AGENTS.md is the
|
|
3050
|
+
* canonical, multi-agent project reference (Claude / Copilot /
|
|
3051
|
+
* Codex / Gemini / etc.) — duplicating it here meant two files
|
|
3052
|
+
* drifting out of sync after every framework change. The generated
|
|
3053
|
+
* CLAUDE.md now redirects there + adds Claude-specific affordances
|
|
3054
|
+
* only.
|
|
3055
|
+
*/
|
|
3056
|
+
function generateClaude(name, _template, pm) {
|
|
3057
|
+
return `# CLAUDE.md — ${name}
|
|
3058
|
+
|
|
3059
|
+
**Read \`./AGENTS.md\` first.** It is the canonical, multi-agent
|
|
3060
|
+
reference for this project (Claude, Copilot, Codex, Gemini, etc.) —
|
|
3061
|
+
project conventions, structure, decorator patterns, env wiring, CLI
|
|
3062
|
+
generators, every gotcha.
|
|
3063
|
+
|
|
3064
|
+
**Then read \`./kickjs-skills.md\`.** That file is the task-oriented
|
|
3065
|
+
skill index — short, rigid recipes keyed to triggers ("add-module",
|
|
3066
|
+
"write-controller-test", "bootstrap-export", "deny-list", …). Use it
|
|
3067
|
+
as the playbook when executing common KickJS workflows.
|
|
3068
|
+
|
|
3069
|
+
This file is a thin Claude-specific layer on top of those two; when
|
|
3070
|
+
they disagree on anything substantive, treat \`AGENTS.md\` as
|
|
3071
|
+
authoritative and flag the discrepancy.
|
|
3072
|
+
|
|
3073
|
+
## Why two files
|
|
3074
|
+
|
|
3075
|
+
\`AGENTS.md\` is what every agent reads. \`CLAUDE.md\` is what
|
|
3076
|
+
Claude Code automatically loads as project context on each
|
|
3077
|
+
conversation. Keeping CLAUDE.md slim avoids two files drifting; the
|
|
3078
|
+
redirect above ensures Claude pulls the canonical content without
|
|
3079
|
+
us copy-pasting.
|
|
3080
|
+
|
|
3081
|
+
## Claude-specific notes
|
|
3082
|
+
|
|
3083
|
+
- **Slash commands** — \`/help\` for Claude Code commands; \`/init\`
|
|
3084
|
+
to refresh project memory if AGENTS.md changes substantially.
|
|
3085
|
+
- **Feedback** — file issues at <https://github.com/anthropics/claude-code/issues>.
|
|
3086
|
+
- **Persistent memory** — Claude maintains user/feedback/project/
|
|
3087
|
+
reference memories under \`.claude/memory/\`. If you ask for
|
|
3088
|
+
something that contradicts a remembered preference, Claude flags
|
|
3089
|
+
it before acting; corrections update memory automatically.
|
|
3090
|
+
- **Long-running tasks** — \`/loop\` and \`/schedule\` for recurring
|
|
3091
|
+
or background work. Useful for "wait for the deploy then open a
|
|
3092
|
+
cleanup PR" or "every Monday triage the issue board" patterns.
|
|
3093
|
+
|
|
3094
|
+
## Quick reference (full version in AGENTS.md)
|
|
3150
3095
|
|
|
3151
3096
|
\`\`\`bash
|
|
3152
|
-
|
|
3153
|
-
kick
|
|
3154
|
-
kick
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
kick add prisma # Prisma ORM adapter
|
|
3159
|
-
kick add drizzle # Drizzle ORM adapter
|
|
3160
|
-
kick add otel # OpenTelemetry tracing
|
|
3161
|
-
kick add --list # Show all available packages
|
|
3162
|
-
\`\`\`
|
|
3163
|
-
|
|
3164
|
-
## Environment Configuration
|
|
3165
|
-
|
|
3166
|
-
The project's typed env schema lives in **\`src/config/index.ts\`** —
|
|
3167
|
-
extend the base schema there with your application-specific keys, and
|
|
3168
|
-
the schema is auto-registered with kickjs at module load. The companion
|
|
3169
|
-
\`src/index.ts\` imports it as a side effect (\`import './config'\`) **before**
|
|
3170
|
-
\`bootstrap()\` runs, so every \`@Service\`, \`@Controller\`, \`@Value\`, and
|
|
3171
|
-
\`ConfigService\` resolution sees the validated extended values.
|
|
3172
|
-
|
|
3173
|
-
> **Do not delete \`import './config'\` from \`src/index.ts\`.** It is the
|
|
3174
|
-
> registration step that wires \`ConfigService\` to your env schema.
|
|
3175
|
-
> Without it, \`config.get('YOUR_KEY')\` returns \`undefined\` for every
|
|
3176
|
-
> user-defined key and \`@Value('YOUR_KEY')\` only works because of a
|
|
3177
|
-
> raw \`process.env\` fallback (Zod coercion + defaults are skipped).
|
|
3178
|
-
|
|
3179
|
-
Edit \`.env\` for variable values. Access them with \`@Value()\`:
|
|
3180
|
-
|
|
3181
|
-
\`\`\`ts
|
|
3182
|
-
import { Value } from '@forinda/kickjs'
|
|
3183
|
-
|
|
3184
|
-
@Service()
|
|
3185
|
-
export class ApiService {
|
|
3186
|
-
@Value('API_KEY')
|
|
3187
|
-
private apiKey!: string
|
|
3188
|
-
|
|
3189
|
-
@Value('PORT', 3000) // With default
|
|
3190
|
-
private port!: number
|
|
3191
|
-
}
|
|
3192
|
-
\`\`\`
|
|
3193
|
-
|
|
3194
|
-
Or use \`ConfigService\`:
|
|
3195
|
-
|
|
3196
|
-
\`\`\`ts
|
|
3197
|
-
import { Service, Autowired, ConfigService } from '@forinda/kickjs'
|
|
3198
|
-
|
|
3199
|
-
@Service()
|
|
3200
|
-
export class AppService {
|
|
3201
|
-
@Autowired()
|
|
3202
|
-
private config!: ConfigService
|
|
3203
|
-
|
|
3204
|
-
getPort() {
|
|
3205
|
-
// typed: number, Zod-coerced from baseEnvSchema
|
|
3206
|
-
return this.config.get('PORT')
|
|
3207
|
-
}
|
|
3208
|
-
}
|
|
3209
|
-
\`\`\`
|
|
3210
|
-
|
|
3211
|
-
Hot-reload of \`.env\` changes during dev is wired up automatically via
|
|
3212
|
-
\`envWatchPlugin()\` in \`vite.config.ts\` — edit \`.env\`, the dev server
|
|
3213
|
-
reloads, and the next \`config.get()\` re-parses with the new values.
|
|
3214
|
-
|
|
3215
|
-
### Standalone Env Utilities (No DI Required)
|
|
3216
|
-
|
|
3217
|
-
These functions work anywhere — scripts, CLI tools, plain files, outside \`@Service\`/\`@Controller\`:
|
|
3218
|
-
|
|
3219
|
-
\`\`\`ts
|
|
3220
|
-
import { defineEnv, loadEnv, getEnv, reloadEnv, resetEnvCache, baseEnvSchema } from '@forinda/kickjs/config'
|
|
3221
|
-
import { z } from 'zod'
|
|
3222
|
-
|
|
3223
|
-
// Define and parse schema
|
|
3224
|
-
const schema = defineEnv((base) =>
|
|
3225
|
-
base.extend({ DATABASE_URL: z.string().url() })
|
|
3226
|
-
)
|
|
3227
|
-
const env = loadEnv(schema) // Parse + validate process.env
|
|
3228
|
-
console.log(env.PORT) // 3000 (coerced to number)
|
|
3229
|
-
console.log(env.DATABASE_URL) // validated URL string
|
|
3230
|
-
|
|
3231
|
-
// Get single value
|
|
3232
|
-
const port = getEnv('PORT') // typed after kick typegen
|
|
3233
|
-
|
|
3234
|
-
// Reload after .env changes (HMR calls this automatically)
|
|
3235
|
-
reloadEnv()
|
|
3236
|
-
|
|
3237
|
-
// Reset cache in tests that swap schemas
|
|
3238
|
-
resetEnvCache()
|
|
3239
|
-
\`\`\`
|
|
3240
|
-
|
|
3241
|
-
| Function | Purpose |
|
|
3242
|
-
|----------|---------|
|
|
3243
|
-
| \`defineEnv(fn)\` | Extend base schema with custom Zod keys |
|
|
3244
|
-
| \`loadEnv(schema?)\` | Parse \`process.env\`, validate, cache, return typed object |
|
|
3245
|
-
| \`getEnv(key, schema?)\` | Get single validated env value |
|
|
3246
|
-
| \`reloadEnv()\` | Re-read \`.env\` from disk, re-parse with same schema |
|
|
3247
|
-
| \`resetEnvCache()\` | Clear parsed cache AND registered schema (for tests) |
|
|
3248
|
-
| \`baseEnvSchema\` | Base Zod schema: \`PORT\`, \`NODE_ENV\`, \`LOG_LEVEL\` |
|
|
3249
|
-
|
|
3250
|
-
## Standalone Utilities (No DI Required)
|
|
3251
|
-
|
|
3252
|
-
These utilities work outside decorated classes:
|
|
3253
|
-
|
|
3254
|
-
### Logger
|
|
3255
|
-
|
|
3256
|
-
\`\`\`ts
|
|
3257
|
-
import { Logger, createLogger } from '@forinda/kickjs'
|
|
3258
|
-
|
|
3259
|
-
const log = Logger.for('MyScript') // Static factory
|
|
3260
|
-
log.info('Processing started')
|
|
3261
|
-
log.error('Something failed')
|
|
3262
|
-
|
|
3263
|
-
const log2 = createLogger('Worker') // Function form
|
|
3264
|
-
\`\`\`
|
|
3265
|
-
|
|
3266
|
-
### Injection Tokens
|
|
3267
|
-
|
|
3268
|
-
\`\`\`ts
|
|
3269
|
-
import { createToken } from '@forinda/kickjs'
|
|
3270
|
-
|
|
3271
|
-
// Type-safe DI tokens for factory/interface binding
|
|
3272
|
-
const DB_URL = createToken<string>('config.database.url')
|
|
3273
|
-
const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')
|
|
3274
|
-
\`\`\`
|
|
3275
|
-
|
|
3276
|
-
### Reactivity
|
|
3277
|
-
|
|
3278
|
-
\`\`\`ts
|
|
3279
|
-
import { ref, computed, watch, reactive } from '@forinda/kickjs'
|
|
3280
|
-
|
|
3281
|
-
const count = ref(0)
|
|
3282
|
-
const doubled = computed(() => count.value * 2)
|
|
3283
|
-
const stop = watch(() => count.value, (val) => console.log(val))
|
|
3284
|
-
count.value++ // logs 1
|
|
3285
|
-
\`\`\`
|
|
3286
|
-
|
|
3287
|
-
### HTTP Errors
|
|
3288
|
-
|
|
3289
|
-
\`\`\`ts
|
|
3290
|
-
import { HttpException, HttpStatus } from '@forinda/kickjs'
|
|
3291
|
-
|
|
3292
|
-
throw new HttpException(HttpStatus.NOT_FOUND, 'User not found')
|
|
3293
|
-
\`\`\`
|
|
3294
|
-
|
|
3295
|
-
## Testing
|
|
3296
|
-
|
|
3297
|
-
Tests live in \`src/**/*.test.ts\`:
|
|
3298
|
-
|
|
3299
|
-
\`\`\`ts
|
|
3300
|
-
import { describe, it, expect, beforeEach } from 'vitest'
|
|
3301
|
-
import { Container } from '@forinda/kickjs'
|
|
3302
|
-
import { createTestApp } from '@forinda/kickjs-testing'
|
|
3303
|
-
|
|
3304
|
-
describe('UserController', () => {
|
|
3305
|
-
beforeEach(() => Container.reset())
|
|
3306
|
-
|
|
3307
|
-
it('should return users', async () => {
|
|
3308
|
-
const app = await createTestApp([UserModule])
|
|
3309
|
-
const res = await app.get('/users')
|
|
3310
|
-
expect(res.status).toBe(200)
|
|
3311
|
-
})
|
|
3312
|
-
})
|
|
3097
|
+
${pm} install # Install dependencies
|
|
3098
|
+
kick dev # Dev server with HMR + typegen
|
|
3099
|
+
kick build && kick start # Production
|
|
3100
|
+
${pm} run test # Vitest
|
|
3101
|
+
${pm} run typecheck # tsc --noEmit
|
|
3102
|
+
${pm} run format # Prettier
|
|
3313
3103
|
\`\`\`
|
|
3314
3104
|
|
|
3315
|
-
|
|
3316
|
-
- \`${pm} run test\` — run all tests
|
|
3317
|
-
- \`${pm} run test:watch\` — watch mode
|
|
3318
|
-
|
|
3319
|
-
## Decorators Reference
|
|
3320
|
-
|
|
3321
|
-
### Route Decorators
|
|
3322
|
-
- \`@Controller()\` — mark a class as an HTTP controller (path comes from \`routes().path\`)
|
|
3323
|
-
- \`@Get('/'), @Post('/'), @Put('/'), @Delete('/'), @Patch('/')\` — HTTP methods
|
|
3324
|
-
- \`@Middleware(fn)\` — attach middleware
|
|
3325
|
-
- \`@Public()\` — skip authentication (requires @forinda/kickjs-auth)
|
|
3326
|
-
- \`@Roles('admin', 'user')\` — role-based access control
|
|
3327
|
-
|
|
3328
|
-
### DI Decorators
|
|
3329
|
-
- \`@Service()\` — singleton service (DI-registered)
|
|
3330
|
-
- \`@Repository()\` — repository (semantic alias for @Service)
|
|
3331
|
-
- \`@Autowired()\` — property injection
|
|
3332
|
-
- \`@Inject('token')\` — token-based injection
|
|
3333
|
-
- \`@Value('ENV_VAR')\` — inject config value
|
|
3334
|
-
|
|
3335
|
-
${template === "cqrs" ? `### CQRS/Event Decorators
|
|
3336
|
-
- \`@Job('job-name')\` — queue job handler
|
|
3337
|
-
- \`@Process('queue-name')\` — queue processor
|
|
3338
|
-
- \`@Cron('0 * * * *')\` — cron schedule
|
|
3339
|
-
- \`@WsController('/path')\` — WebSocket controller
|
|
3340
|
-
- \`@Subscribe('event')\` — WebSocket event handler
|
|
3341
|
-
|
|
3342
|
-
` : ""}${template === "graphql" ? `### GraphQL Decorators
|
|
3343
|
-
- \`@Resolver()\` — GraphQL resolver
|
|
3344
|
-
- \`@Query()\` — GraphQL query
|
|
3345
|
-
- \`@Mutation()\` — GraphQL mutation
|
|
3346
|
-
- \`@Arg('name')\` — resolver argument
|
|
3105
|
+
## v4 framework reminders
|
|
3347
3106
|
|
|
3348
|
-
|
|
3107
|
+
When generating or modifying code in this project, stay aligned with the v4 conventions documented in \`AGENTS.md\`:
|
|
3349
3108
|
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3109
|
+
- **Adapters**: \`defineAdapter()\` factory — never \`class implements AppAdapter\`.
|
|
3110
|
+
- **Plugins**: \`definePlugin()\` factory — never plain function returning \`KickPlugin\`.
|
|
3111
|
+
- **DI tokens**: slash-delimited \`<scope>/<area>/<key>\` (e.g. \`'app/users/repository'\`). First-party uses the reserved \`'kick/'\` prefix; this project owns its own scope.
|
|
3112
|
+
- **Decorators**: \`@Controller()\` (no path arg — mount prefix comes from \`routes().path\`).
|
|
3113
|
+
- **Module entry file** MUST be named \`<name>.module.ts\` and live under \`src/modules/<name>/\`. The Vite plugin auto-discovers \`*.module.[tj]sx?\` for graceful HMR — a misnamed \`projects.ts\` silently degrades every save into a full restart.
|
|
3114
|
+
- **Env**: schema lives in \`src/config/index.ts\`; \`import './config'\` MUST be the first import in \`src/index.ts\` (side-effect registers the schema before any \`@Value\` resolves).
|
|
3115
|
+
- **Assets**: drop new template files into \`src/templates/<namespace>/\`; the dev watcher auto-rebuilds the \`KickAssets\` augmentation + \`assets.x.y()\` re-walks on next call. No restart, no manual build.
|
|
3116
|
+
- **Context Contributors** (\`defineContextDecorator\`) over \`@Middleware()\` for ctx-population work.
|
|
3117
|
+
- **Repos under tests**: \`Container.create()\` for isolation — never \`new Container()\` or \`getInstance().reset()\`.
|
|
3118
|
+
- **Bootstrap export**: \`src/index.ts\` must end with \`export const app = await bootstrap({ ... })\`. The Vite plugin and \`createTestApp\` import the named \`app\`; without the export, HMR silently degrades to full restarts.
|
|
3119
|
+
- **Thin entry file**: aggregate \`modules\`, \`middleware\`, \`plugins\`, \`adapters\` in their own folders (\`src/modules/index.ts\`, \`src/middleware/index.ts\`, …) and pass them by name to \`bootstrap()\` — never inline the lists in \`src/index.ts\`.
|
|
3120
|
+
- **Refresh these files**: \`kick g agents -f\` regenerates \`AGENTS.md\` + \`CLAUDE.md\` from the latest CLI templates. Hand-edited content is overwritten — keep customisation in \`AGENTS.local.md\`.
|
|
3358
3121
|
|
|
3359
|
-
|
|
3360
|
-
- [API Reference](https://forinda.github.io/kick-js/api/)
|
|
3361
|
-
- [CLI Commands](https://forinda.github.io/kick-js/guide/cli-commands.html)
|
|
3362
|
-
- [Decorators Guide](https://forinda.github.io/kick-js/guide/decorators.html)
|
|
3122
|
+
For everything else (controllers, services, modules, RequestContext API, generators, CLI commands, package additions, env wiring, troubleshooting) → \`AGENTS.md\`.
|
|
3363
3123
|
`;
|
|
3364
3124
|
}
|
|
3365
3125
|
/** Generate AGENTS.md with AI agent guide */
|
|
3366
3126
|
function generateAgents(name, template, pm) {
|
|
3367
3127
|
return `# AGENTS.md — AI Agent Guide for ${name}
|
|
3368
3128
|
|
|
3369
|
-
This guide
|
|
3129
|
+
This guide is the **canonical, multi-agent reference** for this KickJS
|
|
3130
|
+
application — Claude, Copilot, Codex, Gemini, etc. all read it first.
|
|
3131
|
+
Per-agent files (\`CLAUDE.md\`, \`GEMINI.md\`, etc.) are thin layers that
|
|
3132
|
+
add tool-specific affordances on top.
|
|
3370
3133
|
|
|
3371
3134
|
## Before You Start
|
|
3372
3135
|
|
|
3373
|
-
1.
|
|
3374
|
-
2. Run
|
|
3375
|
-
3.
|
|
3376
|
-
|
|
3136
|
+
1. Run \`${pm} install\` to install dependencies
|
|
3137
|
+
2. Run \`kick dev\` to verify the app starts
|
|
3138
|
+
3. Read the [KickJS documentation](https://forinda.github.io/kick-js/) for framework details
|
|
3139
|
+
|
|
3140
|
+
## v4 Conventions (don't skip)
|
|
3141
|
+
|
|
3142
|
+
KickJS v4 made a handful of structural changes from v3. Internalise these
|
|
3143
|
+
before generating or modifying code — they are the source of most agent
|
|
3144
|
+
mistakes:
|
|
3145
|
+
|
|
3146
|
+
- **Adapters** — \`defineAdapter()\` factory. Never write \`class Foo implements AppAdapter\`.
|
|
3147
|
+
|
|
3148
|
+
\`\`\`ts
|
|
3149
|
+
export const MyAdapter = defineAdapter<MyOptions>({
|
|
3150
|
+
name: 'MyAdapter',
|
|
3151
|
+
defaults: { ... },
|
|
3152
|
+
build: (config) => ({
|
|
3153
|
+
beforeMount({ app }) { /* ... */ },
|
|
3154
|
+
afterStart({ server }) { /* ... */ },
|
|
3155
|
+
}),
|
|
3156
|
+
})
|
|
3157
|
+
\`\`\`
|
|
3158
|
+
|
|
3159
|
+
- **Plugins** — \`definePlugin()\` factory. Same shape, never plain function returning \`KickPlugin\`.
|
|
3160
|
+
|
|
3161
|
+
- **DI tokens** — slash-delimited \`<scope>/<area>/<key>\`, lower-case, no \`:\` separators:
|
|
3162
|
+
|
|
3163
|
+
\`\`\`ts
|
|
3164
|
+
const USERS_REPO = createToken<UsersRepo>('app/users/repository')
|
|
3165
|
+
const DB = createToken<Database>('app/db/connection')
|
|
3166
|
+
\`\`\`
|
|
3167
|
+
|
|
3168
|
+
The \`kick/\` prefix is reserved for first-party packages; this project
|
|
3169
|
+
owns its own scope (\`app/\`, your domain name, etc.).
|
|
3170
|
+
|
|
3171
|
+
- **\`@Controller()\`** takes **no path argument**. Mount prefix comes from
|
|
3172
|
+
the module's \`routes()\` return value, not the decorator. \`@Controller('/users')\`
|
|
3173
|
+
is a v3 leftover; the linter and codegen reject it.
|
|
3174
|
+
|
|
3175
|
+
- **Env wiring** — \`src/config/index.ts\` calls \`loadEnv(envSchema)\` as a
|
|
3176
|
+
side effect. \`src/index.ts\` MUST have \`import './config'\` as its **first**
|
|
3177
|
+
import (before \`bootstrap()\`). Without it, \`ConfigService.get('YOUR_KEY')\`
|
|
3178
|
+
returns \`undefined\` and \`@Value()\` only works via raw \`process.env\` fallback
|
|
3179
|
+
(Zod coercion + defaults silently skipped).
|
|
3180
|
+
|
|
3181
|
+
- **Module entry files MUST be named \`<name>.module.ts\`** — see the Vite
|
|
3182
|
+
HMR contract at the top of "Module Pattern" below. The CLI enforces this;
|
|
3183
|
+
hand-rolled files must too.
|
|
3184
|
+
|
|
3185
|
+
- **Assets** — drop new template files into \`src/templates/<namespace>/\`
|
|
3186
|
+
(or wherever \`kick.config.ts\` points). The dev watcher auto-rebuilds the
|
|
3187
|
+
\`KickAssets\` augmentation; \`assets.x.y()\` re-walks on next call. No restart,
|
|
3188
|
+
no manual build step.
|
|
3189
|
+
|
|
3190
|
+
- **Context over \`@Middleware()\`** — when a middleware's only job is to
|
|
3191
|
+
populate \`ctx.set('key', value)\`, use \`defineHttpContextDecorator()\`
|
|
3192
|
+
(HTTP) or \`defineContextDecorator()\` (transport-agnostic) instead.
|
|
3193
|
+
Typed via \`ContextMeta\`, ordered via \`dependsOn\`, validated at boot.
|
|
3194
|
+
Reserve \`@Middleware()\` for response short-circuit / stream mutation /
|
|
3195
|
+
pre-route-matching work.
|
|
3196
|
+
|
|
3197
|
+
Two ground rules around the data flow — both stem from the fact that
|
|
3198
|
+
every per-request stage gets its OWN \`RequestContext\` instance, all
|
|
3199
|
+
reading/writing the SAME \`AsyncLocalStorage\`-backed Map:
|
|
3200
|
+
- **\`resolve\` and \`onError\` must RETURN the value.** The runner
|
|
3201
|
+
writes it via \`ctx.set(reg.key, value)\` on your behalf. Direct
|
|
3202
|
+
property assignment (\`ctx.tenant = …\`) sticks to the contributor
|
|
3203
|
+
instance only — the handler instance never sees it.
|
|
3204
|
+
- **Read across instances via \`ctx.set\` / \`ctx.get\`** (or
|
|
3205
|
+
\`requestStore.getStore()?.values.get('key')\` from a service that
|
|
3206
|
+
has no \`ctx\` reference). \`ctx.req\` works because the underlying
|
|
3207
|
+
Express request is shared; bespoke property assignments don't.
|
|
3208
|
+
|
|
3209
|
+
- **Test isolation** — default to \`Container.create()\` for fresh DI state.
|
|
3210
|
+
Never \`new Container()\` and never \`getInstance().reset()\` — both leak
|
|
3211
|
+
registrations between tests.
|
|
3212
|
+
|
|
3213
|
+
\`\`\`ts
|
|
3214
|
+
const container = Container.create()
|
|
3215
|
+
// ... register test-scoped providers, run, discard
|
|
3216
|
+
\`\`\`
|
|
3217
|
+
|
|
3218
|
+
- **Bootstrap export** — \`src/index.ts\` MUST end with
|
|
3219
|
+
\`export const app = await bootstrap({ ... })\`. The Vite plugin imports
|
|
3220
|
+
the named \`app\` symbol to drive HMR module swaps; testing helpers
|
|
3221
|
+
(\`createTestApp\`) and the OpenAPI introspector also rely on it. Drop
|
|
3222
|
+
the \`export\` and \`kick dev\` will silently fall back to a full restart
|
|
3223
|
+
on every save while \`createTestApp\` complains about a missing handle.
|
|
3224
|
+
|
|
3225
|
+
- **Keep \`src/index.ts\` thin** — collect plugins, modules, middleware, and
|
|
3226
|
+
adapters in dedicated folders and re-export aggregated arrays. Do **not**
|
|
3227
|
+
inline registration in the entry file:
|
|
3228
|
+
|
|
3229
|
+
\`\`\`ts
|
|
3230
|
+
// src/modules/index.ts
|
|
3231
|
+
export const modules: AppModuleClass[] = [HelloModule, UsersModule, ...]
|
|
3232
|
+
|
|
3233
|
+
// src/middleware/index.ts
|
|
3234
|
+
export const middleware = [helmet(), cors(), requestId(), ...]
|
|
3235
|
+
|
|
3236
|
+
// src/plugins/index.ts
|
|
3237
|
+
export const plugins = [MetricsPlugin(), AuditPlugin()]
|
|
3238
|
+
|
|
3239
|
+
// src/adapters/index.ts
|
|
3240
|
+
export const adapters = [SwaggerAdapter({ ... }), DevToolsAdapter()]
|
|
3241
|
+
\`\`\`
|
|
3242
|
+
|
|
3243
|
+
\`\`\`ts
|
|
3244
|
+
// src/index.ts — stays small; one import per category
|
|
3245
|
+
import 'reflect-metadata'
|
|
3246
|
+
import './config'
|
|
3247
|
+
import { bootstrap } from '@forinda/kickjs'
|
|
3248
|
+
import { modules } from './modules'
|
|
3249
|
+
import { middleware } from './middleware'
|
|
3250
|
+
import { plugins } from './plugins'
|
|
3251
|
+
import { adapters } from './adapters'
|
|
3252
|
+
|
|
3253
|
+
export const app = await bootstrap({ modules, middleware, plugins, adapters })
|
|
3254
|
+
\`\`\`
|
|
3255
|
+
|
|
3256
|
+
This keeps the entry file diff-friendly, scales to dozens of modules
|
|
3257
|
+
without git churn, and lets each domain own its own registration list.
|
|
3258
|
+
The generators (\`kick g module\`, \`kick g middleware\`, \`kick g plugin\`,
|
|
3259
|
+
\`kick g adapter\`) follow this layout — manual additions should too.
|
|
3260
|
+
|
|
3261
|
+
Everything else (controllers, services, modules, RequestContext API, generators,
|
|
3262
|
+
package additions, env access patterns, troubleshooting) is detailed below.
|
|
3377
3263
|
|
|
3378
3264
|
## Where to Find Things
|
|
3379
3265
|
|
|
@@ -3384,6 +3270,7 @@ This guide helps AI agents (Claude, Copilot, etc.) work effectively on this Kick
|
|
|
3384
3270
|
| Entry point | \`src/index.ts\` |
|
|
3385
3271
|
| Module registry | \`src/modules/index.ts\` |
|
|
3386
3272
|
| Feature modules | \`src/modules/<module-name>/\` |
|
|
3273
|
+
| **Module entry file** | \`src/modules/<name>/<name>.module.ts\` (filename suffix is required — see Vite HMR contract below) |
|
|
3387
3274
|
${template === "graphql" ? "| GraphQL resolvers | `src/resolvers/` |\n" : ""}| Env values | \`.env\` |
|
|
3388
3275
|
| Env schema (Zod) | \`src/config/index.ts\` |
|
|
3389
3276
|
| TypeScript config | \`tsconfig.json\` |
|
|
@@ -3579,14 +3466,13 @@ import { Container } from '@forinda/kickjs'
|
|
|
3579
3466
|
import { createTestApp } from '@forinda/kickjs-testing'
|
|
3580
3467
|
|
|
3581
3468
|
describe('UserController', () => {
|
|
3582
|
-
beforeEach(() => {
|
|
3583
|
-
Container.reset() // Important: isolate DI state
|
|
3584
|
-
})
|
|
3585
|
-
|
|
3586
3469
|
it('should return users', async () => {
|
|
3587
|
-
|
|
3470
|
+
// Container.create() — isolated DI state per test, never new Container()
|
|
3471
|
+
// and never getInstance().reset() (both leak registrations between tests).
|
|
3472
|
+
const container = Container.create()
|
|
3473
|
+
const app = await createTestApp([UserModule], { container })
|
|
3588
3474
|
const res = await app.get('/users')
|
|
3589
|
-
|
|
3475
|
+
|
|
3590
3476
|
expect(res.status).toBe(200)
|
|
3591
3477
|
expect(res.body).toHaveProperty('users')
|
|
3592
3478
|
})
|
|
@@ -3650,7 +3536,7 @@ These work anywhere — scripts, plain files, outside \`@Service\`/\`@Controller
|
|
|
3650
3536
|
|---------|--------|---------|
|
|
3651
3537
|
| \`Logger.for(name)\` | \`@forinda/kickjs\` | \`const log = Logger.for('MyScript')\` |
|
|
3652
3538
|
| \`createLogger(name)\` | \`@forinda/kickjs\` | \`const log = createLogger('Worker')\` |
|
|
3653
|
-
| \`createToken<T>(name)\` | \`@forinda/kickjs\` | \`const TOKEN = createToken<string>('db
|
|
3539
|
+
| \`createToken<T>(name)\` | \`@forinda/kickjs\` | \`const TOKEN = createToken<string>('app/db/url')\` |
|
|
3654
3540
|
| \`ref(value)\` | \`@forinda/kickjs\` | \`const count = ref(0)\` |
|
|
3655
3541
|
| \`computed(fn)\` | \`@forinda/kickjs\` | \`const doubled = computed(() => count.value * 2)\` |
|
|
3656
3542
|
| \`watch(source, cb)\` | \`@forinda/kickjs\` | \`watch(() => count.value, (v) => log(v))\` |
|
|
@@ -3720,13 +3606,15 @@ ${template === "graphql" ? `### GraphQL
|
|
|
3720
3606
|
|
|
3721
3607
|
1. **Forgot to register module** — Add to \`src/modules/index.ts\` exports array
|
|
3722
3608
|
2. **DI not working** — Ensure \`reflect-metadata\` is imported in \`src/index.ts\`
|
|
3723
|
-
3. **Tests failing randomly** —
|
|
3609
|
+
3. **Tests failing randomly** — Sharing the global container between tests. Default to \`Container.create()\` per test (or per \`beforeEach\`) instead of \`new Container()\` / \`getInstance().reset()\`
|
|
3724
3610
|
4. **Routes not found** — Check controller path and module registration
|
|
3725
3611
|
5. **HMR not working** — Two checks: (a) \`vite.config.ts\` has \`hmr: true\`; (b) module file is named \`<name>.module.ts\` (or \`.tsx\`/\`.js\`/\`.jsx\`) and lives under \`src/modules/\`. The Vite plugin auto-discovers \`*.module.[tj]sx?\` for graceful HMR — a misnamed module file (e.g., \`projects.ts\`) silently degrades to a full restart on every save.
|
|
3726
3612
|
6. **Decorators not working** — Check \`tsconfig.json\` has \`experimentalDecorators: true\`
|
|
3727
3613
|
7. **\`config.get('YOUR_KEY')\` returns \`undefined\`** — \`src/index.ts\` is missing \`import './config'\`. That side-effect import registers the env schema with kickjs (\`loadEnv(envSchema)\` runs at module load). Without it, \`ConfigService\` falls back to the base schema (\`PORT\`/\`NODE_ENV\`/\`LOG_LEVEL\` only) and every user-defined key reads as \`undefined\`. \`@Value()\` may *appear* to work because of a raw \`process.env\` fallback, but Zod coercion and schema defaults are silently skipped — investigate \`src/index.ts\` and \`src/config/index.ts\` first.
|
|
3728
3614
|
8. **Used \`@Middleware()\` to compute a value for \`ctx\`** — prefer \`defineContextDecorator()\` (see Context Decorators above). It's typed via \`ContextMeta\`, supports \`dependsOn\` for ordering, and validates the pipeline at boot. \`@Middleware()\` is for response short-circuiting, stream mutation, and pre-route-matching work.
|
|
3729
3615
|
9. **Context contributor's \`dependsOn\` key not produced anywhere** — boot throws \`MissingContributorError\` naming the dependent and the route. Either remove the dep or register a contributor that produces the key (at any precedence level: method/class/module/adapter/global).
|
|
3616
|
+
10. **\`bootstrap()\` not exported** — \`src/index.ts\` calls \`await bootstrap({ ... })\` but discards the return value (no \`export const app = ...\`). Vite HMR can't locate the running instance, so module saves degrade to full restarts; \`createTestApp\`/\`@forinda/kickjs-testing\` consumers can't import the handle either. Always: \`export const app = await bootstrap({ ... })\`.
|
|
3617
|
+
11. **Refresh AGENTS.md / CLAUDE.md after a framework upgrade** — these files are scaffolded by the CLI and don't auto-update. Run \`kick g agents -f\` (or \`kick g agent-docs -f\`) to regenerate from the latest CLI templates after \`kick add\` / version bumps. Hand-edited sections will be overwritten — keep customisation in a separate file like \`AGENTS.local.md\`.
|
|
3730
3618
|
|
|
3731
3619
|
## CLI Commands Reference
|
|
3732
3620
|
|
|
@@ -3759,6 +3647,261 @@ ${template === "graphql" ? `### GraphQL
|
|
|
3759
3647
|
- [Testing](https://forinda.github.io/kick-js/api/testing.html)
|
|
3760
3648
|
`;
|
|
3761
3649
|
}
|
|
3650
|
+
/**
|
|
3651
|
+
* Generate `kickjs-skills.md` — task-oriented "skill" recipes for AI
|
|
3652
|
+
* agents (Claude superpowers, Copilot, etc.). Where AGENTS.md is the
|
|
3653
|
+
* narrative reference, this file lists short, rigid workflows the agent
|
|
3654
|
+
* should follow when it sees the corresponding trigger.
|
|
3655
|
+
*/
|
|
3656
|
+
function generateKickJsSkills(name, _template, pm) {
|
|
3657
|
+
return `# kickjs-skills.md — Task Skills for AI Agents (${name})
|
|
3658
|
+
|
|
3659
|
+
This file is the agent-facing **skills index** for KickJS work in this
|
|
3660
|
+
repo. Each block below is a short, rigid workflow keyed to a specific
|
|
3661
|
+
trigger ("user wants to add a module", "tests are leaking state", etc.).
|
|
3662
|
+
|
|
3663
|
+
- Reference docs (narrative, exhaustive) → \`AGENTS.md\`.
|
|
3664
|
+
- Tool-specific notes → \`CLAUDE.md\`, \`GEMINI.md\`, etc.
|
|
3665
|
+
- **This file** → step-by-step recipes the agent should *execute*.
|
|
3666
|
+
|
|
3667
|
+
Re-run \`kick g agents -f --only skills\` after framework upgrades to refresh.
|
|
3668
|
+
|
|
3669
|
+
---
|
|
3670
|
+
|
|
3671
|
+
## Skill: add-module
|
|
3672
|
+
|
|
3673
|
+
\`\`\`yaml
|
|
3674
|
+
name: kickjs-add-module
|
|
3675
|
+
description: Use when the user asks to add a new feature module (controller + service + repo + DTOs).
|
|
3676
|
+
\`\`\`
|
|
3677
|
+
|
|
3678
|
+
**Trigger phrases**: "add a users module", "scaffold tasks", "new feature for X".
|
|
3679
|
+
|
|
3680
|
+
**Steps**:
|
|
3681
|
+
1. Run \`kick g module <name>\` (use plural form if the project pluralizes — check \`kick.config.ts\`).
|
|
3682
|
+
2. Verify the new folder under \`src/modules/<name>/\` contains \`<name>.module.ts\` (filename suffix is mandatory for HMR).
|
|
3683
|
+
3. Confirm the module appears in \`src/modules/index.ts\` exports — generator does this automatically; verify if you bypassed it.
|
|
3684
|
+
4. Open \`<name>.dto.ts\` and tighten the Zod schemas to real fields (the generator emits placeholders).
|
|
3685
|
+
5. Run \`${pm} run typecheck\` and \`${pm} run test\` before claiming done.
|
|
3686
|
+
|
|
3687
|
+
**Red flags** (stop and ask):
|
|
3688
|
+
- File created as \`<name>.ts\` instead of \`<name>.module.ts\` — Vite won't HMR it.
|
|
3689
|
+
- Module not registered in \`src/modules/index.ts\`.
|
|
3690
|
+
- \`@Controller('/path')\` with a path argument — that's a v3 pattern; remove it (mount comes from \`routes().path\`).
|
|
3691
|
+
|
|
3692
|
+
---
|
|
3693
|
+
|
|
3694
|
+
## Skill: add-adapter
|
|
3695
|
+
|
|
3696
|
+
\`\`\`yaml
|
|
3697
|
+
name: kickjs-add-adapter
|
|
3698
|
+
description: Use when wiring a new lifecycle integration (Swagger, DevTools, Auth, custom).
|
|
3699
|
+
\`\`\`
|
|
3700
|
+
|
|
3701
|
+
**Steps**:
|
|
3702
|
+
1. \`kick g adapter <name>\` to scaffold the boilerplate, OR install via \`kick add <package>\` for first-party adapters.
|
|
3703
|
+
2. The generated file uses \`defineAdapter()\` — never \`class implements AppAdapter\`.
|
|
3704
|
+
3. Add the adapter instance to \`src/adapters/index.ts\` (don't inline in \`src/index.ts\`).
|
|
3705
|
+
4. If the adapter contributes to \`ctx.set/get\`, prefer \`AppAdapter.contributors?()\` over a wrapping middleware.
|
|
3706
|
+
5. Verify with \`kick dev\` that the adapter's lifecycle logs fire.
|
|
3707
|
+
|
|
3708
|
+
**Red flags**:
|
|
3709
|
+
- Inlining the adapter list directly in \`src/index.ts\` (entry file should stay thin).
|
|
3710
|
+
- Returning a plain object instead of going through \`defineAdapter()\` — type inference for \`config\` will be wrong.
|
|
3711
|
+
|
|
3712
|
+
---
|
|
3713
|
+
|
|
3714
|
+
## Skill: write-controller-test
|
|
3715
|
+
|
|
3716
|
+
\`\`\`yaml
|
|
3717
|
+
name: kickjs-write-controller-test
|
|
3718
|
+
description: Use when adding a Vitest test that exercises an HTTP route or DI graph.
|
|
3719
|
+
\`\`\`
|
|
3720
|
+
|
|
3721
|
+
**Template** (copy/paste, adjust):
|
|
3722
|
+
|
|
3723
|
+
\`\`\`ts
|
|
3724
|
+
import { describe, it, expect } from 'vitest'
|
|
3725
|
+
import { Container } from '@forinda/kickjs'
|
|
3726
|
+
import { createTestApp } from '@forinda/kickjs-testing'
|
|
3727
|
+
|
|
3728
|
+
describe('UserController', () => {
|
|
3729
|
+
it('returns users', async () => {
|
|
3730
|
+
const container = Container.create() // isolated DI per test
|
|
3731
|
+
const app = await createTestApp([UserModule], { container })
|
|
3732
|
+
const res = await app.get('/users')
|
|
3733
|
+
expect(res.status).toBe(200)
|
|
3734
|
+
})
|
|
3735
|
+
})
|
|
3736
|
+
\`\`\`
|
|
3737
|
+
|
|
3738
|
+
**Red flags**:
|
|
3739
|
+
- \`new Container()\` — wrong; use \`Container.create()\`.
|
|
3740
|
+
- \`Container.getInstance().reset()\` — wrong; same fix.
|
|
3741
|
+
- Sharing a container across \`it()\` blocks — leaks registrations.
|
|
3742
|
+
|
|
3743
|
+
---
|
|
3744
|
+
|
|
3745
|
+
## Skill: env-wiring-check
|
|
3746
|
+
|
|
3747
|
+
\`\`\`yaml
|
|
3748
|
+
name: kickjs-env-wiring-check
|
|
3749
|
+
description: Use when ConfigService.get('SOME_KEY') returns undefined or @Value silently falls back to process.env.
|
|
3750
|
+
\`\`\`
|
|
3751
|
+
|
|
3752
|
+
**Diagnosis**:
|
|
3753
|
+
1. Open \`src/index.ts\`. The **first non-\`reflect-metadata\`** import MUST be \`import './config'\`.
|
|
3754
|
+
2. Open \`src/config/index.ts\`. It MUST call \`loadEnv(envSchema)\` as a top-level side effect.
|
|
3755
|
+
3. The new key MUST be declared in the Zod schema there. \`@Value('NEW_KEY')\` won't work without a schema entry (it'll fall back to raw \`process.env\` and skip Zod coercion silently).
|
|
3756
|
+
|
|
3757
|
+
**Fix**: add the key to the schema; ensure both side-effect imports above are present.
|
|
3758
|
+
|
|
3759
|
+
---
|
|
3760
|
+
|
|
3761
|
+
## Skill: bootstrap-export
|
|
3762
|
+
|
|
3763
|
+
\`\`\`yaml
|
|
3764
|
+
name: kickjs-bootstrap-export
|
|
3765
|
+
description: Use when HMR is silently doing full restarts on every save, or createTestApp can't find the app handle.
|
|
3766
|
+
\`\`\`
|
|
3767
|
+
|
|
3768
|
+
**Check** \`src/index.ts\`'s last line:
|
|
3769
|
+
|
|
3770
|
+
\`\`\`ts
|
|
3771
|
+
// CORRECT
|
|
3772
|
+
export const app = await bootstrap({ ... })
|
|
3773
|
+
|
|
3774
|
+
// WRONG (HMR degrades to full restart, createTestApp loses the handle)
|
|
3775
|
+
await bootstrap({ ... })
|
|
3776
|
+
\`\`\`
|
|
3777
|
+
|
|
3778
|
+
The Vite plugin imports the named \`app\` symbol; testing helpers do too.
|
|
3779
|
+
|
|
3780
|
+
---
|
|
3781
|
+
|
|
3782
|
+
## Skill: thin-entry-file
|
|
3783
|
+
|
|
3784
|
+
\`\`\`yaml
|
|
3785
|
+
name: kickjs-thin-entry-file
|
|
3786
|
+
description: Use when src/index.ts is accumulating module/middleware/plugin/adapter literals.
|
|
3787
|
+
\`\`\`
|
|
3788
|
+
|
|
3789
|
+
**Refactor target**:
|
|
3790
|
+
|
|
3791
|
+
\`\`\`ts
|
|
3792
|
+
// src/modules/index.ts
|
|
3793
|
+
export const modules: AppModuleClass[] = [HelloModule, UsersModule, ...]
|
|
3794
|
+
|
|
3795
|
+
// src/middleware/index.ts
|
|
3796
|
+
export const middleware = [helmet(), cors(), requestId(), ...]
|
|
3797
|
+
|
|
3798
|
+
// src/plugins/index.ts
|
|
3799
|
+
export const plugins = [MetricsPlugin(), ...]
|
|
3800
|
+
|
|
3801
|
+
// src/adapters/index.ts
|
|
3802
|
+
export const adapters = [SwaggerAdapter({ ... }), DevToolsAdapter()]
|
|
3803
|
+
|
|
3804
|
+
// src/index.ts — stays small
|
|
3805
|
+
import 'reflect-metadata'
|
|
3806
|
+
import './config'
|
|
3807
|
+
import { bootstrap } from '@forinda/kickjs'
|
|
3808
|
+
import { modules } from './modules'
|
|
3809
|
+
import { middleware } from './middleware'
|
|
3810
|
+
import { plugins } from './plugins'
|
|
3811
|
+
import { adapters } from './adapters'
|
|
3812
|
+
export const app = await bootstrap({ modules, middleware, plugins, adapters })
|
|
3813
|
+
\`\`\`
|
|
3814
|
+
|
|
3815
|
+
**Red flags**: any \`new SomeAdapter()\` or \`SomePlugin()\` literal inside \`bootstrap({ ... })\` instead of imported from a category folder.
|
|
3816
|
+
|
|
3817
|
+
---
|
|
3818
|
+
|
|
3819
|
+
## Skill: context-contributor
|
|
3820
|
+
|
|
3821
|
+
\`\`\`yaml
|
|
3822
|
+
name: kickjs-context-contributor
|
|
3823
|
+
description: Use when a middleware's only job is to set ctx values consumed elsewhere — replace with defineHttpContextDecorator (HTTP) or defineContextDecorator (transport-agnostic).
|
|
3824
|
+
\`\`\`
|
|
3825
|
+
|
|
3826
|
+
**Pattern** (HTTP — most common):
|
|
3827
|
+
|
|
3828
|
+
\`\`\`ts
|
|
3829
|
+
import { defineHttpContextDecorator, type RequestContext } from '@forinda/kickjs'
|
|
3830
|
+
|
|
3831
|
+
const LoadTenant = defineHttpContextDecorator({
|
|
3832
|
+
key: 'tenant',
|
|
3833
|
+
deps: { repo: TENANT_REPO },
|
|
3834
|
+
resolve: (ctx, { repo }) => repo.findById(ctx.req.headers['x-tenant-id'] as string),
|
|
3835
|
+
})
|
|
3836
|
+
|
|
3837
|
+
const LoadProject = defineHttpContextDecorator({
|
|
3838
|
+
key: 'project',
|
|
3839
|
+
dependsOn: ['tenant'],
|
|
3840
|
+
resolve: (ctx) => projectsRepo.find(ctx.get('tenant')!.id, ctx.params.id),
|
|
3841
|
+
})
|
|
3842
|
+
|
|
3843
|
+
@LoadTenant
|
|
3844
|
+
@LoadProject
|
|
3845
|
+
@Get('/projects/:id')
|
|
3846
|
+
getProject(ctx: RequestContext) { ctx.json(ctx.get('project')) }
|
|
3847
|
+
\`\`\`
|
|
3848
|
+
|
|
3849
|
+
Use \`defineContextDecorator\` (no Http prefix) when authoring a contributor that must run across HTTP, WebSocket, queue, and cron transports — \`Ctx\` defaults to the smaller \`ExecutionContext\` surface (\`get\` / \`set\` / \`requestId\` only, no \`req\`).
|
|
3850
|
+
|
|
3851
|
+
Precedence high → low: **method > class > module > adapter > global**.
|
|
3852
|
+
Cycles or unmet \`dependsOn\` keys throw \`MissingContributorError\` at boot.
|
|
3853
|
+
|
|
3854
|
+
**Critical rules — all stem from the same shared-via-ALS instance model**:
|
|
3855
|
+
- Every per-request stage (middleware → contributors → handler) gets its OWN \`RequestContext\` instance, but they all read/write the SAME \`AsyncLocalStorage\`-backed Map (\`requestStore.getStore().values\`).
|
|
3856
|
+
- **\`resolve\` and \`onError\` must RETURN the value** — the runner writes it via \`ctx.set(key, value)\`. Direct property assignment (\`ctx.tenant = …\`) sticks to one instance only and the handler instance never sees it.
|
|
3857
|
+
- \`ctx.set('tenant', x)\` then \`ctx.get('tenant')\` works across instances. \`ctx.req.headers[...]\` works (the underlying Express request is shared).
|
|
3858
|
+
- Services can read contributor output without a \`ctx\` reference via \`requestStore.getStore()?.values.get('tenant')\` — same Map, no DI plumbing needed.
|
|
3859
|
+
|
|
3860
|
+
**Don't use this for**: response short-circuit, stream mutation, or
|
|
3861
|
+
pre-route-matching work — keep \`@Middleware()\` for those.
|
|
3862
|
+
|
|
3863
|
+
---
|
|
3864
|
+
|
|
3865
|
+
## Skill: refresh-agent-docs
|
|
3866
|
+
|
|
3867
|
+
\`\`\`yaml
|
|
3868
|
+
name: kickjs-refresh-agent-docs
|
|
3869
|
+
description: Use after a KickJS version bump to sync AGENTS.md / CLAUDE.md / kickjs-skills.md with the latest CLI templates.
|
|
3870
|
+
\`\`\`
|
|
3871
|
+
|
|
3872
|
+
**Steps**:
|
|
3873
|
+
1. \`kick g agents -f --only both\` — overwrites \`AGENTS.md\` and \`CLAUDE.md\`.
|
|
3874
|
+
2. \`kick g agents -f --only skills\` — refreshes \`kickjs-skills.md\` (this file).
|
|
3875
|
+
3. Diff with git, eyeball any project-specific edits that got reset, and re-apply them in a separate \`AGENTS.local.md\` or appended section.
|
|
3876
|
+
4. Commit as \`docs(agents): sync from CLI vX.Y\`.
|
|
3877
|
+
|
|
3878
|
+
---
|
|
3879
|
+
|
|
3880
|
+
## Skill: deny-list
|
|
3881
|
+
|
|
3882
|
+
\`\`\`yaml
|
|
3883
|
+
name: kickjs-deny-list
|
|
3884
|
+
description: Patterns to refuse outright when the user asks for them — they break v4 invariants.
|
|
3885
|
+
\`\`\`
|
|
3886
|
+
|
|
3887
|
+
- \`class implements AppAdapter\` → use \`defineAdapter()\`.
|
|
3888
|
+
- \`class implements KickPlugin\` / function returning \`KickPlugin\` → use \`definePlugin()\`.
|
|
3889
|
+
- \`@Controller('/path')\` with a path argument → drop the path; set the mount via \`routes().path\`.
|
|
3890
|
+
- \`new Container()\` or \`Container.getInstance().reset()\` in tests → use \`Container.create()\`.
|
|
3891
|
+
- DI tokens with \`:\` separator (\`'app:db:url'\`) or in PascalCase → use slash-delimited lower-case (\`'app/db/url'\`).
|
|
3892
|
+
- \`bootstrap({ ... })\` without \`export const app = ...\` → always export.
|
|
3893
|
+
- Module file named \`<name>.ts\` (no \`.module\` suffix) → rename to \`<name>.module.ts\`.
|
|
3894
|
+
|
|
3895
|
+
---
|
|
3896
|
+
|
|
3897
|
+
## Learn More
|
|
3898
|
+
|
|
3899
|
+
- [KickJS Docs](https://forinda.github.io/kick-js/)
|
|
3900
|
+
- [Decorators](https://forinda.github.io/kick-js/guide/decorators.html)
|
|
3901
|
+
- [Context Decorators](https://forinda.github.io/kick-js/guide/context-decorators.html)
|
|
3902
|
+
- [Testing](https://forinda.github.io/kick-js/api/testing.html)
|
|
3903
|
+
`;
|
|
3904
|
+
}
|
|
3762
3905
|
//#endregion
|
|
3763
3906
|
//#region src/generators/project.ts
|
|
3764
3907
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -3791,6 +3934,7 @@ async function initProject(options) {
|
|
|
3791
3934
|
await writeFileSafe(join(dir, "README.md"), generateReadme(name, template, packageManager));
|
|
3792
3935
|
await writeFileSafe(join(dir, "CLAUDE.md"), generateClaude(name, template, packageManager));
|
|
3793
3936
|
await writeFileSafe(join(dir, "AGENTS.md"), generateAgents(name, template, packageManager));
|
|
3937
|
+
await writeFileSafe(join(dir, "kickjs-skills.md"), generateKickJsSkills(name, template, packageManager));
|
|
3794
3938
|
if (options.installDeps) {
|
|
3795
3939
|
console.log(`\n Installing dependencies with ${packageManager}...\n`);
|
|
3796
3940
|
try {
|
|
@@ -3804,7 +3948,7 @@ async function initProject(options) {
|
|
|
3804
3948
|
}
|
|
3805
3949
|
}
|
|
3806
3950
|
try {
|
|
3807
|
-
const { runTypegen } = await import("./typegen-
|
|
3951
|
+
const { runTypegen } = await import("./typegen-C-H8pg-y.mjs");
|
|
3808
3952
|
await runTypegen({
|
|
3809
3953
|
cwd: dir,
|
|
3810
3954
|
allowDuplicates: true,
|