@route-forge/core 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @route-forge/core
2
2
 
3
- 框架无关的命名路由客户端核心:分级懒加载、隔离缓存、并发去重、登录态感知、拦截器。
3
+ 框架无关的命名路由客户端核心:分级懒加载、隔离缓存、并发去重、拦截器。
4
4
 
5
5
  ## 安装
6
6
 
7
7
  ```bash
8
8
  pnpm add @route-forge/core
9
- # 可选:axios 适配器
9
+ # 可选:axios 适配器
10
10
  pnpm add axios
11
11
  ```
12
12
 
@@ -19,10 +19,162 @@ const forge = createRouteForge({
19
19
  endpoint: '/_forge/routes',
20
20
  })
21
21
 
22
+ // 调用 API(自动加载层级 + 填充参数 + 发送请求)
23
+ const user = await forge.api('admin', 'users.show', { user: 123 })
24
+
25
+ // 生成 URL(仅拼路径,不发请求)
22
26
  const url = forge.route('public', 'login.show')
23
- // → 'http://localhost/login'
27
+ // → '/login'
28
+
29
+ // url() 是 route() 的语义别名
30
+ const url2 = forge.url('public', 'login.show')
31
+
32
+ // 检查路由是否存在
33
+ forge.hasRoute('admin', 'users.show') // true / false
34
+
35
+ // 获取路由元信息
36
+ const routes = forge.getRoutes('admin') // 指定层级
37
+ const allRoutes = forge.getRoutes() // 全部层级
38
+
39
+ // 检查层级是否已加载
40
+ if (!forge.isLoaded('admin')) {
41
+ await forge.load('admin')
42
+ }
43
+
44
+ // 失效缓存
45
+ forge.invalidate('admin') // 失效指定层级
46
+ forge.invalidate() // 失效全部
47
+ ```
48
+
49
+ ## URL 前缀(`url_prefix`)
50
+
51
+ 后端可在摘要端点 `config.url_prefix` 中下发 URL 前缀,前端生成路由 URL 时自动拼接,无需手动配置。
52
+
53
+ 支持两种形式:
54
+
55
+ ```ts
56
+ // 1. 路径前缀 — 拼接在 baseURL 之后、路由 URI 之前
57
+ // 后端返回 { "config": { "url_prefix": "/api/v1" } }
58
+ forge.route('public', 'users.show', { user: 1 })
59
+ // → '/api/v1/users/1'
60
+
61
+ // 2. 完整 URL(含协议+域名)— 直接作为基础 URL,忽略客户端 baseURL
62
+ // 适用于前后端不同域名的场景
63
+ // 后端返回 { "config": { "url_prefix": "https://api.example.com" } }
64
+ forge.route('public', 'users.show', { user: 1 })
65
+ // → 'https://api.example.com/users/1'
24
66
  ```
25
67
 
68
+ > `url_prefix` 为后端权威,前端不能覆盖。不下发或为空字符串时不影响 URL 生成。
69
+
70
+ ## 参数智能解析
71
+
72
+ `forge.api()` 的第二参数支持四种数据类型:路径参数(平铺)、`query`(查询参数)、`body`(请求体)、`headers`
73
+ (请求头)。同时提供 `params` 固定 key 用于显式指定路径参数。
74
+
75
+ 当路径参数名与 `query`/`body`/`headers` 冲突时,按类型智能消解:
76
+
77
+ ```ts
78
+ // 向后兼容:原有写法不变
79
+ forge.api('admin', 'users.show', { user: 1, query: { include: 'posts' } })
80
+
81
+ // 冲突消解:query 为 string → 自动识别为路径参数
82
+ // 路由: /search/{query}
83
+ forge.api('admin', 'search.show', { query: 'keyword' })
84
+ // → URL: /search/keyword
85
+
86
+ // 显式 params:同时需要路径参数和 query string
87
+ forge.api('admin', 'search.show', {
88
+ params: { query: 'keyword' }, // → 替换 {query}
89
+ query: { page: 1 }, // → query string
90
+ body: { detailed: true }, // → 请求体
91
+ })
92
+ ```
93
+
94
+ 规则:`params` 优先 > 平铺 `string|number` → 路径参数 > 对象类型按原定义(`query`/`body`/`headers`)。
95
+
96
+ ## 认证(Authentication)
97
+
98
+ Route Forge 不内置登录态管理,认证逻辑通过拦截器实现,灵活且完全可控。
99
+
100
+ ### Token 注入
101
+
102
+ ```ts
103
+ // 声明式(初始化时配置)
104
+ const forge = createRouteForge({
105
+ endpoint: '/_forge/routes',
106
+ interceptors: {
107
+ request: [
108
+ (config) => {
109
+ const token = authStore.getToken()
110
+ if (token) {
111
+ config.headers.Authorization = `Bearer ${token}`
112
+ }
113
+ return config
114
+ },
115
+ ],
116
+ },
117
+ })
118
+
119
+ // 或运行时动态注册
120
+ forge.interceptors.request.use((config) => {
121
+ const token = authStore.getToken()
122
+ if (token) {
123
+ config.headers.Authorization = `Bearer ${token}`
124
+ }
125
+ return config
126
+ })
127
+ ```
128
+
129
+ ### 401 响应处理
130
+
131
+ ```ts
132
+ forge.interceptors.response.use(
133
+ (res) => res, // 2xx 正常通过
134
+ (err) => {
135
+ if (err instanceof HTTPError && err.context?.status === 401) {
136
+ authStore.logout()
137
+ window.location.href = '/login'
138
+ }
139
+ return Promise.reject(err)
140
+ },
141
+ )
142
+ ```
143
+
144
+ ### 登出清理
145
+
146
+ ```ts
147
+ function logout() {
148
+ authStore.clearToken()
149
+ forge.invalidate() // 清空路由缓存
150
+ forge.interceptors.request.clear() // 清空拦截器
151
+ forge.interceptors.response.clear()
152
+ }
153
+ ```
154
+
155
+ > 拦截器 API 与 axios 完全一致(`use` / `eject` / `clear`),如果项目已使用 axios 拦截器,可跳过此节——
156
+ > `adapter: 'auto'` 模式下宿主 axios 的拦截器会自动生效。
157
+
158
+ ## 加载状态跟踪
159
+
160
+ 核心始终跟踪并发 API 请求的加载状态,无需配置。不需要使用时,不调用相关 API 即可。
161
+
162
+ ```ts
163
+ // 查询当前是否处于加载中
164
+ forge.isLoading() // boolean
165
+
166
+ // 订阅状态变更
167
+ const unsub = forge.onLoadingChange((event) => {
168
+ console.log(event.loading) // true / false
169
+ console.log(event.count) // 当前并发请求数
170
+ })
171
+
172
+ // 取消订阅
173
+ unsub()
174
+ ```
175
+
176
+ > 加载状态始终跟踪,用户不使用则不订阅即可。Vue/React 包可通过 `onLoadingChange` 订阅状态变更驱动组件显隐。
177
+
26
178
  ## 文档
27
179
 
28
180
  - 仓库主页: https://github.com/xyj2156/route-forge
package/dist/codegen.cjs CHANGED
@@ -84,21 +84,34 @@ async function fetchLevel(endpoint, level) {
84
84
  const data = await resp.json();
85
85
  return { routes: data.routes ?? {} };
86
86
  }
87
- function generateRouteTypes(routes) {
88
- const entries = Object.entries(routes).map(([name, meta]) => {
89
- const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(" ");
90
- const method = meta.methods?.find((m) => m.toUpperCase() !== "HEAD") ?? "GET";
91
- const methodUpper = method.toUpperCase();
92
- const bodyField = BODY_METHODS.has(methodUpper) ? "\n body: unknown;" : "";
93
- return ` ${JSON.stringify(name)}: {
94
- method: ${JSON.stringify(methodUpper)};
95
- params: { ${params} };${bodyField}
96
- response: unknown;
87
+ function generateRouteTypes(routesByLevel) {
88
+ const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {
89
+ const routeEntries = Object.entries(routes).map(([name, meta]) => {
90
+ const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(" ");
91
+ const method = meta.methods?.find((m) => m.toUpperCase() !== "HEAD") ?? "GET";
92
+ const methodUpper = method.toUpperCase();
93
+ const bodyField = BODY_METHODS.has(methodUpper) ? "\n body: unknown;" : "";
94
+ return ` ${JSON.stringify(name)}: {
95
+ method: ${JSON.stringify(methodUpper)};
96
+ params: { ${params} };${bodyField}
97
+ response: unknown;
98
+ };`;
99
+ });
100
+ return ` ${JSON.stringify(level)}: {
101
+ ${routeEntries.join("\n")}
97
102
  };`;
98
103
  });
99
104
  return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.
100
- export interface ForgeRoutes {
101
- ${entries.join("\n")}
105
+
106
+ /**
107
+ * \u4E8C\u7EA7\u8DEF\u7531\u7C7B\u578B\u6620\u5C04\uFF1Alevel \u2192 routeName \u2192 routeMeta
108
+ * \u53EF\u901A\u8FC7 module augmentation \u589E\u5F3A\uFF1A
109
+ * declare module '@route-forge/core' {
110
+ * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }
111
+ * }
112
+ */
113
+ export interface ForgeRouteMap {
114
+ ${levelEntries.join("\n")}
102
115
  }
103
116
  `;
104
117
  }
@@ -122,28 +135,29 @@ async function main(argv = process.argv.slice(2)) {
122
135
  const levelFetches = await Promise.allSettled(
123
136
  levels.map((lvl) => fetchLevel(opts.endpoint, lvl))
124
137
  );
125
- const merged = {};
138
+ const routesByLevel = {};
126
139
  let failedLevels = 0;
127
140
  levelFetches.forEach((res, idx) => {
128
141
  if (res.status === "fulfilled") {
129
- Object.assign(merged, res.value.routes);
142
+ routesByLevel[levels[idx]] = res.value.routes;
130
143
  } else {
131
144
  failedLevels++;
132
145
  console.warn(`[route-forge/codegen] failed to fetch level "${levels[idx]}": ${res.reason.message}`);
133
146
  }
134
147
  });
135
- if (Object.keys(merged).length === 0) {
148
+ const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);
149
+ if (totalRoutes === 0) {
136
150
  console.error("[route-forge/codegen] no routes collected from any level");
137
151
  process.exit(1);
138
152
  }
139
- const dts = generateRouteTypes(merged);
153
+ const dts = generateRouteTypes(routesByLevel);
140
154
  const fs = await import('fs/promises');
141
155
  const path = await import('path');
142
156
  const outPath = path.resolve(opts.out);
143
157
  const dir = path.dirname(outPath);
144
158
  await fs.mkdir(dir, { recursive: true });
145
159
  await fs.writeFile(outPath, dts, "utf8");
146
- console.log(`[route-forge/codegen] written ${Object.keys(merged).length} routes to ${outPath}`);
160
+ console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);
147
161
  if (failedLevels > 0) {
148
162
  console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);
149
163
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.cjs","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n */\nexport function generateRouteTypes(routes: Record<string, RouteMeta>): string {\n const entries = Object.entries(routes).map(([name, meta]) => {\n const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\n method: ${JSON.stringify(methodUpper)};\n params: { ${params} };${bodyField}\n response: unknown;\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\nexport interface ForgeRoutes {\n${entries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const merged: Record<string, RouteMeta> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n Object.assign(merged, res.value.routes);\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n if (Object.keys(merged).length === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(merged);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${Object.keys(merged).length} routes to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}
1
+ {"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.cjs","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }\n */\nexport function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string {\n const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {\n const routeEntries = Object.entries(routes).map(([name, meta]) => {\n const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\\n method: ${JSON.stringify(methodUpper)};\\n params: { ${params} };${bodyField}\\n response: unknown;\\n };`;\n });\n return ` ${JSON.stringify(level)}: {\\n${routeEntries.join('\\n')}\\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\n\n/**\n * 二级路由类型映射:level → routeName → routeMeta\n * 可通过 module augmentation 增强:\n * declare module '@route-forge/core' {\n * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }\n * }\n */\nexport interface ForgeRouteMap {\n${levelEntries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const routesByLevel: Record<string, Record<string, RouteMeta>> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n routesByLevel[levels[idx]!] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);\n if (totalRoutes === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(routesByLevel);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { R as RouteMeta } from './types-DpbxJ6Yk.cjs';
2
+ import { R as RouteMeta } from './types-yzY9-FWd.cjs';
3
3
 
4
4
  /**
5
5
  * @route-forge/core codegen CLI
@@ -37,8 +37,9 @@ declare function fetchLevel(endpoint: string, level: string): Promise<{
37
37
  }>;
38
38
  /**
39
39
  * 生成 d.ts 内容
40
+ * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }
40
41
  */
41
- declare function generateRouteTypes(routes: Record<string, RouteMeta>): string;
42
+ declare function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string;
42
43
  /**
43
44
  * CLI 主入口
44
45
  */
package/dist/codegen.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { R as RouteMeta } from './types-DpbxJ6Yk.js';
2
+ import { R as RouteMeta } from './types-yzY9-FWd.js';
3
3
 
4
4
  /**
5
5
  * @route-forge/core codegen CLI
@@ -37,8 +37,9 @@ declare function fetchLevel(endpoint: string, level: string): Promise<{
37
37
  }>;
38
38
  /**
39
39
  * 生成 d.ts 内容
40
+ * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }
40
41
  */
41
- declare function generateRouteTypes(routes: Record<string, RouteMeta>): string;
42
+ declare function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string;
42
43
  /**
43
44
  * CLI 主入口
44
45
  */
package/dist/codegen.js CHANGED
@@ -82,21 +82,34 @@ async function fetchLevel(endpoint, level) {
82
82
  const data = await resp.json();
83
83
  return { routes: data.routes ?? {} };
84
84
  }
85
- function generateRouteTypes(routes) {
86
- const entries = Object.entries(routes).map(([name, meta]) => {
87
- const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(" ");
88
- const method = meta.methods?.find((m) => m.toUpperCase() !== "HEAD") ?? "GET";
89
- const methodUpper = method.toUpperCase();
90
- const bodyField = BODY_METHODS.has(methodUpper) ? "\n body: unknown;" : "";
91
- return ` ${JSON.stringify(name)}: {
92
- method: ${JSON.stringify(methodUpper)};
93
- params: { ${params} };${bodyField}
94
- response: unknown;
85
+ function generateRouteTypes(routesByLevel) {
86
+ const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {
87
+ const routeEntries = Object.entries(routes).map(([name, meta]) => {
88
+ const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(" ");
89
+ const method = meta.methods?.find((m) => m.toUpperCase() !== "HEAD") ?? "GET";
90
+ const methodUpper = method.toUpperCase();
91
+ const bodyField = BODY_METHODS.has(methodUpper) ? "\n body: unknown;" : "";
92
+ return ` ${JSON.stringify(name)}: {
93
+ method: ${JSON.stringify(methodUpper)};
94
+ params: { ${params} };${bodyField}
95
+ response: unknown;
96
+ };`;
97
+ });
98
+ return ` ${JSON.stringify(level)}: {
99
+ ${routeEntries.join("\n")}
95
100
  };`;
96
101
  });
97
102
  return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.
98
- export interface ForgeRoutes {
99
- ${entries.join("\n")}
103
+
104
+ /**
105
+ * \u4E8C\u7EA7\u8DEF\u7531\u7C7B\u578B\u6620\u5C04\uFF1Alevel \u2192 routeName \u2192 routeMeta
106
+ * \u53EF\u901A\u8FC7 module augmentation \u589E\u5F3A\uFF1A
107
+ * declare module '@route-forge/core' {
108
+ * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }
109
+ * }
110
+ */
111
+ export interface ForgeRouteMap {
112
+ ${levelEntries.join("\n")}
100
113
  }
101
114
  `;
102
115
  }
@@ -120,28 +133,29 @@ async function main(argv = process.argv.slice(2)) {
120
133
  const levelFetches = await Promise.allSettled(
121
134
  levels.map((lvl) => fetchLevel(opts.endpoint, lvl))
122
135
  );
123
- const merged = {};
136
+ const routesByLevel = {};
124
137
  let failedLevels = 0;
125
138
  levelFetches.forEach((res, idx) => {
126
139
  if (res.status === "fulfilled") {
127
- Object.assign(merged, res.value.routes);
140
+ routesByLevel[levels[idx]] = res.value.routes;
128
141
  } else {
129
142
  failedLevels++;
130
143
  console.warn(`[route-forge/codegen] failed to fetch level "${levels[idx]}": ${res.reason.message}`);
131
144
  }
132
145
  });
133
- if (Object.keys(merged).length === 0) {
146
+ const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);
147
+ if (totalRoutes === 0) {
134
148
  console.error("[route-forge/codegen] no routes collected from any level");
135
149
  process.exit(1);
136
150
  }
137
- const dts = generateRouteTypes(merged);
151
+ const dts = generateRouteTypes(routesByLevel);
138
152
  const fs = await import('fs/promises');
139
153
  const path = await import('path');
140
154
  const outPath = path.resolve(opts.out);
141
155
  const dir = path.dirname(outPath);
142
156
  await fs.mkdir(dir, { recursive: true });
143
157
  await fs.writeFile(outPath, dts, "utf8");
144
- console.log(`[route-forge/codegen] written ${Object.keys(merged).length} routes to ${outPath}`);
158
+ console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);
145
159
  if (failedLevels > 0) {
146
160
  console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);
147
161
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.js","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n */\nexport function generateRouteTypes(routes: Record<string, RouteMeta>): string {\n const entries = Object.entries(routes).map(([name, meta]) => {\n const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\n method: ${JSON.stringify(methodUpper)};\n params: { ${params} };${bodyField}\n response: unknown;\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\nexport interface ForgeRoutes {\n${entries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const merged: Record<string, RouteMeta> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n Object.assign(merged, res.value.routes);\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n if (Object.keys(merged).length === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(merged);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${Object.keys(merged).length} routes to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}
1
+ {"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.js","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }\n */\nexport function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string {\n const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {\n const routeEntries = Object.entries(routes).map(([name, meta]) => {\n const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\\n method: ${JSON.stringify(methodUpper)};\\n params: { ${params} };${bodyField}\\n response: unknown;\\n };`;\n });\n return ` ${JSON.stringify(level)}: {\\n${routeEntries.join('\\n')}\\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\n\n/**\n * 二级路由类型映射:level → routeName → routeMeta\n * 可通过 module augmentation 增强:\n * declare module '@route-forge/core' {\n * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }\n * }\n */\nexport interface ForgeRouteMap {\n${levelEntries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const routesByLevel: Record<string, Record<string, RouteMeta>> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n routesByLevel[levels[idx]!] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);\n if (totalRoutes === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(routesByLevel);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}