@lark-apaas/fullstack-cli 1.1.61-alpha.20260819103003 → 1.1.61-beta.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.
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/scripts/dev.js +9 -12
- package/templates/scripts/dev.sh +9 -5
- package/templates/scripts/lint.js +40 -2
- package/templates/scripts/lib/__test__/preserve-dev-cache.test.ts +0 -168
- package/templates/scripts/lib/preserve-dev-cache.cjs +0 -123
package/dist/index.js
CHANGED
|
@@ -7698,7 +7698,7 @@ function sanitizeStructuredLog(value) {
|
|
|
7698
7698
|
delete sanitized.pid;
|
|
7699
7699
|
return sanitized;
|
|
7700
7700
|
}
|
|
7701
|
-
var TRANSIENT_CONNECTION_ERROR_PATTERN = /ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENETUNREACH|proxy error|\[Proxy\] (?:Error:\s*$|Error during|Connection error|Non-connection error|Headers already sent|Service (?:recovered|did not recover))/i;
|
|
7701
|
+
var TRANSIENT_CONNECTION_ERROR_PATTERN = /ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENETUNREACH|socket hang up|proxy error|\[Proxy\] (?:Error:\s*$|Error during|Connection error|Non-connection error|Headers already sent|Service (?:recovered|did not recover))/i;
|
|
7702
7702
|
function hasErrorInStdLines(lines) {
|
|
7703
7703
|
const filtered = lines.filter((line) => !TRANSIENT_CONNECTION_ERROR_PATTERN.test(line));
|
|
7704
7704
|
const combined = filtered.join("\n");
|
package/package.json
CHANGED
package/templates/scripts/dev.js
CHANGED
|
@@ -252,23 +252,20 @@ process.on('SIGTERM', cleanup);
|
|
|
252
252
|
process.on('SIGINT', cleanup);
|
|
253
253
|
process.on('SIGHUP', cleanup);
|
|
254
254
|
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const { preserveDevCache } = require('./lib/preserve-dev-cache.cjs');
|
|
255
|
+
// Stale dist makes nest --watch skip missing files; watcher won't self-heal.
|
|
256
|
+
function cleanStaleDist() {
|
|
257
|
+
const distPath = path.join(PROJECT_ROOT, 'dist');
|
|
258
|
+
if (fs.existsSync(distPath)) {
|
|
259
|
+
fs.rmSync(distPath, { recursive: true, force: true });
|
|
260
|
+
logEvent('INFO', 'main', 'Cleaned dist/ to force full rebuild');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
264
263
|
|
|
265
264
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
266
265
|
async function main() {
|
|
267
266
|
logEvent('INFO', 'main', '========== Dev session started ==========');
|
|
268
267
|
|
|
269
|
-
|
|
270
|
-
log: (level, msg) => logEvent(level, 'main', msg),
|
|
271
|
-
});
|
|
268
|
+
cleanStaleDist();
|
|
272
269
|
|
|
273
270
|
// Initialize action plugins
|
|
274
271
|
writeOutput('\n🔌 Initializing action plugins...\n');
|
package/templates/scripts/dev.sh
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# `npm run dev` 入口;按
|
|
3
|
-
#
|
|
2
|
+
# `npm run dev` 入口;按 MIAODA_DEP_CACHE_DIR 是否非空判断运行环境(与 miaoda-cli
|
|
3
|
+
# isSandboxEnv() 同口径):
|
|
4
|
+
# - 非空(沙箱平台注入的依赖缓存目录)→ 直接跑 dev.js
|
|
4
5
|
# (保活 / restart loop / 文件日志 —— 沙箱生产形态)。脚本同步由平台 pod 启动阶段做过,
|
|
5
6
|
# dev 入口不再额外 `npm run upgrade`。
|
|
6
7
|
# - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.js:纯 stdout、崩了就崩、Agent 友好。
|
|
7
|
-
# 显式想跑本地路径可用 `npm run dev:local
|
|
8
|
+
# 显式想跑本地路径可用 `npm run dev:local`(绕过本判断)。
|
|
9
|
+
#
|
|
10
|
+
# 不用 SANDBOX_ID:它由 sandbox_console 在绑定沙箱时才写入 .force/environment/env,预热池实例
|
|
11
|
+
# 在绑定前拿不到,会被误判成本地。保留为附加条件只为兼容,不影响判定结果。
|
|
8
12
|
set -euo pipefail
|
|
9
13
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
14
|
|
|
11
|
-
if [ -n "${SANDBOX_ID:-}" ]; then
|
|
15
|
+
if [ -n "${MIAODA_DEP_CACHE_DIR:-}" ] || [ -n "${SANDBOX_ID:-}" ]; then
|
|
12
16
|
exec node "$SCRIPT_DIR/dev.js" "$@"
|
|
13
17
|
fi
|
|
14
18
|
|
|
@@ -18,7 +22,7 @@ if [ ! -f "$SCRIPT_DIR/dev-local.js" ]; then
|
|
|
18
22
|
fi
|
|
19
23
|
|
|
20
24
|
# 本地启动前先跑一次 miaoda app sync:同步 platform-controlled 内容 + 升 @lark-apaas/* 到
|
|
21
|
-
# latest + 迁移老 npm scripts
|
|
25
|
+
# latest + 迁移老 npm scripts。沙箱不走这里(上面的沙箱分支已经 exec return)。
|
|
22
26
|
npx -y @lark-apaas/miaoda-cli@latest app sync || echo "[dev] miaoda app sync 失败,按现状继续" >&2
|
|
23
27
|
|
|
24
28
|
exec node "$SCRIPT_DIR/dev-local.js" "$@"
|
|
@@ -76,13 +76,49 @@ function isStylelintTarget(filePath) {
|
|
|
76
76
|
return filePath.endsWith('.css');
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
const STYLELINT_CONFIG_FILES = [
|
|
80
|
+
'.stylelintrc',
|
|
81
|
+
'.stylelintrc.js',
|
|
82
|
+
'.stylelintrc.cjs',
|
|
83
|
+
'.stylelintrc.json',
|
|
84
|
+
'stylelint.config.js',
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 老模板(fullstack-nestjs-template 1.x 时代)的应用跑不了 stylelint,跑了就挂、把 pre-commit
|
|
89
|
+
* 卡死。两种形态都实测过:
|
|
90
|
+
*
|
|
91
|
+
* - 缺 scripts.stylelint → `npm error Missing script: "stylelint"`
|
|
92
|
+
* - 缺 .stylelintrc.* → `ConfigurationError: No configuration provided`
|
|
93
|
+
*
|
|
94
|
+
* 注意光判断 stylelint 装没装拦不住:@lark-apaas/fullstack-presets 的 dependencies 里有
|
|
95
|
+
* stylelint,只要 dep 了 presets 二进制就在。缺任一件就跳过这一步。
|
|
96
|
+
*/
|
|
97
|
+
function canRunStylelint() {
|
|
98
|
+
let pkg;
|
|
99
|
+
try {
|
|
100
|
+
pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!pkg.scripts || !pkg.scripts.stylelint) return false;
|
|
106
|
+
|
|
107
|
+
return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
async function runDefaultLint() {
|
|
80
111
|
const taskSpecs = [
|
|
81
112
|
[getBinName('npm'), ['run', 'eslint']],
|
|
82
113
|
[getBinName('npm'), ['run', 'type:check']],
|
|
83
|
-
[getBinName('npm'), ['run', 'stylelint']],
|
|
84
114
|
];
|
|
85
115
|
|
|
116
|
+
if (canRunStylelint()) {
|
|
117
|
+
taskSpecs.push([getBinName('npm'), ['run', 'stylelint']]);
|
|
118
|
+
} else {
|
|
119
|
+
console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
|
|
120
|
+
}
|
|
121
|
+
|
|
86
122
|
process.exit(await runTasksSerially(taskSpecs));
|
|
87
123
|
}
|
|
88
124
|
|
|
@@ -120,7 +156,9 @@ async function runSelectiveLint(inputFiles) {
|
|
|
120
156
|
taskSpecs.push([getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]]);
|
|
121
157
|
}
|
|
122
158
|
|
|
123
|
-
if (stylelintFiles.length > 0) {
|
|
159
|
+
if (stylelintFiles.length > 0 && !canRunStylelint()) {
|
|
160
|
+
console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
|
|
161
|
+
} else if (stylelintFiles.length > 0) {
|
|
124
162
|
taskSpecs.push([getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]]);
|
|
125
163
|
}
|
|
126
164
|
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
-
const { preserveDevCache, detectHtmlMode } = require('../preserve-dev-cache.cjs');
|
|
8
|
-
|
|
9
|
-
interface LogEntry {
|
|
10
|
-
level: string;
|
|
11
|
-
msg: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function makeLogger(entries: LogEntry[]) {
|
|
15
|
-
return (level: string, msg: string) => {
|
|
16
|
-
entries.push({ level, msg });
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const DEV_HTML =
|
|
21
|
-
'<html><head><meta name="miaoda-html-mode" content="dev"><title>x</title></head><body></body></html>';
|
|
22
|
-
const PROD_HTML =
|
|
23
|
-
'<html><head><meta name="miaoda-html-mode" content="prod"><title>x</title></head><body></body></html>';
|
|
24
|
-
const UNMARKED_HTML = '<html><head><title>x</title></head><body></body></html>';
|
|
25
|
-
|
|
26
|
-
describe('detectHtmlMode', () => {
|
|
27
|
-
it('reads dev marker', () => {
|
|
28
|
-
expect(detectHtmlMode(DEV_HTML)).toBe('dev');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('reads prod marker', () => {
|
|
32
|
-
expect(detectHtmlMode(PROD_HTML)).toBe('prod');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('returns unknown when no marker', () => {
|
|
36
|
-
expect(detectHtmlMode(UNMARKED_HTML)).toBe('unknown');
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('preserveDevCache', () => {
|
|
41
|
-
let tmpRoot: string;
|
|
42
|
-
let logs: LogEntry[];
|
|
43
|
-
|
|
44
|
-
function setupDist(
|
|
45
|
-
files: Record<string, string | null> = {}
|
|
46
|
-
): { distPath: string; clientDir: string; serverDir: string } {
|
|
47
|
-
const distPath = path.join(tmpRoot, 'dist');
|
|
48
|
-
const clientDir = path.join(distPath, 'client');
|
|
49
|
-
const serverDir = path.join(distPath, 'server');
|
|
50
|
-
fs.mkdirSync(clientDir, { recursive: true });
|
|
51
|
-
fs.mkdirSync(serverDir, { recursive: true });
|
|
52
|
-
// 默认放个 nest 编译产物 + routes JSON, 用来验证保留策略
|
|
53
|
-
fs.writeFileSync(path.join(serverDir, 'main.js'), '/* nest bundle */');
|
|
54
|
-
fs.writeFileSync(
|
|
55
|
-
path.join(distPath, 'api-routes.json'),
|
|
56
|
-
JSON.stringify([])
|
|
57
|
-
);
|
|
58
|
-
for (const [rel, content] of Object.entries(files)) {
|
|
59
|
-
if (content === null) continue;
|
|
60
|
-
const abs = path.join(distPath, rel);
|
|
61
|
-
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
62
|
-
fs.writeFileSync(abs, content);
|
|
63
|
-
}
|
|
64
|
-
return { distPath, clientDir, serverDir };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'preserve-dev-cache-'));
|
|
69
|
-
logs = [];
|
|
70
|
-
delete process.env.FORCE_CLEAN_DIST;
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
afterEach(() => {
|
|
74
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
75
|
-
delete process.env.FORCE_CLEAN_DIST;
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('no-op when dist/ does not exist', () => {
|
|
79
|
-
const result = preserveDevCache(tmpRoot, { log: makeLogger(logs) });
|
|
80
|
-
expect(result.action).toBe('no_dist');
|
|
81
|
-
expect(fs.existsSync(path.join(tmpRoot, 'dist'))).toBe(false);
|
|
82
|
-
expect(logs[0].msg).toMatch(/no dist/);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('preserves entire dist/client when index.html has dev marker', () => {
|
|
86
|
-
const { clientDir, serverDir } = setupDist({
|
|
87
|
-
'client/index.html': DEV_HTML,
|
|
88
|
-
'client/assets/foo.js': '/* dev asset */',
|
|
89
|
-
});
|
|
90
|
-
const result = preserveDevCache(tmpRoot, { log: makeLogger(logs) });
|
|
91
|
-
expect(result.action).toBe('preserved');
|
|
92
|
-
expect(result.mode).toBe('dev');
|
|
93
|
-
expect(fs.existsSync(path.join(clientDir, 'index.html'))).toBe(true);
|
|
94
|
-
expect(fs.existsSync(path.join(clientDir, 'assets', 'foo.js'))).toBe(true);
|
|
95
|
-
expect(fs.existsSync(path.join(serverDir, 'main.js'))).toBe(true);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('cleans index.html + assets/ but preserves server/ when marker=prod', () => {
|
|
99
|
-
const { distPath, clientDir, serverDir } = setupDist({
|
|
100
|
-
'client/index.html': PROD_HTML,
|
|
101
|
-
'client/assets/index-abc.js': '/* build product */',
|
|
102
|
-
'client/assets/index-def.css': '/* build product */',
|
|
103
|
-
});
|
|
104
|
-
const result = preserveDevCache(tmpRoot, { log: makeLogger(logs) });
|
|
105
|
-
expect(result.action).toBe('cleaned_polluted');
|
|
106
|
-
expect(result.mode).toBe('prod');
|
|
107
|
-
expect(fs.existsSync(path.join(clientDir, 'index.html'))).toBe(false);
|
|
108
|
-
expect(fs.existsSync(path.join(clientDir, 'assets'))).toBe(false);
|
|
109
|
-
// dist/server + routes JSON 保留
|
|
110
|
-
expect(fs.existsSync(path.join(serverDir, 'main.js'))).toBe(true);
|
|
111
|
-
expect(fs.existsSync(path.join(distPath, 'api-routes.json'))).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('cleans same way when marker is missing (treats as pollution)', () => {
|
|
115
|
-
const { clientDir, serverDir } = setupDist({
|
|
116
|
-
'client/index.html': UNMARKED_HTML,
|
|
117
|
-
'client/assets/foo.js': '/* whatever */',
|
|
118
|
-
});
|
|
119
|
-
const result = preserveDevCache(tmpRoot, { log: makeLogger(logs) });
|
|
120
|
-
expect(result.action).toBe('cleaned_polluted');
|
|
121
|
-
expect(result.mode).toBe('unknown');
|
|
122
|
-
expect(fs.existsSync(path.join(clientDir, 'index.html'))).toBe(false);
|
|
123
|
-
expect(fs.existsSync(path.join(clientDir, 'assets'))).toBe(false);
|
|
124
|
-
expect(fs.existsSync(path.join(serverDir, 'main.js'))).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('no-op when dist/client/index.html is missing (nest --watch has partial dist)', () => {
|
|
128
|
-
const { distPath, serverDir } = setupDist();
|
|
129
|
-
const result = preserveDevCache(tmpRoot, { log: makeLogger(logs) });
|
|
130
|
-
expect(result.action).toBe('no_index');
|
|
131
|
-
expect(fs.existsSync(distPath)).toBe(true);
|
|
132
|
-
expect(fs.existsSync(path.join(serverDir, 'main.js'))).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('FORCE_CLEAN_DIST=true wipes entire dist regardless of marker', () => {
|
|
136
|
-
const { distPath } = setupDist({
|
|
137
|
-
'client/index.html': DEV_HTML,
|
|
138
|
-
'client/assets/foo.js': 'x',
|
|
139
|
-
});
|
|
140
|
-
const result = preserveDevCache(tmpRoot, {
|
|
141
|
-
log: makeLogger(logs),
|
|
142
|
-
forceClean: true,
|
|
143
|
-
});
|
|
144
|
-
expect(result.action).toBe('force_cleaned');
|
|
145
|
-
expect(fs.existsSync(distPath)).toBe(false);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('FORCE_CLEAN_DIST env var enables force clean path', () => {
|
|
149
|
-
process.env.FORCE_CLEAN_DIST = 'true';
|
|
150
|
-
const { distPath } = setupDist({
|
|
151
|
-
'client/index.html': DEV_HTML,
|
|
152
|
-
});
|
|
153
|
-
const result = preserveDevCache(tmpRoot, { log: makeLogger(logs) });
|
|
154
|
-
expect(result.action).toBe('force_cleaned');
|
|
155
|
-
expect(fs.existsSync(distPath)).toBe(false);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('emits INFO log with mode and action', () => {
|
|
159
|
-
setupDist({
|
|
160
|
-
'client/index.html': PROD_HTML,
|
|
161
|
-
'client/assets/foo.js': 'x',
|
|
162
|
-
});
|
|
163
|
-
preserveDevCache(tmpRoot, { log: makeLogger(logs) });
|
|
164
|
-
const msgs = logs.map((l) => l.msg).join('\n');
|
|
165
|
-
expect(msgs).toContain('prod');
|
|
166
|
-
expect(msgs).toContain('cleaned');
|
|
167
|
-
});
|
|
168
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// dev.js 启动时替代无脑 rm -rf dist/ 的 lib: 保留 dist/server (nest --watch 增量编译起点)
|
|
4
|
-
// + 按 HTML mode marker 决定 dist/client 是保留还是清污染。
|
|
5
|
-
// 跟 preset 侧 html-output plugin 的 injectHtmlModeMarker / detectHtmlMode 配套:
|
|
6
|
-
// preset 写盘时打 marker (dev 版 vs prod 污染版), 这里读 marker 定去留。
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 从 HTML 里回读 <meta name="miaoda-html-mode" content="dev|prod"> marker。
|
|
13
|
-
* 与 packages/tools/fullstack-vite-preset/src/vite-plugins/html-output-plugin.ts
|
|
14
|
-
* 的 detectHtmlMode 同源, 为了让 dev.js 模板脚本零依赖跑起来这里重复一份。
|
|
15
|
-
*/
|
|
16
|
-
function detectHtmlMode(htmlContent) {
|
|
17
|
-
const match = htmlContent.match(
|
|
18
|
-
/<meta\s+name=["']miaoda-html-mode["']\s+content=["'](dev|prod)["'][^>]*>/i
|
|
19
|
-
);
|
|
20
|
-
return (match && match[1]) || 'unknown';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 定向清理 dist/ 的可复用副本, 替代 dev.js 之前无脑 `rm -rf dist/` 的 cleanStaleDist。
|
|
25
|
-
*
|
|
26
|
-
* 决策规则:
|
|
27
|
-
* - FORCE_CLEAN_DIST=true → 走 escape hatch 全清 (preserveDevCache 逻辑 / 保留的
|
|
28
|
-
* dist/server 出 stale 时兜底)
|
|
29
|
-
* - dist/ 不存在 → 无动作
|
|
30
|
-
* - dist/client/index.html 存在且 marker=dev → 整个 dist/client/ 保留 (dev cache hit)
|
|
31
|
-
* - 存在但 marker=prod 或缺失 → 只清 dist/client/index.html + dist/client/assets/
|
|
32
|
-
* (build 污染部分), dist/server/ 与 routes JSON 保留
|
|
33
|
-
* - 不存在 → 无动作, htmlOutputPlugin 稍后会写入
|
|
34
|
-
*
|
|
35
|
-
* @param {string} projectRoot 项目根目录
|
|
36
|
-
* @param {object} [options]
|
|
37
|
-
* @param {(level: string, msg: string) => void} [options.log] 日志回调, 默认 console.log
|
|
38
|
-
* @param {boolean} [options.forceClean] 覆盖 FORCE_CLEAN_DIST env var 判断 (给测试用)
|
|
39
|
-
* @returns {{ action: 'no_dist' | 'force_cleaned' | 'no_index' | 'preserved' | 'cleaned_polluted' | 'read_failed_cleaned' | 'cleanup_failed', mode: string }}
|
|
40
|
-
*/
|
|
41
|
-
function preserveDevCache(projectRoot, options) {
|
|
42
|
-
const log = (options && options.log) || defaultLogger;
|
|
43
|
-
const forceClean =
|
|
44
|
-
options && typeof options.forceClean === 'boolean'
|
|
45
|
-
? options.forceClean
|
|
46
|
-
: process.env.FORCE_CLEAN_DIST === 'true';
|
|
47
|
-
|
|
48
|
-
const distPath = path.join(projectRoot, 'dist');
|
|
49
|
-
if (!fs.existsSync(distPath)) {
|
|
50
|
-
log('INFO', 'preserveDevCache: no dist/, will be created by vite/nest');
|
|
51
|
-
return { action: 'no_dist', mode: 'unknown' };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (forceClean) {
|
|
55
|
-
fs.rmSync(distPath, { recursive: true, force: true });
|
|
56
|
-
log(
|
|
57
|
-
'INFO',
|
|
58
|
-
'preserveDevCache: FORCE_CLEAN_DIST=true, cleaned entire dist/'
|
|
59
|
-
);
|
|
60
|
-
return { action: 'force_cleaned', mode: 'unknown' };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const clientDir = path.join(distPath, 'client');
|
|
64
|
-
const indexHtml = path.join(clientDir, 'index.html');
|
|
65
|
-
|
|
66
|
-
if (!fs.existsSync(indexHtml)) {
|
|
67
|
-
log(
|
|
68
|
-
'INFO',
|
|
69
|
-
'preserveDevCache: no dist/client/index.html, other artifacts preserved'
|
|
70
|
-
);
|
|
71
|
-
return { action: 'no_index', mode: 'unknown' };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let mode = 'unknown';
|
|
75
|
-
let readFailed = false;
|
|
76
|
-
try {
|
|
77
|
-
const content = fs.readFileSync(indexHtml, 'utf-8');
|
|
78
|
-
mode = detectHtmlMode(content);
|
|
79
|
-
} catch (e) {
|
|
80
|
-
log(
|
|
81
|
-
'WARN',
|
|
82
|
-
`preserveDevCache: read dist/client/index.html failed: ${e.message}, treating as polluted`
|
|
83
|
-
);
|
|
84
|
-
readFailed = true;
|
|
85
|
-
mode = 'unknown';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (mode === 'dev') {
|
|
89
|
-
log('INFO', 'preserveDevCache: dist/client preserved (dev cache hit)');
|
|
90
|
-
return { action: 'preserved', mode: 'dev' };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// marker=prod 或 unknown → build:client 污染 (或人为放的产物), 定向清 index.html + assets/
|
|
94
|
-
// dist/server/, dist/*.json 保留
|
|
95
|
-
try {
|
|
96
|
-
fs.rmSync(indexHtml, { force: true });
|
|
97
|
-
const assetsDir = path.join(clientDir, 'assets');
|
|
98
|
-
if (fs.existsSync(assetsDir)) {
|
|
99
|
-
fs.rmSync(assetsDir, { recursive: true, force: true });
|
|
100
|
-
}
|
|
101
|
-
log(
|
|
102
|
-
'INFO',
|
|
103
|
-
`preserveDevCache: dist/client/index.html was ${mode}, cleaned index.html + assets/ (dist/server preserved)`
|
|
104
|
-
);
|
|
105
|
-
return {
|
|
106
|
-
action: readFailed ? 'read_failed_cleaned' : 'cleaned_polluted',
|
|
107
|
-
mode,
|
|
108
|
-
};
|
|
109
|
-
} catch (e) {
|
|
110
|
-
log(
|
|
111
|
-
'WARN',
|
|
112
|
-
`preserveDevCache: cleanup failed: ${e.message}, continuing anyway (index.html / assets may still be polluted)`
|
|
113
|
-
);
|
|
114
|
-
return { action: 'cleanup_failed', mode };
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function defaultLogger(level, msg) {
|
|
119
|
-
// eslint-disable-next-line no-console
|
|
120
|
-
console.log(`[${level}] [main] ${msg}`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
module.exports = { detectHtmlMode, preserveDevCache };
|