@route-forge/core 2.2.0 → 3.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
@@ -44,8 +44,9 @@ const url2 = forge.url('public', 'login.show') // url() is a semantic alias o
44
44
 
45
45
  // Route existence / metadata inspection
46
46
  forge.hasRoute('admin', 'users.show') // true / false
47
- forge.getRoutes('admin') // snapshot of one level (deep copy)
47
+ forge.getRoutes('admin') // snapshot of one level (deep copy; throws UnknownLevelError if the level is not declared)
48
48
  forge.getRoutes() // all loaded levels, grouped by level
49
+ forge.getLevels() // declared levels (incl. unassigned); [] until ready on network bootstrap
49
50
 
50
51
  // Level loading & cache management
51
52
  await forge.load('admin') // load a level (concurrent calls deduplicated)
@@ -97,17 +98,18 @@ The three loading phases and how to track them:
97
98
 
98
99
  | Option | Type | Default | Description |
99
100
  |--------|------|---------|-------------|
100
- | `endpoint` | `string` | | summary/manifest endpoint path (network source). Optional: at least one of `endpoint`, `summary`, or an embedded `window.__ROUTE_FORGE__` must exist, otherwise `createRouteForge` throws `TypeError` |
101
+ | `endpoint` | `string` | `/_forge/routes` | summary/manifest endpoint path (network source). Optional: when all three sources (`endpoint` / `summary` / embedded `window.__ROUTE_FORGE__`) are absent, network bootstrap falls back to the backend-agreed default summary endpoint `/_forge/routes` (no throw) |
101
102
  | `summary` | `SummaryResponse` | — | Provide the summary directly (tests / non-global bootstrap), skipping the summary HTTP request. Takes lower priority than an embedded `window.__ROUTE_FORGE__` |
102
103
  | `levels` | `string[]` | auto-discovered | discovered from the summary when omitted; when given, intersected with the backend summary (the frontend cannot declare levels the backend doesn't know) |
103
104
  | `eager` | `string[]` | backend `load:'eager'` levels | levels preloaded after discovery; union with the backend marks when given |
104
105
  | `adapter` | `'auto' \| 'axios' \| 'builtin' \| Fetcher` | `'auto'` | see "Adapters" below |
105
106
  | `cache.ttl` | `number` (seconds) | `3600` | frontend fallback TTL; the backend's global `config.cache_ttl` is the ceiling — the effective TTL is `min(backend, frontend)` (frontend may shorten but never extend; `0` = forever; `config.cache_ttl: null` = don't cache) |
106
107
  | `cache.storage` | `'memory' \| 'sessionStorage' \| 'localStorage'` | `'memory'` | cache backend; storage modes keep an in-memory mirror and invalidate cross-tab writes via `storage` events |
107
- | `interceptors.request` | array | none | declarative request interceptors: plain function (treated as `onFulfilled`) or `[onFulfilled?, onRejected?]` tuple |
108
- | `interceptors.response` | array | none | declarative response interceptors, same shapes |
108
+ | `interceptors.request` | `Fn \| [resolve?, reject?] \| { resolve?, reject? }` | none | declarative **single** request interceptor: a function ( `resolve`), a `[resolve?, reject?]` tuple, or a `{ resolve?, reject? }` object; register multiple via runtime `use()` |
109
+ | `interceptors.response` | `Fn \| [resolve?, reject?] \| { resolve?, reject? }` | none | declarative response interceptor, same shapes (first `resolve` receives `ResponseData`) |
109
110
  | `timeout` | `number` (ms) | `30000` | global timeout; a single call can override it via `params.timeout` |
110
111
  | `baseURL` | `string` | `''` | base prepended to every generated URL |
112
+ | `warnings` | `boolean` | `true` | silences non-fatal `console.warn` output when `false` (`console.error` is never silenced); vue/react render-degrade warnings follow the same switch via `forge.warnings` |
111
113
  | `strict` | `boolean` | — | **Deprecated, ignored.** Frontend validation is always on (unknown level → `UnknownLevelError`, unknown route → `UnknownRouteError`, missing required param → `MissingRouteParamError`); silently ignoring typos hides bugs. The backend's `strict_mode` is a manifest-generation concern and unrelated to the frontend |
112
114
 
113
115
  ## Embedded bootstrap (optional hydration)
