@tbox.cn/app-template-h5 0.1.5 → 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 +20 -20
- package/pnpm-workspace.template.yaml +2 -2
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,8 +965,8 @@ 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.
|
|
969
|
-
resolution: {integrity: sha512-
|
|
968
|
+
'@tbox.cn/app-contracts@0.12.3':
|
|
969
|
+
resolution: {integrity: sha512-b71WtU1Kk9rC3lwaefb48ymKPgkdb7MJcJQ0lTeUZu/jSNHsqQ4ZZI9ZUbHQ/rzYvWmZbdxe5H94328jAfJoVA==}
|
|
970
970
|
engines: {node: '>=20.0.0'}
|
|
971
971
|
|
|
972
972
|
'@tbox.cn/app-sdk-client@0.21.1':
|
|
@@ -977,17 +977,17 @@ packages:
|
|
|
977
977
|
resolution: {integrity: sha512-hvdcXntg4iJniVu9LEwo5A/vov3L08kRlJLTgStYRdAlH9AJXWLDxFITMNf7YJX8W+j9wjJmmS7Sv88iswxM+w==}
|
|
978
978
|
engines: {node: '>=20.0.0'}
|
|
979
979
|
|
|
980
|
-
'@tbox.cn/app-sdk-core@0.20.
|
|
981
|
-
resolution: {integrity: sha512-
|
|
980
|
+
'@tbox.cn/app-sdk-core@0.20.2':
|
|
981
|
+
resolution: {integrity: sha512-KDcFdIrIEOlwDvIqBu29CzR5vDjt0BCADMBrgrd5WlRqjrPM1/jM/RcaRO6gRuElmQCp5LfsoAkVeYTZE/6rpw==}
|
|
982
982
|
engines: {node: '>=20.0.0'}
|
|
983
983
|
|
|
984
|
-
'@tbox.cn/app-sdk-server@0.
|
|
985
|
-
resolution: {integrity: sha512-
|
|
984
|
+
'@tbox.cn/app-sdk-server@0.22.0':
|
|
985
|
+
resolution: {integrity: sha512-St5cP43/y/pH/s6ubXX44Rf0wmysL19tZRvTWSUV/0EmO7ouoRmtTzsWHe+XnqkcUnRgfSYm/evRse+K9ATNhg==}
|
|
986
986
|
engines: {node: '>=20.0.0'}
|
|
987
987
|
hasBin: true
|
|
988
988
|
|
|
989
|
-
'@tbox.cn/app-toolkit@0.7.
|
|
990
|
-
resolution: {integrity: sha512-
|
|
989
|
+
'@tbox.cn/app-toolkit@0.7.3':
|
|
990
|
+
resolution: {integrity: sha512-Tdh1XkqRX0DGvTxflrfgJ4WFVW5wiq9cGOd7hpFrGzQ95mXFiSUKyQMgMtIum8kbReUq+aPI5hv8Z87AugZ1yQ==}
|
|
991
991
|
engines: {node: '>=20.0.0'}
|
|
992
992
|
|
|
993
993
|
'@types/babel__core@7.20.5':
|
|
@@ -2838,7 +2838,7 @@ snapshots:
|
|
|
2838
2838
|
react: 18.3.1
|
|
2839
2839
|
zod: 3.25.76
|
|
2840
2840
|
|
|
2841
|
-
'@tbox.cn/app-contracts@0.12.
|
|
2841
|
+
'@tbox.cn/app-contracts@0.12.3':
|
|
2842
2842
|
dependencies:
|
|
2843
2843
|
'@ag-ui/core': 0.0.47
|
|
2844
2844
|
react: 18.3.1
|
|
@@ -2855,23 +2855,23 @@ snapshots:
|
|
|
2855
2855
|
dependencies:
|
|
2856
2856
|
'@tbox.cn/app-contracts': 0.12.0
|
|
2857
2857
|
|
|
2858
|
-
'@tbox.cn/app-sdk-core@0.20.
|
|
2858
|
+
'@tbox.cn/app-sdk-core@0.20.2':
|
|
2859
2859
|
dependencies:
|
|
2860
|
-
'@tbox.cn/app-contracts': 0.12.
|
|
2860
|
+
'@tbox.cn/app-contracts': 0.12.3
|
|
2861
2861
|
|
|
2862
|
-
'@tbox.cn/app-sdk-server@0.
|
|
2862
|
+
'@tbox.cn/app-sdk-server@0.22.0':
|
|
2863
2863
|
dependencies:
|
|
2864
2864
|
'@ag-ui/core': 0.0.47
|
|
2865
|
-
'@tbox.cn/app-contracts': 0.12.
|
|
2866
|
-
'@tbox.cn/app-sdk-core': 0.20.
|
|
2867
|
-
'@tbox.cn/app-toolkit': 0.7.
|
|
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
|
|
2868
2868
|
express: 4.22.2
|
|
2869
2869
|
jose: 6.2.8
|
|
2870
2870
|
zod: 3.25.76
|
|
2871
2871
|
transitivePeerDependencies:
|
|
2872
2872
|
- supports-color
|
|
2873
2873
|
|
|
2874
|
-
'@tbox.cn/app-toolkit@0.7.
|
|
2874
|
+
'@tbox.cn/app-toolkit@0.7.3':
|
|
2875
2875
|
dependencies:
|
|
2876
2876
|
ajv: 8.20.0
|
|
2877
2877
|
zod: 3.25.76
|
|
@@ -35,11 +35,11 @@ catalog:
|
|
|
35
35
|
"@types/express": 4.17.25
|
|
36
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.
|
|
38
|
+
"@tbox.cn/app-sdk-server": 0.22.0
|
|
39
39
|
"@tbox.cn/app-agent-sdk-server": 0.1.4
|
|
40
40
|
"@tbox.cn/app-agent-sdk-client": 0.1.3
|
|
41
41
|
"@tbox.cn/app-contracts": 0.12.3
|
|
42
|
-
"@tbox.cn/app-contracts-mall": 0.12.
|
|
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
|