@tbox.cn/app-template-h5 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ # 应用 workspace 模板:tbox-app create 用它生成应用的 pnpm-workspace.yaml。
2
+ # 本文件不触发 workspace root(非 pnpm-workspace.yaml 文件名)。
3
+ # 同步规则(guard-workspace h5 组恒等式校验):
4
+ # - catalog 键集 = 本模板成员 catalog: 引用并集 ∪ 平台三键(平台完备性:codegen 展开恒改写为 catalog:);
5
+ # 值与根 pnpm-workspace.yaml 同键全等(sync-catalog 维护)。
6
+ # - 非 catalog 顶层键与根镜像(packages/overrides 除外)。
7
+ packages:
8
+ - apps/*
9
+ - packages/*
10
+
11
+ onlyBuiltDependencies:
12
+ - esbuild
13
+
14
+ allowBuilds:
15
+ esbuild: true
16
+ lefthook: false
17
+
18
+ resolvePeersFromWorkspaceRoot: true
19
+
20
+ # 递归脚本并发上限(pnpm -r 默认 4):全速并行时多包 vitest worker 独立 transform
21
+ # 同一源码树,CI 峰值内存曾致 V8 fatal(SIGABRT)。本地提速可 CLI 旗标覆盖。
22
+ workspaceConcurrency: 2
23
+
24
+ catalog:
25
+ typescript: 5.9.3
26
+ tsx: 4.23.11
27
+ vite: 6.4.3
28
+ vitest: 4.1.10
29
+ react: 18.3.1
30
+ react-dom: 18.3.1
31
+ "@types/react": 18.3.31
32
+ "@types/react-dom": 18.3.7
33
+ "@types/node": 24.13.3
34
+ express: 4.22.2
35
+ "@types/express": 4.17.25
36
+ "@tbox.cn/app-sdk": 0.20.0
37
+ "@tbox.cn/app-contracts": 0.11.0
38
+ "@tbox.cn/app-contracts-mall": 0.11.0
39
+ eslint: 9.39.5
40
+ typescript-eslint: 8.67.0
41
+ eslint-plugin-react-hooks: 5.2.0
42
+ tsup: 8.5.1
43
+ "@vitejs/plugin-react": 4.7.0
44
+ happy-dom: 20.11.2
45
+ postcss-pxtorem: 6.1.0
46
+ supertest: 7.2.2
47
+ "@types/supertest": 7.2.1
48
+
49
+ # debug 钉版止血(与根 pnpm-workspace.yaml 同步维护;见 2026-09-16-ci-internal-registry-pollution.md):
50
+ # 模板锁种子投影会清 overrides 段致 debug 重解析为 4.4.3(R5 子集校验失败),故模板必须同携此键;
51
+ # 段恒居文件末尾——create --local-deps 的 overrides 条目按"并入末尾段"注入(cli/src/commands/create.ts)。
52
+ # 平台修复内网 registry 数据后随根一并移除。
53
+ overrides:
54
+ "debug@4.4.3": "4.4.1"
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Usage: build.sh [--skip-server]
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
8
+ cd "$PROJECT_DIR"
9
+ echo "Project directory: $PROJECT_DIR"
10
+
11
+ export NODE_ENV=production
12
+
13
+ SKIP_SERVER=false
14
+ for arg in "$@"; do
15
+ [ "$arg" = "--skip-server" ] && SKIP_SERVER=true
16
+ done
17
+ echo "Skip server: $SKIP_SERVER"
18
+
19
+ # 零依赖守卫 install 前 fail-fast(幂等毫秒级)
20
+ pnpm check:workspace
21
+
22
+ echo "Installing dependencies..."
23
+ CI=true pnpm install
24
+
25
+ # 构建序列单源 = package.json build / build:client(守卫经 check 组合注入);本脚本只管环境与 install
26
+ if [ "$SKIP_SERVER" = false ]; then
27
+ pnpm build
28
+ else
29
+ pnpm build:client
30
+ fi
31
+
32
+ echo "All builds completed successfully!"
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from 'node:fs';
4
+ import { readdir, readFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import process from 'node:process';
7
+
8
+ const projectDir = path.resolve(process.argv[2] ?? path.join(import.meta.dirname, '..'));
9
+ const allowedViteConfigDirs = new Set(['apps/client']);
10
+ const ignoredDirs = new Set([
11
+ '.agents',
12
+ '.claude',
13
+ '.codex',
14
+ '.cursor',
15
+ '.git',
16
+ '.node',
17
+ '.pnpm-store',
18
+ '.tmp',
19
+ '.vite',
20
+ 'dist',
21
+ 'dist-ssr',
22
+ 'node_modules',
23
+ ]);
24
+ const nestedLockfiles = new Set([
25
+ 'bun.lock',
26
+ 'bun.lockb',
27
+ 'package-lock.json',
28
+ 'pnpm-lock.yaml',
29
+ 'pnpm-lock.template.yaml',
30
+ 'yarn.lock',
31
+ ]);
32
+ const viteConfigPattern = /^vite\.config\.(?:js|mjs|cjs|ts|mts|cts)$/;
33
+
34
+ function relativeDir(filePath) {
35
+ const relative = path.relative(projectDir, path.dirname(filePath));
36
+ return relative === '' ? '.' : relative.split(path.sep).join('/');
37
+ }
38
+
39
+ async function walk(dir, findings) {
40
+ const entries = await readdir(dir, { withFileTypes: true });
41
+
42
+ for (const entry of entries) {
43
+ if (entry.isSymbolicLink()) continue;
44
+
45
+ const fullPath = path.join(dir, entry.name);
46
+ if (entry.isDirectory()) {
47
+ if (!ignoredDirs.has(entry.name)) await walk(fullPath, findings);
48
+ continue;
49
+ }
50
+ if (!entry.isFile()) continue;
51
+
52
+ const dirName = relativeDir(fullPath);
53
+ const relativePath = path.relative(projectDir, fullPath).split(path.sep).join('/');
54
+
55
+ if (nestedLockfiles.has(entry.name) && dirName !== '.') {
56
+ findings.nestedLockfiles.push(relativePath);
57
+ }
58
+
59
+ if (viteConfigPattern.test(entry.name) && !allowedViteConfigDirs.has(dirName)) {
60
+ findings.detachedViteConfigs.push(relativePath);
61
+ }
62
+ }
63
+ }
64
+
65
+ function printFailure(findings) {
66
+ console.error('');
67
+ console.error('[check-workspace] 工程边界校验失败。');
68
+
69
+ if (findings.nestedLockfiles.length > 0) {
70
+ console.error('\n检测到嵌套 lockfile(本项目只允许根 pnpm-lock.yaml):');
71
+ for (const file of findings.nestedLockfiles) console.error(` - ${file}`);
72
+ }
73
+ if (findings.detachedViteConfigs.length > 0) {
74
+ console.error('\n检测到 apps/client/ 之外的 Vite 项目配置:');
75
+ for (const file of findings.detachedViteConfigs) console.error(` - ${file}`);
76
+ }
77
+
78
+ console.error('');
79
+ process.exitCode = 1;
80
+ }
81
+
82
+ /** 向上查找最近的 pnpm-workspace.yaml(模板目录并入根 workspace 后本目录可无 workspace 文件,属父 workspace 成员) */
83
+ function findWorkspaceFile(dir) {
84
+ let cur = path.resolve(dir);
85
+ for (;;) {
86
+ const f = path.join(cur, 'pnpm-workspace.yaml');
87
+ if (existsSync(f)) return f;
88
+ const parent = path.dirname(cur);
89
+ if (parent === cur) return null;
90
+ cur = parent;
91
+ }
92
+ }
93
+
94
+ async function main() {
95
+ const workspaceFile = findWorkspaceFile(projectDir);
96
+ if (!workspaceFile) {
97
+ console.error('[check-workspace] 缺少 pnpm-workspace.yaml(本目录及父级均未找到)。');
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+
102
+ const findings = {
103
+ nestedLockfiles: [],
104
+ detachedViteConfigs: [],
105
+ };
106
+
107
+ await walk(projectDir, findings);
108
+
109
+ if (Object.values(findings).some((items) => items.length > 0)) {
110
+ printFailure(findings);
111
+ return;
112
+ }
113
+
114
+ console.log('[check-workspace] 工程边界校验通过(apps/* workspace)');
115
+ }
116
+
117
+ main().catch((error) => {
118
+ console.error('[check-workspace] 校验执行失败:', error);
119
+ process.exitCode = 1;
120
+ });
package/scripts/dev.sh ADDED
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
6
+ cd "$PROJECT_DIR"
7
+ echo "Project directory: $PROJECT_DIR"
8
+
9
+ echo "Starting development server..."
10
+ echo " - http://localhost:8000 (API + 页面 + HMR)"
11
+ # 相对 filter:本脚本在仓内被 pnpm dev h5(native preset)调用,名称 filter 会三模板同名命中
12
+ pnpm --filter ./apps/server dev
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
6
+ cd "$PROJECT_DIR"
7
+
8
+ echo "Installing dependencies..."
9
+ CI=true pnpm install
10
+
11
+ echo "Prepare complete!"
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
6
+ cd "$PROJECT_DIR"
7
+
8
+ echo "Starting production server..."
9
+ echo "NODE_ENV: $NODE_ENV"
10
+
11
+ # 相对 filter:仓内直跑时名称 filter 会三模板同名命中
12
+ NODE_ENV=production pnpm --filter ./apps/server start
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+ # 停止本应用全部进程。杀面收敛原则(docs/postmortem/0001):
3
+ # 1) cmdline 匹配:锚定本项目路径,逐字符 ERE 字面量化(BSD/GNU 无歧义),[^ ]* 锁单参数
4
+ # 2) 端口定位:只取 LISTEN 态——持有连接的客户端(编排探活/代理父进程)不入杀面
5
+ # PORT 契约:须与启动环境一致(生产形态 cmdline 不含路径,端口是唯一定位手段)
6
+ # 边界:根级浅层 <P>/node_modules/.bin/x 直调形态不在 pattern 内(模板内无此调用形态;.pnpm 深路径匹配)
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
10
+ cd "$PROJECT_DIR"
11
+ PORT=${PORT:-8000}
12
+
13
+ esc_ere() { # 元字符以 [c] 字面量化;单遍扫描防插入物自污染(BSD \+ 为量词,已实证)。
14
+ # 特例:^ 用 [\^]——裸置双引擎皆为行首锚(实证),[^] 则是非法未闭合括号表达式;\ 用 [\\]
15
+ local s="$1" i c out=""
16
+ for ((i=0; i<${#s}; i++)); do
17
+ c=${s:$i:1}
18
+ case $c in
19
+ '.'|'+'|'*'|'?'|'$'|'('|')'|'{'|'}'|'|'|'['|']') out+="[$c]" ;;
20
+ '^') out+='[\^]' ;;
21
+ '\') out+='[\\]' ;;
22
+ *) out+=$c ;;
23
+ esac
24
+ done
25
+ printf '%s' "$out"
26
+ }
27
+
28
+ LISTEN_TOOLS_OK=1 # 主作用域预计算(listen_pids 经命令替换运行于子 shell,内赋值不外传——R4b 实证)
29
+ command -v lsof >/dev/null 2>&1 || command -v ss >/dev/null 2>&1 || LISTEN_TOOLS_OK=0
30
+ listen_pids() { # 两级降级:lsof(macOS 恒有)→ ss(iproute2);参数化端口便于多端口扩展
31
+ local p=${1:-$PORT}
32
+ if command -v lsof >/dev/null 2>&1; then
33
+ lsof -nP -t -i :"$p" -sTCP:LISTEN 2>/dev/null | sort -u
34
+ elif command -v ss >/dev/null 2>&1; then
35
+ ss -ltnpH "sport = :$p" 2>/dev/null | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u
36
+ else
37
+ echo "Warning: lsof/ss 均不可用,端口清理跳过(cmdline 兜底仍执行)" >&2
38
+ fi
39
+ }
40
+
41
+ echo "=== Cleaning up residual processes ==="
42
+ ESC=$(esc_ere "$PROJECT_DIR")
43
+ pkill -TERM -f "$ESC/[^ ]*/node_modules" 2>/dev/null || true # 先于端口杀:杜绝 watch 复活窗口
44
+
45
+ PIDS=$(listen_pids "$PORT")
46
+ if [ -n "$PIDS" ]; then
47
+ echo "=== Stopping server on port $PORT ==="
48
+ for PID in $PIDS; do echo "Killing process $PID..."; kill -9 "$PID" 2>/dev/null || true; done
49
+ sleep 1
50
+ if [ -n "$(listen_pids "$PORT")" ]; then
51
+ echo "Warning: Port $PORT still in use"
52
+ else
53
+ echo "Port $PORT is now free"
54
+ fi
55
+ elif [ "$LISTEN_TOOLS_OK" = 1 ]; then
56
+ echo "=== Port $PORT: no listener ==="
57
+ else
58
+ echo "=== Port $PORT: listener status unknown(定位工具缺失)==="
59
+ fi
60
+ echo "=== All services stopped ==="
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "allowImportingTsExtensions": true,
14
+ "noEmit": true,
15
+ "incremental": true,
16
+ "useDefineForClassFields": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noFallthroughCasesInSwitch": true
20
+ }
21
+ }