@lark-apaas/coding-templates 0.1.43-alpha.1 → 0.1.43-alpha.11
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/meta.json +8 -2
- package/package.json +1 -1
- package/template-html/.spark_project +17 -0
- package/template-html/index.html +19 -20
- package/template-html/package.json +17 -2
- package/template-html/scripts/build.sh +28 -16
- package/template-html/scripts/mock-health.cjs +32 -0
- package/template-html/src/main.js +38 -0
- package/template-html/src/styles.css +1 -0
- package/template-html/vite.config.ts +70 -0
- package/template-vite-react/.spark/meta.json +8 -0
- package/template-vite-react/{.spark-project → .spark_project} +1 -1
- package/template-vite-react/README.md +55 -32
- package/template-vite-react/components.json +1 -1
- package/template-vite-react/package.json +6 -5
- package/template-vite-react/scripts/dev.cjs +162 -0
- package/template-vite-react/scripts/measure-boot.sh +219 -0
- package/template-vite-react/scripts/mock-health.cjs +1 -1
- package/template-vite-react/src/assets/logo.svg +4 -0
- package/template-vite-react/{client/src → src}/index.tsx +9 -2
- package/template-vite-react/tsconfig.app.json +3 -3
- package/template-vite-react/tsconfig.json +1 -1
- package/template-vite-react/vite.config.ts +1 -2
- package/template-html/_gitignore +0 -2
- package/template-html/routes.json +0 -1
- /package/template-vite-react/{client/index.html → index.html} +0 -0
- /package/template-vite-react/{client/public → public}/favicon.svg +0 -0
- /package/template-vite-react/{client/public → public}/icons.svg +0 -0
- /package/template-vite-react/{client/src → src}/app.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/layout.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/accordion.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/alert-dialog.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/alert.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/aspect-ratio.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/avatar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/badge.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/breadcrumb.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/button-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/button.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/calendar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/card.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/carousel.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/chart.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/checkbox.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/collapsible.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/command.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/context-menu.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/dialog.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/drawer.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/dropdown-menu.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/empty.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/field.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/form.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/hover-card.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/image.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/input-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/input-otp.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/input.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/item.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/kbd.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/label.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/menubar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/native-select.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/navigation-menu.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/pagination.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/popover.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/progress.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/radio-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/resizable.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/scroll-area.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/select.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/separator.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/sheet.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/sidebar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/skeleton.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/slider.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/sonner.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/spinner.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/switch.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/table.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/tabs.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/textarea.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/toggle-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/toggle.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/tooltip.tsx +0 -0
- /package/template-vite-react/{client/src → src}/hooks/use-mobile.ts +0 -0
- /package/template-vite-react/{client/src → src}/index.css +0 -0
- /package/template-vite-react/{client/src → src}/pages/NotFoundPage/NotFoundPage.tsx +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* jsPage 模板 dev launcher。
|
|
4
|
+
*
|
|
5
|
+
* 跟全栈 dev.js 对齐:每个子进程的 stdout/stderr 经 readline 逐行拼上
|
|
6
|
+
* 时间戳 + [name] 前缀,同时写到 logs/<name>.std.log(沙箱外壳 dev-logs
|
|
7
|
+
* middleware 监听的标准路径)和当前 stdout。
|
|
8
|
+
*
|
|
9
|
+
* jsPage 只需要 client(vite) + health 两个进程,没有 server。
|
|
10
|
+
* 端口约定(保持跟 .spark_project healthCheck 一致):
|
|
11
|
+
* - vite → 8001(CLIENT_DEV_PORT 可覆盖)
|
|
12
|
+
* - mock-health → 3000(占位,老沙箱探针兼容用)
|
|
13
|
+
*
|
|
14
|
+
* 进程组与孤儿清理:
|
|
15
|
+
* - 子进程 detached: true → 自己一个 PGID,cleanup 时 process.kill(-pid) 整组带走
|
|
16
|
+
* - 监听 SIGINT/SIGTERM/SIGHUP/uncaughtException 触发 cleanup
|
|
17
|
+
* - 启动前 killOrphansByPort 清理端口残留孤儿(应对沙箱保活重启)
|
|
18
|
+
*
|
|
19
|
+
* 触发重启场景:
|
|
20
|
+
* - `pkill -f "npm run dev"` 杀 npm 进程 → dev.cjs 收 SIGHUP → cleanup
|
|
21
|
+
* - 沙箱保活机制重启 npm run dev → 新 dev.cjs → killOrphansByPort 清理旧 vite/mock-health
|
|
22
|
+
*/
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const readline = require('readline');
|
|
28
|
+
const { spawn, execSync } = require('child_process');
|
|
29
|
+
|
|
30
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
31
|
+
process.chdir(ROOT);
|
|
32
|
+
|
|
33
|
+
const LOG_DIR = process.env.LOG_DIR || 'logs';
|
|
34
|
+
const CLIENT_DEV_PORT = process.env.CLIENT_DEV_PORT || '8001';
|
|
35
|
+
const HEALTH_PORT = '3000';
|
|
36
|
+
|
|
37
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
38
|
+
|
|
39
|
+
function timestamp() {
|
|
40
|
+
const now = new Date();
|
|
41
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
42
|
+
return (
|
|
43
|
+
`${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
|
|
44
|
+
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function log(msg) {
|
|
49
|
+
const line = `[${timestamp()}] [dev] ${msg}\n`;
|
|
50
|
+
try { process.stdout.write(line); } catch {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 启动前清理端口占用的孤儿进程(沙箱保活重启时关键):
|
|
55
|
+
* 之前 dev.cjs 被 pkill 杀掉后,子进程的 vite/mock-health 可能没被一起带走,
|
|
56
|
+
* 保活机制重启新 dev.cjs 时发现端口被占,导致 vite/mock-health 起不来。
|
|
57
|
+
*/
|
|
58
|
+
function killOrphansByPort(port) {
|
|
59
|
+
try {
|
|
60
|
+
const out = execSync(`lsof -ti:${port}`, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
61
|
+
.toString()
|
|
62
|
+
.trim();
|
|
63
|
+
if (!out) return [];
|
|
64
|
+
const pids = out.split('\n').filter(Boolean);
|
|
65
|
+
for (const pid of pids) {
|
|
66
|
+
try {
|
|
67
|
+
process.kill(Number(pid), 'SIGKILL');
|
|
68
|
+
log(`killed orphan pid=${pid} on :${port}`);
|
|
69
|
+
} catch {
|
|
70
|
+
// 进程可能已退出
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return pids;
|
|
74
|
+
} catch {
|
|
75
|
+
// lsof 没找到任何占用,正常情况
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const managed = [];
|
|
81
|
+
|
|
82
|
+
function startProcess({ name, command, args, logFileName }) {
|
|
83
|
+
const logFd = logFileName
|
|
84
|
+
? fs.openSync(path.join(LOG_DIR, logFileName), 'a')
|
|
85
|
+
: null;
|
|
86
|
+
|
|
87
|
+
const child = spawn(command, args, {
|
|
88
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
89
|
+
shell: false,
|
|
90
|
+
cwd: ROOT,
|
|
91
|
+
env: process.env,
|
|
92
|
+
detached: true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const pipeLines = (stream) => {
|
|
96
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
97
|
+
rl.on('line', (line) => {
|
|
98
|
+
const msg = `[${timestamp()}] [${name}] ${line}\n`;
|
|
99
|
+
try { process.stdout.write(msg); } catch {}
|
|
100
|
+
if (logFd != null) {
|
|
101
|
+
try { fs.writeSync(logFd, msg); } catch {}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
pipeLines(child.stdout);
|
|
106
|
+
pipeLines(child.stderr);
|
|
107
|
+
|
|
108
|
+
managed.push({ name, child });
|
|
109
|
+
return child;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 先清掉可能残留的孤儿进程,再启动新子进程
|
|
113
|
+
killOrphansByPort(CLIENT_DEV_PORT);
|
|
114
|
+
killOrphansByPort(HEALTH_PORT);
|
|
115
|
+
|
|
116
|
+
startProcess({
|
|
117
|
+
name: 'client',
|
|
118
|
+
command: 'npx',
|
|
119
|
+
args: ['vite', '--port', CLIENT_DEV_PORT, '--host', '0.0.0.0'],
|
|
120
|
+
logFileName: 'client.std.log',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
startProcess({
|
|
124
|
+
name: 'health',
|
|
125
|
+
command: 'node',
|
|
126
|
+
args: ['scripts/mock-health.cjs'],
|
|
127
|
+
// health 进程不单独写 log,透传到 stdout 就够
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
let stopping = false;
|
|
131
|
+
function cleanup(signal) {
|
|
132
|
+
if (stopping) return;
|
|
133
|
+
stopping = true;
|
|
134
|
+
log(`cleanup triggered by ${signal}`);
|
|
135
|
+
|
|
136
|
+
for (const { child } of managed) {
|
|
137
|
+
if (!child.pid) continue;
|
|
138
|
+
try { process.kill(-child.pid, signal || 'SIGTERM'); } catch {}
|
|
139
|
+
}
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
for (const { child } of managed) {
|
|
142
|
+
if (!child.pid) continue;
|
|
143
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
|
|
144
|
+
}
|
|
145
|
+
// 兜底再扫一次端口,确保孤儿被清理
|
|
146
|
+
killOrphansByPort(CLIENT_DEV_PORT);
|
|
147
|
+
killOrphansByPort(HEALTH_PORT);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}, 2000);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
process.on('SIGINT', () => cleanup('SIGTERM'));
|
|
153
|
+
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
|
154
|
+
// pkill -f "npm run dev" 把 npm 杀掉后,dev.cjs 会收到 SIGHUP(controlling
|
|
155
|
+
// tty 关闭),Node 默认行为是直接退出不跑 handler。注册 SIGHUP handler 触发
|
|
156
|
+
// cleanup,让子进程组跟着一起死,避免变孤儿
|
|
157
|
+
process.on('SIGHUP', () => cleanup('SIGTERM'));
|
|
158
|
+
|
|
159
|
+
// 任意子进程退出就触发整体 cleanup,避免半残留
|
|
160
|
+
Promise.race(
|
|
161
|
+
managed.map(({ child }) => new Promise((r) => child.on('exit', r)))
|
|
162
|
+
).then(() => cleanup('SIGTERM'));
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 测沙箱(或本机)模板从 0 到"预览可访问"的耗时拆解。
|
|
3
|
+
# 默认只测 dev 启动那段(不跑 npm install),符合沙箱里依赖已装好的常见场景。
|
|
4
|
+
#
|
|
5
|
+
# 用法:
|
|
6
|
+
# bash scripts/measure-boot.sh # 默认:只测 dev 启动(warm)
|
|
7
|
+
# bash scripts/measure-boot.sh --cold # 冷启动:清光 vite 预打包缓存
|
|
8
|
+
# bash scripts/measure-boot.sh --with-install # 顺带跑一次 npm install 测装包耗时
|
|
9
|
+
# bash scripts/measure-boot.sh --with-install --cold # 模拟首次冷启(清 node_modules + .vite)
|
|
10
|
+
# bash scripts/measure-boot.sh --port 18001 # 临时换端口
|
|
11
|
+
# bash scripts/measure-boot.sh --base /app/whatever # 临时换 CLIENT_BASE_PATH
|
|
12
|
+
#
|
|
13
|
+
# 兼容:--skip-install 等同于不传(默认就是跳过),保留作为 no-op 防止旧脚本/手势失败。
|
|
14
|
+
#
|
|
15
|
+
# 输出:每段耗时 + 总耗时,写到 ./logs/boot-timing.log(同时打到 stdout)
|
|
16
|
+
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
T1=0; T2=0
|
|
19
|
+
DEV_PID=0
|
|
20
|
+
|
|
21
|
+
WITH_INSTALL=0
|
|
22
|
+
COLD=0
|
|
23
|
+
PORT=""
|
|
24
|
+
BASE="/app/probe"
|
|
25
|
+
LOG_DIR="./logs"
|
|
26
|
+
LOG_FILE="$LOG_DIR/boot-timing.log"
|
|
27
|
+
|
|
28
|
+
while [ $# -gt 0 ]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
--with-install) WITH_INSTALL=1; shift ;;
|
|
31
|
+
--skip-install) shift ;; # no-op,兼容旧手势;默认就是跳过
|
|
32
|
+
--cold) COLD=1; shift ;;
|
|
33
|
+
--port) PORT="$2"; shift 2 ;;
|
|
34
|
+
--base) BASE="$2"; shift 2 ;;
|
|
35
|
+
*) echo "未知参数: $1"; exit 1 ;;
|
|
36
|
+
esac
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
mkdir -p "$LOG_DIR"
|
|
40
|
+
: > "$LOG_FILE"
|
|
41
|
+
|
|
42
|
+
now_ms() {
|
|
43
|
+
# GNU date 支持 %N,macOS 的 BSD date 不支持——优先用 python3 兜底,避免 sandbox 上 BSD 镜像出错。
|
|
44
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
45
|
+
python3 -c 'import time; print(int(time.time()*1000))'
|
|
46
|
+
else
|
|
47
|
+
local raw="$(date +%s%3N)"
|
|
48
|
+
case "$raw" in
|
|
49
|
+
*N) echo $(($(date +%s) * 1000));; # date 不支持 %N,降级到秒级精度
|
|
50
|
+
*) echo "$raw";;
|
|
51
|
+
esac
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
log() {
|
|
55
|
+
local msg="[$(date +%H:%M:%S)] $*"
|
|
56
|
+
echo "$msg" | tee -a "$LOG_FILE"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# 自动从 package.json 提取 vite 端口
|
|
60
|
+
if [ -z "$PORT" ]; then
|
|
61
|
+
PORT=$(grep -oE -- '--port[[:space:]]+[0-9]+' package.json 2>/dev/null | head -1 | awk '{print $2}')
|
|
62
|
+
PORT="${PORT:-8001}"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
export CLIENT_BASE_PATH="$BASE"
|
|
66
|
+
|
|
67
|
+
log "=============================================="
|
|
68
|
+
log "Boot timing — port=$PORT base=$BASE with-install=$WITH_INSTALL cold=$COLD"
|
|
69
|
+
log "=============================================="
|
|
70
|
+
|
|
71
|
+
# ── 冷启动:清光本地缓存(不计入耗时,T_TOTAL_START 之前完成)────────
|
|
72
|
+
if [ "$COLD" = "1" ]; then
|
|
73
|
+
log "[cold] 清缓存以模拟冷启动..."
|
|
74
|
+
# vite 依赖预打包缓存(永远要清,热启动的最大区别就在这)
|
|
75
|
+
if [ -d node_modules/.vite ]; then
|
|
76
|
+
log " - rm -rf node_modules/.vite"
|
|
77
|
+
rm -rf node_modules/.vite
|
|
78
|
+
fi
|
|
79
|
+
# 本脚本上轮日志
|
|
80
|
+
if [ -d "$LOG_DIR" ]; then
|
|
81
|
+
log " - 清 $LOG_DIR/dev.log(避免 grep 命中旧 ready 行)"
|
|
82
|
+
: > "$LOG_DIR/dev.log" 2>/dev/null || true
|
|
83
|
+
fi
|
|
84
|
+
# 项目级 dist(一般不影响 dev,但顺手)
|
|
85
|
+
[ -d dist ] && rm -rf dist
|
|
86
|
+
# 默认保留 node_modules(典型冷启动对照:装好依赖、只清 .vite)
|
|
87
|
+
# 仅 --with-install 模式才清 node_modules 触发 npm install 重装
|
|
88
|
+
if [ "$WITH_INSTALL" = "1" ]; then
|
|
89
|
+
if [ -d node_modules ]; then
|
|
90
|
+
log " - rm -rf node_modules(--with-install 模式:清依赖以模拟首次冷启)"
|
|
91
|
+
rm -rf node_modules
|
|
92
|
+
fi
|
|
93
|
+
# npm 全局缓存留着(不动 /tmp/.npmcache / ~/.npm),不然太慢且不真实
|
|
94
|
+
else
|
|
95
|
+
log " - 保留 node_modules(默认模式:只清 .vite 缓存)"
|
|
96
|
+
fi
|
|
97
|
+
log "[cold] 缓存清理完成"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
T_TOTAL_START=$(now_ms)
|
|
101
|
+
|
|
102
|
+
# ── 阶段 1: npm install ──────────────────────────────────────────────
|
|
103
|
+
if [ "$WITH_INSTALL" = "1" ]; then
|
|
104
|
+
log "[1/4] npm install ..."
|
|
105
|
+
T1=$(now_ms)
|
|
106
|
+
npm install --silent > "$LOG_DIR/install.log" 2>&1
|
|
107
|
+
RC=$?
|
|
108
|
+
T2=$(now_ms)
|
|
109
|
+
if [ $RC -ne 0 ]; then
|
|
110
|
+
log " ❌ npm install 失败(exit=$RC,详见 $LOG_DIR/install.log)"
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
log " ✅ 耗时 $((T2 - T1)) ms"
|
|
114
|
+
else
|
|
115
|
+
log "[1/4] 跳过 npm install(默认;用 --with-install 启用)"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# ── 阶段 2: 启动 dev(进程拉起到出现 \"VITE ready\" 提示)──────────────
|
|
119
|
+
log "[2/4] npm run dev — 等 vite ready 提示 ..."
|
|
120
|
+
|
|
121
|
+
# 杀光占用目标端口的所有进程(不依赖进程名匹配,沙箱里 vite 可能被各种 wrapper 拉起)
|
|
122
|
+
kill_port() {
|
|
123
|
+
local p="$1"
|
|
124
|
+
local pids
|
|
125
|
+
pids=$(lsof -tiTCP:"$p" -sTCP:LISTEN 2>/dev/null | tr '\n' ' ')
|
|
126
|
+
if [ -n "$pids" ]; then
|
|
127
|
+
log " 清理 :$p 上的进程: $pids"
|
|
128
|
+
# shellcheck disable=SC2086
|
|
129
|
+
kill -9 $pids 2>/dev/null || true
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
kill_port "$PORT"
|
|
133
|
+
kill_port 3000
|
|
134
|
+
# 再保险一道:按名字
|
|
135
|
+
pkill -f 'concurrently.*vite' 2>/dev/null || true
|
|
136
|
+
pkill -f 'mock-health' 2>/dev/null || true
|
|
137
|
+
sleep 1
|
|
138
|
+
|
|
139
|
+
# 二次校验:端口必须真的空了
|
|
140
|
+
if lsof -tiTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
141
|
+
log " ❌ 清理后 :$PORT 仍被占用,请手动 kill 后重试"
|
|
142
|
+
log " lsof -i :$PORT"
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# 截断 log 文件确保 grep 命中的是本次启动的输出(不是旧 log 残留)
|
|
147
|
+
: > "$LOG_DIR/dev.log"
|
|
148
|
+
|
|
149
|
+
T3=$(now_ms)
|
|
150
|
+
nohup npm run dev > "$LOG_DIR/dev.log" 2>&1 &
|
|
151
|
+
DEV_PID=$!
|
|
152
|
+
|
|
153
|
+
# 等 vite ready 提示(最多 60s)
|
|
154
|
+
T_READY=""
|
|
155
|
+
for i in $(seq 1 600); do
|
|
156
|
+
if grep -q 'VITE.*ready' "$LOG_DIR/dev.log" 2>/dev/null; then
|
|
157
|
+
T_READY=$(now_ms)
|
|
158
|
+
break
|
|
159
|
+
fi
|
|
160
|
+
# 短路:如果 dev 进程已经退出且没出现 ready,提早失败
|
|
161
|
+
if ! kill -0 "$DEV_PID" 2>/dev/null; then
|
|
162
|
+
log " ❌ dev 进程提前退出,看 $LOG_DIR/dev.log"
|
|
163
|
+
tail -20 "$LOG_DIR/dev.log" | sed 's/^/ /' | tee -a "$LOG_FILE"
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
166
|
+
sleep 0.1
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
if [ -z "$T_READY" ]; then
|
|
170
|
+
log " ❌ 60s 内未看到 vite ready 提示,看 $LOG_DIR/dev.log"
|
|
171
|
+
kill ${DEV_PID} 2>/dev/null
|
|
172
|
+
exit 1
|
|
173
|
+
fi
|
|
174
|
+
log " ✅ 耗时 $((T_READY - T3)) ms"
|
|
175
|
+
|
|
176
|
+
# ── 阶段 3: 端口可 TCP 连接 ──────────────────────────────────────────
|
|
177
|
+
log "[3/4] 端口 :$PORT 接受 TCP 连接 ..."
|
|
178
|
+
T4=$(now_ms)
|
|
179
|
+
T_TCP=""
|
|
180
|
+
for i in $(seq 1 100); do
|
|
181
|
+
if (echo > /dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then
|
|
182
|
+
T_TCP=$(now_ms)
|
|
183
|
+
break
|
|
184
|
+
fi
|
|
185
|
+
sleep 0.05
|
|
186
|
+
done
|
|
187
|
+
if [ -z "$T_TCP" ]; then
|
|
188
|
+
log " ❌ 5s 内端口不可连接"
|
|
189
|
+
kill ${DEV_PID} 2>/dev/null
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
log " ✅ 耗时 $((T_TCP - T4)) ms(自 ready 后再 $((T_TCP - T_READY)) ms)"
|
|
193
|
+
|
|
194
|
+
# ── 阶段 4: 首屏 HTML 200 ─────────────────────────────────────────────
|
|
195
|
+
log "[4/4] curl 首屏(含 base normalize rewrite 路径)..."
|
|
196
|
+
T5=$(now_ms)
|
|
197
|
+
URL="http://127.0.0.1:$PORT${BASE}?probe=1"
|
|
198
|
+
HTTP_CODE=$(curl -sS -o "$LOG_DIR/first.html" -w '%{http_code}' --max-time 10 "$URL" 2>&1)
|
|
199
|
+
T6=$(now_ms)
|
|
200
|
+
SIZE=$(wc -c < "$LOG_DIR/first.html" 2>/dev/null | tr -d ' ')
|
|
201
|
+
|
|
202
|
+
log " URL=$URL"
|
|
203
|
+
log " HTTP=$HTTP_CODE size=${SIZE}B 耗时 $((T6 - T5)) ms"
|
|
204
|
+
|
|
205
|
+
if [ "$HTTP_CODE" != "200" ]; then
|
|
206
|
+
log " ⚠️ 非 200,看 $LOG_DIR/first.html"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
# ── 汇总 ──────────────────────────────────────────────────────────────
|
|
210
|
+
T_TOTAL_END=$(now_ms)
|
|
211
|
+
log "=============================================="
|
|
212
|
+
log "总耗时 $((T_TOTAL_END - T_TOTAL_START)) ms"
|
|
213
|
+
log "dev 进程 PID=${DEV_PID}(kill 后退出:kill ${DEV_PID})"
|
|
214
|
+
log " install: $([ $WITH_INSTALL = 1 ] && echo "$((T2 - T1)) ms" || echo skipped)"
|
|
215
|
+
log " vite ready: $((T_READY - T3)) ms"
|
|
216
|
+
log " tcp accept after ready: $((T_TCP - T_READY)) ms"
|
|
217
|
+
log " first html curl: $((T6 - T5)) ms"
|
|
218
|
+
log "=============================================="
|
|
219
|
+
log "详细日志:$LOG_FILE / $LOG_DIR/dev.log"
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
|
2
|
+
<rect width="64" height="64" rx="12" fill="currentColor" opacity="0.1"/>
|
|
3
|
+
<path d="M20 32 L28 40 L44 24" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
</svg>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { StrictMode } from "react";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import { BrowserRouter } from "react-router-dom";
|
|
4
|
-
import {
|
|
4
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
5
|
+
import { AppContainer, ErrorRender } from "@lark-apaas/client-toolkit-lite";
|
|
5
6
|
import App from "./app";
|
|
6
7
|
import "./index.css";
|
|
7
8
|
|
|
@@ -9,7 +10,13 @@ createRoot(document.getElementById("root")!).render(
|
|
|
9
10
|
<StrictMode>
|
|
10
11
|
<BrowserRouter basename={process.env.CLIENT_BASE_PATH || "/"}>
|
|
11
12
|
<AppContainer>
|
|
12
|
-
<
|
|
13
|
+
<ErrorBoundary
|
|
14
|
+
fallbackRender={({ error, resetErrorBoundary }) => (
|
|
15
|
+
<ErrorRender error={error} resetErrorBoundary={resetErrorBoundary} />
|
|
16
|
+
)}
|
|
17
|
+
>
|
|
18
|
+
<App />
|
|
19
|
+
</ErrorBoundary>
|
|
13
20
|
</AppContainer>
|
|
14
21
|
</BrowserRouter>
|
|
15
22
|
</StrictMode>,
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
5
|
"baseUrl": ".",
|
|
6
6
|
"paths": {
|
|
7
|
-
"@/*": ["./
|
|
7
|
+
"@/*": ["./src/*"],
|
|
8
8
|
"@shared/*": ["./shared/*"]
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"include": ["
|
|
12
|
-
"exclude": ["
|
|
11
|
+
"include": ["src", "shared"],
|
|
12
|
+
"exclude": ["src/components/ui"]
|
|
13
13
|
}
|
|
@@ -2,10 +2,9 @@ import path from 'path'
|
|
|
2
2
|
import { defineConfig } from '@lark-apaas/coding-vite-preset'
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
-
root: path.resolve(__dirname, 'client'),
|
|
6
5
|
resolve: {
|
|
7
6
|
alias: {
|
|
8
|
-
'@': path.resolve(__dirname, '
|
|
7
|
+
'@': path.resolve(__dirname, 'src'),
|
|
9
8
|
'@shared': path.resolve(__dirname, 'shared'),
|
|
10
9
|
},
|
|
11
10
|
},
|
package/template-html/_gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "version": 1, "type": "html", "fallback": "index.html" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|