@@ -116,7 +118,7 @@ Summary discovery reads from one source in this cascade: **embedded `window.__RO
116
118
 
117
119
  For Laravel/Blade server-rendered first pages, the backend `@forgeSummary` directive inlines the summary as a one-shot, non-enumerable `window.__ROUTE_FORGE__` accessor that self-deletes on first read. When core finds it, it **skips the summary HTTP round-trip and completes discovery synchronously** — `route()` / `ready()` work immediately after `createRouteForge()` returns, eliminating the "routes not ready" first-paint flash. Level route tables are still lazy-loaded per level over HTTP (protected routes never enter the public HTML). A module-level memo lets a second instance (React StrictMode / a second provider) reuse the summary after the global is gone.
118
120
 
119
- If no embed exists (standalone SPA, Vite dev), core falls back to the network summary automatically. `createRouteForge({ summary })` is the explicit, test/SSR-friendly entry.
121
+ If no embed exists (standalone SPA, Vite dev), core falls back to the network summary automatically. `createRouteForge({ summary })` is the explicit, test/SSR-friendly entry. When the summary comes entirely from the embed, the `options` argument itself may be omitted — call `createRouteForge()` bare.
120
122
 
121
123
  > Honest scope: the one-shot self-delete only shrinks the summary's runtime footprint on `window`; the data is still present in the HTML source. This is a latency/flash optimization, **not** an XSS- or network-egavesdropping-proof boundary.
122
124
 
@@ -191,7 +193,7 @@ bound.useRoutePrefix('posts') // returns a NEW BoundForge with the new p
191
193
  // Global methods still work: bound.isLoading() / bound.onLoadingChange()
192
194
  ```
193
195
 
194
- > Every `use()` call returns a fresh `BoundForge` (not cached); `forge.use()` without arguments returns the forge itself.
196
+ > Every `use()` call returns a fresh `BoundForge` (not cached); `forge.use()` without arguments returns the forge itself. A `prefix` with trailing separators is normalized before joining (`use('admin', 'users.')` + `'show'` resolves to `admin.users.show`, not `admin.users..show`); the exposed `prefix` keeps its original value.
195
197
 
196
198
  ## Request cancellation
197
199
 
@@ -213,27 +215,28 @@ The interceptor API matches axios (`use` / `eject` / `clear`); request intercept
213
215
  const forge = createRouteForge({
214
216
  endpoint: '/_forge/routes',
215
217
  interceptors: {
216
- request: [
217
- (config) => {
218
- const token = authStore.getToken()
219
- if (token) config.headers.Authorization = `Bearer ${token}`
220
- return config // must return a RequestConfig object, otherwise RF_FE_006 is thrown
221
- },
222
- ],
223
- response: [
224
- (resp) => resp.data, // unwrap: api() resolves with business data directly
225
- [undefined, (err) => { // tuple form: [onFulfilled?, onRejected?]
218
+ // Declarative config describes ONE interceptor: a function (→ resolve),
219
+ // a [resolve?, reject?] tuple, or a { resolve?, reject? } object.
220
+ // To register several, call forge.interceptors.*.use() at runtime.
221
+ request: (config) => {
222
+ const token = authStore.getToken()
223
+ if (token) config.headers.Authorization = `Bearer ${token}`
224
+ return config // must return a RequestConfig object, otherwise RF_FE_006 is thrown
225
+ },
226
+ response: {
227
+ resolve: (resp) => resp.data, // unwrap: api() resolves with business data directly
228
+ reject: (err) => { // { resolve, reject } object form
226
229
  if (err instanceof HTTPError && err.context?.status === 401) {
227
230
  authStore.logout()
228
231
  window.location.href = '/login'
229
232
  }
230
233
  return Promise.reject(err)
231
- }],
232
- ],
234
+ },
235
+ },
233
236
  },
234
237
  })
235
238
 
236
- // Runtime registration / removal / clearing
239
+ // Runtime registration / removal / clearing — call use() repeatedly to register multiple interceptors
237
240
  const id = forge.interceptors.request.use((config) => { /* ... */ return config })
238
241
  forge.interceptors.request.eject(id)
239
242
  forge.interceptors.request.clear()
@@ -251,7 +254,7 @@ function logout() {
251
254
  }
252
255
  ```
253
256
 
254
- > With `adapter: 'auto'` reusing host axios, interceptors already registered on the host axios instance run first; Route Forge interceptors run after them.
257
+ > With `adapter: 'auto'` reusing host axios, the ordering differs per chain: Route Forge request interceptors run **before** host axios request interceptors (core finishes the forge request chain, then calls `axios.request()`), while host response interceptors run inside axios, **before** the Route Forge response chain.
255
258
  > Metadata fetching (summary / level tables) goes through the adapter's raw channel and never passes the business interceptor chains, so unwrapping interceptors can't corrupt it.
256
259
 
257
260
  ## Loading-state tracking
@@ -281,7 +284,7 @@ Two ways to define it:
281
284
  npx route-forge-codegen \
282
285
  --endpoint http://localhost/_forge/routes \
283
286
  --out src/types/forge-routes.d.ts \
284
- [--levels public,admin] [--responseTypes path/to/map.json]
287
+ [--levels public,admin]
285
288
  ```
