@_tc/template-core 0.0.1-bate.47 → 0.0.1-bate.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,506 @@
1
+ # TemplateCore AI 助手速读
2
+
3
+ 这份文档给在消费方项目中协助开发的 AI 助手/代码代理阅读。项目本身才是 `@_tc/template-core` 的使用方;AI 通过这份速读文档快速理解包的用法和边界。它是 `README.md` 的精简版,按渐进式披露组织:先用下面的最小信息完成常见任务,只有需要深入时再读末尾的参考文档。
4
+
5
+ ## 1. 先看结论
6
+
7
+ TemplateCore 是一个 TypeScript + Koa + React 的后台框架包。它提供:
8
+
9
+ - Koa 服务启动和约定式后端加载。
10
+ - 内置 Dashboard、Schema CRUD 页面和 React UI 组件库。
11
+ - `model/` 配置驱动的菜单、项目和 CRUD 页面。
12
+ - 前端构建 `buildFE()` 和消费方 Node/backend 构建 `buildBE()`。
13
+ - 类型扩展机制,用于扩展 SchemaForm 字段、CallCom 组件和 Koa app 类型。
14
+
15
+ 优先使用公开入口,不要从包内部源码路径导入。
16
+
17
+ | 入口 | 用途 |
18
+ | --- | --- |
19
+ | `@_tc/template-core` | `serverStart`、`baseFn`、Koa/Controller/Service 类型。 |
20
+ | `@_tc/template-core/bundler` | `buildFE()`、`buildBE()`。 |
21
+ | `@_tc/template-core/fe` | 前端初始化、请求方法、Dashboard/Schema 类型、共享状态、业务前端组件。 |
22
+ | `@_tc/template-core/fe/rc` | React UI 组件和 SchemaForm 组件。 |
23
+ | `@_tc/template-core/fe/tailwind_ui.css` | 内置前端/UI 全局样式。 |
24
+ | `@_tc/template-core/model` | `ModelDataType` 等 model 配置类型。 |
25
+
26
+ ## 2. 最小业务项目
27
+
28
+ 推荐结构:
29
+
30
+ ```text
31
+ my-admin/
32
+ ├── app/
33
+ │ ├── controller/
34
+ │ │ └── product.ts
35
+ │ └── router/
36
+ │ └── product.ts
37
+ ├── config/
38
+ │ └── config.default.ts
39
+ ├── model/
40
+ │ └── product/
41
+ │ ├── mode.ts
42
+ │ └── project/
43
+ │ └── demo.ts
44
+ └── index.ts
45
+ ```
46
+
47
+ 启动入口:
48
+
49
+ ```ts
50
+ import { join } from 'path'
51
+ import { serverStart } from '@_tc/template-core'
52
+ import { buildFE } from '@_tc/template-core/bundler'
53
+
54
+ async function main() {
55
+ await serverStart({
56
+ name: 'my-admin',
57
+ baseDir: join(__dirname, '.'),
58
+ })
59
+
60
+ await buildFE('dev')
61
+ }
62
+
63
+ main().catch((error) => {
64
+ console.error(error)
65
+ process.exit(1)
66
+ })
67
+ ```
68
+
69
+ 只使用后端能力时,可以不调用 `buildFE('dev')`。
70
+
71
+ 配置:
72
+
73
+ ```ts
74
+ // config/config.default.ts
75
+ export default {
76
+ port: 9000,
77
+ }
78
+ ```
79
+
80
+ 访问内置后台:
81
+
82
+ ```text
83
+ http://localhost:9000/dash?projk=demo
84
+ ```
85
+
86
+ `projk` 对应 `model/{modelKey}/project/{projectKey}.ts`。
87
+
88
+ ## 3. 后端约定
89
+
90
+ `serverStart({ baseDir })` 会按目录自动加载业务代码:
91
+
92
+ ```text
93
+ app/controller/**/*.(js|ts) -> app.controller
94
+ app/service/**/*.(js|ts) -> app.service
95
+ app/middleware/**/*.(js|ts) -> app.middlewares
96
+ app/router/**/*.(js|ts) -> Koa router
97
+ app/router-schema/**/*.(js|ts) -> app.routerSchema
98
+ app/extend/*.(js|ts) -> app.extends
99
+ config/config.default.(js|ts) -> app.config
100
+ config/config.{env}.(js|ts) -> app.config
101
+ model/**/*.(js|ts) -> 项目模型配置
102
+ ```
103
+
104
+ Controller 示例:
105
+
106
+ ```ts
107
+ import { baseFn, type ControllerFN, type Ctx } from '@_tc/template-core'
108
+
109
+ const products = [
110
+ { product_id: '1', product_name: '商品 A', price: 100 },
111
+ ]
112
+
113
+ const getProductController = ((app) => {
114
+ const BaseController = baseFn.baseControllerFn(app)
115
+
116
+ return class ProductController extends BaseController {
117
+ list = async (ctx: Ctx) => {
118
+ this.success(ctx, {
119
+ data: products,
120
+ page: Number(ctx.request.query.page || 1),
121
+ pageSize: Number(ctx.request.query.pageSize || 10),
122
+ total: products.length,
123
+ })
124
+ }
125
+
126
+ detail = async (ctx: Ctx) => {
127
+ const item = products.find((product) => {
128
+ return product.product_id === ctx.request.query.product_id
129
+ })
130
+
131
+ this.success(ctx, item ?? null)
132
+ }
133
+ }
134
+ }) satisfies ControllerFN
135
+
136
+ export default getProductController
137
+ ```
138
+
139
+ Router 示例:
140
+
141
+ ```ts
142
+ import type { KoaApp, Router } from '@_tc/template-core'
143
+
144
+ export default (app: KoaApp, router: Router) => {
145
+ router.get('/api/product/list', app.controller.product.list)
146
+ router.get('/api/product', app.controller.product.detail)
147
+ }
148
+ ```
149
+
150
+ 路由挂载顺序:
151
+
152
+ - 每个 `app/router/*.ts` 文件都会获得独立的 `koa-router` 实例。
153
+ - 可以通过 `router.level = number` 控制挂载顺序,数值越小越早挂载。
154
+ - 默认 `level` 是 `0`,框架兜底 router 是 `99`。
155
+ - 通配、兜底、重定向类路由建议设置较大的 `level`,避免抢先匹配业务 API。
156
+
157
+ ```ts
158
+ import type { KoaApp, Router } from '@_tc/template-core'
159
+
160
+ export default (app: KoaApp, router: Router) => {
161
+ router.level = 10
162
+ router.get('/api/fallback/:id', app.controller.fallback.detail)
163
+ }
164
+ ```
165
+
166
+ 内置响应约定:`BaseController.success(ctx, data)` 返回 `{ data, code: 0, message: 'ok' }`。
167
+
168
+ 内置扩展:
169
+
170
+ - `app.extends.$fetch`:Node 侧 axios 风格 fetch 实例。
171
+ - `app.extends.db`:默认 SQLite 数据存储,可用业务 `app/extend/db.ts` 覆盖。
172
+
173
+ ## 4. model 配置
174
+
175
+ `model` 是内置 Dashboard 和 Schema CRUD 的数据源。
176
+
177
+ ```text
178
+ model/{modelKey}/mode.ts -> 模型模板
179
+ model/{modelKey}/project/{projectKey}.ts -> 项目覆盖
180
+ ```
181
+
182
+ `mode.ts` 示例:
183
+
184
+ ```ts
185
+ import type { ModelDataType } from '@_tc/template-core/model'
186
+
187
+ const model: ModelDataType = {
188
+ mode: 'MB',
189
+ name: '商品后台',
190
+ desc: '商品管理',
191
+ homePage: '/_sidebar_/product?projk=demo',
192
+ menuLayout: 'left',
193
+ menu: [
194
+ {
195
+ key: 'product',
196
+ name: '商品列表',
197
+ menuType: 'module',
198
+ moduleType: 'schema',
199
+ schemaConfig: {
200
+ api: '/api/product',
201
+ schema: {
202
+ type: 'object',
203
+ properties: {
204
+ product_id: {
205
+ type: 'string',
206
+ label: '商品 ID',
207
+ tableOption: { width: 160 },
208
+ detailPanelOption: {},
209
+ editFormOption: {
210
+ comType: 'input',
211
+ disabled: true,
212
+ },
213
+ },
214
+ product_name: {
215
+ type: 'string',
216
+ label: '商品名称',
217
+ tableOption: {},
218
+ searchOption: { comType: 'input' },
219
+ createFormOption: { comType: 'input' },
220
+ editFormOption: { comType: 'input' },
221
+ detailPanelOption: {},
222
+ },
223
+ price: {
224
+ type: 'number',
225
+ label: '价格',
226
+ tableOption: {},
227
+ createFormOption: { comType: 'inputNumber' },
228
+ editFormOption: { comType: 'inputNumber' },
229
+ detailPanelOption: {},
230
+ },
231
+ },
232
+ required: ['product_name'],
233
+ },
234
+ componentConfig: {
235
+ createForm: {
236
+ title: '新增商品',
237
+ saveBtnText: '创建',
238
+ },
239
+ editForm: {
240
+ title: '编辑商品',
241
+ saveBtnText: '保存',
242
+ fetchKey: 'product_id',
243
+ },
244
+ detailPanel: {
245
+ title: '商品详情',
246
+ fetchKey: 'product_id',
247
+ },
248
+ },
249
+ tableConfig: {
250
+ headerButtons: [
251
+ {
252
+ label: '新增',
253
+ eventKey: 'callComponent',
254
+ variant: 'primary',
255
+ eventOption: { comName: 'createForm' },
256
+ },
257
+ ],
258
+ rowButtons: [
259
+ {
260
+ label: '编辑',
261
+ eventKey: 'callComponent',
262
+ eventOption: { comName: 'editForm' },
263
+ },
264
+ {
265
+ label: '详情',
266
+ eventKey: 'callComponent',
267
+ eventOption: { comName: 'detailPanel' },
268
+ },
269
+ {
270
+ label: '删除',
271
+ eventKey: 'remove',
272
+ eventOption: {
273
+ params: {
274
+ product_id: '$schema::product_id',
275
+ },
276
+ },
277
+ },
278
+ ],
279
+ },
280
+ },
281
+ },
282
+ ],
283
+ }
284
+
285
+ export default model
286
+ ```
287
+
288
+ `project/demo.ts` 示例:
289
+
290
+ ```ts
291
+ export default {
292
+ name: 'Demo 企业',
293
+ desc: 'Demo 企业商品后台',
294
+ homePage: '/_sidebar_/product?projk=demo',
295
+ }
296
+ ```
297
+
298
+ 合并规则:
299
+
300
+ - `project` 会继承同目录上层 `mode`。
301
+ - 对象递归合并,`project` 覆盖 `mode`。
302
+ - 数组按 `key` 合并:同 key 覆盖,不同 key 新增。
303
+ - `model/index.ts` 和 `model/index.js` 会被 loader 跳过。
304
+
305
+ 常见菜单模块:
306
+
307
+ | `moduleType` | 用途 |
308
+ | --- | --- |
309
+ | `schema` | 配置驱动 CRUD 页面,使用 `schemaConfig`。 |
310
+ | `custom` | 自定义前端页面,使用 `customConfig.path`。 |
311
+ | `iframe` | iframe 页面,使用 `iframeConfig.path`。 |
312
+ | `sidebar` | 顶部菜单下挂左侧菜单,使用 `sidebarConfig.menu`。 |
313
+
314
+ ## 5. 前端使用
315
+
316
+ 使用内置组件或自定义前端入口时,确保引入样式:
317
+
318
+ ```ts
319
+ import '@_tc/template-core/fe/tailwind_ui.css'
320
+ ```
321
+
322
+ 前端公共能力优先从 `@_tc/template-core/fe` 导入:
323
+
324
+ ```ts
325
+ import {
326
+ get,
327
+ post,
328
+ request,
329
+ schemaEventBus,
330
+ schemaStore,
331
+ useSchemaStore,
332
+ LanguageSwitch,
333
+ ThemeSwitch,
334
+ } from '@_tc/template-core/fe'
335
+ ```
336
+
337
+ UI 组件从 `@_tc/template-core/fe/rc` 导入:
338
+
339
+ ```tsx
340
+ import { Button, DataTable, Form, Input, Modal, Select } from '@_tc/template-core/fe/rc'
341
+ ```
342
+
343
+ 常用前端能力:
344
+
345
+ - 请求方法:`request`、`get`、`post`、`put`、`patch`、`del`。
346
+ - Schema 通信:`schemaEventBus`、`eventsInfo`、`merge`。
347
+ - 共享状态:`modeStore`、`schemaStore`、`apiFreezerStore` 及对应 hooks。
348
+ - 多语言:`i18nStore.addResources()`,配置文案可写 `$i18n::...`。
349
+ - 主题:`ThemeSwitch` 会同步根节点 `dark` class 和 `localStorage`。
350
+
351
+ 不要直接依赖 `frontend/src/...`、`packages/react/ui/...` 这类包内部路径。
352
+
353
+ ## 6. 构建
354
+
355
+ 前端资源构建:
356
+
357
+ ```ts
358
+ import { buildFE } from '@_tc/template-core/bundler'
359
+
360
+ await buildFE('dev')
361
+ await buildFE('prod', { output: 'run' })
362
+ ```
363
+
364
+ `buildFE('dev')` 是构建一次并监听业务 `frontend/` 变化后重建,不是 Vite dev server/HMR。
365
+
366
+ Node/backend 构建:
367
+
368
+ ```ts
369
+ import { buildBE } from '@_tc/template-core/bundler'
370
+
371
+ await buildBE()
372
+ ```
373
+
374
+ `buildBE()` 默认:
375
+
376
+ - 构建当前工作目录的 `index.ts`、`index.js`、`app`、`config`、`model`。
377
+ - 输出到 `dist`。
378
+ - 输出格式为 `cjs`。
379
+ - 外部化 Node 内置模块和 npm 包。
380
+ - 默认不生成 `.d.ts`。
381
+ - 默认不注入 alias;需要时显式传 `alias`。
382
+
383
+ 自定义示例:
384
+
385
+ ```ts
386
+ await buildBE({
387
+ input: ['app', 'config', 'model/index.ts'],
388
+ outDir: 'dist',
389
+ format: ['es', 'cjs'],
390
+ dts: true,
391
+ alias: {
392
+ '@app': './app',
393
+ '@model': './model',
394
+ },
395
+ })
396
+ ```
397
+
398
+ 消费方脚本示例:
399
+
400
+ ```js
401
+ // scripts/build-be.mjs
402
+ import { buildBE } from '@_tc/template-core/bundler'
403
+
404
+ await buildBE({
405
+ input: ['index.ts', 'index.js', 'app', 'config', 'model'],
406
+ outDir: 'dist',
407
+ format: 'cjs',
408
+ })
409
+ ```
410
+
411
+ 监听文件变化重新构建:
412
+
413
+ ```js
414
+ // scripts/watch-be.mjs
415
+ import chokidar from 'chokidar'
416
+ import { buildBE } from '@_tc/template-core/bundler'
417
+
418
+ const input = ['index.ts', 'index.js', 'app', 'config', 'model']
419
+
420
+ let building = false
421
+ let pending = false
422
+
423
+ async function runBuild() {
424
+ if (building) {
425
+ pending = true
426
+ return
427
+ }
428
+
429
+ building = true
430
+
431
+ try {
432
+ await buildBE({ input, outDir: 'dist', format: 'cjs' })
433
+ } finally {
434
+ building = false
435
+
436
+ if (pending) {
437
+ pending = false
438
+ await runBuild()
439
+ }
440
+ }
441
+ }
442
+
443
+ await runBuild()
444
+
445
+ chokidar
446
+ .watch(input, {
447
+ ignored: ['dist/**', 'node_modules/**'],
448
+ ignoreInitial: true,
449
+ })
450
+ .on('all', runBuild)
451
+ ```
452
+
453
+ ```json
454
+ {
455
+ "scripts": {
456
+ "build:be": "node scripts/build-be.mjs",
457
+ "build:be:watch": "node scripts/watch-be.mjs"
458
+ }
459
+ }
460
+ ```
461
+
462
+ ## 7. 常见任务速查
463
+
464
+ 新增后端接口:
465
+
466
+ 1. 在 `app/controller/*.ts` 写 controller。
467
+ 2. 在 `app/router/*.ts` 注册路由。
468
+ 3. 返回数据时优先用 `this.success(ctx, data)`。
469
+
470
+ 新增一个 Schema CRUD 页面:
471
+
472
+ 1. 在 `model/{modelKey}/mode.ts` 或 `project/{projectKey}.ts` 添加菜单项。
473
+ 2. 菜单项使用 `menuType: 'module'`、`moduleType: 'schema'`。
474
+ 3. `schemaConfig.api` 指向后端资源 API。
475
+ 4. 字段在 `schema.properties` 中配置 `tableOption`、`searchOption`、`createFormOption`、`editFormOption`、`detailPanelOption`。
476
+ 5. 需要新增/编辑/详情时,在 `componentConfig` 和 `tableConfig` 配置按钮与弹层。
477
+
478
+ 新增自定义前端页面:
479
+
480
+ 1. 在业务 `frontend/` 下添加入口 `*.entry.tsx`。
481
+ 2. 需要样式时引入 `@_tc/template-core/fe/tailwind_ui.css`。
482
+ 3. 在 `model` 菜单中使用 `moduleType: 'custom'`,让 `customConfig.path` 指向页面路径。
483
+ 4. 使用 `buildFE('dev')` 或 `buildFE('prod')` 生成资源。
484
+
485
+ 扩展前端类型或组件:
486
+
487
+ 1. SchemaForm 字段类型、CallCom 组件、Koa app 类型都支持 TypeScript 声明合并。
488
+ 2. 运行时组件通常放在业务 `frontend/extended/`。
489
+ 3. 优先查看公开入口 `.d.ts` 和 npm 包根目录 `README.md`;如果当前环境是源码仓库,再继续读 `docs/type-extension.md` 和 `docs/schema-form.md`。
490
+
491
+ ## 8. 继续深入
492
+
493
+ 需要更多信息时,按任务读取:
494
+
495
+ | 任务 | 建议阅读 |
496
+ | --- | --- |
497
+ | 完整安装和使用说明 | npm 包根目录 `README.md` |
498
+ | 生成项目、model、CRUD | `.skills/tc-generator/SKILL.md` |
499
+ | 组件选型和用法 | `.skills/tc-component-usage-skills/SKILL.md` |
500
+ | model/schema 配置细节 | `.skills/tc-generator/reference/model-schema.md` |
501
+ | 完整 CRUD 示例 | `.skills/tc-generator/reference/example.md` |
502
+ | 可复制项目骨架 | `.skills/tc-generator/reference/project-template/` |
503
+ | UI 组件现场预览 | 启动服务后访问 `/ui-components` |
504
+ | 已安装包的类型细节 | 查看对应公开入口的 `.d.ts` 文件 |
505
+
506
+ 如果当前环境是源码仓库而不是 npm 安装目录,还可以继续读 `docs/`、`frontend/README.md`、`model/README.md`、`bundler/README.md` 和 `BUILD.md`。
package/README.md CHANGED
@@ -158,7 +158,7 @@ http://localhost:9000/dash?projk=demo
158
158
  | 入口 | 用途 |
