@_tc/template-core 0.0.1-bate.53 → 0.0.1-bate.55
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/.skills/tc-component-usage-skills/SKILL.md +2 -0
- package/.skills/tc-component-usage-skills/reference/component-api.md +3 -3
- package/.skills/tc-component-usage-skills/reference/patterns.md +2 -2
- package/.skills/tc-component-usage-skills/reference/template-core-frontend.md +1 -1
- package/.skills/tc-generator/reference/project-template/model/product/project/default.js +2 -0
- package/.skills/tc-generator/reference/runtime-extensions.md +70 -0
- package/AGENT_README.md +67 -12
- package/README.md +94 -13
- package/cjs/app/extend/crypto.js +1 -0
- package/cjs/app/view/entry.tpl +3 -0
- package/cjs/bundler/index.js +1 -1
- package/cjs/bundler/utils.js +3 -1
- package/esm/app/extend/crypto.js +31 -0
- package/esm/app/view/entry.tpl +3 -0
- package/esm/bundler/index.js +3 -7
- package/esm/bundler/utils.js +94 -59
- package/fe/frontend/apps/dash/Dashboard.js +2 -2
- package/fe/frontend/apps/ui-components/index.js +2 -10
- package/fe/frontend/src/common/auth/index.d.ts +6 -2
- package/fe/frontend/src/common/auth/index.js +13 -10
- package/fe/frontend/src/common/request.js +1 -1
- package/fe/frontend/src/index.d.ts +2 -1
- package/fe/frontend/src/index.js +2 -1
- package/fe/frontend/src/stores/mode.js +1 -1
- package/fe/model/types/data/component.d.ts +2 -2
- package/model/frontend/src/common/auth/index.d.ts +6 -2
- package/model/model/types/data/component.d.ts +2 -2
- package/package.json +1 -1
- package/types/app/extend/crypto.d.ts +64 -0
- package/types/app/type.d.ts +1 -0
- package/types/app/typings.d.ts +2 -0
- package/types/bundler/index.d.ts +1 -1
- package/types/index.d.ts +1 -0
|
@@ -119,6 +119,8 @@ When consuming the published TemplateCore package, `@_tc/template-core/fe/rc` re
|
|
|
119
119
|
import { Button, DataTable, TableSearch } from '@_tc/template-core/fe/rc'
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
Inside this source repository, use `@tc/ui-react` for the raw UI workspace package. The `@_tc/template-core/fe/rc` path is a published TemplateCore package export generated into `dist/package.json`.
|
|
123
|
+
|
|
122
124
|
Style entry:
|
|
123
125
|
|
|
124
126
|
```tsx
|
|
@@ -310,7 +310,7 @@ interface SchemaFormProps<T> {
|
|
|
310
310
|
buttonClassName?: string
|
|
311
311
|
submitButtonProps?: Omit<ButtonElementProps, 'type' | 'onClick' | 'children'>
|
|
312
312
|
cancelButtonProps?: Omit<ButtonElementProps, 'onClick' | 'children'>
|
|
313
|
-
preserve?: boolean // default
|
|
313
|
+
preserve?: boolean // passed through to rc-field-form; TemplateCore does not set a default
|
|
314
314
|
}
|
|
315
315
|
```
|
|
316
316
|
|
|
@@ -709,8 +709,8 @@ Max 5 visible messages.
|
|
|
709
709
|
|
|
710
710
|
```typescript
|
|
711
711
|
interface NotificationProps {
|
|
712
|
-
type
|
|
713
|
-
title
|
|
712
|
+
type?: 'info' | 'success' | 'warning' | 'error' // default: 'info'
|
|
713
|
+
title?: ReactNode
|
|
714
714
|
message?: ReactNode
|
|
715
715
|
duration?: number
|
|
716
716
|
closable?: boolean
|
|
@@ -350,10 +350,10 @@ import { Notification } from '@tc/ui-react'
|
|
|
350
350
|
|
|
351
351
|
## Compound Component Pattern
|
|
352
352
|
|
|
353
|
-
Three
|
|
353
|
+
Three component families use context-based composition:
|
|
354
354
|
|
|
355
355
|
1. **Menu**: `<Menu>` + `<Menu.Item>` + `<Menu.SubMenu>` + `<Menu.CollapseToggle>`
|
|
356
|
-
2. **Radio**: `<
|
|
356
|
+
2. **Radio**: `<RadioGroup>` + `<Radio>`
|
|
357
357
|
3. **Breadcrumb**: `<Breadcrumb>` + `<BreadcrumbList>` + `<BreadcrumbItem>` + ...
|
|
358
358
|
|
|
359
359
|
Children auto-detect parent via Context and inherit configuration.
|
|
@@ -9,7 +9,7 @@ import { AsyncSelect, LanguageSwitch, ThemeSwitch } from '@_tc/template-core/fe'
|
|
|
9
9
|
import { Button, DataTable, TableSearch } from '@_tc/template-core/fe/rc'
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Inside this repository, `@tc/ui-react`
|
|
12
|
+
Inside this repository, use `@tc/ui-react` for the raw UI workspace package. In published business projects, prefer `@_tc/template-core/fe/rc` for UI components and `@_tc/template-core/fe` for frontend runtime helpers.
|
|
13
13
|
|
|
14
14
|
## LanguageSwitch
|
|
15
15
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
name: "Default Product Project",
|
|
3
3
|
desc: "Project-level overrides can be added here.",
|
|
4
|
+
// Single-project scaffolds can include projk directly. For shared multi-project
|
|
5
|
+
// models, prefer keeping mode.js project-neutral and override homePage per project.
|
|
4
6
|
homePage: "/_sidebar_/product?projk=default",
|
|
5
7
|
};
|
|
@@ -76,6 +76,74 @@ module.exports = (app) => {
|
|
|
76
76
|
|
|
77
77
|
If the runtime does not support the built-in SQLite module or the project needs another store, generate `app/extend/db.js` or `app/extend/db.ts` and preserve the same method names so existing controllers keep working.
|
|
78
78
|
|
|
79
|
+
## Default Crypto
|
|
80
|
+
|
|
81
|
+
`app.extends.crypto` is available by default for backend code. It uses `app.config.signKey` as the default signing secret.
|
|
82
|
+
|
|
83
|
+
Use it for lightweight signed payloads, auth tokens, API signatures, base64url encoding, and constant-time string comparison.
|
|
84
|
+
|
|
85
|
+
Common methods:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
app.extends.crypto.base64urlEncode(value)
|
|
89
|
+
app.extends.crypto.base64urlDecode(value)
|
|
90
|
+
app.extends.crypto.base64urlDecodeToBuffer(value)
|
|
91
|
+
app.extends.crypto.hmacSign(payload, { secret, algorithm })
|
|
92
|
+
app.extends.crypto.safeEqual(left, right)
|
|
93
|
+
app.extends.crypto.createSignedPayload(payload, { secret, algorithm, separator })
|
|
94
|
+
app.extends.crypto.verifySignedPayload(token, { secret, algorithm, separator })
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Signed payload example:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
module.exports = (app) => {
|
|
101
|
+
return class AuthService {
|
|
102
|
+
createToken(account) {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
|
|
105
|
+
return app.extends.crypto.createSignedPayload({
|
|
106
|
+
account,
|
|
107
|
+
exp: now + 1000 * 60 * 60 * 24 * 7,
|
|
108
|
+
iat: now,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
verifyToken(token) {
|
|
113
|
+
const payload = app.extends.crypto.verifySignedPayload(
|
|
114
|
+
token && token.replace(/^Bearer\s+/i, "")
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (!payload || Date.now() > payload.exp) return null;
|
|
118
|
+
|
|
119
|
+
return payload;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
comparePassword(inputPassword, storedPassword) {
|
|
123
|
+
return app.extends.crypto.safeEqual(inputPassword, storedPassword);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Custom signing options:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
const token = app.extends.crypto.createSignedPayload(
|
|
133
|
+
{ id: 1, scope: "admin" },
|
|
134
|
+
{
|
|
135
|
+
secret: "custom-secret",
|
|
136
|
+
algorithm: "sha512",
|
|
137
|
+
separator: ".",
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const payload = app.extends.crypto.verifySignedPayload(token, {
|
|
142
|
+
secret: "custom-secret",
|
|
143
|
+
algorithm: "sha512",
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
79
147
|
## Dashboard Component Slot
|
|
80
148
|
|
|
81
149
|
Use `frontend/extended/dash/components.tsx` to fill supported Dashboard slots. The current slot is:
|
|
@@ -177,6 +245,8 @@ Import UI components from the published UI re-export:
|
|
|
177
245
|
import { Button, DataTable, TableSearch } from '@_tc/template-core/fe/rc'
|
|
178
246
|
```
|
|
179
247
|
|
|
248
|
+
Inside this source repository, use `@tc/ui-react` for the raw UI workspace package. The `@_tc/template-core/fe/rc` path is for published TemplateCore consumers.
|
|
249
|
+
|
|
180
250
|
`LanguageSwitch` updates the shared i18n language and persists through `tc_language`. `ThemeSwitch` toggles the root `dark` class and persists through `tc_theme`. `AsyncSelect` loads options from a `fetchConfig` endpoint returning `{ label, value, searchText? }[]`.
|
|
181
251
|
|
|
182
252
|
## SchemaForm Extensions
|
package/AGENT_README.md
CHANGED
|
@@ -12,7 +12,7 @@ TemplateCore 是一个 TypeScript + Koa + React 的后台框架包。它提供
|
|
|
12
12
|
- 前端构建 `buildFE()` 和消费方 Node/backend 构建 `buildBE()`。
|
|
13
13
|
- 类型扩展机制,用于扩展 SchemaForm 字段、CallCom 组件和 Koa app 类型。
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
优先使用公开入口,不要从包内部源码路径导入。下面这些入口以 npm 发布产物为准;源码仓库根 `package.json` 的 `exports` 会在构建发布包时生成到 `dist/package.json`。
|
|
16
16
|
|
|
17
17
|
| 入口 | 用途 |
|
|
18
18
|
| --- | --- |
|
|
@@ -83,11 +83,11 @@ export default {
|
|
|
83
83
|
http://localhost:9000/dash?projk=demo
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
`projk` 对应 `model/{modelKey}/project/{projectKey}.ts
|
|
86
|
+
`projk` 对应 `model/{modelKey}/project/{projectKey}.ts` 里的 `projectKey`,运行时按 project key 全局查找,建议保持全局唯一。
|
|
87
87
|
|
|
88
88
|
## 3. 后端约定
|
|
89
89
|
|
|
90
|
-
`serverStart({ baseDir })`
|
|
90
|
+
`serverStart({ baseDir })` 会按目录自动加载业务代码;`model/` 由内置 project service 通过 `modelLoader(app)` 读取,不会在 `serverStart` 阶段直接挂到 `app.model`:
|
|
91
91
|
|
|
92
92
|
```text
|
|
93
93
|
app/controller/**/*.(js|ts) -> app.controller
|
|
@@ -99,7 +99,7 @@ app/router-schema/**/*.(js|ts) -> app.routerSchema
|
|
|
99
99
|
app/extend/*.(js|ts) -> app.extends
|
|
100
100
|
config/config.default.(js|ts) -> app.config
|
|
101
101
|
config/config.{env}.(js|ts) -> app.config
|
|
102
|
-
model/**/*.(js|ts) ->
|
|
102
|
+
model/**/*.(js|ts) -> 内置 project service 读取的项目模型配置
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
Controller 示例:
|
|
@@ -184,6 +184,7 @@ export default (_app: KoaApp) => {
|
|
|
184
184
|
内置扩展:
|
|
185
185
|
|
|
186
186
|
- `app.extends.$fetch`:Node 侧 axios 风格 fetch 实例。
|
|
187
|
+
- `app.extends.crypto`:Node 侧加密辅助方法,支持 base64url 编解码、HMAC 签名、签名 payload 和常量时间比较。
|
|
187
188
|
- `app.extends.db`:默认 SQLite 数据存储,可用业务 `app/extend/db.ts` 覆盖。
|
|
188
189
|
|
|
189
190
|
## 4. model 配置
|
|
@@ -361,11 +362,64 @@ import { Button, DataTable, Form, Input, Modal, Select } from '@_tc/template-cor
|
|
|
361
362
|
- 请求方法:`request`、`get`、`post`、`put`、`patch`、`del`。
|
|
362
363
|
- Schema 通信:`schemaEventBus`、`eventsInfo`、`merge`。
|
|
363
364
|
- 共享状态:`modeStore`、`schemaStore`、`apiFreezerStore` 及对应 hooks。
|
|
364
|
-
- 多语言:`i18nStore.addResources()`,配置文案可写 `$i18n::...`。
|
|
365
|
+
- 多语言:`i18nStore.getState().addResources()`,配置文案可写 `$i18n::...`。
|
|
365
366
|
- 主题:`ThemeSwitch` 会同步根节点 `dark` class 和 `localStorage`。
|
|
366
367
|
|
|
367
368
|
不要直接依赖 `frontend/src/...`、`packages/react/ui/...` 这类包内部路径。
|
|
368
369
|
|
|
370
|
+
### 自定义前端入口和 HTML 插槽
|
|
371
|
+
|
|
372
|
+
前端入口必须命名为 `.entry.tsx`、`.entry.ts`、`.entry.jsx` 或 `.entry.js`,否则 `buildFE()` 不会扫描。
|
|
373
|
+
|
|
374
|
+
最小入口:
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
// frontend/admin/admin.entry.tsx
|
|
378
|
+
import { initApp } from '@_tc/template-core/fe'
|
|
379
|
+
import type { ReactNode } from 'react'
|
|
380
|
+
|
|
381
|
+
const AdminApp = ({ children }: { children: ReactNode }) => (
|
|
382
|
+
<main>{children}</main>
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
initApp(AdminApp, {
|
|
386
|
+
initModeData: false,
|
|
387
|
+
})
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
如果需要给页面加额外 `meta`、预连接资源或启动脚本,可以在入口同目录放 `.html` 插槽文件。这个文件不是完整 HTML,只能包含 `tc-slot` 块:
|
|
391
|
+
|
|
392
|
+
```html
|
|
393
|
+
<!-- frontend/admin/admin.html -->
|
|
394
|
+
<!-- tc-slot:head -->
|
|
395
|
+
<meta name="description" content="Admin Console" />
|
|
396
|
+
<link rel="preconnect" href="https://example.com" />
|
|
397
|
+
<!-- /tc-slot:head -->
|
|
398
|
+
|
|
399
|
+
<!-- tc-slot:body-before-root -->
|
|
400
|
+
<div id="boot-mask"></div>
|
|
401
|
+
<!-- /tc-slot:body-before-root -->
|
|
402
|
+
|
|
403
|
+
<!-- tc-slot:body-after-root -->
|
|
404
|
+
<script>
|
|
405
|
+
window.__ADMIN_BOOT_TIME__ = Date.now()
|
|
406
|
+
</script>
|
|
407
|
+
<!-- /tc-slot:body-after-root -->
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
命名规则:
|
|
411
|
+
|
|
412
|
+
```text
|
|
413
|
+
frontend/admin/admin.entry.tsx
|
|
414
|
+
frontend/admin/admin.html yes
|
|
415
|
+
|
|
416
|
+
frontend/report/index.entry.tsx
|
|
417
|
+
frontend/report/index.html yes
|
|
418
|
+
frontend/report/report.html yes
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
不要在插槽文件里写 `<!DOCTYPE html>`、`<html>`、`<body>` 或 `<div id="root"></div>`。页面外壳由 TemplateCore 的 `app/view/entry.tpl` 统一生成,`window._basePath`、`window._projKey`、`window._signKey`、`window.appOptions` 也由它注入。构建器会把合成后的临时 HTML 写入 `.tc-entry-html/`,这个目录不要提交,也不要在业务代码中引用。
|
|
422
|
+
|
|
369
423
|
## 6. 构建
|
|
370
424
|
|
|
371
425
|
前端资源构建:
|
|
@@ -495,8 +549,9 @@ chokidar
|
|
|
495
549
|
|
|
496
550
|
1. 在业务 `frontend/` 下添加入口 `*.entry.tsx`。
|
|
497
551
|
2. 需要样式时引入 `@_tc/template-core/fe/tailwind_ui.css`。
|
|
498
|
-
3.
|
|
499
|
-
4.
|
|
552
|
+
3. 如需改 head 或 root 前后内容,只添加入口同目录 `.html` 插槽文件,不要写完整 HTML。
|
|
553
|
+
4. 在 `model` 菜单中使用 `moduleType: 'custom'`,让 `customConfig.path` 指向页面路径。
|
|
554
|
+
5. 使用 `buildFE('dev')` 或 `buildFE('prod')` 生成资源。
|
|
500
555
|
|
|
501
556
|
扩展前端类型或组件:
|
|
502
557
|
|
|
@@ -513,11 +568,11 @@ chokidar
|
|
|
513
568
|
| 任务 | 建议阅读 |
|
|
514
569
|
| --- | --- |
|
|
515
570
|
| 完整安装和使用说明 | npm 包根目录 `README.md` |
|
|
516
|
-
| 生成项目、model、CRUD |
|
|
517
|
-
| 组件选型和用法 |
|
|
518
|
-
| model/schema 配置细节 |
|
|
519
|
-
| 完整 CRUD 示例 |
|
|
520
|
-
| 可复制项目骨架 |
|
|
571
|
+
| 生成项目、model、CRUD | npm 包:`.skills/tc-generator/SKILL.md`;源码仓库:`skills/tc-generator/SKILL.md` |
|
|
572
|
+
| 组件选型和用法 | npm 包:`.skills/tc-component-usage-skills/SKILL.md`;源码仓库:`skills/tc-component-usage-skills/SKILL.md` |
|
|
573
|
+
| model/schema 配置细节 | npm 包:`.skills/tc-generator/reference/model-schema.md`;源码仓库:`skills/tc-generator/reference/model-schema.md` |
|
|
574
|
+
| 完整 CRUD 示例 | npm 包:`.skills/tc-generator/reference/example.md`;源码仓库:`skills/tc-generator/reference/example.md` |
|
|
575
|
+
| 可复制项目骨架 | npm 包:`.skills/tc-generator/reference/project-template/`;源码仓库:`skills/tc-generator/reference/project-template/` |
|
|
521
576
|
| UI 组件现场预览 | 启动服务后访问 `/ui-components` |
|
|
522
577
|
| 已安装包的类型细节 | 查看对应公开入口的 `.d.ts` 文件 |
|
|
523
578
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ pnpm add @_tc/template-core
|
|
|
12
12
|
|
|
13
13
|
- Node.js >= 18
|
|
14
14
|
- pnpm >= 8
|
|
15
|
-
- 使用内置前端时,需要安装 React、Vite
|
|
15
|
+
- 使用内置前端时,需要安装 peer dependencies;完整清单以包内 `package.json` 的 `peerDependencies` 为准,包括 Koa、React、Vite、路由、状态、表单和 UI 相关依赖。
|
|
16
16
|
|
|
17
17
|
## 可选:给项目 AI 助手的文档与 Skill
|
|
18
18
|
|
|
@@ -28,9 +28,9 @@ pnpm add @_tc/template-core
|
|
|
28
28
|
如果你的 Agent 支持本地 skills 目录,可以从已安装的 npm 包复制安装。以 Codex 为例:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
mkdir -p
|
|
32
|
-
cp -R node_modules/@_tc/template-core/.skills/tc-generator
|
|
33
|
-
cp -R node_modules/@_tc/template-core/.skills/tc-component-usage-skills
|
|
31
|
+
mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills"
|
|
32
|
+
cp -R node_modules/@_tc/template-core/.skills/tc-generator "${CODEX_HOME:-$HOME/.codex}/skills/tc-generator"
|
|
33
|
+
cp -R node_modules/@_tc/template-core/.skills/tc-component-usage-skills "${CODEX_HOME:-$HOME/.codex}/skills/tc-component-usage-skills"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
安装后重启对应 Agent,让新 skill 生效。使用时可以直接说”用 tc-generator 生成一个商品管理模块”或”用 tc-component-usage-skills 帮我写一个带搜索的分页表格”。
|
|
@@ -99,7 +99,6 @@ main().catch((error) => {
|
|
|
99
99
|
| `baseDir` | `string` | 否 | - | 项目根目录,通常为 `join(__dirname, '.')` |
|
|
100
100
|
| `pageBasePage` | `string` | 否 | `'/'` | 页面访问前缀,所有前端页面路由的基础路径 |
|
|
101
101
|
| `homePage` | `string` | 否 | - | 应用首页路径 |
|
|
102
|
-
| `frameBaseDir` | `string` | 否 | - | 框架根目录,通常不需要手动设置 |
|
|
103
102
|
| `additionalPublicPaths` | `string | string[]` | 否 | - | 追加静态资源目录和 Nunjucks 模板查找目录,必须传绝对路径 |
|
|
104
103
|
|
|
105
104
|
框架会自动读取 `config/config.default.ts` 中的配置(如 `port`、`env` 等)。
|
|
@@ -117,7 +116,7 @@ buildFE(mode: 'dev' | 'prod', options?: BuildFEOptions)
|
|
|
117
116
|
- `'prod'`:生产模式,构建优化后的静态资源
|
|
118
117
|
|
|
119
118
|
**options.output**:构建输出位置
|
|
120
|
-
- `'frame'
|
|
119
|
+
- `'frame'`(默认):输出到当前导入格式对应的框架静态目录,例如 `node_modules/@_tc/template-core/esm/app/public/dist` 或 `node_modules/@_tc/template-core/cjs/app/public/dist`
|
|
121
120
|
- `'run'`:输出到项目根目录的 `app/public/dist`,适合 TypeScript 编译后的项目
|
|
122
121
|
- `() => string`:自定义函数,返回输出目录的绝对路径
|
|
123
122
|
|
|
@@ -153,7 +152,9 @@ http://localhost:9000/dash?projk=demo
|
|
|
153
152
|
|
|
154
153
|
`projk` 对应 `model/{modelKey}/project/{projectKey}.ts` 里的 `projectKey`。
|
|
155
154
|
|
|
156
|
-
##
|
|
155
|
+
## 常用公开入口
|
|
156
|
+
|
|
157
|
+
完整 exports 以发布包内 `package.json` 为准,除下表外还包含 `./fe/main`、`./fe/*`、`./fe/rc/*`、`./fe/rc/hooks/*` 等子路径。
|
|
157
158
|
|
|
158
159
|
| 入口 | 用途 |
|
|
159
160
|
| --- | --- |
|
|
@@ -292,12 +293,12 @@ import type {
|
|
|
292
293
|
|
|
293
294
|
### 前端多语言
|
|
294
295
|
|
|
295
|
-
TemplateCore
|
|
296
|
+
TemplateCore 前端通过 `@_tc/template-core/fe` 暴露 i18n 能力。框架内置中文、英文资源,项目可以继续追加自己的资源。
|
|
296
297
|
|
|
297
298
|
**追加自定义语言资源**:
|
|
298
299
|
|
|
299
300
|
```ts
|
|
300
|
-
import { i18nStore } from '@
|
|
301
|
+
import { i18nStore } from '@_tc/template-core/fe'
|
|
301
302
|
|
|
302
303
|
i18nStore.getState().addResources('zh-CN', {
|
|
303
304
|
business: {
|
|
@@ -383,7 +384,7 @@ const title = '$i18n::business.product.createTitle'
|
|
|
383
384
|
**切换语言**:
|
|
384
385
|
|
|
385
386
|
```ts
|
|
386
|
-
import { i18nStore } from '@
|
|
387
|
+
import { i18nStore } from '@_tc/template-core/fe'
|
|
387
388
|
|
|
388
389
|
i18nStore.getState().setLanguage('en-US')
|
|
389
390
|
i18nStore.getState().setLanguage('zh-CN')
|
|
@@ -582,7 +583,7 @@ try {
|
|
|
582
583
|
| --- | --- | --- |
|
|
583
584
|
| `Modal` | 模态框组件 | `@_tc/template-core/fe/rc` |
|
|
584
585
|
| `Drawer` | 抽屉组件 | `@_tc/template-core/fe/rc` |
|
|
585
|
-
| `
|
|
586
|
+
| `message` | 消息提示方法 | `@_tc/template-core/fe/rc` |
|
|
586
587
|
| `Notification` | 通知组件 | `@_tc/template-core/fe/rc` |
|
|
587
588
|
| `ConfirmDialog` | 确认对话框组件 | `@_tc/template-core/fe/rc` |
|
|
588
589
|
| `Popup` | 弹出层组件 | `@_tc/template-core/fe/rc` |
|
|
@@ -658,6 +659,7 @@ model/**/*.(js|ts) -> 项目模型配置
|
|
|
658
659
|
内置扩展:
|
|
659
660
|
|
|
660
661
|
- `app.extends.$fetch`:Node 侧基于 `fetch` 的 axios 风格请求实例,支持 `get/post/put/patch/delete` 和 `create(config)`。
|
|
662
|
+
- `app.extends.crypto`:Node 侧加密辅助方法,支持 base64url 编解码、HMAC 签名、签名 payload 和常量时间比较。
|
|
661
663
|
- `app.extends.db`:默认 SQLite 框架数据库,提供 `getDBData/setDBData` 等通用数据方法。
|
|
662
664
|
|
|
663
665
|
### 数据库扩展
|
|
@@ -721,6 +723,36 @@ SQL 注入边界:
|
|
|
721
723
|
- `queryDB`、`runDB`、`getDBFirst` 是原始 SQL 入口,用户输入必须放到 `params`,不要拼接到 SQL 字符串里。
|
|
722
724
|
- `execDB` 没有参数绑定能力,只建议用于固定 SQL,例如建表或迁移。
|
|
723
725
|
|
|
726
|
+
### 加密扩展
|
|
727
|
+
|
|
728
|
+
`app.extends.crypto` 默认使用 `app.config.signKey` 作为签名密钥,适合生成登录 token、接口签名和安全比较。
|
|
729
|
+
|
|
730
|
+
常用方法:
|
|
731
|
+
|
|
732
|
+
- `base64urlEncode(value)`:把字符串或二进制数据编码为 base64url。
|
|
733
|
+
- `base64urlDecode(value)`:把 base64url 解码回 UTF-8 字符串。
|
|
734
|
+
- `base64urlDecodeToBuffer(value)`:把 base64url 解码回 Buffer。
|
|
735
|
+
- `hmacSign(payload, options)`:对文本做 HMAC 签名,默认算法 `sha256`。
|
|
736
|
+
- `safeEqual(left, right)`:常量时间比较字符串。
|
|
737
|
+
- `createSignedPayload(payload, options)`:生成 `payload.signature` 形式的签名串。
|
|
738
|
+
- `verifySignedPayload(token, options)`:验证并解析签名串,失败返回 `null`。
|
|
739
|
+
|
|
740
|
+
```ts
|
|
741
|
+
const token = app.extends.crypto.createSignedPayload({
|
|
742
|
+
account: 'admin',
|
|
743
|
+
exp: Date.now() + 1000 * 60 * 60 * 24 * 7,
|
|
744
|
+
iat: Date.now(),
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
const payload = app.extends.crypto.verifySignedPayload<{
|
|
748
|
+
account: string
|
|
749
|
+
exp: number
|
|
750
|
+
iat: number
|
|
751
|
+
}>(token)
|
|
752
|
+
|
|
753
|
+
const same = app.extends.crypto.safeEqual('abc', 'abc')
|
|
754
|
+
```
|
|
755
|
+
|
|
724
756
|
```ts
|
|
725
757
|
// 推荐:参数绑定
|
|
726
758
|
const users = app.extends.db.queryDB(
|
|
@@ -1536,6 +1568,55 @@ frontend/admin/admin.entry.tsx
|
|
|
1536
1568
|
|
|
1537
1569
|
入口文件名必须包含 `.entry.`,构建器才会扫描。
|
|
1538
1570
|
|
|
1571
|
+
最小入口示例:
|
|
1572
|
+
|
|
1573
|
+
```tsx
|
|
1574
|
+
// frontend/admin/admin.entry.tsx
|
|
1575
|
+
import { initApp } from '@_tc/template-core/fe'
|
|
1576
|
+
import type { ReactNode } from 'react'
|
|
1577
|
+
|
|
1578
|
+
const AdminApp = ({ children }: { children: ReactNode }) => (
|
|
1579
|
+
<main>{children}</main>
|
|
1580
|
+
)
|
|
1581
|
+
|
|
1582
|
+
initApp(AdminApp, {
|
|
1583
|
+
initModeData: false,
|
|
1584
|
+
})
|
|
1585
|
+
```
|
|
1586
|
+
|
|
1587
|
+
如果需要给页面加额外 `meta`、预连接资源或启动脚本,可以在入口同目录放 `.html` 插槽文件。这个文件不是完整 HTML,只能包含 `tc-slot` 块:
|
|
1588
|
+
|
|
1589
|
+
```html
|
|
1590
|
+
<!-- frontend/admin/admin.html -->
|
|
1591
|
+
<!-- tc-slot:head -->
|
|
1592
|
+
<meta name="description" content="Admin Console" />
|
|
1593
|
+
<link rel="preconnect" href="https://example.com" />
|
|
1594
|
+
<!-- /tc-slot:head -->
|
|
1595
|
+
|
|
1596
|
+
<!-- tc-slot:body-before-root -->
|
|
1597
|
+
<div id="boot-mask"></div>
|
|
1598
|
+
<!-- /tc-slot:body-before-root -->
|
|
1599
|
+
|
|
1600
|
+
<!-- tc-slot:body-after-root -->
|
|
1601
|
+
<script>
|
|
1602
|
+
window.__ADMIN_BOOT_TIME__ = Date.now()
|
|
1603
|
+
</script>
|
|
1604
|
+
<!-- /tc-slot:body-after-root -->
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
命名规则:
|
|
1608
|
+
|
|
1609
|
+
```text
|
|
1610
|
+
frontend/admin/admin.entry.tsx
|
|
1611
|
+
frontend/admin/admin.html yes
|
|
1612
|
+
|
|
1613
|
+
frontend/report/index.entry.tsx
|
|
1614
|
+
frontend/report/index.html yes
|
|
1615
|
+
frontend/report/report.html yes
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
不要在插槽文件里写 `<!DOCTYPE html>`、`<html>`、`<body>` 或 `<div id="root"></div>`。页面外壳由 TemplateCore 的 `app/view/entry.tpl` 统一生成,`window._basePath`、`window._projKey`、`window._signKey`、`window.appOptions` 也由它注入。
|
|
1619
|
+
|
|
1539
1620
|
扩展 Dashboard 路由:
|
|
1540
1621
|
|
|
1541
1622
|
```tsx
|
|
@@ -1597,7 +1678,7 @@ buildFE('dev')
|
|
|
1597
1678
|
buildFE('prod')
|
|
1598
1679
|
```
|
|
1599
1680
|
|
|
1600
|
-
|
|
1681
|
+
默认输出到当前导入格式对应的框架静态目录,例如 `esm/app/public/dist` 或 `cjs/app/public/dist`。如果要输出到项目:
|
|
1601
1682
|
|
|
1602
1683
|
```ts
|
|
1603
1684
|
buildFE('prod', {
|
|
@@ -1768,4 +1849,4 @@ await buildBE({
|
|
|
1768
1849
|
- Dashboard 登录守卫放在 `frontend/extended/dash/routeGuard.ts`;登录页通常不在 Dash 路由下,返回登录页 URL 即可。
|
|
1769
1850
|
- 服务端兜底守卫放在业务 `app/router-guard.ts|js`,不是 `app/business` 子目录。
|
|
1770
1851
|
- 前端入口必须命名为 `.entry.tsx`、`.entry.ts`、`.entry.jsx` 或 `.entry.js`。
|
|
1771
|
-
-
|
|
1852
|
+
- 入口同目录的 `.html` 只能作为插槽文件使用,不能写完整 HTML 文档;页面外壳始终由 `app/view/entry.tpl` 生成。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require(`../../_virtual/_rolldown/runtime.js`);let e=require(`node:crypto`);var t=`sha256`,n=`.`,r=`template-core-crypto`,i=(e,t)=>String(t??e.config.signKey??r),a=e=>e??t,o=e=>e||n,s=e=>Buffer.isBuffer(e)?e:Buffer.from(e),c=t=>{let n=e=>s(e).toString(`base64url`),r=e=>Buffer.from(e,`base64url`),c=e=>r(e).toString(`utf8`),l=(n,r)=>(0,e.createHmac)(a(r?.algorithm),i(t,r?.secret)).update(n).digest(`base64url`),u=(t,n)=>{let r=Buffer.from(t),i=Buffer.from(n);return r.length===i.length?(0,e.timingSafeEqual)(r,i):!1};return{base64urlEncode:n,base64urlDecode:c,base64urlDecodeToBuffer:r,hmacSign:l,safeEqual:u,createSignedPayload:(e,t)=>{let r=o(t?.separator),i=n(JSON.stringify(e));return`${i}${r}${l(i,t)}`},verifySignedPayload:(e,t)=>{if(!e)return null;let n=o(t?.separator),[r,i,...a]=e.split(n);if(!r||!i||a.length>0||!u(l(r,t),i))return null;try{return JSON.parse(c(r))}catch{return null}}}};module.exports=c;
|
package/cjs/app/view/entry.tpl
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/x-icon" href="/static/logo.png" />
|
|
7
7
|
<title>{{ name }}</title>
|
|
8
|
+
<!-- TC_HEAD_SLOT -->
|
|
8
9
|
</head>
|
|
9
10
|
|
|
10
11
|
<script id="input">
|
|
@@ -22,7 +23,9 @@
|
|
|
22
23
|
</script>
|
|
23
24
|
|
|
24
25
|
<body>
|
|
26
|
+
<!-- TC_BODY_BEFORE_ROOT_SLOT -->
|
|
25
27
|
<div id="root"></div>
|
|
28
|
+
<!-- TC_BODY_AFTER_ROOT_SLOT -->
|
|
26
29
|
</body>
|
|
27
30
|
|
|
28
31
|
<!-- {{InsertScript}} -->
|
package/cjs/bundler/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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();
|
|
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`:return r.dev();case`prod`:return i.prod();default:return t.logRed(`Usage: npx tsx scripts/index.ts [dev|prod]`),Promise.resolve()}};exports.buildBE=e.buildBE,exports.buildFE=a;
|
package/cjs/bundler/utils.js
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.js`),t=require(`../packages/utils/path.js`);require(`../packages/utils/index.js`);const n=require(`../packages/common/log/index.js`),r=require(`./state.js`);let i=require(`path`);i=e.__toESM(i);let a=require(`glob`);a=e.__toESM(a);let o=require(`fs`),s=require(`vite`),c=require(`@tailwindcss/vite`);c=e.__toESM(c);let l=require(`module`),u=require(`url`);var d=typeof __filename==`string`?__filename:(0,u.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,l.createRequire)(d),h=i.default.resolve(f,`../../`),g=i.default.resolve(f,`../`),_=i.default.resolve(h,`./fe/frontend`),v=i.default.resolve(p,`.`),y=i.default.resolve(v,`./frontend`),b=e=>i.default.dirname(m.resolve(`${e}/package.json`))
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.js`),t=require(`../packages/utils/path.js`);require(`../packages/utils/index.js`);const n=require(`../packages/common/log/index.js`),r=require(`./state.js`);let i=require(`path`);i=e.__toESM(i);let a=require(`glob`);a=e.__toESM(a);let o=require(`fs`),s=require(`vite`),c=require(`@tailwindcss/vite`);c=e.__toESM(c);let l=require(`module`),u=require(`url`);var d=typeof __filename==`string`?__filename:(0,u.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,l.createRequire)(d),h=i.default.resolve(f,`../../`),g=i.default.resolve(f,`../`),_=i.default.resolve(h,`./fe/frontend`),v=i.default.resolve(p,`.`),y=i.default.resolve(v,`./frontend`),b=e=>i.default.dirname(m.resolve(`${e}/package.json`)),x=i.default.join(v,`.tc-entry-html`),S=`<!-- {{InsertScript}} -->`,C={head:`<!-- TC_HEAD_SLOT -->`,bodyBeforeRoot:`<!-- TC_BODY_BEFORE_ROOT_SLOT -->`,bodyAfterRoot:`<!-- TC_BODY_AFTER_ROOT_SLOT -->`},w=[`head`,`body-before-root`,`body-after-root`],T=()=>({head:``,"body-before-root":``,"body-after-root":``}),E=e=>e.split(i.default.sep).join(`/`),D=e=>{let t=E(e);return t.startsWith(`.`)||t.startsWith(`/`)?t:`./${t}`},O=e=>/<html[\s>]/i.test(e)&&e.includes(`window['_basePath']`)&&/<div\s+id=["']root["']\s*><\/div>/i.test(e),k=e=>{let t=T(),r=(0,o.readFileSync)(e,`utf-8`),i=/<!--\s*tc-slot:([\w-]+)\s*-->([\s\S]*?)<!--\s*\/tc-slot:\1\s*-->/g,a=!1,s=r;if(r.replace(i,(n,r,i)=>{if(a=!0,!w.includes(r))throw Error(`Invalid html slot "${r}" in ${e}. Allowed slots: ${w.join(`, `)}.`);return t[r]+=i.trim()?`${i}\n`:``,s=s.replace(n,``),n}),s.trim()){if(O(r))return n.logYellow(`ignored legacy generated entry html: ${e}`),T();throw Error([`Invalid custom entry html: ${e}.`,`Custom entry html can only contain tc-slot blocks, for example:`,`<!-- tc-slot:head -->`,`<meta name="description" content="..." />`,`<!-- /tc-slot:head -->`].join(`
|
|
2
|
+
`))}return a&&n.logYellow(`using custom entry html slots: ${e}`),t},A=(e,n)=>{let r=i.default.basename(e),s=[i.default.join(e,`${n}.html`),i.default.join(e,`${r}.html`)].find(e=>(0,o.existsSync)(e));if(s)return s;if(a.sync(t.resolve(e,`*.html`)).length)throw Error([`Entry html filename is not supported in ${e}.`,`Use ${n}.html or ${r}.html as a tc-slot file.`].join(`
|
|
3
|
+
`))},j=(e,t)=>e.replace(C.head,t.head).replace(C.bodyBeforeRoot,t[`body-before-root`]).replace(C.bodyAfterRoot,t[`body-after-root`]),M=(e,t,n,r)=>{let a=i.default.join(x,`${n}.html`),s=D(i.default.relative(i.default.dirname(a),t));return(0,o.writeFileSync)(a,j(e,r).replace(S,`<script type="module" src="${s}"><\/script>`)),a};function N(){n.logWhite(`frameFEPath: `+_),n.logWhite(`runFEPath: `+y);let e=[_,y].reduce((e,n)=>{let r=[`js`,`jsx`,`ts`,`tsx`].map(e=>t.resolve(n,`/**`,`/*.`+e)),i=a.sync(r).filter(e=>e.includes(`.entry.`)).filter(e=>!e.includes(`.d.ts`));return[...e,...i]},[]),r={};if(!e.length)return r;(0,o.rmSync)(x,{recursive:!0,force:!0}),(0,o.mkdirSync)(x,{recursive:!0});let s=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);for(let t=0;t<e.length;t++){let n=e[t],a=i.default.resolve(n,`../`),o=A(a,i.default.basename(n).replace(/\.entry\.[^.]+$/,``)),c=o?i.default.basename(o,`.html`):a.split(i.default.sep).at(-1);r[c]=M(s,n,c,o?k(o):T())}return r}function P(){let e=r.buildFESharedState.options.output??`frame`;return e!==`frame`&&n.logRed(`Please pay attention to the build output.`),e===`run`?i.default.join(v,`./app/public/dist`):typeof e==`function`?e():i.default.join(g,`./app/public/dist`)}var F=async e=>{let t=(await import(`chokidar`)).watch(y,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),r=!1;t.on(`change`,async t=>{if(!r){r=!0,n.logYellow(`changed: ${t}`);try{await e?.()}catch(e){n.logRed(e)}finally{r=!1}}}),n.logYellow(`watching ${y}/...`)};function I(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=P();if(!(0,o.existsSync)(e))return;let r=a.sync(`**/*.html`,{cwd:e,dot:!0});for(let n of r){let r=i.default.join(e,n),a=i.default.basename(n,`.html`),s=`.entry`;a.indexOf(s)===-1&&(a+=s);for(let n of t){let t=a+n,s=i.default.join(e,t);r!==s&&(0,o.copyFileSync)(r,s)}t.some(t=>r===i.default.join(e,a+t))||(0,o.unlinkSync)(r)}let s=[...new Set(r.map(e=>i.default.dirname(e)))].sort().reverse(),c=e=>{if(!((0,o.existsSync)(e)?(0,o.readdirSync)(e):[1]).length){(0,o.rmdirSync)(e);let t=e.split(i.default.sep).slice(0,-1);t.length&&c(t.join(i.default.sep))}};for(let t of s)t&&t!==`.`&&c(i.default.join(e,t));let l=t.flatMap(t=>a.sync(`**/*`+t,{cwd:e,dot:!0}));for(let e of l)n.logYellow(`final file path -----> `+e)}}}function L(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y,s=`./defaultAlias`])=>{let c=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),l=i.default.resolve(f,s);return c&&(l=c),e[n]=l,e},{})}var R=(...e)=>new RegExp(e.join(`[\\/]`)),z=e=>`_${e}_tcC`,B=async(e,t)=>{try{await(0,s.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),I([`.tpl`,`.html`]),(0,c.default)()],build:{outDir:P(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rolldownOptions:{input:e,output:{codeSplitting:{groups:[{name:z(`__nm`),test:R(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:z(`r`),test:R(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:z(`r-d`),test:R(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:z(`r-r-d`),test:R(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:z(`ajv`),test:R(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:z(`ui-lib`),test:R(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:z(`ui-f-lib`),test:R(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),"@tc/ui-react/hooks":i.default.resolve(f,`../../fe/packages/react/hooks/`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/react/ui/`),...L([[`@tc/scalability/SchemaForm/frameData`,`./extended/SchemaForm/data`,_],[`@tc/scalability/SchemaForm/data`,`./extended/SchemaForm/data`],[`@tc/scalability/SchemaPage/CallCom/data`,`./extended/SchemaPage/CallCom/data`],[`@tc/scalability/dash/customRoutes`,`./extended/dash/customRoutes`],[`@tc/scalability/dash/components`,`./extended/dash/components`],[`@tc/scalability/dash/routeGuard`,`./extended/dash/routeGuard`,void 0,`./defaultRouteGuard`]])}}}),n.logJoinColorized(n.colorize(`builded`,`green`),n.colorize(` -> `,`white`),n.colorize(`time: `,`cyan`),n.colorize(new Date,`pink`))}catch(e){if(n.logRed(e),t!==`dev`)throw e}};exports.VBuildFE=B,exports.entries=N,exports.outDir=P,exports.watchFiles=F;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHmac as e, timingSafeEqual as t } from "node:crypto";
|
|
2
|
+
//#region app/extend/crypto.ts
|
|
3
|
+
var n = "sha256", r = ".", i = "template-core-crypto", a = (e, t) => String(t ?? e.config.signKey ?? i), o = (e) => e ?? n, s = (e) => e || r, c = (e) => Buffer.isBuffer(e) ? e : Buffer.from(e), l = (n) => {
|
|
4
|
+
let r = (e) => c(e).toString("base64url"), i = (e) => Buffer.from(e, "base64url"), l = (e) => i(e).toString("utf8"), u = (t, r) => e(o(r?.algorithm), a(n, r?.secret)).update(t).digest("base64url"), d = (e, n) => {
|
|
5
|
+
let r = Buffer.from(e), i = Buffer.from(n);
|
|
6
|
+
return r.length === i.length ? t(r, i) : !1;
|
|
7
|
+
};
|
|
8
|
+
return {
|
|
9
|
+
base64urlEncode: r,
|
|
10
|
+
base64urlDecode: l,
|
|
11
|
+
base64urlDecodeToBuffer: i,
|
|
12
|
+
hmacSign: u,
|
|
13
|
+
safeEqual: d,
|
|
14
|
+
createSignedPayload: (e, t) => {
|
|
15
|
+
let n = s(t?.separator), i = r(JSON.stringify(e));
|
|
16
|
+
return `${i}${n}${u(i, t)}`;
|
|
17
|
+
},
|
|
18
|
+
verifySignedPayload: (e, t) => {
|
|
19
|
+
if (!e) return null;
|
|
20
|
+
let n = s(t?.separator), [r, i, ...a] = e.split(n);
|
|
21
|
+
if (!r || !i || a.length > 0 || !d(u(r, t), i)) return null;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(l(r));
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { l as default };
|
package/esm/app/view/entry.tpl
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/x-icon" href="/static/logo.png" />
|
|
7
7
|
<title>{{ name }}</title>
|
|
8
|
+
<!-- TC_HEAD_SLOT -->
|
|
8
9
|
</head>
|
|
9
10
|
|
|
10
11
|
<script id="input">
|
|
@@ -22,7 +23,9 @@
|
|
|
22
23
|
</script>
|
|
23
24
|
|
|
24
25
|
<body>
|
|
26
|
+
<!-- TC_BODY_BEFORE_ROOT_SLOT -->
|
|
25
27
|
<div id="root"></div>
|
|
28
|
+
<!-- TC_BODY_AFTER_ROOT_SLOT -->
|
|
26
29
|
</body>
|
|
27
30
|
|
|
28
31
|
<!-- {{InsertScript}} -->
|
package/esm/bundler/index.js
CHANGED
|
@@ -6,13 +6,9 @@ import { prod as i } from "./prod.js";
|
|
|
6
6
|
//#region bundler/index.ts
|
|
7
7
|
var a = (e, a) => {
|
|
8
8
|
switch (n(a), e) {
|
|
9
|
-
case "dev":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
case "prod":
|
|
13
|
-
i();
|
|
14
|
-
break;
|
|
15
|
-
default: t("Usage: npx tsx scripts/index.ts [dev|prod]");
|
|
9
|
+
case "dev": return r();
|
|
10
|
+
case "prod": return i();
|
|
11
|
+
default: return t("Usage: npx tsx scripts/index.ts [dev|prod]"), Promise.resolve();
|
|
16
12
|
}
|
|
17
13
|
};
|
|
18
14
|
//#endregion
|
package/esm/bundler/utils.js
CHANGED
|
@@ -4,16 +4,54 @@ import { colorize as t, logJoinColorized as n, logRed as r, logWhite as i, logYe
|
|
|
4
4
|
import { buildFESharedState as o } from "./state.js";
|
|
5
5
|
import s from "path";
|
|
6
6
|
import * as c from "glob";
|
|
7
|
-
import { copyFileSync as l, existsSync as u,
|
|
8
|
-
import { build as
|
|
9
|
-
import
|
|
10
|
-
import { createRequire as
|
|
11
|
-
import { fileURLToPath as
|
|
7
|
+
import { copyFileSync as l, existsSync as u, mkdirSync as d, readFileSync as f, readdirSync as p, rmSync as m, rmdirSync as h, unlinkSync as g, writeFileSync as _ } from "fs";
|
|
8
|
+
import { build as v } from "vite";
|
|
9
|
+
import y from "@tailwindcss/vite";
|
|
10
|
+
import { createRequire as b } from "module";
|
|
11
|
+
import { fileURLToPath as x } from "url";
|
|
12
12
|
//#region bundler/utils.ts
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
var S = typeof __filename == "string" ? __filename : x(import.meta.url), C = s.dirname(S), w = process.cwd(), T = b(S), E = s.resolve(C, "../../"), D = s.resolve(C, "../"), O = s.resolve(E, "./fe/frontend"), k = s.resolve(w, "."), A = s.resolve(k, "./frontend"), j = (e) => s.dirname(T.resolve(`${e}/package.json`)), M = s.join(k, ".tc-entry-html"), N = "<!-- {{InsertScript}} -->", P = {
|
|
14
|
+
head: "<!-- TC_HEAD_SLOT -->",
|
|
15
|
+
bodyBeforeRoot: "<!-- TC_BODY_BEFORE_ROOT_SLOT -->",
|
|
16
|
+
bodyAfterRoot: "<!-- TC_BODY_AFTER_ROOT_SLOT -->"
|
|
17
|
+
}, F = [
|
|
18
|
+
"head",
|
|
19
|
+
"body-before-root",
|
|
20
|
+
"body-after-root"
|
|
21
|
+
], I = () => ({
|
|
22
|
+
head: "",
|
|
23
|
+
"body-before-root": "",
|
|
24
|
+
"body-after-root": ""
|
|
25
|
+
}), L = (e) => e.split(s.sep).join("/"), R = (e) => {
|
|
26
|
+
let t = L(e);
|
|
27
|
+
return t.startsWith(".") || t.startsWith("/") ? t : `./${t}`;
|
|
28
|
+
}, z = (e) => /<html[\s>]/i.test(e) && e.includes("window['_basePath']") && /<div\s+id=["']root["']\s*><\/div>/i.test(e), B = (e) => {
|
|
29
|
+
let t = I(), n = f(e, "utf-8"), r = /<!--\s*tc-slot:([\w-]+)\s*-->([\s\S]*?)<!--\s*\/tc-slot:\1\s*-->/g, i = !1, o = n;
|
|
30
|
+
if (n.replace(r, (n, r, a) => {
|
|
31
|
+
if (i = !0, !F.includes(r)) throw Error(`Invalid html slot "${r}" in ${e}. Allowed slots: ${F.join(", ")}.`);
|
|
32
|
+
return t[r] += a.trim() ? `${a}\n` : "", o = o.replace(n, ""), n;
|
|
33
|
+
}), o.trim()) {
|
|
34
|
+
if (z(n)) return a(`ignored legacy generated entry html: ${e}`), I();
|
|
35
|
+
throw Error([
|
|
36
|
+
`Invalid custom entry html: ${e}.`,
|
|
37
|
+
"Custom entry html can only contain tc-slot blocks, for example:",
|
|
38
|
+
"<!-- tc-slot:head -->",
|
|
39
|
+
"<meta name=\"description\" content=\"...\" />",
|
|
40
|
+
"<!-- /tc-slot:head -->"
|
|
41
|
+
].join("\n"));
|
|
42
|
+
}
|
|
43
|
+
return i && a(`using custom entry html slots: ${e}`), t;
|
|
44
|
+
}, V = (t, n) => {
|
|
45
|
+
let r = s.basename(t), i = [s.join(t, `${n}.html`), s.join(t, `${r}.html`)].find((e) => u(e));
|
|
46
|
+
if (i) return i;
|
|
47
|
+
if (c.sync(e(t, "*.html")).length) throw Error([`Entry html filename is not supported in ${t}.`, `Use ${n}.html or ${r}.html as a tc-slot file.`].join("\n"));
|
|
48
|
+
}, H = (e, t) => e.replace(P.head, t.head).replace(P.bodyBeforeRoot, t["body-before-root"]).replace(P.bodyAfterRoot, t["body-after-root"]), U = (e, t, n, r) => {
|
|
49
|
+
let i = s.join(M, `${n}.html`), a = R(s.relative(s.dirname(i), t));
|
|
50
|
+
return _(i, H(e, r).replace(N, `<script type="module" src="${a}"><\/script>`)), i;
|
|
51
|
+
};
|
|
52
|
+
function W() {
|
|
53
|
+
i("frameFEPath: " + O), i("runFEPath: " + A);
|
|
54
|
+
let t = [O, A].reduce((t, n) => {
|
|
17
55
|
let r = [
|
|
18
56
|
"js",
|
|
19
57
|
"jsx",
|
|
@@ -23,26 +61,23 @@ function A() {
|
|
|
23
61
|
return [...t, ...i];
|
|
24
62
|
}, []), n = {};
|
|
25
63
|
if (!t.length) return n;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let e = a.split(s.sep).at(-1), t = r.replace("<!-- {{InsertScript}} -->", `<script type="module" src="./${e}"><\/script>`), i = o.split(s.sep).at(-1), c = s.join(o, i + ".html");
|
|
35
|
-
h(c, t), n[i] = c;
|
|
36
|
-
}
|
|
64
|
+
m(M, {
|
|
65
|
+
recursive: !0,
|
|
66
|
+
force: !0
|
|
67
|
+
}), d(M, { recursive: !0 });
|
|
68
|
+
let r = f(s.resolve(D, "./app/view/entry.tpl"), "utf-8");
|
|
69
|
+
for (let e = 0; e < t.length; e++) {
|
|
70
|
+
let i = t[e], a = s.resolve(i, "../"), o = V(a, s.basename(i).replace(/\.entry\.[^.]+$/, "")), c = o ? s.basename(o, ".html") : a.split(s.sep).at(-1);
|
|
71
|
+
n[c] = U(r, i, c, o ? B(o) : I());
|
|
37
72
|
}
|
|
38
73
|
return n;
|
|
39
74
|
}
|
|
40
|
-
function
|
|
75
|
+
function G() {
|
|
41
76
|
let e = o.options.output ?? "frame";
|
|
42
|
-
return e !== "frame" && r("Please pay attention to the build output."), e === "run" ? s.join(
|
|
77
|
+
return e !== "frame" && r("Please pay attention to the build output."), e === "run" ? s.join(k, "./app/public/dist") : typeof e == "function" ? e() : s.join(D, "./app/public/dist");
|
|
43
78
|
}
|
|
44
|
-
var
|
|
45
|
-
let t = (await import("chokidar")).watch(
|
|
79
|
+
var K = async (e) => {
|
|
80
|
+
let t = (await import("chokidar")).watch(A, {
|
|
46
81
|
ignored: /(^|[\/\\])\../,
|
|
47
82
|
persistent: !0,
|
|
48
83
|
ignoreInitial: !0
|
|
@@ -58,15 +93,15 @@ var M = async (e) => {
|
|
|
58
93
|
n = !1;
|
|
59
94
|
}
|
|
60
95
|
}
|
|
61
|
-
}), a(`watching ${
|
|
96
|
+
}), a(`watching ${A}/...`);
|
|
62
97
|
};
|
|
63
|
-
function
|
|
98
|
+
function q(e = ".html") {
|
|
64
99
|
let t = [...new Set(Array.isArray(e) ? e : [e])];
|
|
65
100
|
return {
|
|
66
101
|
name: "flatten-html",
|
|
67
102
|
apply: "build",
|
|
68
103
|
closeBundle() {
|
|
69
|
-
let e =
|
|
104
|
+
let e = G();
|
|
70
105
|
if (!u(e)) return;
|
|
71
106
|
let n = c.sync("**/*.html", {
|
|
72
107
|
cwd: e,
|
|
@@ -79,11 +114,11 @@ function N(e = ".html") {
|
|
|
79
114
|
let t = i + r, a = s.join(e, t);
|
|
80
115
|
n !== a && l(n, a);
|
|
81
116
|
}
|
|
82
|
-
t.some((t) => n === s.join(e, i + t)) ||
|
|
117
|
+
t.some((t) => n === s.join(e, i + t)) || g(n);
|
|
83
118
|
}
|
|
84
119
|
let r = [...new Set(n.map((e) => s.dirname(e)))].sort().reverse(), i = (e) => {
|
|
85
|
-
if (!(u(e) ?
|
|
86
|
-
|
|
120
|
+
if (!(u(e) ? p(e) : [1]).length) {
|
|
121
|
+
h(e);
|
|
87
122
|
let t = e.split(s.sep).slice(0, -1);
|
|
88
123
|
t.length && i(t.join(s.sep));
|
|
89
124
|
}
|
|
@@ -97,7 +132,7 @@ function N(e = ".html") {
|
|
|
97
132
|
}
|
|
98
133
|
};
|
|
99
134
|
}
|
|
100
|
-
function
|
|
135
|
+
function J(e) {
|
|
101
136
|
let t = [
|
|
102
137
|
"",
|
|
103
138
|
".ts",
|
|
@@ -105,73 +140,73 @@ function P(e) {
|
|
|
105
140
|
".js",
|
|
106
141
|
".jsx"
|
|
107
142
|
];
|
|
108
|
-
return e.reduce((e, [n, r, i =
|
|
109
|
-
let o = t.map((e) => s.resolve(i, r + e)).find((e) => u(e)), c = s.resolve(
|
|
143
|
+
return e.reduce((e, [n, r, i = A, a = "./defaultAlias"]) => {
|
|
144
|
+
let o = t.map((e) => s.resolve(i, r + e)).find((e) => u(e)), c = s.resolve(C, a);
|
|
110
145
|
return o && (c = o), e[n] = c, e;
|
|
111
146
|
}, {});
|
|
112
147
|
}
|
|
113
|
-
var
|
|
148
|
+
var Y = (...e) => new RegExp(e.join("[\\/]")), X = (e) => `_${e}_tcC`, Z = async (e, i) => {
|
|
114
149
|
try {
|
|
115
|
-
await
|
|
150
|
+
await v({
|
|
116
151
|
configFile: !1,
|
|
117
152
|
base: "/dist",
|
|
118
153
|
plugins: [
|
|
119
154
|
(await import("@vitejs/plugin-react")).default(),
|
|
120
|
-
|
|
121
|
-
|
|
155
|
+
q([".tpl", ".html"]),
|
|
156
|
+
y()
|
|
122
157
|
],
|
|
123
158
|
build: {
|
|
124
|
-
outDir:
|
|
159
|
+
outDir: G(),
|
|
125
160
|
emptyOutDir: !0,
|
|
126
161
|
sourcemap: i === "dev" ? "inline" : "hidden",
|
|
127
162
|
rolldownOptions: {
|
|
128
163
|
input: e,
|
|
129
164
|
output: { codeSplitting: { groups: [
|
|
130
165
|
{
|
|
131
|
-
name:
|
|
132
|
-
test:
|
|
166
|
+
name: X("__nm"),
|
|
167
|
+
test: Y("node_modules"),
|
|
133
168
|
maxSize: 5e5,
|
|
134
169
|
minShareCount: 2,
|
|
135
170
|
priority: 1
|
|
136
171
|
},
|
|
137
172
|
{
|
|
138
|
-
name:
|
|
139
|
-
test:
|
|
173
|
+
name: X("r"),
|
|
174
|
+
test: Y("node_modules", "react"),
|
|
140
175
|
minShareCount: 2,
|
|
141
176
|
maxSize: 5e5,
|
|
142
177
|
priority: 5
|
|
143
178
|
},
|
|
144
179
|
{
|
|
145
|
-
name:
|
|
146
|
-
test:
|
|
180
|
+
name: X("r-d"),
|
|
181
|
+
test: Y("node_modules", "react-dom"),
|
|
147
182
|
minShareCount: 2,
|
|
148
183
|
maxSize: 2e5,
|
|
149
184
|
priority: 10
|
|
150
185
|
},
|
|
151
186
|
{
|
|
152
|
-
name:
|
|
153
|
-
test:
|
|
187
|
+
name: X("r-r-d"),
|
|
188
|
+
test: Y("node_modules", "react-router-dom"),
|
|
154
189
|
minShareCount: 2,
|
|
155
190
|
maxSize: 5e5,
|
|
156
191
|
priority: 10
|
|
157
192
|
},
|
|
158
193
|
{
|
|
159
|
-
name:
|
|
160
|
-
test:
|
|
194
|
+
name: X("ajv"),
|
|
195
|
+
test: Y("node_modules", "ajv"),
|
|
161
196
|
minShareCount: 2,
|
|
162
197
|
maxSize: 2e5,
|
|
163
198
|
priority: 10
|
|
164
199
|
},
|
|
165
200
|
{
|
|
166
|
-
name:
|
|
167
|
-
test:
|
|
201
|
+
name: X("ui-lib"),
|
|
202
|
+
test: Y("components"),
|
|
168
203
|
minShareCount: 2,
|
|
169
204
|
maxSize: 2e5,
|
|
170
205
|
priority: 5
|
|
171
206
|
},
|
|
172
207
|
{
|
|
173
|
-
name:
|
|
174
|
-
test:
|
|
208
|
+
name: X("ui-f-lib"),
|
|
209
|
+
test: Y("components", ".*?[Ff]orm.*?"),
|
|
175
210
|
minShareCount: 2,
|
|
176
211
|
maxSize: 5e4,
|
|
177
212
|
priority: 10
|
|
@@ -180,15 +215,15 @@ var F = (...e) => new RegExp(e.join("[\\/]")), I = (e) => `_${e}_tcC`, L = async
|
|
|
180
215
|
}
|
|
181
216
|
},
|
|
182
217
|
resolve: { alias: {
|
|
183
|
-
react:
|
|
184
|
-
"react-dom":
|
|
185
|
-
"@tc/ui-react/hooks": s.resolve(
|
|
186
|
-
"@tc/ui-react": s.resolve(
|
|
187
|
-
...
|
|
218
|
+
react: j("react"),
|
|
219
|
+
"react-dom": j("react-dom"),
|
|
220
|
+
"@tc/ui-react/hooks": s.resolve(C, "../../fe/packages/react/hooks/"),
|
|
221
|
+
"@tc/ui-react": s.resolve(C, "../../fe/packages/react/ui/"),
|
|
222
|
+
...J([
|
|
188
223
|
[
|
|
189
224
|
"@tc/scalability/SchemaForm/frameData",
|
|
190
225
|
"./extended/SchemaForm/data",
|
|
191
|
-
|
|
226
|
+
O
|
|
192
227
|
],
|
|
193
228
|
["@tc/scalability/SchemaForm/data", "./extended/SchemaForm/data"],
|
|
194
229
|
["@tc/scalability/SchemaPage/CallCom/data", "./extended/SchemaPage/CallCom/data"],
|
|
@@ -208,4 +243,4 @@ var F = (...e) => new RegExp(e.join("[\\/]")), I = (e) => `_${e}_tcC`, L = async
|
|
|
208
243
|
}
|
|
209
244
|
};
|
|
210
245
|
//#endregion
|
|
211
|
-
export {
|
|
246
|
+
export { Z as VBuildFE, W as entries, G as outDir, K as watchFiles };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { getAuthToken } from "../../src/common/auth/index.js";
|
|
1
2
|
import { getText } from "../../src/common/language.js";
|
|
2
3
|
import { generateMenuItemData } from "../../src/common/generateMenuData.js";
|
|
3
|
-
import { getAuthTokenSync } from "../../src/common/auth/index.js";
|
|
4
4
|
import defaultAlias_default from "../../../bundler/defaultAlias.js";
|
|
5
5
|
import { Menu } from "../../../packages/react/ui/components/Menu/Menu.js";
|
|
6
6
|
import "../../../packages/react/ui/components/index.js";
|
|
@@ -113,7 +113,7 @@ var Dashboard = () => {
|
|
|
113
113
|
if (initData && projectInfo) {
|
|
114
114
|
if (isInit.current) return;
|
|
115
115
|
if (typeof defaultRouteGuard_default === "function") {
|
|
116
|
-
const authToken =
|
|
116
|
+
const authToken = getAuthToken();
|
|
117
117
|
const loginUrl = defaultRouteGuard_default({
|
|
118
118
|
projectInfo,
|
|
119
119
|
params,
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import { frontendLangKeys } from "../../src/language/index.js";
|
|
2
|
-
import { getText } from "../../src/common/language.js";
|
|
3
|
-
import { Button } from "../../../packages/react/ui/components/Button/Button.js";
|
|
4
|
-
import "../../../packages/react/ui/components/index.js";
|
|
5
1
|
import TestPage from "../../../packages/react/ui/components/testPage/index.js";
|
|
6
|
-
import { jsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
3
|
//#region frontend/apps/ui-components/index.tsx
|
|
8
4
|
var ui_components_default = () => {
|
|
9
|
-
return /* @__PURE__ */
|
|
10
|
-
/* @__PURE__ */ jsx("h1", { children: getText(frontendLangKeys.uiComponentsTitle) }),
|
|
11
|
-
/* @__PURE__ */ jsx(Button, { children: getText(frontendLangKeys.uiComponentsButton) }),
|
|
12
|
-
/* @__PURE__ */ jsx(TestPage, {})
|
|
13
|
-
] });
|
|
5
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(TestPage, {}) });
|
|
14
6
|
};
|
|
15
7
|
//#endregion
|
|
16
8
|
export { ui_components_default as default };
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
export declare const localKeyMap: {
|
|
2
|
+
AUTH_LOCAL_KEY: string;
|
|
3
|
+
API_AUTH_LOCAL_KEY: string;
|
|
4
|
+
};
|
|
1
5
|
export declare const AUTH_HEADER_KEY = "Authorization";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
6
|
+
export declare function getAuthToken(): string | null;
|
|
7
|
+
export declare function setAuthToken(t: string): void | null;
|
|
4
8
|
export declare function clearAuthToken(): Promise<void>;
|
|
5
9
|
export declare const API_AUTH_HEADER_KEY = "projk";
|
|
6
10
|
export declare const getApiAuth: () => string;
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
//#region frontend/src/common/auth/index.ts
|
|
2
|
-
var
|
|
3
|
-
|
|
2
|
+
var localKeyMap = {
|
|
3
|
+
AUTH_LOCAL_KEY: "auth_token",
|
|
4
|
+
API_AUTH_LOCAL_KEY: "p_J_k"
|
|
5
|
+
};
|
|
4
6
|
var AUTH_HEADER_KEY = "Authorization";
|
|
5
|
-
function
|
|
7
|
+
function getAuthToken() {
|
|
6
8
|
if (typeof localStorage === "undefined") return null;
|
|
7
|
-
return localStorage.getItem(AUTH_LOCAL_KEY);
|
|
9
|
+
return localStorage.getItem(localKeyMap.AUTH_LOCAL_KEY);
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
function setAuthToken(t) {
|
|
12
|
+
if (typeof localStorage === "undefined") return null;
|
|
13
|
+
return localStorage.setItem(localKeyMap.AUTH_LOCAL_KEY, t);
|
|
11
14
|
}
|
|
12
15
|
async function clearAuthToken() {
|
|
13
16
|
if (typeof localStorage === "undefined") return;
|
|
14
|
-
localStorage.removeItem(AUTH_LOCAL_KEY);
|
|
17
|
+
localStorage.removeItem(localKeyMap.AUTH_LOCAL_KEY);
|
|
15
18
|
}
|
|
16
19
|
var API_AUTH_HEADER_KEY = "projk";
|
|
17
20
|
var getApiAuth = () => {
|
|
18
|
-
return localStorage.getItem(API_AUTH_LOCAL_KEY) ?? "";
|
|
21
|
+
return localStorage.getItem(localKeyMap.API_AUTH_LOCAL_KEY) ?? "";
|
|
19
22
|
};
|
|
20
23
|
var setApiAuth = (v) => {
|
|
21
|
-
return localStorage.setItem(API_AUTH_LOCAL_KEY, v);
|
|
24
|
+
return localStorage.setItem(localKeyMap.API_AUTH_LOCAL_KEY, v);
|
|
22
25
|
};
|
|
23
26
|
//#endregion
|
|
24
|
-
export { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken,
|
|
27
|
+
export { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken, localKeyMap, setApiAuth, setAuthToken };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken } from "./auth/index.js";
|
|
1
2
|
import { frontendLangKeys } from "../language/index.js";
|
|
2
3
|
import { getText } from "./language.js";
|
|
3
|
-
import { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken } from "./auth/index.js";
|
|
4
4
|
import { FreezeState, apiFreezerStore } from "../stores/apiFreezer.js";
|
|
5
5
|
import FetchAxios from "../../../packages/common/http/index.js";
|
|
6
6
|
import md5 from "md5";
|
|
@@ -2,10 +2,11 @@ import "./typing/scalability";
|
|
|
2
2
|
export type * from "../apps/dash/types";
|
|
3
3
|
export * from "./main";
|
|
4
4
|
export type { CallComComponentsMap, CallComRenderer } from "./defaultPages/SchemaPage/schemaType";
|
|
5
|
-
export type { FormFieldSchema, SchemaFormComponentsMap, SchemaFormNamespace, SelectProps } from "../../packages/react/ui/index";
|
|
6
5
|
export * from "./components/index";
|
|
6
|
+
export type { FormFieldSchema, SchemaFormComponentsMap, SchemaFormNamespace, SelectProps } from "../../packages/react/ui/index";
|
|
7
7
|
export { del, get, patch, post, put, request } from "./common/request";
|
|
8
8
|
export * from "./defaultPages/SchemaPage/data/eventInfo";
|
|
9
9
|
export { merge } from "./defaultPages/SchemaPage/utils/permissions";
|
|
10
10
|
export * from "./exportStore";
|
|
11
11
|
export { i18n, i18nStore } from "../../packages/react/ui/i18n/index";
|
|
12
|
+
export { getAuthToken, localKeyMap, setAuthToken } from "./common/auth/index";
|
package/fe/frontend/src/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAuthToken, localKeyMap, setAuthToken } from "./common/auth/index.js";
|
|
1
2
|
import { i18n, i18nStore } from "../../packages/common/i18n/index.js";
|
|
2
3
|
import "../../packages/react/ui/i18n/index.js";
|
|
3
4
|
import { FreezeState, apiFreezerStore, useApiFreezer } from "./stores/apiFreezer.js";
|
|
@@ -16,4 +17,4 @@ import ThemeSwitch_default from "./components/ThemeSwitch/index.js";
|
|
|
16
17
|
import "./components/index.js";
|
|
17
18
|
import { eventsInfo } from "./defaultPages/SchemaPage/data/eventInfo.js";
|
|
18
19
|
import { merge } from "./defaultPages/SchemaPage/utils/permissions.js";
|
|
19
|
-
export { AsyncSelect_default as AsyncSelect, FreezeState, HeaderView, LanguageSwitch_default as LanguageSwitch, ThemeSwitch_default as ThemeSwitch, apiFreezerStore, del, eventsInfo, generateRouter, get, i18n, i18nStore, initApp, merge, modeStore, patch, post, put, request, schemaEventBus, schemaStore, themeSwitchStorageKey, useApiFreezer, useModeStore, useSchemaStore };
|
|
20
|
+
export { AsyncSelect_default as AsyncSelect, FreezeState, HeaderView, LanguageSwitch_default as LanguageSwitch, ThemeSwitch_default as ThemeSwitch, apiFreezerStore, del, eventsInfo, generateRouter, get, getAuthToken, i18n, i18nStore, initApp, localKeyMap, merge, modeStore, patch, post, put, request, schemaEventBus, schemaStore, setAuthToken, themeSwitchStorageKey, useApiFreezer, useModeStore, useSchemaStore };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createStoreHook } from "../../../packages/react/ui/lib/createStoreHook.js";
|
|
2
1
|
import { setApiAuth } from "../common/auth/index.js";
|
|
2
|
+
import { createStoreHook } from "../../../packages/react/ui/lib/createStoreHook.js";
|
|
3
3
|
import { getModelList, getProjectData } from "../api/baseInfo.js";
|
|
4
4
|
import { deserializationUrlSearch } from "../common/menu.js";
|
|
5
5
|
import { createStore } from "zustand";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FormFieldType, FormItemProps, SchemaFormAllPropsMap } from "../../../packages/react/ui/index";
|
|
2
1
|
import type { BuiltInCallComConfigMap } from "../../../frontend/src/defaultPages/SchemaPage/components/CallCom/builtIn";
|
|
2
|
+
import type { FormFieldType, FormItemProps, SchemaFormAllPropsMap } from "../../../packages/react/ui/index";
|
|
3
3
|
export type { BuiltInCallComConfigMap, BuiltInCallComNames } from "../../../frontend/src/defaultPages/SchemaPage/components/CallCom/builtIn";
|
|
4
4
|
export declare namespace CallComNamespace {
|
|
5
5
|
interface ConfigMap {
|
|
@@ -11,7 +11,7 @@ export type ComponentConfig = BuiltInCallComConfigMap & Partial<CallComNamespace
|
|
|
11
11
|
export type ComponentNames = Extract<keyof ComponentConfig, string>;
|
|
12
12
|
type FormComponentOption = FormItemProps & {
|
|
13
13
|
visible?: boolean;
|
|
14
|
-
|
|
14
|
+
disabled?: boolean;
|
|
15
15
|
default?: any;
|
|
16
16
|
} & ComponentOption;
|
|
17
17
|
export type BuiltInCallComOptionMap = {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
export declare const localKeyMap: {
|
|
2
|
+
AUTH_LOCAL_KEY: string;
|
|
3
|
+
API_AUTH_LOCAL_KEY: string;
|
|
4
|
+
};
|
|
1
5
|
export declare const AUTH_HEADER_KEY = "Authorization";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
6
|
+
export declare function getAuthToken(): string | null;
|
|
7
|
+
export declare function setAuthToken(t: string): void | null;
|
|
4
8
|
export declare function clearAuthToken(): Promise<void>;
|
|
5
9
|
export declare const API_AUTH_HEADER_KEY = "projk";
|
|
6
10
|
export declare const getApiAuth: () => string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FormFieldType, FormItemProps, SchemaFormAllPropsMap } from "../../../../fe/packages/react/ui/index";
|
|
2
1
|
import type { BuiltInCallComConfigMap } from "../../../../fe/frontend/src/defaultPages/SchemaPage/components/CallCom/builtIn";
|
|
2
|
+
import type { FormFieldType, FormItemProps, SchemaFormAllPropsMap } from "../../../../fe/packages/react/ui/index";
|
|
3
3
|
export type { BuiltInCallComConfigMap, BuiltInCallComNames } from "../../../../fe/frontend/src/defaultPages/SchemaPage/components/CallCom/builtIn";
|
|
4
4
|
export declare namespace CallComNamespace {
|
|
5
5
|
interface ConfigMap {
|
|
@@ -11,7 +11,7 @@ export type ComponentConfig = BuiltInCallComConfigMap & Partial<CallComNamespace
|
|
|
11
11
|
export type ComponentNames = Extract<keyof ComponentConfig, string>;
|
|
12
12
|
type FormComponentOption = FormItemProps & {
|
|
13
13
|
visible?: boolean;
|
|
14
|
-
|
|
14
|
+
disabled?: boolean;
|
|
15
15
|
default?: any;
|
|
16
16
|
} & ComponentOption;
|
|
17
17
|
export type BuiltInCallComOptionMap = {
|
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { KoaApp } from "../type";
|
|
2
|
+
export type CryptoHashAlgorithm = 'sha256' | 'sha384' | 'sha512';
|
|
3
|
+
export interface HmacSignOptions {
|
|
4
|
+
secret?: string;
|
|
5
|
+
algorithm?: CryptoHashAlgorithm;
|
|
6
|
+
}
|
|
7
|
+
export interface SignedPayloadOptions extends HmacSignOptions {
|
|
8
|
+
separator?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface VerifySignedPayloadOptions extends HmacSignOptions {
|
|
11
|
+
separator?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface CryptoExtend {
|
|
14
|
+
/**
|
|
15
|
+
* 将 UTF-8 字符串或二进制数据编码为 base64url 字符串。
|
|
16
|
+
*
|
|
17
|
+
* base64url 不包含 `+`、`/` 和结尾 `=`,适合放在 URL、Cookie、Header 或 token 片段中。
|
|
18
|
+
*/
|
|
19
|
+
base64urlEncode: (value: string | Buffer | Uint8Array) => string;
|
|
20
|
+
/**
|
|
21
|
+
* 将 base64url 字符串解码为 UTF-8 字符串。
|
|
22
|
+
*
|
|
23
|
+
* 常用于还原 token payload;如果原始内容不是 UTF-8 文本,请使用 `base64urlDecodeToBuffer`。
|
|
24
|
+
*/
|
|
25
|
+
base64urlDecode: (value: string) => string;
|
|
26
|
+
/**
|
|
27
|
+
* 将 base64url 字符串解码为 Buffer。
|
|
28
|
+
*
|
|
29
|
+
* 适合处理二进制 payload,避免 UTF-8 字符串转换造成数据损坏。
|
|
30
|
+
*/
|
|
31
|
+
base64urlDecodeToBuffer: (value: string) => Buffer;
|
|
32
|
+
/**
|
|
33
|
+
* 使用 HMAC 对文本内容签名,并返回 base64url 签名。
|
|
34
|
+
*
|
|
35
|
+
* 默认使用 `app.config.signKey` 作为密钥、`sha256` 作为算法;可通过 options 覆盖。
|
|
36
|
+
*/
|
|
37
|
+
hmacSign: (payload: string, options?: HmacSignOptions) => string;
|
|
38
|
+
/**
|
|
39
|
+
* 常量时间比较两个字符串是否相等。
|
|
40
|
+
*
|
|
41
|
+
* 用于比较密码、签名、token 等敏感值,降低普通字符串比较带来的计时攻击风险。
|
|
42
|
+
*/
|
|
43
|
+
safeEqual: (left: string, right: string) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* 创建带签名的 payload 字符串。
|
|
46
|
+
*
|
|
47
|
+
* 返回格式默认为 `${base64url(JSON.stringify(payload))}.${hmacSign(encodedPayload)}`,
|
|
48
|
+
* 适合生成轻量登录 token 或一次性校验串。
|
|
49
|
+
*/
|
|
50
|
+
createSignedPayload: <T>(payload: T, options?: SignedPayloadOptions) => string;
|
|
51
|
+
/**
|
|
52
|
+
* 验证带签名的 payload 字符串并还原原始对象。
|
|
53
|
+
*
|
|
54
|
+
* 签名不匹配、格式错误或 JSON 解析失败时返回 null,不向外抛出解析异常。
|
|
55
|
+
*/
|
|
56
|
+
verifySignedPayload: <T = unknown>(signedPayload: string | null | undefined, options?: VerifySignedPayloadOptions) => T | null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 创建框架加密扩展对象。
|
|
60
|
+
*
|
|
61
|
+
* 该扩展会挂载到 `app.extends.crypto`,用于复用 base64url 编码、HMAC 签名、签名 payload 和安全比较能力。
|
|
62
|
+
*/
|
|
63
|
+
declare const _default: (app: KoaApp) => CryptoExtend;
|
|
64
|
+
export default _default;
|
package/types/app/type.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export type KoaRouter = import('./typings').KoaRouter;
|
|
|
3
3
|
export type Ctx = KoaApp['context'];
|
|
4
4
|
export type ControllerFN<T extends object = object> = (app: KoaApp) => new () => T;
|
|
5
5
|
export type ServiceFN<T extends object = object> = (app: KoaApp) => new () => T;
|
|
6
|
+
export type RouterFN = (app: KoaApp, router: KoaRouter) => void;
|
package/types/app/typings.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { DefaultAppConfig } from "../config/config.default";
|
|
|
4
4
|
import type getProjectController from "./controller/project";
|
|
5
5
|
import type getViewController from "./controller/view";
|
|
6
6
|
import type getFetch from "./extend/$fetch";
|
|
7
|
+
import type getCrypto from "./extend/crypto";
|
|
7
8
|
import type getDB from "./extend/db";
|
|
8
9
|
import type generateErrorMessage from "./extend/generateErrorMessage";
|
|
9
10
|
import type getLogger from "./extend/logger";
|
|
@@ -21,6 +22,7 @@ declare module '../packages/core/index' {
|
|
|
21
22
|
view: GetInstance<typeof getViewController>;
|
|
22
23
|
}
|
|
23
24
|
interface IExtendsAugmented {
|
|
25
|
+
crypto: ReturnType<typeof getCrypto>;
|
|
24
26
|
db: UnwrapPromise<ReturnType<typeof getDB>>;
|
|
25
27
|
logger: UnwrapPromise<ReturnType<typeof getLogger>>;
|
|
26
28
|
generateErrorMessage: ReturnType<typeof generateErrorMessage>;
|
package/types/bundler/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { BuildFEOptions } from "./state";
|
|
2
2
|
export { buildBE } from "./buildBE";
|
|
3
3
|
export type { BuildBEAlias, BuildBEFormat, BuildBEOptions, BuildBEResult } from "./buildBE";
|
|
4
|
-
export declare const buildFE: (mode: "dev" | "prod", options?: BuildFEOptions) => void
|
|
4
|
+
export declare const buildFE: (mode: "dev" | "prod", options?: BuildFEOptions) => Promise<void>;
|
package/types/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import "./app/typings";
|
|
|
4
4
|
import { MOmit } from "./typings/type";
|
|
5
5
|
export type { FrameworkAugment, KoaApp } from "./packages/core/index";
|
|
6
6
|
export type { ControllerFN, Ctx, ServiceFN } from "./app/type";
|
|
7
|
+
export type { CryptoExtend, CryptoHashAlgorithm, HmacSignOptions, SignedPayloadOptions, VerifySignedPayloadOptions, } from "./app/extend/crypto";
|
|
7
8
|
export type { DB, DBDataOptions, DBDataRecord, DBFactory, DBRunResult, FrameworkDB, ListDBDataOptions, SQLiteParams, SQLiteValue, } from "./app/extend/db";
|
|
8
9
|
export type { Router };
|
|
9
10
|
export declare const serverStart: (options?: MOmit<StartOptions, "frameBaseDir">) => Promise<import("./packages/core/index").KoaApp>;
|