286
289
 
287
290
  ```ts
@@ -348,13 +351,14 @@ All errors extend `ForgeError` and carry a stable `code` field (the `ForgeErrorC
348
351
  |-------------|------|---------|
349
352
  | `UnknownRouteError` | `RF_FE_001` | route name not found in the loaded level |
350
353
  | `UnknownLevelError` | `RF_FE_002` | level not declared (frontend validation is always on) |
351
- | `MissingRouteParamError` | `RF_FE_003` | required path parameter missing (no backend default); also thrown when a path parameter receives an object |
354
+ | `MissingRouteParamError` | `RF_FE_003` | required path parameter missing (no backend default) |
355
+ | `InvalidPathParamError` | `RF_FE_003` | path parameter received a non-primitive value (object/array) |
352
356
  | `AdapterNotFoundError` | `RF_FE_005` | `adapter: 'axios'` but no usable host axios |
353
357
  | `InvalidInterceptorReturnError` | `RF_FE_006` | a request interceptor did not return a RequestConfig object |
354
358
  | `NetworkError` | `RF_FE_007` | network-layer failure (DNS, refused connection…); `cause` keeps the original error |
355
- | `HTTPError` | `RF_FE_008` | non-2xx HTTP response; `context.status` holds the status code |
359
+ | `HTTPError` | `RF_FE_008` | non-2xx HTTP response; `context.status` holds the status code; `response` carries the full ResponseData (e.g. Laravel 422 validation errors via `err.response.data.errors`) |
356
360
  | `RequestAbortedError` | `RF_FE_009` | request cancelled via `abort()` / AbortSignal |
357
- | `ForgeError` (guard) | `RF_FE_010` | `route()` / `hasRoute()` called before auto-discovery completed |
361
+ | `DiscoveryNotReadyError` | `RF_FE_010` | `route()` / `hasRoute()` called before auto-discovery completed (sync guard; `api()` awaits discovery internally) |
358
362
 
359
363
  Error object shape:
360
364
 
@@ -365,9 +369,13 @@ Error object shape:
365
369
  level?: string, // related level
366
370
  context?: Record<string, unknown>, // extra context (HTTP status, url, method…)
367
371
  cause?: unknown, // original underlying error
372
+ response?: ResponseData, // HTTPError only: full response (status/headers/data/config),
373
+ // inspectable step by step in the onRejected chain and final catch
368
374
  }