159
159
  | --- | --- |
160
160
  | `@_tc/template-core` | 服务启动、基础 Controller/Service、Koa 类型 |
161
- | `@_tc/template-core/bundler` | 构建入口,提供前端资源构建 `buildFE` 和消费方 Node/backend 构建 `buildFB` |
161
+ | `@_tc/template-core/bundler` | 构建入口,提供前端资源构建 `buildFE` 和消费方 Node/backend 构建 `buildBE` |
162
162
  | `@_tc/template-core/fe` | 前端初始化、Dash 路由扩展、请求方法、SchemaPage 事件、共享状态和内置前端组件 |
163
163
  | `@_tc/template-core/fe/rc` | UI/SchemaForm 组件和类型 |
164
164
  | `@_tc/template-core/fe/tailwind_ui.css` | 前端全局样式入口,源码对应 `frontend/src/main.css` |
@@ -844,6 +844,22 @@ export default (app: KoaApp, router: Router) => {
844
844
  }
845
845
  ```
846
846
 
847
+ 路由挂载顺序:
848
+
849
+ - 每个 `app/router/*.ts` 文件都会获得独立的 `koa-router` 实例。
850
+ - 可以通过 `router.level = number` 控制挂载顺序,数值越小越早挂载。
851
+ - 默认 `level` 是 `0`,框架兜底 router 是 `99`。
852
+ - 通配、兜底、重定向类路由建议设置较大的 `level`,避免抢先匹配业务 API。
853
+
854
+ ```ts
855
+ import type { KoaApp, Router } from '@_tc/template-core'
856
+
857
+ export default (app: KoaApp, router: Router) => {
858
+ router.level = 10
859
+ router.get('/api/fallback/:id', app.controller.fallback.detail)
860
+ }
861
+ ```
862
+
847
863
  ## model 数据如何配置
848
864
 
849
865
  `model` 是后台菜单、项目和 Schema 页面的数据源。框架会读取:
@@ -1543,16 +1559,113 @@ buildFE('prod', {
1543
1559
  })
1544
1560
  ```
1545
1561
 
1546
- ## Node/backend 构建 buildFB
1562
+ ## Node/backend 构建 buildBE
1547
1563
 
1548
- 消费方 Node/backend 侧构建使用 `buildFB()`。它复用发布包内的 `scripts/vite-build/buildEntries`,只是提供消费方默认配置。
1564
+ 消费方 Node/backend 侧构建使用 `buildBE()`。它复用发布包内的 `scripts/vite-build/buildEntries`,只是提供消费方默认配置。
1549
1565
 
1550
1566
  最小用法:
1551
1567
 
1552
1568
  ```ts
1553
- import { buildFB } from '@_tc/template-core/bundler'
1569
+ import { buildBE } from '@_tc/template-core/bundler'
1570
+
1571
+ await buildBE()
1572
+ ```
1573
+
1574
+ 消费方脚本示例:
1575
+
1576
+ ```js
1577
+ // scripts/build-be.mjs
1578
+ import { buildBE } from '@_tc/template-core/bundler'
1554
1579
 
1555
- await buildFB()
1580
+ await buildBE({
1581
+ rootDir: process.cwd(),
1582
+ input: ['index.ts', 'index.js', 'app', 'config', 'model'],
1583
+ outDir: 'dist',
1584
+ format: 'cjs',
1585
+ alias: {
1586
+ '@app': './app',
1587
+ '@model': './model',
1588
+ },
1589
+ })
1590
+ ```
1591
+
1592
+ ```json
1593
+ {
1594
+ "scripts": {
1595
+ "build:be": "node scripts/build-be.mjs"
1596
+ }
1597
+ }
1598
+ ```
1599
+
1600
+ 监听文件变化并重新构建:
1601
+
1602
+ ```js
1603
+ // scripts/watch-be.mjs
1604
+ import chokidar from 'chokidar'
1605
+ import { buildBE } from '@_tc/template-core/bundler'
1606
+
1607
+ const input = ['index.ts', 'index.js', 'app', 'config', 'model']
1608
+
1609
+ let building = false
1610
+ let pending = false
1611
+
1612
+ async function runBuild() {
1613
+ if (building) {
1614
+ pending = true
1615
+ return
1616
+ }
1617
+
1618
+ building = true
1619
+
1620
+ try {
1621
+ console.log('[buildBE] building...')
1622
+ await buildBE({
1623
+ rootDir: process.cwd(),
1624
+ input,
1625
+ outDir: 'dist',
1626
+ format: 'cjs',
1627
+ alias: {
1628
+ '@app': './app',
1629
+ '@model': './model',
1630
+ },
1631
+ })
1632
+ console.log('[buildBE] done')
1633
+ } catch (error) {
1634
+ console.error('[buildBE] failed')
1635
+ console.error(error)
1636
+ } finally {
1637
+ building = false
1638
+
1639
+ if (pending) {
1640
+ pending = false
1641
+ await runBuild()
1642
+ }
1643
+ }
1644
+ }
1645
+
1646
+ await runBuild()
1647
+
1648
+ chokidar
1649
+ .watch(input, {
1650
+ ignored: ['dist/**', 'node_modules/**'],
1651
+ ignoreInitial: true,
1652
+ })
1653
+ .on('all', async (_event, filePath) => {
1654
+ console.log(`[buildBE] changed: ${filePath}`)
1655
+ await runBuild()
1656
+ })
1657
+ ```
1658
+
1659
+ ```json
1660
+ {
1661
+ "scripts": {
1662
+ "build:be": "node scripts/build-be.mjs",
1663
+ "build:be:watch": "node scripts/watch-be.mjs"
1664
+ },
1665
+ "devDependencies": {
1666
+ "chokidar": "^4.0.3"
1667
+ }
1668
+ }
1556
1669
  ```
1557
1670
 
1558
1671
  默认会在当前工作目录构建这些入口:
@@ -1561,12 +1674,12 @@ await buildFB()
1561
1674
  ['index.ts', 'index.js', 'app', 'config', 'model']
1562
1675
  ```
1563
1676
 
1564
- 不存在的默认入口会自动跳过;如果没有任何匹配源码,构建仍会失败。输出到 `dist`,格式为 `cjs`。默认 `outputStructure: "preserve"` 按源路径输出,`bundleDependencies: false` 会 external Node 内置模块和 npm 包,不会把依赖打进产物。`buildFB` 会按同一组 input 扫描并复制内置白名单资源扩展,主要覆盖 `app`、`config`、`model` 目录。
1677
+ 不存在的默认入口会自动跳过;如果没有任何匹配源码,构建仍会失败。输出到 `dist`,格式为 `cjs`。默认 `outputStructure: "preserve"` 按源路径输出,`bundleDependencies: false` 会 external Node 内置模块和 npm 包,不会把依赖打进产物。`buildBE` 会按同一组 input 扫描并复制内置白名单资源扩展,主要覆盖 `app`、`config`、`model` 目录。
1565
1678
 
1566
1679
  常见配置:
1567
1680
 
1568
1681
  ```ts
1569
- await buildFB({
1682
+ await buildBE({
1570
1683
  input: ['app', 'config', 'model'],
1571
1684
  outDir: 'dist',
1572
1685
  format: ['es', 'cjs'],
@@ -1580,7 +1693,7 @@ await buildFB({
1580
1693
  如果需要声明文件:
1581
1694
 
1582
1695
  ```ts
1583
- await buildFB({
1696
+ await buildBE({
1584
1697
  dts: {
1585
1698
  outDir: 'types',
1586
1699
  tsconfig: 'tsconfig.json',
@@ -1588,12 +1701,12 @@ await buildFB({
1588
1701
  })
1589
1702
  ```
1590
1703
 
1591
- `buildEntries()` 底层默认生成 d.ts,但 `buildFB()` 默认关闭声明文件;需要时按上面这样显式传 `dts`。
1704
+ `buildEntries()` 底层默认生成 d.ts,但 `buildBE()` 默认关闭声明文件;需要时按上面这样显式传 `dts`。
1592
1705
 
1593
1706
  如果传入自定义 `input`,缺失路径默认会报错;需要跳过时显式打开:
1594
1707
 
1595
1708
  ```ts
1596
- await buildFB({
1709
+ await buildBE({
1597
1710
  input: ['app', 'config', 'model'],
1598
1711
  allowMissingInput: true,
1599
1712
  })
@@ -1 +1 @@
1
- var e=e=>JSON.stringify(e).replace(/</g,`\\u003c`).replace(/>/g,`\\u003e`).replace(/&/g,`\\u0026`).replace(/\u2028/g,`\\u2028`).replace(/\u2029/g,`\\u2029`),t=(t=>class{async renderPage(n){t.extends.logger.log(` - render page: `+n.params.page);try{t.extends.renderView(`dist/${n.params.page}.entry.tpl`,n,{projKey:n.query.projk,name:t.options?.name,env:t.envs.get(),basePath:e(`${t.options.pageBasePage}${n.params.page}`),projKeyJson:e(String(n.query.projk??``)),signKey:e(t.config.signKey),options:e(t.options)})}catch(e){console.log(`-------------- render view error --------------`),console.log(e),t.extends.generateErrorMessage(`template not found`,{status:404,showError:!0,code:0})}}});module.exports=t;
1
+ var e=e=>JSON.stringify(e).replace(/</g,`\\u003c`).replace(/>/g,`\\u003e`).replace(/&/g,`\\u0026`).replace(/\u2028/g,`\\u2028`).replace(/\u2029/g,`\\u2029`),t=(t=>class{async renderPage(n){if(!n.path.includes(`${t.options.apiPrefix}/`)){t.extends.logger.log(` - render page: `+n.params.page);try{t.extends.renderView(`dist/${n.params.page}.entry.tpl`,n,{projKey:n.query.projk,name:t.options?.name,env:t.envs.get(),basePath:e(`${t.options.pageBasePage}${n.params.page}`),projKeyJson:e(String(n.query.projk??``)),signKey:e(t.config.signKey),options:e(t.options)})}catch(e){console.log(`-------------- render view error --------------`),console.log(e),t.extends.generateErrorMessage(`template not found`,{status:404,showError:!0,code:0})}}}});module.exports=t;
@@ -1 +1 @@
1
- const e=require(`../../_virtual/_rolldown/runtime.js`);let t=require(`ajv`);t=e.__toESM(t);var n=new t.default,r=`http://json-schema.org/draft-07/schema#`,i=(e,t,i)=>{if(t[e]&&i[e]){i[e].$schema=r;let a=n.compile(i[e]);return[a(t[e]),a]}},a=e=>async(t,r)=>{let{path:a,method:o}=t;if(a.indexOf(`/api`)<0)return r();let s=e.currentUseRouter,c;for(let e of s){let t=e.match(a,`get`);if(t){c=t;break}}let l=c?.path[0]?.path;if(l){let r=e.extends.parsingParamsOnUrl(l,a),{body:s,query:c,headers:u}=t.request,d={params:r,body:s,query:c,headers:u};e.extends.logger?.info(`--[api params verify] [${o} ${a}] body: ${JSON.stringify(s)}--`),e.extends.logger?.info(`--[api params verify] [${o} ${a}] query: ${JSON.stringify(c)}--`),e.extends.logger?.info(`--[api params verify] [${o} ${a}] params: ${JSON.stringify(r)}--`),e.extends.logger?.info(`--[api params verify] [${o} ${a}] headers: ${JSON.stringify(u)}--`);let f=e.routerSchema[l]?.[o.toLowerCase()];if(f){let t=!0,r,a=[`headers`,`body`,`query`,`params`];for(let e=0;e<a.length;e++){let n=a[e];if(t){let e=i(n,{...d},f);t=e?.[0]??!0,r=e?.[1]}}if(!t){let t=`request vaidate fail: ${n.errorsText(r?.errors)}`;e.extends.logger?.error(`--[api valid error] message: ${t}--`),e.extends.generateErrorMessage(t,{showError:!0,code:442})}}}return r()};module.exports=a;
1
+ const e=require(`../../_virtual/_rolldown/runtime.js`);let t=require(`ajv`);t=e.__toESM(t);var n=new t.default,r=`http://json-schema.org/draft-07/schema#`,i=(e,t,i)=>{if(t[e]&&i[e]){i[e].$schema=r;let a=n.compile(i[e]);return[a(t[e]),a]}},a=e=>async(t,r)=>{let{path:a,method:o}=t;if(a.indexOf(e.options.apiPrefix)<0)return r();let s=e.currentUseRouter,c;for(let e of s){let t=e.match(a,`get`);if(t){c=t;break}}let l=c?.path[0]?.path;if(l){let r=e.extends.parsingParamsOnUrl(l,a),{body:s,query:c,headers:u}=t.request,d={params:r,body:s,query:c,headers:u};e.extends.logger?.info(`--[api params verify] [${o} ${a}] body: ${JSON.stringify(s)}--`),e.extends.logger?.info(`--[api params verify] [${o} ${a}] query: ${JSON.stringify(c)}--`),e.extends.logger?.info(`--[api params verify] [${o} ${a}] params: ${JSON.stringify(r)}--`),e.extends.logger?.info(`--[api params verify] [${o} ${a}] headers: ${JSON.stringify(u)}--`);let f=e.routerSchema[l]?.[o.toLowerCase()];if(f){let t=!0,r,a=[`headers`,`body`,`query`,`params`];for(let e=0;e<a.length;e++){let n=a[e];if(t){let e=i(n,{...d},f);t=e?.[0]??!0,r=e?.[1]}}if(!t){let t=`request vaidate fail: ${n.errorsText(r?.errors)}`;e.extends.logger?.error(`--[api valid error] message: ${t}--`),e.extends.generateErrorMessage(t,{showError:!0,code:442})}}}return r()};module.exports=a;
@@ -1 +1 @@
1
- const e=require(`../../_virtual/_rolldown/runtime.js`);let t=require(`md5`);t=e.__toESM(t);var n=1e3*60*10,r=e=>async(r,i)=>{let{path:a,method:o}=r;if(a.includes(`api`)&&!e.config?.apiSignVerify?.whiteList?.includes(a)){e.extends.logger?.info(`--[api-sign-verify]--`);let{s_sign:i,s_t:s}=r.request.header,{signKey:c}=e.config,l=(0,t.default)(`${c}_${s}`);e.extends.logger?.info(`[${o} ${a}] signature: ${l}`),(!s||!i||i.toLowerCase()!==l||Date.now()-Number(s)>n)&&e.extends.generateErrorMessage(`signature not correct or api timeout`,{showError:!0,code:100014})}await i()};module.exports=r;
1
+ const e=require(`../../_virtual/_rolldown/runtime.js`);let t=require(`md5`);t=e.__toESM(t);var n=1e3*60*10,r=e=>async(r,i)=>{let{path:a,method:o}=r;if(a.includes(e.options.apiPrefix)&&!e.config?.apiSignVerify?.whiteList?.includes(a)){e.extends.logger?.info(`--[api-sign-verify]--`);let{s_sign:i,s_t:s}=r.request.header,{signKey:c}=e.config,l=(0,t.default)(`${c}_${s}`);e.extends.logger?.info(`[${o} ${a}] signature: ${l}`),(!s||!i||i.toLowerCase()!==l||Date.now()-Number(s)>n)&&e.extends.generateErrorMessage(`signature not correct or api timeout`,{showError:!0,code:100014})}await i()};module.exports=r;
@@ -1 +1 @@
1
- var e=()=>async(e,t)=>{let n=e.headers.projk,{path:r}=e;if(r.indexOf(`/api/proj/`)!==-1&&(!n||[`undefined`,`null`].includes(n))){e.status=200,e.body={code:110,data:null,message:`请求不合法`};return}e.extInfo={key:n},await t()};module.exports=e;
1
+ var e=e=>async(t,n)=>{let r=t.headers.projk,{path:i}=t;if(i.indexOf(`${e.options.apiPrefix}/proj/`)!==-1&&(!r||[`undefined`,`null`].includes(r))){t.status=200,t.body={code:110,data:null,message:`请求不合法`};return}t.extInfo={key:r},await n()};module.exports=e;
@@ -1 +1 @@
1
- var e=(e,t)=>{let{project:n}=e.controller;t.get(`/api/project/model_list`,n.getModelList),t.get(`/api/project/list`,n.getProjectList),t.get(`/api/project/:key`,n.getProject)};module.exports=e;
1
+ var e=(e,t)=>{let{project:n}=e.controller,{apiPrefix:r}=e.options;t.get(`${r}/project/model_list`,n.getModelList),t.get(`${r}/project/list`,n.getProjectList),t.get(`${r}/project/:key`,n.getProject)};module.exports=e;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../scripts/vite-build/build.js`);require(`../scripts/vite-build/index.js`);var t=[`index.ts`,`index.js`,`app`,`config`,`model`];async function n(n={}){return e.buildEntries({...n,rootDir:n.rootDir,input:n.input??t,outDir:n.outDir??`dist`,format:n.format??`cjs`,alias:n.alias,allowMissingInput:n.allowMissingInput??!n.input,outputStructure:n.outputStructure??`preserve`,bundleDependencies:n.bundleDependencies??!1,externalNodeBuiltins:n.externalNodeBuiltins??!0,dts:n.dts??!1,copy:n.copy??{input:n.input??t,extensions:{input:[`.tpl`,`.html`,`.css`,`.scss`,`.sass`,`.less`,`.svg`,`.png`,`.jpg`,`.jpeg`,`.gif`,`.webp`,`.ico`,`.json`,`.md`,`.txt`,`.xml`,`.yml`,`.yaml`],mergeDefault:!1}}})}exports.buildFB=n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../scripts/vite-build/build.js`);require(`../scripts/vite-build/index.js`);var t=[`index.ts`,`index.js`,`app`,`config`,`model`];async function n(n={}){return e.buildEntries({...n,rootDir:n.rootDir,input:n.input??t,outDir:n.outDir??`dist`,format:n.format??`cjs`,alias:n.alias,allowMissingInput:n.allowMissingInput??!n.input,outputStructure:n.outputStructure??`preserve`,bundleDependencies:n.bundleDependencies??!1,externalNodeBuiltins:n.externalNodeBuiltins??!0,dts:n.dts??!1,copy:n.copy??{input:n.input??t,extensions:{input:[`.tpl`,`.html`,`.css`,`.scss`,`.sass`,`.less`,`.svg`,`.png`,`.jpg`,`.jpeg`,`.gif`,`.webp`,`.ico`,`.json`,`.md`,`.txt`,`.xml`,`.yml`,`.yaml`],mergeDefault:!1}}})}exports.buildBE=n;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./buildFB.js`),t=require(`../packages/common/log/index.js`),n=require(`./state.js`),r=require(`./dev.js`),i=require(`./prod.js`);var a=(e,a)=>{switch(n.setBuildFEOptions(a),e){case`dev`:r.dev();break;case`prod`:i.prod();break;default:t.logRed(`Usage: npx tsx scripts/index.ts [dev|prod]`)}};exports.buildFB=e.buildFB,exports.buildFE=a;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./buildBE.js`),t=require(`../packages/common/log/index.js`),n=require(`./state.js`),r=require(`./dev.js`),i=require(`./prod.js`);var a=(e,a)=>{switch(n.setBuildFEOptions(a),e){case`dev`:r.dev();break;case`prod`:i.prod();break;default:t.logRed(`Usage: npx tsx scripts/index.ts [dev|prod]`)}};exports.buildBE=e.buildBE,exports.buildFE=a;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../../_virtual/_rolldown/runtime.js`),t=require(`./env.js`),n=require(`../utils/path.js`),r=require(`../utils/runFileFn.js`);require(`../utils/index.js`);const i=require(`./loader/config.js`),a=require(`./loader/controller.js`),o=require(`./loader/extend.js`),s=require(`./loader/middleware.js`),c=require(`./loader/model.js`),l=require(`./loader/router.js`),u=require(`./loader/router-schema.js`),d=require(`./loader/service.js`);let f=require(`koa`);f=e.__toESM(f,1);var p=c,m=async(e={})=>{let r=new f.default;r.hooks={},r.hooks.initing?.();let{frameBaseDir:c,baseDir:p,...m}=e;r.options=m,r.options.pageBasePage=r.options.pageBasePage??`/`;let g=r.baseDir=p??process.cwd(),_=r.frameBaseDir=c??n.resolve(__dirname,`..`);r.businessAppPath=n.resolve(g,`app`),r.frameAppPath=n.resolve(_,`app`),r.paths=[{type:`frame`,path:r.frameAppPath},{type:`business`,path:r.businessAppPath}],r.envs=t(),console.log(`-- [init] env: ${r.envs.get()} --`),i(r),console.log(`-- [init] loader config done --`),await o(r),console.log(`-- [init] loader extend done --`),u(r),console.log(`-- [init] loader routerSchema done --`),d(r),console.log(`-- [init] loader service done --`),a(r),console.log(`-- [init] loader controller done --`),s(r),console.log(`-- [init] loader middleware done --`),h(r),l(r),console.log(`-- [init] loader router done --`);try{let e=r.config.port||process.env.PORT||`8080`,n=parseInt(e+``,10),i=process.env.IP||`0.0.0.0`;r.listen(n,i),console.log(`Server port: http://localhost:${n}`),console.log(`run env is ${t().get()}`)}catch(e){console.error(e)}return r.hooks.inited?.(),r},h=e=>{e.paths.forEach(t=>{for(let i of[`ts`,`js`]){let a=`middleware.${i}`,o=n.resolve(t.path,a);try{r.runFileFn(o)?.(e),console.log(`-- [init] loader ${t.type} appMiddleware ${a} done -- (${t.path})`);break}catch(e){let n=e;if(!(n?.code===`MODULE_NOT_FOUND`&&typeof n?.message==`string`&&(n.message.includes(`Cannot find module '${o}'`)||n.message.includes(`Cannot find module "${o}"`))))throw console.log(`-- [error] loader ${t.type} appMiddleware ${a} error -- (${o})`),e}}})};exports.loaderModel=p,exports.start=m;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../../_virtual/_rolldown/runtime.js`),t=require(`./env.js`),n=require(`../utils/path.js`),r=require(`../utils/runFileFn.js`);require(`../utils/index.js`);const i=require(`./loader/config.js`),a=require(`./loader/controller.js`),o=require(`./loader/extend.js`),s=require(`./loader/middleware.js`),c=require(`./loader/model.js`),l=require(`./loader/router.js`),u=require(`./loader/router-schema.js`),d=require(`./loader/service.js`);let f=require(`koa`);f=e.__toESM(f,1);var p=c,m=async(e={})=>{let r=new f.default;r.hooks={},r.hooks.initing?.();let{frameBaseDir:c,baseDir:p,...m}=e;r.options=m,r.options.pageBasePage=r.options.pageBasePage??`/`,r.options.apiPrefix=r.options.apiPrefix||`/api`;let g=r.baseDir=p??process.cwd(),_=r.frameBaseDir=c??n.resolve(__dirname,`..`);r.businessAppPath=n.resolve(g,`app`),r.frameAppPath=n.resolve(_,`app`),r.paths=[{type:`frame`,path:r.frameAppPath},{type:`business`,path:r.businessAppPath}],r.envs=t(),console.log(`-- [init] env: ${r.envs.get()} --`),i(r),console.log(`-- [init] loader config done --`),await o(r),console.log(`-- [init] loader extend done --`),u(r),console.log(`-- [init] loader routerSchema done --`),d(r),console.log(`-- [init] loader service done --`),a(r),console.log(`-- [init] loader controller done --`),s(r),console.log(`-- [init] loader middleware done --`),h(r),l(r),console.log(`-- [init] loader router done --`);try{let e=r.config.port||process.env.PORT||`8080`,n=parseInt(e+``,10),i=process.env.IP||`0.0.0.0`;r.listen(n,i),console.log(`Server port: http://localhost:${n}`),console.log(`run env is ${t().get()}`)}catch(e){console.error(e)}return r.hooks.inited?.(),r},h=e=>{e.paths.forEach(t=>{for(let i of[`ts`,`js`]){let a=`middleware.${i}`,o=n.resolve(t.path,a);try{r.runFileFn(o)?.(e),console.log(`-- [init] loader ${t.type} appMiddleware ${a} done -- (${t.path})`);break}catch(e){let n=e;if(!(n?.code===`MODULE_NOT_FOUND`&&typeof n?.message==`string`&&(n.message.includes(`Cannot find module '${o}'`)||n.message.includes(`Cannot find module "${o}"`))))throw console.log(`-- [error] loader ${t.type} appMiddleware ${a} error -- (${o})`),e}}})};exports.loaderModel=p,exports.start=m;
@@ -1,23 +1,25 @@
1
1
  //#region app/controller/view.ts
2
2
  var e = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029"), t = ((t) => class {
3
3
  async renderPage(n) {
4
- t.extends.logger.log(" - render page: " + n.params.page);
5
- try {
6
- t.extends.renderView(`dist/${n.params.page}.entry.tpl`, n, {
7
- projKey: n.query.projk,
8
- name: t.options?.name,
9
- env: t.envs.get(),
10
- basePath: e(`${t.options.pageBasePage}${n.params.page}`),
11
- projKeyJson: e(String(n.query.projk ?? "")),
12
- signKey: e(t.config.signKey),
13
- options: e(t.options)
14
- });
15
- } catch (e) {
16
- console.log("-------------- render view error --------------"), console.log(e), t.extends.generateErrorMessage("template not found", {
17
- status: 404,
18
- showError: !0,
19
- code: 0
20
- });
4
+ if (!n.path.includes(`${t.options.apiPrefix}/`)) {
5
+ t.extends.logger.log(" - render page: " + n.params.page);
6
+ try {
7
+ t.extends.renderView(`dist/${n.params.page}.entry.tpl`, n, {
8
+ projKey: n.query.projk,
9
+ name: t.options?.name,
10
+ env: t.envs.get(),
11
+ basePath: e(`${t.options.pageBasePage}${n.params.page}`),
12
+ projKeyJson: e(String(n.query.projk ?? "")),
13
+ signKey: e(t.config.signKey),
14
+ options: e(t.options)
15
+ });
16
+ } catch (e) {
17
+ console.log("-------------- render view error --------------"), console.log(e), t.extends.generateErrorMessage("template not found", {
18
+ status: 404,
19
+ showError: !0,
20
+ code: 0
21
+ });
22
+ }
21
23
  }
22
24
  }
23
25
  });
@@ -8,7 +8,7 @@ var t = new e(), n = "http://json-schema.org/draft-07/schema#", r = (e, r, i) =>
8
8
  }
9
9
  }, i = (e) => async (n, i) => {
10
10
  let { path: a, method: o } = n;
11
- if (a.indexOf("/api") < 0) return i();
11
+ if (a.indexOf(e.options.apiPrefix) < 0) return i();
12
12
  let s = e.currentUseRouter, c;
13
13
  for (let e of s) {
14
14
  let t = e.match(a, "get");
@@ -2,7 +2,7 @@ import e from "md5";
2
2
  //#region app/middleware/api-sign-verify.ts
3
3
  var t = 1e3 * 60 * 10, n = (n) => async (r, i) => {
4
4
  let { path: a, method: o } = r;
5
- if (a.includes("api") && !n.config?.apiSignVerify?.whiteList?.includes(a)) {
5
+ if (a.includes(n.options.apiPrefix) && !n.config?.apiSignVerify?.whiteList?.includes(a)) {
6
6
  n.extends.logger?.info("--[api-sign-verify]--");
7
7
  let { s_sign: i, s_t: s } = r.request.header, { signKey: c } = n.config, l = e(`${c}_${s}`);
8
8
  n.extends.logger?.info(`[${o} ${a}] signature: ${l}`), (!s || !i || i.toLowerCase() !== l || Date.now() - Number(s) > t) && n.extends.generateErrorMessage("signature not correct or api timeout", {
@@ -1,15 +1,15 @@
1
1
  //#region app/middleware/project-handler.ts
2
- var e = () => async (e, t) => {
3
- let n = e.headers.projk, { path: r } = e;
4
- if (r.indexOf("/api/proj/") !== -1 && (!n || ["undefined", "null"].includes(n))) {
5
- e.status = 200, e.body = {
2
+ var e = (e) => async (t, n) => {
3
+ let r = t.headers.projk, { path: i } = t;
4
+ if (i.indexOf(`${e.options.apiPrefix}/proj/`) !== -1 && (!r || ["undefined", "null"].includes(r))) {
5
+ t.status = 200, t.body = {
6
6
  code: 110,
7
7
  data: null,
8
8
  message: "请求不合法"
9
9
  };
10
10
  return;
11
11
  }
12
- e.extInfo = { key: n }, await t();
12
+ t.extInfo = { key: r }, await n();
13
13
  };
14
14
  //#endregion
15
15
  export { e as default };
@@ -1,7 +1,7 @@
1
1
  //#region app/router/project.ts
2
2
  var e = (e, t) => {
3
- let { project: n } = e.controller;
4
- t.get("/api/project/model_list", n.getModelList), t.get("/api/project/list", n.getProjectList), t.get("/api/project/:key", n.getProject);
3
+ let { project: n } = e.controller, { apiPrefix: r } = e.options;
4
+ t.get(`${r}/project/model_list`, n.getModelList), t.get(`${r}/project/list`, n.getProjectList), t.get(`${r}/project/:key`, n.getProject);
5
5
  };
6
6
  //#endregion
7
7
  export { e as default };
@@ -1,6 +1,6 @@
1
1
  import { buildEntries as e } from "../scripts/vite-build/build.js";
2
2
  import "../scripts/vite-build/index.js";
3
- //#region bundler/buildFB.ts
3
+ //#region bundler/buildBE.ts
4
4
  var t = [
5
5
  "index.ts",
6
6
  "index.js",
@@ -51,4 +51,4 @@ async function n(n = {}) {
51
51
  });
52
52
  }
53
53
  //#endregion
54
- export { n as buildFB };
54
+ export { n as buildBE };
@@ -1,4 +1,4 @@
1
- import { buildFB as e } from "./buildFB.js";
1
+ import { buildBE as e } from "./buildBE.js";
2
2
  import { logRed as t } from "../packages/common/log/index.js";
3
3
  import { setBuildFEOptions as n } from "./state.js";
4
4
  import { dev as r } from "./dev.js";
@@ -16,4 +16,4 @@ var a = (e, a) => {
16
16
  }
17
17
  };
18
18
  //#endregion
19
- export { e as buildFB, a as buildFE };
19
+ export { e as buildBE, a as buildFE };
@@ -16,7 +16,7 @@ var f = s, p = async (n = {}) => {
16
16
  let s = new d();
17
17
  s.hooks = {}, s.hooks.initing?.();
18
18
  let { frameBaseDir: f, baseDir: p, ...h } = n;
19
- s.options = h, s.options.pageBasePage = s.options.pageBasePage ?? "/";
19
+ s.options = h, s.options.pageBasePage = s.options.pageBasePage ?? "/", s.options.apiPrefix = s.options.apiPrefix || "/api";
20
20
  let g = s.baseDir = p ?? process.cwd(), _ = s.frameBaseDir = f ?? t(__dirname, "..");
21
21
  s.businessAppPath = t(g, "app"), s.frameAppPath = t(_, "app"), s.paths = [{
22
22
  type: "frame",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_tc/template-core",
3
- "version": "0.0.1-bate.47",
3
+ "version": "0.0.1-bate.49",
4
4
  "description": "A full-stack TypeScript admin framework powered by Koa, React, and Vite - monorepo root",
5
5
  "types": "./types/index.d.ts",
6
6
  "exports": {
@@ -1,4 +1,4 @@
1
- import type { Ctx } from "../type";
2
1
  import type Koa from "koa";
3
- declare const _default: () => (ctx: Ctx, next: Koa.Next) => Promise<void>;
2
+ import type { Ctx, KoaApp } from "../type";
3
+ declare const _default: (app: KoaApp) => (ctx: Ctx, next: Koa.Next) => Promise<void>;
4
4
  export default _default;
@@ -1,4 +1,4 @@
1
- import type { KoaApp } from "../type";
2
1
  import type Router from "koa-router";
2
+ import type { KoaApp } from "../type";
3
3
  declare const _default: (app: KoaApp, router: Router) => void;
4
4
  export default _default;
@@ -0,0 +1,13 @@
1
+ import type { Alias } from "vite";
2
+ import type { BuildEntriesConfig, BuildEntriesResult, BuildFormat } from "../scripts/vite-build/types";
3
+ export type BuildBEAlias = Record<string, string> | Alias[];
4
+ export type BuildBEFormat = BuildFormat;
5
+ export type BuildBEOptions = Omit<BuildEntriesConfig, 'input' | 'format' | 'outDir' | 'rootDir' | 'alias'> & {
6
+ rootDir?: string;
7
+ input?: string[];
8
+ outDir?: string;
9
+ format?: BuildBEFormat | BuildBEFormat[];
10
+ alias?: BuildBEAlias;
11
+ };
12
+ export type BuildBEResult = BuildEntriesResult;
13
+ export declare function buildBE(options?: BuildBEOptions): Promise<BuildBEResult>;
@@ -1,4 +1,4 @@
1
1
  import type { BuildFEOptions } from "./state";
2
- export { buildFB } from "./buildFB";
3
- export type { BuildFBAlias, BuildFBFormat, BuildFBOptions, BuildFBResult } from "./buildFB";
2
+ export { buildBE } from "./buildBE";
3
+ export type { BuildBEAlias, BuildBEFormat, BuildBEOptions, BuildBEResult } from "./buildBE";
4
4
  export declare const buildFE: (mode: "dev" | "prod", options?: BuildFEOptions) => void;
@@ -109,6 +109,13 @@ export interface StartOptions {
109
109
  * - 主要是配合 buildFE 修改了输出路径时使用
110
110
  */
