@forinda/kickjs-cli 5.0.2 → 5.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.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @forinda/kickjs-cli v5.1.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import { t as __exportAll } from "./rolldown-runtime-BM29JyaJ.mjs";
12
+ import { existsSync } from "node:fs";
13
+ import { isAbsolute, join, relative, resolve } from "node:path";
14
+ import { access, readFile } from "node:fs/promises";
15
+ //#region src/config.ts
16
+ var config_exports = /* @__PURE__ */ __exportAll({
17
+ BUILTIN_REPO_TYPES: () => BUILTIN_REPO_TYPES,
18
+ PACKAGE_MANAGERS: () => PACKAGE_MANAGERS,
19
+ defineConfig: () => defineConfig,
20
+ loadKickConfig: () => loadKickConfig,
21
+ resolveModuleConfig: () => resolveModuleConfig,
22
+ validateAssetMap: () => validateAssetMap
23
+ });
24
+ const PACKAGE_MANAGERS = [
25
+ "pnpm",
26
+ "npm",
27
+ "yarn",
28
+ "bun"
29
+ ];
30
+ const BUILTIN_REPO_TYPES = [
31
+ "drizzle",
32
+ "inmemory",
33
+ "prisma"
34
+ ];
35
+ /** Helper to define a type-safe kick.config.ts */
36
+ function defineConfig(config) {
37
+ return config;
38
+ }
39
+ /** Resolve module config from `modules.*` block. */
40
+ function resolveModuleConfig(config) {
41
+ if (!config) return {};
42
+ const mc = {
43
+ dir: config.modules?.dir,
44
+ repo: config.modules?.repo,
45
+ schemaDir: config.modules?.schemaDir,
46
+ pluralize: config.modules?.pluralize,
47
+ prismaClientPath: config.modules?.prismaClientPath
48
+ };
49
+ if (mc.repo && typeof mc.repo === "string" && !BUILTIN_REPO_TYPES.includes(mc.repo)) console.warn(` Warning: modules.repo '${mc.repo}' is not a built-in type (${BUILTIN_REPO_TYPES.join(", ")}). It will generate a stub repository. Use { name: '${mc.repo}' } to silence this warning.`);
50
+ return mc;
51
+ }
52
+ const CONFIG_FILES = [
53
+ "kick.config.ts",
54
+ "kick.config.js",
55
+ "kick.config.mjs",
56
+ "kick.config.json"
57
+ ];
58
+ /** Load kick.config.* from the project root */
59
+ async function loadKickConfig(cwd) {
60
+ for (const filename of CONFIG_FILES) {
61
+ const filepath = join(cwd, filename);
62
+ try {
63
+ await access(filepath);
64
+ } catch {
65
+ continue;
66
+ }
67
+ if (filename.endsWith(".json")) {
68
+ const content = await readFile(filepath, "utf-8");
69
+ return JSON.parse(content);
70
+ }
71
+ try {
72
+ const { pathToFileURL } = await import("node:url");
73
+ const mod = await import(pathToFileURL(filepath).href);
74
+ const config = mod.default ?? mod;
75
+ const warnings = validateAssetMap(config, cwd);
76
+ for (const warning of warnings) console.warn(` Warning: ${warning}`);
77
+ return config;
78
+ } catch (err) {
79
+ if (filename.endsWith(".ts")) console.warn(`Warning: Failed to load ${filename}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);
80
+ continue;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ /**
86
+ * Validate `assetMap` entries on a loaded config. Returns a list of
87
+ * human-readable warnings; the caller decides how to surface them
88
+ * (typically `console.warn`). Never throws — `kick g` and other
89
+ * unrelated commands should keep working even when the assetMap is
90
+ * misconfigured.
91
+ *
92
+ * Checks:
93
+ *
94
+ * - Each entry's `src` is a non-empty string.
95
+ * - The `src` directory exists on disk (otherwise the typegen + build
96
+ * steps will fail later with cryptic errors).
97
+ * - `dest` doesn't escape the project root (defensive — a `dest:
98
+ * '../../etc'` typo could write files outside the workspace).
99
+ * - The namespace key is a non-empty string and doesn't include a
100
+ * `/` (would conflict with the `<namespace>/<key>` manifest format).
101
+ */
102
+ function validateAssetMap(config, cwd) {
103
+ const warnings = [];
104
+ if (!config?.assetMap) return warnings;
105
+ const root = resolve(cwd);
106
+ for (const [namespace, entry] of Object.entries(config.assetMap)) {
107
+ if (!namespace || namespace.includes("/")) {
108
+ warnings.push(`assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`);
109
+ continue;
110
+ }
111
+ if (typeof entry?.src !== "string" || entry.src.length === 0) {
112
+ warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`);
113
+ continue;
114
+ }
115
+ if (!existsSync(resolve(cwd, entry.src))) warnings.push(`assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`);
116
+ if (entry.dest) {
117
+ if (escapesRoot(resolve(cwd, entry.dest), root)) warnings.push(`assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`);
118
+ }
119
+ }
120
+ return warnings;
121
+ }
122
+ /**
123
+ * Returns true when `path` (absolute) resolves outside of `root`
124
+ * (also absolute). Uses `path.relative` for accuracy:
125
+ *
126
+ * - The result is empty when paths are identical (inside).
127
+ * - It starts with `..` when the path traverses outside the root.
128
+ * - It's absolute (Windows: cross-drive) when there's no relative
129
+ * path between them.
130
+ *
131
+ * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`
132
+ * matching `/app2/...`, or case-mismatches on macOS / Windows).
133
+ */
134
+ function escapesRoot(path, root) {
135
+ const rel = relative(root, path);
136
+ return rel === "" ? false : rel.startsWith("..") || isAbsolute(rel);
137
+ }
138
+ //#endregion
139
+ export { resolveModuleConfig as i, config_exports as n, loadKickConfig as r, PACKAGE_MANAGERS as t };
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @forinda/kickjs-cli v5.1.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import { t as __exportAll } from "./rolldown-runtime-BM29JyaJ.mjs";
12
+ import { isAbsolute, join, relative, resolve } from "node:path";
13
+ import { access, readFile } from "node:fs/promises";
14
+ import { existsSync } from "node:fs";
15
+ //#region src/config.ts
16
+ var config_exports = /* @__PURE__ */ __exportAll({
17
+ BUILTIN_REPO_TYPES: () => BUILTIN_REPO_TYPES,
18
+ PACKAGE_MANAGERS: () => PACKAGE_MANAGERS,
19
+ defineConfig: () => defineConfig,
20
+ loadKickConfig: () => loadKickConfig,
21
+ resolveModuleConfig: () => resolveModuleConfig,
22
+ validateAssetMap: () => validateAssetMap
23
+ });
24
+ const PACKAGE_MANAGERS = [
25
+ "pnpm",
26
+ "npm",
27
+ "yarn",
28
+ "bun"
29
+ ];
30
+ const BUILTIN_REPO_TYPES = [
31
+ "drizzle",
32
+ "inmemory",
33
+ "prisma"
34
+ ];
35
+ /** Helper to define a type-safe kick.config.ts */
36
+ function defineConfig(config) {
37
+ return config;
38
+ }
39
+ /** Resolve module config from `modules.*` block. */
40
+ function resolveModuleConfig(config) {
41
+ if (!config) return {};
42
+ const mc = {
43
+ dir: config.modules?.dir,
44
+ repo: config.modules?.repo,
45
+ schemaDir: config.modules?.schemaDir,
46
+ pluralize: config.modules?.pluralize,
47
+ prismaClientPath: config.modules?.prismaClientPath
48
+ };
49
+ if (mc.repo && typeof mc.repo === "string" && !BUILTIN_REPO_TYPES.includes(mc.repo)) console.warn(` Warning: modules.repo '${mc.repo}' is not a built-in type (${BUILTIN_REPO_TYPES.join(", ")}). It will generate a stub repository. Use { name: '${mc.repo}' } to silence this warning.`);
50
+ return mc;
51
+ }
52
+ const CONFIG_FILES = [
53
+ "kick.config.ts",
54
+ "kick.config.js",
55
+ "kick.config.mjs",
56
+ "kick.config.json"
57
+ ];
58
+ /** Load kick.config.* from the project root */
59
+ async function loadKickConfig(cwd) {
60
+ for (const filename of CONFIG_FILES) {
61
+ const filepath = join(cwd, filename);
62
+ try {
63
+ await access(filepath);
64
+ } catch {
65
+ continue;
66
+ }
67
+ if (filename.endsWith(".json")) {
68
+ const content = await readFile(filepath, "utf-8");
69
+ return JSON.parse(content);
70
+ }
71
+ try {
72
+ const { pathToFileURL } = await import("node:url");
73
+ const mod = await import(pathToFileURL(filepath).href);
74
+ const config = mod.default ?? mod;
75
+ const warnings = validateAssetMap(config, cwd);
76
+ for (const warning of warnings) console.warn(` Warning: ${warning}`);
77
+ return config;
78
+ } catch (err) {
79
+ if (filename.endsWith(".ts")) console.warn(`Warning: Failed to load ${filename}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);
80
+ continue;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ /**
86
+ * Validate `assetMap` entries on a loaded config. Returns a list of
87
+ * human-readable warnings; the caller decides how to surface them
88
+ * (typically `console.warn`). Never throws — `kick g` and other
89
+ * unrelated commands should keep working even when the assetMap is
90
+ * misconfigured.
91
+ *
92
+ * Checks:
93
+ *
94
+ * - Each entry's `src` is a non-empty string.
95
+ * - The `src` directory exists on disk (otherwise the typegen + build
96
+ * steps will fail later with cryptic errors).
97
+ * - `dest` doesn't escape the project root (defensive — a `dest:
98
+ * '../../etc'` typo could write files outside the workspace).
99
+ * - The namespace key is a non-empty string and doesn't include a
100
+ * `/` (would conflict with the `<namespace>/<key>` manifest format).
101
+ */
102
+ function validateAssetMap(config, cwd) {
103
+ const warnings = [];
104
+ if (!config?.assetMap) return warnings;
105
+ const root = resolve(cwd);
106
+ for (const [namespace, entry] of Object.entries(config.assetMap)) {
107
+ if (!namespace || namespace.includes("/")) {
108
+ warnings.push(`assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`);
109
+ continue;
110
+ }
111
+ if (typeof entry?.src !== "string" || entry.src.length === 0) {
112
+ warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`);
113
+ continue;
114
+ }
115
+ if (!existsSync(resolve(cwd, entry.src))) warnings.push(`assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`);
116
+ if (entry.dest) {
117
+ if (escapesRoot(resolve(cwd, entry.dest), root)) warnings.push(`assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`);
118
+ }
119
+ }
120
+ return warnings;
121
+ }
122
+ /**
123
+ * Returns true when `path` (absolute) resolves outside of `root`
124
+ * (also absolute). Uses `path.relative` for accuracy:
125
+ *
126
+ * - The result is empty when paths are identical (inside).
127
+ * - It starts with `..` when the path traverses outside the root.
128
+ * - It's absolute (Windows: cross-drive) when there's no relative
129
+ * path between them.
130
+ *
131
+ * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`
132
+ * matching `/app2/...`, or case-mismatches on macOS / Windows).
133
+ */
134
+ function escapesRoot(path, root) {
135
+ const rel = relative(root, path);
136
+ return rel === "" ? false : rel.startsWith("..") || isAbsolute(rel);
137
+ }
138
+ //#endregion
139
+ export { resolveModuleConfig as a, loadKickConfig as i, config_exports as n, defineConfig as r, PACKAGE_MANAGERS as t };
140
+
141
+ //# sourceMappingURL=config-C_LQNClP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-C_LQNClP.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { readFile, access } from 'node:fs/promises'\nimport { isAbsolute, join, relative, resolve } from 'node:path'\n\nimport type { KickCliPlugin } from './plugin/types'\n\n/** A custom command that developers can register via kick.config.ts */\nexport interface KickCommandDefinition {\n /** The command name (e.g. 'db:migrate', 'seed', 'proto:gen') */\n name: string\n /** Description shown in --help */\n description: string\n /**\n * Shell command(s) to run. Can be a single string or an array of\n * sequential steps. Use {args} as a placeholder for CLI arguments.\n *\n * @example\n * 'npx drizzle-kit migrate'\n * ['npx drizzle-kit generate', 'npx drizzle-kit migrate']\n */\n steps: string | string[]\n /** Optional aliases (e.g. ['migrate'] for 'db:migrate') */\n aliases?: string[]\n}\n\n/** Project pattern — controls what generators produce and which deps are installed */\nexport type ProjectPattern = 'rest' | 'ddd' | 'cqrs' | 'minimal'\n\n/** Package manager used for `kick add` and other dep-installing commands */\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport const PACKAGE_MANAGERS: readonly PackageManager[] = ['pnpm', 'npm', 'yarn', 'bun']\n\n/** Built-in repository types with first-class code generation support */\nexport type BuiltinRepoType = 'drizzle' | 'inmemory' | 'prisma'\n\nexport const BUILTIN_REPO_TYPES: readonly string[] = ['drizzle', 'inmemory', 'prisma']\n\n/** Custom repository type — generates a stub with TODO markers */\nexport interface CustomRepoType {\n name: string\n}\n\n/** Repository type — built-in string or custom object */\nexport type RepoTypeConfig = BuiltinRepoType | CustomRepoType\n\n/**\n * Supported schema validators for `kick typegen` body/query/params\n * type extraction. Only `'zod'` ships built-in for now; other libraries\n * (Joi, Yup, JSON Schema) will be added later as the adapter system\n * grows. Set to `false` (or omit) to disable schema-driven body typing\n * entirely (the route entries will keep `body: unknown`).\n */\nexport type SchemaValidator = 'zod' | false\n\n/**\n * One entry in the typed `assetMap` config record (`assets-plan.md`).\n * Each entry names a source directory whose files become addressable\n * via the `assets.<name>.*` typed accessor at runtime.\n */\nexport interface AssetMapEntry {\n /**\n * Source directory, relative to project root. Required. The directory\n * must exist when `kick build` runs — `loadKickConfig` warns when an\n * entry points at a missing directory but doesn't fail the load\n * (the typegen + build steps surface the error in context instead).\n */\n src: string\n /**\n * Destination directory inside `dist/`. Defaults to `dist/<name>/`\n * where `<name>` is the assetMap key. Override when the consumer of\n * the assets expects a non-standard layout (e.g. an existing\n * downstream tool reads from `dist/templates/...`).\n */\n dest?: string\n /**\n * Glob pattern for which files to include. Defaults to `**\\/*` (all\n * files). Files that don't match are NOT copied — `assetMap` is\n * selective by design (unlike `copyDirs` which copies everything).\n */\n glob?: string\n}\n\n/** Typegen settings — controls .kickjs/types/* generation */\nexport interface TypegenConfig {\n /**\n * Source directory to scan for controllers and decorators.\n * Defaults to `'src'`.\n */\n srcDir?: string\n /**\n * Output directory for generated `.d.ts` files.\n * Defaults to `'.kickjs/types'`.\n */\n outDir?: string\n /**\n * Schema validator used to derive `body` types from route metadata.\n *\n * - `'zod'` — emit `z.infer<typeof <importedSchema>>` for any schema\n * referenced as a named identifier in `@Get/@Post/...({ body, query, params })`.\n * - `false` — disable schema-driven body typing.\n *\n * Future: `'joi' | 'yup' | 'json-schema'` plus a `{ name; module }`\n * escape hatch for custom adapters.\n *\n * @default 'zod'\n */\n schemaValidator?: SchemaValidator\n /**\n * Path to the project's env schema file (relative to project root).\n * Must default-export a `defineEnv(...)` schema for typegen to emit\n * the typed `KickEnv` global registry.\n *\n * Set to `false` to disable env typing entirely.\n *\n * @default 'src/env.ts'\n */\n envFile?: string | false\n /**\n * Built-in or user typegen plugin ids to skip during `kick typegen`,\n * `kick dev`, and `kick typegen --watch`.\n *\n * The plugin still loads and merge-time conflict detection still\n * runs — only the `generate()` invocation is skipped — so adopters\n * who want to hand-write `KickDbRegister` (manual typeof-schema\n * augmentation) can disable `'kick/db'` and keep the rest:\n *\n * @example\n * typegen: {\n * disable: ['kick/db'], // hand-written register.ts owns the type\n * }\n *\n * Unrecognised ids are ignored — the list is treated as a wishlist,\n * not a strict registry.\n */\n disable?: string[]\n}\n\n/** Module generation settings — controls how `kick g module` produces code */\nexport interface ModuleConfig {\n /** Where modules live (default: 'src/modules') */\n dir?: string\n /**\n * Default repository implementation for generators.\n *\n * Built-in types (string): `'drizzle'`, `'inmemory'`, `'prisma'`\n * — generate fully working repository code.\n *\n * Custom types (object): `{ name: 'typeorm' }`\n * — generate a stub repository with TODO markers.\n *\n * @example\n * repo: 'prisma' // built-in\n * repo: { name: 'typeorm' } // custom\n */\n repo?: RepoTypeConfig\n /** Schema output directory (e.g. 'src/db/schema' for Drizzle, 'prisma/' for Prisma) */\n schemaDir?: string\n /**\n * Whether to pluralize module names in generated code.\n * When true (default), `kick g module user` creates `src/modules/users/`.\n * When false, it creates `src/modules/user/` and uses singular names throughout.\n */\n pluralize?: boolean\n /**\n * Import path for the Prisma generated client in `--repo prisma` templates.\n * Must resolve within `src/` for path alias compatibility.\n *\n * @default '@prisma/client' (Prisma 5/6)\n * @example\n * prismaClientPath: '@/generated/prisma/client' // Prisma 7+\n * prismaClientPath: './generated/prisma/client' // relative\n */\n prismaClientPath?: string\n}\n\n/** Configuration for the kick.config.ts file */\nexport interface KickConfig {\n /**\n * Project pattern — controls default generator behavior.\n * - 'rest' — Express + Swagger (default)\n * - 'ddd' — Full DDD modules with use cases, entities, value objects\n * - 'cqrs' — CQRS with commands, queries, events, WebSocket + queue\n * - 'minimal' — Bare Express with no scaffolding\n */\n pattern?: ProjectPattern\n /**\n * Module generation settings — directory, repo type, pluralization, schema dir.\n *\n * @example\n * modules: {\n * dir: 'src/modules',\n * repo: 'prisma',\n * pluralize: false,\n * schemaDir: 'prisma/',\n * }\n */\n modules?: ModuleConfig\n /**\n * Package manager used by `kick add` (and any future dep-installing command)\n * to install dependencies. When set, overrides lockfile auto-detection so\n * commands always use the project's intended package manager.\n *\n * Priority (highest first):\n * 1. `--pm` flag on the CLI\n * 2. `packageManager` in kick.config\n * 3. `packageManager` field in package.json (corepack convention)\n * 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)\n * 5. `'npm'`\n *\n * @example\n * packageManager: 'pnpm'\n */\n packageManager?: PackageManager\n\n /**\n * Directories to copy to dist/ after build.\n * Useful for EJS templates, email templates, static assets, etc.\n *\n * @example\n * ```ts\n * copyDirs: [\n * 'src/views', // copies to dist/src/views\n * { src: 'src/views', dest: 'dist/views' }, // custom dest\n * 'src/emails',\n * ]\n * ```\n */\n copyDirs?: Array<string | { src: string; dest?: string }>\n /**\n * Build output settings. The asset manager + `kick build`'s copy\n * steps honour these — adopters who use Vite's `build.outDir =\n * 'out'` (or any non-default) should mirror the value here so\n * `assets.x.y()` paths line up with where Vite actually wrote.\n *\n * @example\n * ```ts\n * build: { outDir: 'out' }\n * ```\n */\n build?: {\n /**\n * Output directory, relative to project root. Defaults to\n * `'dist'`. The asset manager emits its manifest + copies\n * assetMap entries into this directory (under a per-namespace\n * subdirectory by default; override per-entry via `dest`).\n */\n outDir?: string\n }\n /**\n * Typed, addressable assets — see `assets-plan.md`. Each entry maps\n * a logical namespace name to a source directory. The build pipeline\n * auto-derives the necessary copy step + emits a manifest at\n * `dist/.kickjs-assets.json`; the runtime exposes\n * `import { assets } from '@forinda/kickjs'` so adopters can resolve\n * paths without dev/prod branching.\n *\n * `copyDirs` is unchanged — `assetMap` is a separate, opt-in surface.\n * Adopters who want raw directory copies keep using `copyDirs`; those\n * who want typed addressable assets add `assetMap` entries.\n *\n * @example\n * ```ts\n * assetMap: {\n * mails: { src: 'src/templates/mails' },\n * reports: { src: 'src/templates/reports', glob: '**\\/*.{ejs,html}' },\n * schemas: { src: 'src/schemas', glob: '**\\/*.json' },\n * }\n * ```\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Typegen settings — controls `.kickjs/types/*` generation including\n * the schema validator used for body type extraction.\n *\n * @example\n * ```ts\n * typegen: {\n * schemaValidator: 'zod',\n * }\n * ```\n */\n typegen?: TypegenConfig\n /** Custom commands that extend the CLI */\n commands?: KickCommandDefinition[]\n /**\n * CLI plugins — bundled commands + typegens contributed by external\n * packages (e.g. `@forinda/kickjs-cli-drizzle`). Plugin commands\n * appear first; adopter `commands` overrides plugin commands of the\n * same name. Duplicate commands or typegen ids across two plugins\n * fail-fast at CLI startup.\n *\n * @example\n * import { drizzlePlugin } from '@forinda/kickjs-cli-drizzle'\n * export default defineConfig({\n * plugins: [drizzlePlugin({ schemaPath: 'src/db/schema' })],\n * })\n */\n plugins?: KickCliPlugin[]\n /** Code style overrides (auto-detected from prettier when possible) */\n style?: {\n semicolons?: boolean\n quotes?: 'single' | 'double'\n trailingComma?: 'all' | 'es5' | 'none'\n indent?: number\n }\n}\n\n/** Helper to define a type-safe kick.config.ts */\nexport function defineConfig(config: KickConfig): KickConfig {\n return config\n}\n\n/** Resolve module config from `modules.*` block. */\nexport function resolveModuleConfig(config: KickConfig | null): ModuleConfig {\n if (!config) return {}\n const mc: ModuleConfig = {\n dir: config.modules?.dir,\n repo: config.modules?.repo,\n schemaDir: config.modules?.schemaDir,\n pluralize: config.modules?.pluralize,\n prismaClientPath: config.modules?.prismaClientPath,\n }\n\n // Warn if a string repo value isn't a known built-in\n if (mc.repo && typeof mc.repo === 'string' && !BUILTIN_REPO_TYPES.includes(mc.repo)) {\n console.warn(\n ` Warning: modules.repo '${mc.repo}' is not a built-in type (${BUILTIN_REPO_TYPES.join(', ')}).` +\n ` It will generate a stub repository. Use { name: '${mc.repo}' } to silence this warning.`,\n )\n }\n\n return mc\n}\n\nconst CONFIG_FILES = ['kick.config.ts', 'kick.config.js', 'kick.config.mjs', 'kick.config.json']\n\n/** Load kick.config.* from the project root */\nexport async function loadKickConfig(cwd: string): Promise<KickConfig | null> {\n for (const filename of CONFIG_FILES) {\n const filepath = join(cwd, filename)\n try {\n await access(filepath)\n } catch {\n continue\n }\n\n if (filename.endsWith('.json')) {\n const content = await readFile(filepath, 'utf-8')\n return JSON.parse(content)\n }\n\n // For .ts/.js/.mjs — dynamic import (use file URL for cross-platform compat)\n try {\n const { pathToFileURL } = await import('node:url')\n const mod = await import(pathToFileURL(filepath).href)\n const config = (mod.default ?? mod) as KickConfig\n // Surface assetMap mistakes at config-load time so they aren't\n // hidden inside the build pipeline. Warnings only — a typo\n // shouldn't block `kick g`, `kick typegen`, etc.\n const warnings = validateAssetMap(config, cwd)\n for (const warning of warnings) console.warn(` Warning: ${warning}`)\n return config\n } catch (err) {\n if (filename.endsWith('.ts')) {\n console.warn(\n `Warning: Failed to load ${filename}. TypeScript config files require ` +\n 'a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.',\n )\n }\n continue\n }\n }\n return null\n}\n\n/**\n * Validate `assetMap` entries on a loaded config. Returns a list of\n * human-readable warnings; the caller decides how to surface them\n * (typically `console.warn`). Never throws — `kick g` and other\n * unrelated commands should keep working even when the assetMap is\n * misconfigured.\n *\n * Checks:\n *\n * - Each entry's `src` is a non-empty string.\n * - The `src` directory exists on disk (otherwise the typegen + build\n * steps will fail later with cryptic errors).\n * - `dest` doesn't escape the project root (defensive — a `dest:\n * '../../etc'` typo could write files outside the workspace).\n * - The namespace key is a non-empty string and doesn't include a\n * `/` (would conflict with the `<namespace>/<key>` manifest format).\n */\nexport function validateAssetMap(config: KickConfig | null, cwd: string): string[] {\n const warnings: string[] = []\n if (!config?.assetMap) return warnings\n\n const root = resolve(cwd)\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!namespace || namespace.includes('/')) {\n warnings.push(\n `assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`,\n )\n continue\n }\n if (typeof entry?.src !== 'string' || entry.src.length === 0) {\n warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`)\n continue\n }\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) {\n warnings.push(\n `assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`,\n )\n }\n if (entry.dest) {\n const destAbs = resolve(cwd, entry.dest)\n // path.relative is the right primitive for \"is X inside Y?\" —\n // a raw startsWith() prefix match has two failure modes the\n // earlier version hit: (a) `/app` is a prefix of `/app2/...`\n // even though they're different directories, and (b) it's\n // case-sensitive on filesystems that aren't (macOS default,\n // Windows). path.relative handles both correctly + accounts\n // for `..` traversal in the destination.\n if (escapesRoot(destAbs, root)) {\n warnings.push(\n `assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`,\n )\n }\n }\n }\n return warnings\n}\n\n/**\n * Returns true when `path` (absolute) resolves outside of `root`\n * (also absolute). Uses `path.relative` for accuracy:\n *\n * - The result is empty when paths are identical (inside).\n * - It starts with `..` when the path traverses outside the root.\n * - It's absolute (Windows: cross-drive) when there's no relative\n * path between them.\n *\n * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`\n * matching `/app2/...`, or case-mismatches on macOS / Windows).\n */\nfunction escapesRoot(path: string, root: string): boolean {\n const rel = relative(root, path)\n return rel === '' ? false : rel.startsWith('..') || isAbsolute(rel)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,mBAA8C;CAAC;CAAQ;CAAO;CAAQ;CAAM;AAKzF,MAAa,qBAAwC;CAAC;CAAW;CAAY;CAAS;;AAiRtF,SAAgB,aAAa,QAAgC;AAC3D,QAAO;;;AAIT,SAAgB,oBAAoB,QAAyC;AAC3E,KAAI,CAAC,OAAQ,QAAO,EAAE;CACtB,MAAM,KAAmB;EACvB,KAAK,OAAO,SAAS;EACrB,MAAM,OAAO,SAAS;EACtB,WAAW,OAAO,SAAS;EAC3B,WAAW,OAAO,SAAS;EAC3B,kBAAkB,OAAO,SAAS;EACnC;AAGD,KAAI,GAAG,QAAQ,OAAO,GAAG,SAAS,YAAY,CAAC,mBAAmB,SAAS,GAAG,KAAK,CACjF,SAAQ,KACN,4BAA4B,GAAG,KAAK,4BAA4B,mBAAmB,KAAK,KAAK,CAAC,sDACvC,GAAG,KAAK,8BAChE;AAGH,QAAO;;AAGT,MAAM,eAAe;CAAC;CAAkB;CAAkB;CAAmB;CAAmB;;AAGhG,eAAsB,eAAe,KAAyC;AAC5E,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,WAAW,KAAK,KAAK,SAAS;AACpC,MAAI;AACF,SAAM,OAAO,SAAS;UAChB;AACN;;AAGF,MAAI,SAAS,SAAS,QAAQ,EAAE;GAC9B,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ;AACjD,UAAO,KAAK,MAAM,QAAQ;;AAI5B,MAAI;GACF,MAAM,EAAE,kBAAkB,MAAM,OAAO;GACvC,MAAM,MAAM,MAAM,OAAO,cAAc,SAAS,CAAC;GACjD,MAAM,SAAU,IAAI,WAAW;GAI/B,MAAM,WAAW,iBAAiB,QAAQ,IAAI;AAC9C,QAAK,MAAM,WAAW,SAAU,SAAQ,KAAK,cAAc,UAAU;AACrE,UAAO;WACA,KAAK;AACZ,OAAI,SAAS,SAAS,MAAM,CAC1B,SAAQ,KACN,2BAA2B,SAAS,4GAErC;AAEH;;;AAGJ,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAgB,iBAAiB,QAA2B,KAAuB;CACjF,MAAM,WAAqB,EAAE;AAC7B,KAAI,CAAC,QAAQ,SAAU,QAAO;CAE9B,MAAM,OAAO,QAAQ,IAAI;AACzB,MAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,OAAO,SAAS,EAAE;AAChE,MAAI,CAAC,aAAa,UAAU,SAAS,IAAI,EAAE;AACzC,YAAS,KACP,iBAAiB,UAAU,uDAC5B;AACD;;AAEF,MAAI,OAAO,OAAO,QAAQ,YAAY,MAAM,IAAI,WAAW,GAAG;AAC5D,YAAS,KAAK,YAAY,UAAU,qCAAqC;AACzE;;AAGF,MAAI,CAAC,WADU,QAAQ,KAAK,MAAM,IAAI,CACf,CACrB,UAAS,KACP,YAAY,UAAU,SAAS,MAAM,IAAI,+CAC1C;AAEH,MAAI,MAAM;OASJ,YARY,QAAQ,KAAK,MAAM,KAAK,EAQf,KAAK,CAC5B,UAAS,KACP,YAAY,UAAU,UAAU,MAAM,KAAK,yDAC5C;;;AAIP,QAAO;;;;;;;;;;;;;;AAeT,SAAS,YAAY,MAAc,MAAuB;CACxD,MAAM,MAAM,SAAS,MAAM,KAAK;AAChC,QAAO,QAAQ,KAAK,QAAQ,IAAI,WAAW,KAAK,IAAI,WAAW,IAAI"}