369
375
  ```
370
376
 
377
+ > Typical use — Laravel 422 field-level error echo: `err.response.data.errors` is directly available in both the response interceptor and the final catch, no extra request needed.
378
+
371
379
  ## Utility exports
372
380
 
373
381
  Besides `createRouteForge`, the core package exports these building blocks for advanced scenarios:
package/README_zh.md CHANGED
@@ -44,8 +44,9 @@ const url2 = forge.url('public', 'login.show') // url() 是 route() 的语义
44
44
 
45
45
  // 检查路由是否存在 / 获取路由元信息
46
46
  forge.hasRoute('admin', 'users.show') // true / false
47
- forge.getRoutes('admin') // 指定层级的路由表快照(深拷贝)
47
+ forge.getRoutes('admin') // 指定层级的路由表快照(深拷贝;层级未声明抛 UnknownLevelError)
48
48
  forge.getRoutes() // 全部已加载层级(按 level 分组)
49
+ forge.getLevels() // 已声明层级(含 unassigned);网络引导就绪前为 []
49
50
 
50
51
  // 层级加载与缓存管理
51
52
  await forge.load('admin') // 加载层级(并发自动去重)
@@ -98,17 +99,18 @@ await forge.ready()
98
99
 
99
100
  | 选项 | 类型 | 默认值 | 说明 |
100
101
  |------|------|--------|------|
101
- | `endpoint` | `string` | | 摘要/manifest 端点路径(网络来源)。可选:`endpoint`、`summary`、页面内嵌 `window.__ROUTE_FORGE__` 三者必有一,否则 `createRouteForge` 抛 `TypeError` |
102
+ | `endpoint` | `string` | `/_forge/routes` | 摘要/manifest 端点路径(网络来源)。可省略:`endpoint` / `summary` / 页面内嵌 `window.__ROUTE_FORGE__` 三源皆无时,网络引导回退到与后端约定的默认摘要端点 `/_forge/routes`(不再抛错) |
102
103
  | `summary` | `SummaryResponse` | — | 直接提供摘要数据(测试 / 非全局引导),跳过摘要 HTTP;优先级低于页面内嵌 `window.__ROUTE_FORGE__` |
103
104
  | `levels` | `string[]` | 自动发现 | 不传时从摘要自动发现;显式传入时取与后端摘要的**交集**(前端不能声明后端不存在的层级) |
104
105
  | `eager` | `string[]` | 后端 `load:'eager'` 层级 | 预加载层级;显式传入时与后端标记取**并集** |
105
106
  | `adapter` | `'auto' \| 'axios' \| 'builtin' \| Fetcher` | `'auto'` | 见下方「Adapter 适配」 |
106
107
  | `cache.ttl` | `number`(秒) | `3600` | 前端兜底 TTL;后端全局 `config.cache_ttl` 为上限,实际取 `min(后端, 前端)`(前端只能缩短不能延长,`0` 永久,`config.cache_ttl: null` 不缓存) |
107
108
  | `cache.storage` | `'memory' \| 'sessionStorage' \| 'localStorage'` | `'memory'` | 缓存介质;storage 模式维护内存镜像并通过 `storage` 事件感知跨 tab 失效 |
108
- | `interceptors.request` | 数组 | 无 | 声明式请求拦截器:单函数(视为 `onFulfilled`)或 `[onFulfilled?, onRejected?]` 元组 |
109
- | `interceptors.response` | 数组 | 无 | 声明式响应拦截器,形式同上 |
109
+ | `interceptors.request` | `Fn \| [resolve?, reject?] \| { resolve?, reject? }` | 无 | 声明式**单个**请求拦截器:函数(→ `resolve`)、`[resolve?, reject?]` 元组或 `{ resolve?, reject? }` 对象;需要多个改用运行时 `use()` |
110
+ | `interceptors.response` | `Fn \| [resolve?, reject?] \| { resolve?, reject? }` | 无 | 声明式响应拦截器,形式同上(首段 `resolve` 接收 `ResponseData`) |
110
111
  | `timeout` | `number`(毫秒) | `30000` | 全局超时;单次请求可用 `params.timeout` 覆盖 |
111
112
  | `baseURL` | `string` | `''` | 拼接在所有生成 URL 之前的基础地址 |
113
+ | `warnings` | `boolean` | `true` | 设为 `false` 时静音非致命 `console.warn`(`console.error` 永不静音);vue/react 渲染期降级警告经 `forge.warnings` 遵循同一开关 |
112
114
  | `strict` | `boolean` | — | **已废弃,传入无效**。前端校验始终开启(层级未声明抛 `UnknownLevelError`、路由名不存在抛 `UnknownRouteError`、必填参数缺失抛 `MissingRouteParamError`),静默忽略会掩盖拼写错误。后端的 `strict_mode` 是 manifest 生成侧语义,与前端无关 |
113
115
 
114
116
  ## 内嵌引导(可选 hydration)
@@ -117,7 +119,7 @@ await forge.ready()
117
119
 
118
120
  对 Laravel/Blade 服务端直出的首页,后端 `@forgeSummary` 指令把摘要内联为一个一次性、不可枚举、读后自删的 `window.__ROUTE_FORGE__` 访问器。core 命中它时**跳过摘要 HTTP 往返、同步完成 discovery**——`createRouteForge()` 返回后 `route()` / `ready()` 立即可用,消除首屏"路由未就绪"闪烁。层级路由表**仍按 level 走 HTTP 懒加载**(受保护路由不进公开 HTML)。core 的 module 级 memo 让第二个实例(React StrictMode / 第二个 Provider)在全局自删后仍能复用摘要。
119
121
 
120
- 若页面无内嵌(SPA 独立部署 / Vite dev),core 自动回落网络摘要。`createRouteForge({ summary })` 是显式、便于测试/SSR 的入口。
122
+ 若页面无内嵌(SPA 独立部署 / Vite dev),core 自动回落网络摘要。`createRouteForge({ summary })` 是显式、便于测试/SSR 的入口。当摘要完全来自内嵌时,`options` 参数本身也可省略——直接 `createRouteForge()` 无参调用。
121
123
 
122
124
  > 诚实边界:一次性自删只缩小摘要在 `window` 上的运行时驻留面,数据仍随 HTML 源码可见;这是延迟/闪烁优化,**不是**抗 XSS 或抗网络窃取的硬边界。
123
125
 
@@ -191,7 +193,7 @@ bound.useRoutePrefix('posts') // 以新前缀返回新的 BoundForge(
191
193
  // 全局方法照常可用:bound.isLoading() / bound.onLoadingChange()
192
194
  ```