111
111
  additionalPublicPaths?: string | string[];
112
+ /**
113
+ * API 访问前缀。
114
+ * 为空或空字符串时会使用默认值 `/api`。
115
+ * 配置后会影响内置项目 API 路由,以及 API 参数校验、签名校验、项目处理等中间件的路径匹配。
116
+ * @default '/api'
117
+ */
118
+ apiPrefix?: string;
112
119
  /**
113
120
  * - 页面访问前缀 默认为 /
114
121
  * @default '/'
@@ -1,13 +0,0 @@
1
- import type { Alias } from "vite";
2
- import type { BuildEntriesConfig, BuildEntriesResult, BuildFormat } from "../scripts/vite-build/types";
3
- export type BuildFBAlias = Record<string, string> | Alias[];
4
- export type BuildFBFormat = BuildFormat;
5
- export type BuildFBOptions = Omit<BuildEntriesConfig, 'input' | 'format' | 'outDir' | 'rootDir' | 'alias'> & {
6
- rootDir?: string;
7
- input?: string[];
8
- outDir?: string;
9
- format?: BuildFBFormat | BuildFBFormat[];
10
- alias?: BuildFBAlias;
11
- };
12
- export type BuildFBResult = BuildEntriesResult;
13
- export declare function buildFB(options?: BuildFBOptions): Promise<BuildFBResult>;