@tbox.cn/app-template-h5 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/apps/server/src/dev.ts +84 -44
- package/package.json +2 -2
- package/pnpm-lock.template.yaml +31 -13
- package/pnpm-workspace.template.yaml +6 -6
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ pnpm install
|
|
|
35
35
|
| `PORT` | server | 服务端口(默认 8000) |
|
|
36
36
|
| `HOST` | server | 监听地址(默认 0.0.0.0) |
|
|
37
37
|
| `VITE_API_URL` | client | API base(空串/未设 = 同源相对路径);经 `configureRuntime` 注入 |
|
|
38
|
+
| `TBOX_DEV_TOKEN` | server | 沙箱 dev server 鉴权 token(方案 W;平台运行时注入,**本地无需设置**):形状 `^[A-Za-z0-9_-]{20,128}$`。在场 → protected 双面裁决——dev 面(凭据 + 浏览器导航/同源信号)完整 vite 能力,static 面(无凭据/跨源子资源)纯 dist + SSE 刷新(`GET /__tbox_dev_hot_reload`),零源码泄漏;缺席 → classic(与历史行为字节级等价)。形状不合规 → fail-closed 全 static |
|
|
39
|
+
| `TBOX_DEV_SSE_KEEPALIVE_MS` | server | protected 面 SSE 心跳间隔 ms(缺省 15000;smoke 覆盖用) |
|
|
38
40
|
|
|
39
41
|
## 验收
|
|
40
42
|
|
package/apps/server/src/dev.ts
CHANGED
|
@@ -2,82 +2,122 @@
|
|
|
2
2
|
* vite dev 入口(仅 tsx 运行:pnpm dev;物理不进 tsup bundle——forbid-vite 插件拦截)。
|
|
3
3
|
* middlewareMode + 单端口 8000:HTTP 与 HMR WS 全走应用端口。
|
|
4
4
|
* 部署形态一律走 dist/index.js(静态产物,零 HMR WS)。
|
|
5
|
+
*
|
|
6
|
+
* 方案 W(沙箱 dev server 鉴权保护):TBOX_DEV_TOKEN 在场 → protected 双面裁决
|
|
7
|
+
* (dev 面 = 凭据 + 导航/同源信号放行 vite;static 面 = 纯 dist + SSE 刷新,零源码泄漏);
|
|
8
|
+
* 缺席 → classic,与历史行为字节级等价。裁决机制真源 = SDK platform/dev-runtime
|
|
9
|
+
* (createTboxDevRuntime),本文件只做装配:vite 实例 + fs.allow 收窄 + 面无关自由区。
|
|
5
10
|
*/
|
|
6
11
|
import { createServer } from 'http';
|
|
7
12
|
import path from 'path';
|
|
8
|
-
import {
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
9
14
|
import { fileURLToPath } from 'url';
|
|
10
|
-
import { createServer as createViteServer } from 'vite';
|
|
11
|
-
import {
|
|
15
|
+
import { build as viteBuild, createServer as createViteServer, searchForWorkspaceRoot } from 'vite';
|
|
16
|
+
import {
|
|
17
|
+
createTboxDevRuntime,
|
|
18
|
+
injectScript,
|
|
19
|
+
installInspector,
|
|
20
|
+
isReady,
|
|
21
|
+
resolveDevTokenFromEnv,
|
|
22
|
+
} from '@tbox.cn/app-sdk-server/platform';
|
|
23
|
+
import { createLogger } from '@tbox.cn/app-sdk-server';
|
|
12
24
|
import { app, start } from './app.js';
|
|
13
25
|
|
|
14
26
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const serverLogger = createLogger('server');
|
|
15
28
|
const clientRoot = path.resolve(__dirname, '../../client');
|
|
29
|
+
const appRoot = path.resolve(__dirname, '../..');
|
|
16
30
|
|
|
17
31
|
// HMR WS 独立路径与协议常量(scripts/smoke-dev-hmr.sh 断言消费,不得改名):
|
|
18
|
-
// dev
|
|
32
|
+
// dev 基础设施命名空间(双下划线),与业务命名空间解耦。协议白名单与 Spanner 子协议
|
|
33
|
+
// 补写内聚于 SDK handleUpgrade(本模板 upgrade 形态 = server.on 直连,见文件尾)。
|
|
19
34
|
const HMR_WS_PATH = '/__vite_hmr';
|
|
20
35
|
|
|
21
36
|
// HMR WS hub:永不 listen 的纯事件载体。hmr.server 在场 → vite 不自建独立端口,
|
|
22
37
|
// 客户端按页面同源端口连接。vite 对 hub 仅 on/off('upgrade')。
|
|
23
38
|
const hmrHub = createServer();
|
|
24
39
|
|
|
40
|
+
const tokenState = resolveDevTokenFromEnv(process.env);
|
|
41
|
+
const protectedMode = tokenState.mode === 'protected';
|
|
42
|
+
|
|
43
|
+
// protected 面 fs.allow 收窄(classic 恒默认不传——红线):client 源 + 工作区依赖真源
|
|
44
|
+
// (filter(existsSync):生成应用无 packages/ 时自动滤除,不影响沙箱半径)。
|
|
45
|
+
const workspaceRoot = searchForWorkspaceRoot(clientRoot);
|
|
46
|
+
const fsAllowNarrowed = protectedMode
|
|
47
|
+
? [
|
|
48
|
+
...new Set(
|
|
49
|
+
[
|
|
50
|
+
clientRoot,
|
|
51
|
+
path.join(appRoot, 'packages'),
|
|
52
|
+
path.join(appRoot, 'node_modules'),
|
|
53
|
+
path.join(workspaceRoot, 'packages'),
|
|
54
|
+
path.join(workspaceRoot, 'node_modules'),
|
|
55
|
+
].filter((p) => existsSync(p)),
|
|
56
|
+
),
|
|
57
|
+
]
|
|
58
|
+
: undefined;
|
|
59
|
+
|
|
25
60
|
const vite = await createViteServer({
|
|
26
61
|
root: clientRoot,
|
|
27
62
|
server: {
|
|
28
63
|
middlewareMode: true,
|
|
29
64
|
allowedHosts: true, // 沙箱域名 Host 透传放行(dev-only;dist 物理不含 vite)
|
|
30
|
-
fs: {
|
|
65
|
+
fs: {
|
|
66
|
+
...(fsAllowNarrowed ? { allow: fsAllowNarrowed } : {}),
|
|
67
|
+
deny: ['.env', '.env.*', '*.{crt,pem}', '**/.git/**'],
|
|
68
|
+
},
|
|
31
69
|
hmr: { server: hmrHub, path: HMR_WS_PATH },
|
|
32
70
|
},
|
|
33
|
-
appType: 'custom', // 不加 Vite 自己的 SPA fallback
|
|
71
|
+
appType: 'custom', // 不加 Vite 自己的 SPA fallback,HTML 路由由 runtime.final() 统一处理
|
|
34
72
|
});
|
|
35
73
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
74
|
+
const runtime = createTboxDevRuntime({
|
|
75
|
+
vite: { middlewares: vite.middlewares, transformIndexHtml: vite.transformIndexHtml },
|
|
76
|
+
hmrHub,
|
|
77
|
+
hmrPath: path.posix.join(vite.config.base, HMR_WS_PATH),
|
|
78
|
+
tokenState,
|
|
79
|
+
clientRoot,
|
|
80
|
+
clientDist: path.join(clientRoot, 'dist'),
|
|
81
|
+
spaExcludePrefixes: ['/api'],
|
|
82
|
+
decorateHtml: (html) => (isReady() ? injectScript(html) : html),
|
|
83
|
+
logger: serverLogger,
|
|
84
|
+
sseKeepaliveMs: process.env.TBOX_DEV_SSE_KEEPALIVE_MS ? Number(process.env.TBOX_DEV_SSE_KEEPALIVE_MS) : undefined,
|
|
85
|
+
});
|
|
40
86
|
|
|
41
|
-
|
|
87
|
+
// 面裁决:dev 面 → vite middlewares;static 面 → next()(落 final 的 dist 静态)
|
|
88
|
+
app.use(runtime.gate);
|
|
42
89
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const rawHtml = await readFile(path.join(clientRoot, 'index.html'), 'utf-8');
|
|
50
|
-
const template = await vite.transformIndexHtml(req.originalUrl, rawHtml);
|
|
51
|
-
const html = isReady() ? injectScript(template) : template;
|
|
52
|
-
res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
|
|
53
|
-
} catch (e) {
|
|
54
|
-
next(e);
|
|
55
|
-
}
|
|
90
|
+
// ── 自由区(模板差异 + 应用自定义 middleware;dev/static 两面恒可见)──
|
|
91
|
+
// 平台微调(inspector):入口级接线(RUNTIME_PATH 路由 + text/html 装配),两面共用
|
|
92
|
+
installInspector(app);
|
|
93
|
+
// 探针路由:smoke-dev-protected.sh ⑩ 断言两面可见性(dev 基础设施,dist 物理不含)
|
|
94
|
+
app.get('/__tbox_dev_freezone_probe', (_req, res) => {
|
|
95
|
+
res.type('text/plain').send('freezone-ok');
|
|
56
96
|
});
|
|
97
|
+
// ── 自由区结束 ──
|
|
98
|
+
|
|
99
|
+
app.use(runtime.final);
|
|
100
|
+
|
|
101
|
+
// protected 面构建通知:watch 增量构建 → BUNDLE_END → SSE 广播 → static 面 reload。
|
|
102
|
+
// fire-and-forget;watch 启动 emptyOutDir 清空 dist 为已定价行为(首建完成前 static 面纯 404)。
|
|
103
|
+
if (protectedMode) {
|
|
104
|
+
viteBuild({ root: clientRoot, build: { watch: {}, sourcemap: false } })
|
|
105
|
+
.then((result) => {
|
|
106
|
+
const watcher = Array.isArray(result) ? result[0] : result;
|
|
107
|
+
if (!watcher || !('on' in watcher)) return;
|
|
108
|
+
watcher.on('event', (e) => {
|
|
109
|
+
if (e.code === 'BUNDLE_END') runtime.notifyBuildEnd();
|
|
110
|
+
});
|
|
111
|
+
})
|
|
112
|
+
.catch((err) => console.error('[dev] watch build failed:', err));
|
|
113
|
+
}
|
|
57
114
|
|
|
58
115
|
const server = start();
|
|
59
116
|
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// 子协议补写(2026-09-21,同 agent/support 模板——Spanner 网关把 WS 升级的
|
|
63
|
-
// Sec-WebSocket-Protocol 视为 hop-by-hop 在下游拨号时剥除,详见 tbox-app 仓
|
|
64
|
-
// .agents/notes/implemented/bug-fix/2026-09-21-spanner-ws-subprotocol-strip-reload-loop.md):
|
|
65
|
-
// 无协议头的 HMR 路径升级补写 vite-hmr 让下游握手成立——否则 destroy → WebKit(Safari/iOS
|
|
66
|
-
// 支付宝·微信,宽松接受网关乐观 101)open 即断 → vite-ping 探测恒成 → location.reload()
|
|
67
|
-
// 无限循环(Chrome/Blink 严格校验协议回显恒不 open 免疫)。其余非 vite 协议值仍销毁
|
|
68
|
-
// (smoke ⑤⑥ 语义不变);网关透传修复落地后补写自动退化为死分支——长留不移除。
|
|
117
|
+
// 升级处理:唯一 WS 入口 = runtime.handleUpgrade(认领 /__vite_hmr + 子协议白名单 +
|
|
118
|
+
// protected token 校验失败 401;其余一律销毁——本模板无业务 WS,smoke-dev-hmr 断言消费)
|
|
69
119
|
server.on('upgrade', (request, socket, head) => {
|
|
70
|
-
|
|
71
|
-
if (pathname !== viteHmrPath) {
|
|
72
|
-
socket.destroy();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const protocol = request.headers['sec-websocket-protocol'];
|
|
76
|
-
if (!protocol) {
|
|
77
|
-
request.headers['sec-websocket-protocol'] = 'vite-hmr';
|
|
78
|
-
} else if (protocol !== 'vite-hmr' && protocol !== 'vite-ping') {
|
|
120
|
+
if (!runtime.handleUpgrade(request, socket, head)) {
|
|
79
121
|
socket.destroy();
|
|
80
|
-
return;
|
|
81
122
|
}
|
|
82
|
-
hmrHub.emit('upgrade', request, socket, head);
|
|
83
123
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tbox.cn/app-template-h5",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "tbox 移动 H5 全栈模板(单 workspace:apps/client + apps/server),运行时与页面微调经 @tbox.cn/app-sdk。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"eslint-plugin-react-hooks": "5.2.0",
|
|
36
36
|
"typescript-eslint": "8.67.0",
|
|
37
37
|
"vite": "6.4.3",
|
|
38
|
-
"@tbox.cn/app-sdk-server": "0.
|
|
38
|
+
"@tbox.cn/app-sdk-server": "0.22.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"dev": "pnpm --filter ./apps/server dev",
|
package/pnpm-lock.template.yaml
CHANGED
|
@@ -10,8 +10,8 @@ catalogs:
|
|
|
10
10
|
specifier: 0.21.1
|
|
11
11
|
version: 0.21.1
|
|
12
12
|
'@tbox.cn/app-sdk-server':
|
|
13
|
-
specifier: 0.
|
|
14
|
-
version: 0.
|
|
13
|
+
specifier: 0.22.0
|
|
14
|
+
version: 0.22.0
|
|
15
15
|
'@types/express':
|
|
16
16
|
specifier: 4.17.25
|
|
17
17
|
version: 4.17.25
|
|
@@ -82,7 +82,7 @@ importers:
|
|
|
82
82
|
devDependencies:
|
|
83
83
|
'@tbox.cn/app-sdk-server':
|
|
84
84
|
specifier: 'catalog:'
|
|
85
|
-
version: 0.
|
|
85
|
+
version: 0.22.0
|
|
86
86
|
eslint:
|
|
87
87
|
specifier: 'catalog:'
|
|
88
88
|
version: 9.39.5
|
|
@@ -140,7 +140,7 @@ importers:
|
|
|
140
140
|
dependencies:
|
|
141
141
|
'@tbox.cn/app-sdk-server':
|
|
142
142
|
specifier: 'catalog:'
|
|
143
|
-
version: 0.
|
|
143
|
+
version: 0.22.0
|
|
144
144
|
express:
|
|
145
145
|
specifier: 'catalog:'
|
|
146
146
|
version: 4.22.2
|
|
@@ -965,6 +965,10 @@ packages:
|
|
|
965
965
|
resolution: {integrity: sha512-GFthajvUSE7tY6Y5kLJgg2dRuZQdLfxq/WAEHOh+Q7RTwg7mpuLsWiNhXISkXMAtpxg0RkSgtwyRq33QS0ay3w==}
|
|
966
966
|
engines: {node: '>=20.0.0'}
|
|
967
967
|
|
|
968
|
+
'@tbox.cn/app-contracts@0.12.3':
|
|
969
|
+
resolution: {integrity: sha512-b71WtU1Kk9rC3lwaefb48ymKPgkdb7MJcJQ0lTeUZu/jSNHsqQ4ZZI9ZUbHQ/rzYvWmZbdxe5H94328jAfJoVA==}
|
|
970
|
+
engines: {node: '>=20.0.0'}
|
|
971
|
+
|
|
968
972
|
'@tbox.cn/app-sdk-client@0.21.1':
|
|
969
973
|
resolution: {integrity: sha512-hN+tIj8kh11PD0HMhq5KUs/nZYjxS+ps3F3HTTmOHWXNXzilYafFxeIWJ1bXKVm1Di5EUIjNaRPUDkP6HAcHvw==}
|
|
970
974
|
engines: {node: '>=20.0.0'}
|
|
@@ -973,13 +977,17 @@ packages:
|
|
|
973
977
|
resolution: {integrity: sha512-hvdcXntg4iJniVu9LEwo5A/vov3L08kRlJLTgStYRdAlH9AJXWLDxFITMNf7YJX8W+j9wjJmmS7Sv88iswxM+w==}
|
|
974
978
|
engines: {node: '>=20.0.0'}
|
|
975
979
|
|
|
976
|
-
'@tbox.cn/app-sdk-
|
|
977
|
-
resolution: {integrity: sha512-
|
|
980
|
+
'@tbox.cn/app-sdk-core@0.20.2':
|
|
981
|
+
resolution: {integrity: sha512-KDcFdIrIEOlwDvIqBu29CzR5vDjt0BCADMBrgrd5WlRqjrPM1/jM/RcaRO6gRuElmQCp5LfsoAkVeYTZE/6rpw==}
|
|
982
|
+
engines: {node: '>=20.0.0'}
|
|
983
|
+
|
|
984
|
+
'@tbox.cn/app-sdk-server@0.22.0':
|
|
985
|
+
resolution: {integrity: sha512-St5cP43/y/pH/s6ubXX44Rf0wmysL19tZRvTWSUV/0EmO7ouoRmtTzsWHe+XnqkcUnRgfSYm/evRse+K9ATNhg==}
|
|
978
986
|
engines: {node: '>=20.0.0'}
|
|
979
987
|
hasBin: true
|
|
980
988
|
|
|
981
|
-
'@tbox.cn/app-toolkit@0.
|
|
982
|
-
resolution: {integrity: sha512-
|
|
989
|
+
'@tbox.cn/app-toolkit@0.7.3':
|
|
990
|
+
resolution: {integrity: sha512-Tdh1XkqRX0DGvTxflrfgJ4WFVW5wiq9cGOd7hpFrGzQ95mXFiSUKyQMgMtIum8kbReUq+aPI5hv8Z87AugZ1yQ==}
|
|
983
991
|
engines: {node: '>=20.0.0'}
|
|
984
992
|
|
|
985
993
|
'@types/babel__core@7.20.5':
|
|
@@ -2830,6 +2838,12 @@ snapshots:
|
|
|
2830
2838
|
react: 18.3.1
|
|
2831
2839
|
zod: 3.25.76
|
|
2832
2840
|
|
|
2841
|
+
'@tbox.cn/app-contracts@0.12.3':
|
|
2842
|
+
dependencies:
|
|
2843
|
+
'@ag-ui/core': 0.0.47
|
|
2844
|
+
react: 18.3.1
|
|
2845
|
+
zod: 3.25.76
|
|
2846
|
+
|
|
2833
2847
|
'@tbox.cn/app-sdk-client@0.21.1':
|
|
2834
2848
|
dependencies:
|
|
2835
2849
|
'@tbox.cn/app-contracts': 0.12.0
|
|
@@ -2841,19 +2855,23 @@ snapshots:
|
|
|
2841
2855
|
dependencies:
|
|
2842
2856
|
'@tbox.cn/app-contracts': 0.12.0
|
|
2843
2857
|
|
|
2844
|
-
'@tbox.cn/app-sdk-
|
|
2858
|
+
'@tbox.cn/app-sdk-core@0.20.2':
|
|
2859
|
+
dependencies:
|
|
2860
|
+
'@tbox.cn/app-contracts': 0.12.3
|
|
2861
|
+
|
|
2862
|
+
'@tbox.cn/app-sdk-server@0.22.0':
|
|
2845
2863
|
dependencies:
|
|
2846
2864
|
'@ag-ui/core': 0.0.47
|
|
2847
|
-
'@tbox.cn/app-contracts': 0.12.
|
|
2848
|
-
'@tbox.cn/app-sdk-core': 0.20.
|
|
2849
|
-
'@tbox.cn/app-toolkit': 0.
|
|
2865
|
+
'@tbox.cn/app-contracts': 0.12.3
|
|
2866
|
+
'@tbox.cn/app-sdk-core': 0.20.2
|
|
2867
|
+
'@tbox.cn/app-toolkit': 0.7.3
|
|
2850
2868
|
express: 4.22.2
|
|
2851
2869
|
jose: 6.2.8
|
|
2852
2870
|
zod: 3.25.76
|
|
2853
2871
|
transitivePeerDependencies:
|
|
2854
2872
|
- supports-color
|
|
2855
2873
|
|
|
2856
|
-
'@tbox.cn/app-toolkit@0.
|
|
2874
|
+
'@tbox.cn/app-toolkit@0.7.3':
|
|
2857
2875
|
dependencies:
|
|
2858
2876
|
ajv: 8.20.0
|
|
2859
2877
|
zod: 3.25.76
|
|
@@ -33,13 +33,13 @@ catalog:
|
|
|
33
33
|
"@types/node": 24.13.3
|
|
34
34
|
express: 4.22.2
|
|
35
35
|
"@types/express": 4.17.25
|
|
36
|
-
"@tbox.cn/app-sdk-core": 0.20.
|
|
36
|
+
"@tbox.cn/app-sdk-core": 0.20.2
|
|
37
37
|
"@tbox.cn/app-sdk-client": 0.21.1
|
|
38
|
-
"@tbox.cn/app-sdk-server": 0.
|
|
39
|
-
"@tbox.cn/app-agent-sdk-server": 0.1.
|
|
40
|
-
"@tbox.cn/app-agent-sdk-client": 0.1.
|
|
41
|
-
"@tbox.cn/app-contracts": 0.12.
|
|
42
|
-
"@tbox.cn/app-contracts-mall": 0.12.
|
|
38
|
+
"@tbox.cn/app-sdk-server": 0.22.0
|
|
39
|
+
"@tbox.cn/app-agent-sdk-server": 0.1.4
|
|
40
|
+
"@tbox.cn/app-agent-sdk-client": 0.1.3
|
|
41
|
+
"@tbox.cn/app-contracts": 0.12.3
|
|
42
|
+
"@tbox.cn/app-contracts-mall": 0.12.4
|
|
43
43
|
eslint: 9.39.5
|
|
44
44
|
typescript-eslint: 8.67.0
|
|
45
45
|
eslint-plugin-react-hooks: 5.2.0
|