193
195
 
194
- > `use()` 每次调用都返回新的 `BoundForge`(不缓存);`forge.use()` 不传参时返回 forge 自身。
196
+ > `use()` 每次调用都返回新的 `BoundForge`(不缓存);`forge.use()` 不传参时返回 forge 自身。prefix 尾部的 separator(含连续多个)会先归一化再拼接(`use('admin', 'users.')` + `'show'` 解析为 `admin.users.show` 而非 `admin.users..show`);`bound.prefix` 暴露值保持原样。
195
197
 
196
198
  ## 请求取消
197
199
 
@@ -213,27 +215,27 @@ req.abort() // 请求被中止,Promise reject 为 RequestAbortedError(RF_F
213
215
  const forge = createRouteForge({
214
216
  endpoint: '/_forge/routes',
215
217
  interceptors: {
216
- request: [
217
- (config) => {
218
- const token = authStore.getToken()
219
- if (token) config.headers.Authorization = `Bearer ${token}`
220
- return config // 必须返回 RequestConfig 对象,否则抛 RF_FE_006
221
- },
222
- ],
223
- response: [
224
- (resp) => resp.data, // 统一解包:api() 直接 resolve 业务数据
225
- [undefined, (err) => { // 元组形式:[onFulfilled?, onRejected?]
218
+ // 声明式配置每个键只描述「一个」拦截器:函数(→ resolve)、[resolve?, reject?] 元组、或 { resolve?, reject? } 对象。
219
+ // 需要注册多个?改用运行时 forge.interceptors.*.use()
220
+ request: (config) => {
221
+ const token = authStore.getToken()
222
+ if (token) config.headers.Authorization = `Bearer ${token}`
223
+ return config // 必须返回 RequestConfig 对象,否则抛 RF_FE_006
224
+ },
225
+ response: {
226
+ resolve: (resp) => resp.data, // 统一解包:api() 直接 resolve 业务数据
227
+ reject: (err) => { // { resolve, reject } 对象形式
226
228
  if (err instanceof HTTPError && err.context?.status === 401) {
227
229
  authStore.logout()
228
230
  window.location.href = '/login'
229
231
  }
230
232
  return Promise.reject(err)
231
- }],
232
- ],
233
+ },
234
+ },
233
235
  },
234
236
  })
235
237
 
236
- // 运行时动态注册 / 移除 / 清空
238
+ // 运行时动态注册 / 移除 / 清空 —— 可多次 use() 注册多个拦截器
237
239
  const id = forge.interceptors.request.use((config) => { /* ... */ return config })
238
240
  forge.interceptors.request.eject(id)
239
241
  forge.interceptors.request.clear()
@@ -251,7 +253,7 @@ function logout() {
251
253
  }
252
254
  ```
253
255
 
254
- > `adapter: 'auto'` 复用宿主 axios 时,宿主已注册的 axios 拦截器会先执行,Route Forge 拦截器在其后执行。
256
+ > `adapter: 'auto'` 复用宿主 axios 时,执行顺序按链路方向区分:Route Forge 请求拦截链先执行(core 先跑完 forge 请求链,再调用 `axios.request()`),宿主请求拦截器在其后;响应链则宿主响应拦截器在 axios 内部先行,Route Forge 响应拦截链在其后执行。
255
257
  > 元信息拉取(摘要 / 层级路由表)走 adapter 原始通道,不经过业务拦截链,避免被解包类拦截器干扰。
256
258
 
257
259
  ## 加载状态跟踪
@@ -281,7 +283,7 @@ Vue / React 包可基于 `onLoadingChange` 驱动组件级加载指示。
281
283
  npx route-forge-codegen \
282
284
  --endpoint http://localhost/_forge/routes \
283
285
  --out src/types/forge-routes.d.ts \
284
- [--levels public,admin] [--responseTypes path/to/map.json]
286
+ [--levels public,admin]
285
287
  ```
286
288
 
287
289
  ```ts
@@ -348,13 +350,14 @@ const data = await forge.api('unassigned', 'some.route')
348
350
  |--------|------|----------|
349
351
  | `UnknownRouteError` | `RF_FE_001` | 路由名不存在于已加载层级中 |
350
352
  | `UnknownLevelError` | `RF_FE_002` | 层级未在 levels 声明(前端校验始终开启) |
351
- | `MissingRouteParamError` | `RF_FE_003` | 必填路径参数缺失(无后端默认值);路径参数传入对象同样报此码 |
353
+ | `MissingRouteParamError` | `RF_FE_003` | 必填路径参数缺失(无后端默认值) |
354
+ | `InvalidPathParamError` | `RF_FE_003` | 路径参数收到非原始值(对象/数组) |
352
355
  | `AdapterNotFoundError` | `RF_FE_005` | `adapter: 'axios'` 但宿主未安装 / 无有效 axios |
353
356
  | `InvalidInterceptorReturnError` | `RF_FE_006` | 请求拦截器未返回 RequestConfig 对象 |
354
357
  | `NetworkError` | `RF_FE_007` | 网络层失败(DNS、连接被拒等),`cause` 保留原始错误 |
355
- | `HTTPError` | `RF_FE_008` | HTTP 非 2xx,`context.status` 为状态码 |
358
+ | `HTTPError` | `RF_FE_008` | HTTP 非 2xx,`context.status` 为状态码;`response` 携带完整 ResponseData(如 Laravel 422 校验错误 `err.response.data.errors`) |
356
359
  | `RequestAbortedError` | `RF_FE_009` | 请求被 `abort()` / AbortSignal 取消 |
357
- | `ForgeError`(守卫) | `RF_FE_010` | auto-discovery 未完成时调用 `route()` / `hasRoute()` |
360
+ | `DiscoveryNotReadyError` | `RF_FE_010` | auto-discovery 未完成时调用 `route()` / `hasRoute()`(同步守卫;`api()` 内部自动 await discovery) |
358
361
 
359
362
  错误对象结构:
360
363
 
@@ -365,9 +368,13 @@ const data = await forge.api('unassigned', 'some.route')
365
368
  level?: string, // 关联层级
366
369
  context?: Record<string, unknown>, // 附加上下文(如 HTTP 状态码、url、method)
367
370
  cause?: unknown, // 原始底层错误
371
+ response?: ResponseData, // 仅 HTTPError:完整响应(status/headers/data/config),
372
+ // 供响应拦截器 onRejected 与最终 catch 逐段检查响应体
368
373
  }
369
374
  ```
370
375
 
376
+ > 典型场景——Laravel 422 字段级错误回显:`err.response.data.errors` 直接可用,响应拦截器与最终 catch 都能读到,无需再发请求。
377
+
371
378
  ## 工具导出
372
379
 
373
380
  除 `createRouteForge` 外,core 包还导出以下工具件,供高级场景按需使用:
package/dist/codegen.cjs CHANGED
@@ -26,10 +26,11 @@ function parseArgs(argv) {
26
26
  opts.out = next();
27
27
  } else if (arg?.startsWith("--out=")) {
28
28
  opts.out = arg.slice("--out=".length);
29
- } else if (arg === "--responseTypes") {
30
- opts.responseTypes = next();
31
- } else if (arg?.startsWith("--responseTypes=")) {
32
- opts.responseTypes = arg.slice("--responseTypes=".length);
29
+ } else if (arg === "--responseTypes" || arg?.startsWith("--responseTypes=")) {
30
+ console.error(
31
+ '[route-forge/codegen] --responseTypes has been removed (it was never implemented). Edit the "response" field in the generated d.ts directly, or use module augmentation on ForgeRouteMap.'
32
+ );
33
+ process.exit(1);
33
34
  } else if (arg === "--help" || arg === "-h") {
34
35
  printHelp();
35
36
  process.exit(0);
@@ -48,8 +49,7 @@ function parseArgs(argv) {
48
49
  return {
49
50
  endpoint: opts.endpoint,
50
51
  levels: opts.levels ?? [],
51
- out: opts.out,
52
- responseTypes: opts.responseTypes
52
+ out: opts.out
53
53
  };
54
54
  }
55
55
  function printHelp() {
@@ -57,13 +57,12 @@ function printHelp() {
57
57
  route-forge codegen - generate TS route types from backend summary endpoint
58
58
 
59
59
  Usage:
60
- npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]
60
+ npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c]
61
61
 
62
62
  Options:
63
63
  --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)
64
64
  --levels a,b,c Optional: explicit level list (skip auto-discovery)
65
65
  --out PATH Output .d.ts file path
66
- --responseTypes PATH Optional: JSON file mapping route names to response types
67
66
  -h, --help Show this help
68
67
  `);
69
68
  }
@@ -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 * 拉取摘要端点(仅用于自动发现层级名;unassigned 现是 levels 中的真实层级,与其余层级一样按 HTTP 懒加载拉取)\n */\nexport async function fetchSummary(endpoint: string): Promise<{\n levels: Record<string, unknown>;\n}> {\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 defaults = meta.parameter_defaults ?? {};\n const params = (meta.parameters ?? []).map((p) => {\n // URI 中的 {p?} 或有后端默认值的参数 → 生成可选字段,避免调用方被迫传参\n const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;\n return `${p}${optional ? '?' : ''}: string | number;`;\n }).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 // 未指定 --levels 时从摘要端点自动发现层级(unassigned 现是 levels 中的真实层级,一并纳入)\n let levels = opts.levels;\n // 摘要端点数据(用于自动发现层级)\n let summaryData: Awaited<ReturnType<typeof fetchSummary>> | null = null;\n if (levels.length === 0) {\n try {\n summaryData = await fetchSummary(opts.endpoint);\n levels = Object.keys(summaryData.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 // 所有层级(含 unassigned 真实层级)统一按 HTTP 懒加载拉取(SPEC §3.1.6)\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 const lvl = levels[idx]!;\n if (res.status === 'fulfilled') {\n routesByLevel[lvl] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${lvl}\": ${(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
+ {"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}\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\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' || arg?.startsWith('--responseTypes=')) {\n // 该参数从未实现(解析后被静默忽略);现显式报错并给出迁移方式,不再无声吞掉\n console.error(\n '[route-forge/codegen] --responseTypes has been removed (it was never implemented). ' +\n 'Edit the \"response\" field in the generated d.ts directly, or use module augmentation on ForgeRouteMap.',\n );\n process.exit(1);\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 };\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]\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 -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点(仅用于自动发现层级名;unassigned 现是 levels 中的真实层级,与其余层级一样按 HTTP 懒加载拉取)\n */\nexport async function fetchSummary(endpoint: string): Promise<{\n levels: Record<string, unknown>;\n}> {\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 defaults = meta.parameter_defaults ?? {};\n const params = (meta.parameters ?? []).map((p) => {\n // URI 中的 {p?} 或有后端默认值的参数 → 生成可选字段,避免调用方被迫传参\n const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;\n return `${p}${optional ? '?' : ''}: string | number;`;\n }).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 // 未指定 --levels 时从摘要端点自动发现层级(unassigned 现是 levels 中的真实层级,一并纳入)\n let levels = opts.levels;\n // 摘要端点数据(用于自动发现层级)\n let summaryData: Awaited<ReturnType<typeof fetchSummary>> | null = null;\n if (levels.length === 0) {\n try {\n summaryData = await fetchSummary(opts.endpoint);\n levels = Object.keys(summaryData.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 // 所有层级(含 unassigned 真实层级)统一按 HTTP 懒加载拉取(SPEC §3.1.6)\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 const lvl = levels[idx]!;\n if (res.status === 'fulfilled') {\n routesByLevel[lvl] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${lvl}\": ${(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-CDKE8rw-.cjs';
2
+ import { R as RouteMeta } from './types-B_vdcRqx.cjs';
3
3
 
4
4
  /**
5
5
  * @route-forge/core codegen CLI
@@ -16,11 +16,10 @@ interface CodegenOptions {
16
16
  endpoint: string;
17
17
  levels: string[];
18
18
  out: string;
19
- responseTypes?: string;
20
19
  }
21
20
  /**
22
21
  * 解析 argv(最小手写实现,不引入 commander/yargs)
23
- * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH
22
+ * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH
24
23
  */
25
24
  declare function parseArgs(argv: string[]): CodegenOptions;
26
25
  /**
package/dist/codegen.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { R as RouteMeta } from './types-CDKE8rw-.js';
2
+ import { R as RouteMeta } from './types-B_vdcRqx.js';
3
3
 
4
4
  /**
5
5
  * @route-forge/core codegen CLI
@@ -16,11 +16,10 @@ interface CodegenOptions {
16
16
  endpoint: string;
17
17
  levels: string[];
18
18
  out: string;
19
- responseTypes?: string;
20
19
  }
21
20
  /**
22
21
  * 解析 argv(最小手写实现,不引入 commander/yargs)
23
- * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH
22
+ * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH
24
23
  */
25
24
  declare function parseArgs(argv: string[]): CodegenOptions;
26
25
  /**
package/dist/codegen.js CHANGED
@@ -24,10 +24,11 @@ function parseArgs(argv) {
24
24
  opts.out = next();
25
25
  } else if (arg?.startsWith("--out=")) {
26
26
  opts.out = arg.slice("--out=".length);
27
- } else if (arg === "--responseTypes") {
28
- opts.responseTypes = next();
29
- } else if (arg?.startsWith("--responseTypes=")) {
30
- opts.responseTypes = arg.slice("--responseTypes=".length);
27
+ } else if (arg === "--responseTypes" || arg?.startsWith("--responseTypes=")) {
28
+ console.error(
29
+ '[route-forge/codegen] --responseTypes has been removed (it was never implemented). Edit the "response" field in the generated d.ts directly, or use module augmentation on ForgeRouteMap.'
30
+ );
31
+ process.exit(1);
31
32
  } else if (arg === "--help" || arg === "-h") {
32
33
  printHelp();
33
34
  process.exit(0);
@@ -46,8 +47,7 @@ function parseArgs(argv) {
46
47
  return {
47
48
  endpoint: opts.endpoint,
48
49
  levels: opts.levels ?? [],
49
- out: opts.out,
50
- responseTypes: opts.responseTypes
50
+ out: opts.out
51
51
  };
52
52
  }
53
53
  function printHelp() {
@@ -55,13 +55,12 @@ function printHelp() {
55
55
  route-forge codegen - generate TS route types from backend summary endpoint
56
56
 
57
57
  Usage:
58
- npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]
58
+ npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c]
59
59
 
60
60
  Options:
61
61
  --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)
62
62
  --levels a,b,c Optional: explicit level list (skip auto-discovery)
63
63
  --out PATH Output .d.ts file path
64
- --responseTypes PATH Optional: JSON file mapping route names to response types
65
64
  -h, --help Show this help
66
65
  `);
67
66
  }
@@ -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 * 拉取摘要端点(仅用于自动发现层级名;unassigned 现是 levels 中的真实层级,与其余层级一样按 HTTP 懒加载拉取)\n */\nexport async function fetchSummary(endpoint: string): Promise<{\n levels: Record<string, unknown>;\n}> {\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 defaults = meta.parameter_defaults ?? {};\n const params = (meta.parameters ?? []).map((p) => {\n // URI 中的 {p?} 或有后端默认值的参数 → 生成可选字段,避免调用方被迫传参\n const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;\n return `${p}${optional ? '?' : ''}: string | number;`;\n }).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 // 未指定 --levels 时从摘要端点自动发现层级(unassigned 现是 levels 中的真实层级,一并纳入)\n let levels = opts.levels;\n // 摘要端点数据(用于自动发现层级)\n let summaryData: Awaited<ReturnType<typeof fetchSummary>> | null = null;\n if (levels.length === 0) {\n try {\n summaryData = await fetchSummary(opts.endpoint);\n levels = Object.keys(summaryData.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 // 所有层级(含 unassigned 真实层级)统一按 HTTP 懒加载拉取(SPEC §3.1.6)\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 const lvl = levels[idx]!;\n if (res.status === 'fulfilled') {\n routesByLevel[lvl] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${lvl}\": ${(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
+ {"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}\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\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' || arg?.startsWith('--responseTypes=')) {\n // 该参数从未实现(解析后被静默忽略);现显式报错并给出迁移方式,不再无声吞掉\n console.error(\n '[route-forge/codegen] --responseTypes has been removed (it was never implemented). ' +\n 'Edit the \"response\" field in the generated d.ts directly, or use module augmentation on ForgeRouteMap.',\n );\n process.exit(1);\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 };\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]\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 -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点(仅用于自动发现层级名;unassigned 现是 levels 中的真实层级,与其余层级一样按 HTTP 懒加载拉取)\n */\nexport async function fetchSummary(endpoint: string): Promise<{\n levels: Record<string, unknown>;\n}> {\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 defaults = meta.parameter_defaults ?? {};\n const params = (meta.parameters ?? []).map((p) => {\n // URI 中的 {p?} 或有后端默认值的参数 → 生成可选字段,避免调用方被迫传参\n const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;\n return `${p}${optional ? '?' : ''}: string | number;`;\n }).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 // 未指定 --levels 时从摘要端点自动发现层级(unassigned 现是 levels 中的真实层级,一并纳入)\n let levels = opts.levels;\n // 摘要端点数据(用于自动发现层级)\n let summaryData: Awaited<ReturnType<typeof fetchSummary>> | null = null;\n if (levels.length === 0) {\n try {\n summaryData = await fetchSummary(opts.endpoint);\n levels = Object.keys(summaryData.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 // 所有层级(含 unassigned 真实层级)统一按 HTTP 懒加载拉取(SPEC §3.1.6)\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 const lvl = levels[idx]!;\n if (res.status === 'fulfilled') {\n routesByLevel[lvl] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${lvl}\": ${(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"]}