@lark-apaas/fullstack-cli 1.1.61 → 1.1.62-alpha.20260821103740
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/.gitignore.append +2 -0
- package/templates/scripts/build.sh +12 -0
- package/templates/scripts/dev.js +10 -9
- package/templates/scripts/dev.sh +5 -9
- package/templates/scripts/lib/__test__/preserve-dev-cache.test.ts +164 -0
- package/templates/scripts/lib/preserve-dev-cache.cjs +122 -0
- package/templates/scripts/lint.js +2 -40
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|
|
|
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;
|
|
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
|
@@ -149,6 +149,18 @@ if [ -d "$DIST_DIR/client" ]; then
|
|
|
149
149
|
find "$DIST_DIR/client" -maxdepth 1 -name "*.html" -exec mv {} "$DIST_DIR/dist/client/" \;
|
|
150
150
|
fi
|
|
151
151
|
|
|
152
|
+
# 拷贝 shared/static → dist/shared/static
|
|
153
|
+
# Nest server 生产从 process.cwd() + shared/static 读 (nestjs-core static.controller.ts:100),
|
|
154
|
+
# 生产 cwd = dist/, 所以目标是 dist/shared/static。coding-preset 的 static-assets plugin
|
|
155
|
+
# 之前 closeBundle 拷贝, 现在删除后交给 build.sh 显式做 (跟 jsPage 的 output_static rsync 对齐)。
|
|
156
|
+
# 排除 .ts/.tsx/.js/.jsx: 避免 shared/static/ 里若有开发脚本被误传到生产。
|
|
157
|
+
if [ -d "$ROOT_DIR/shared/static" ]; then
|
|
158
|
+
mkdir -p "$DIST_DIR/shared/static"
|
|
159
|
+
rsync -a --exclude='*.ts' --exclude='*.tsx' --exclude='*.js' --exclude='*.jsx' \
|
|
160
|
+
"$ROOT_DIR/shared/static/" "$DIST_DIR/shared/static/"
|
|
161
|
+
echo " Static → dist/shared/static/"
|
|
162
|
+
fi
|
|
163
|
+
|
|
152
164
|
# server 相关产物准备(only_frontend_change=true 时跳过)
|
|
153
165
|
if [[ "${only_frontend_change:-false}" == "true" ]]; then
|
|
154
166
|
echo " [skip] 跳过 run.sh/.env 复制 (only_frontend_change=true)"
|
package/templates/scripts/dev.js
CHANGED
|
@@ -252,20 +252,21 @@ 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
|
-
}
|
|
255
|
+
// dist 缓存策略:
|
|
256
|
+
// 沙箱首次启动 → 保留 dist/, 让 nest --watch 复用上次编译产物
|
|
257
|
+
// 后续启动 (agent 主动触发 npm run dev) → 全清 dist/, 让模型面对新鲜产物避免 stale
|
|
258
|
+
// 依赖升级 (lockfile hash 变) → 同后续启动 (清 dist), log 里点出 "deps changed"
|
|
259
|
+
// FORCE_CLEAN_DIST=true → escape hatch, 强清
|
|
260
|
+
// 具体逻辑抽到 lib/preserve-dev-cache.cjs 便于单测。
|
|
261
|
+
const { preserveDevCache } = require('./lib/preserve-dev-cache.cjs');
|
|
263
262
|
|
|
264
263
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
265
264
|
async function main() {
|
|
266
265
|
logEvent('INFO', 'main', '========== Dev session started ==========');
|
|
267
266
|
|
|
268
|
-
|
|
267
|
+
preserveDevCache(PROJECT_ROOT, {
|
|
268
|
+
log: (level, msg) => logEvent(level, 'main', msg),
|
|
269
|
+
});
|
|
269
270
|
|
|
270
271
|
// Initialize action plugins
|
|
271
272
|
writeOutput('\n🔌 Initializing action plugins...\n');
|
package/templates/scripts/dev.sh
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# `npm run dev` 入口;按
|
|
3
|
-
#
|
|
4
|
-
# - 非空(沙箱平台注入的依赖缓存目录)→ 直接跑 dev.js
|
|
2
|
+
# `npm run dev` 入口;按 SANDBOX_ID 是否非空判断运行环境:
|
|
3
|
+
# - SANDBOX_ID 非空(沙箱平台注入应用所属沙箱 ID)→ 直接跑 dev.js
|
|
5
4
|
# (保活 / restart loop / 文件日志 —— 沙箱生产形态)。脚本同步由平台 pod 启动阶段做过,
|
|
6
5
|
# dev 入口不再额外 `npm run upgrade`。
|
|
7
6
|
# - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.js:纯 stdout、崩了就崩、Agent 友好。
|
|
8
|
-
# 显式想跑本地路径可用 `npm run dev:local
|
|
9
|
-
#
|
|
10
|
-
# 不用 SANDBOX_ID:它由 sandbox_console 在绑定沙箱时才写入 .force/environment/env,预热池实例
|
|
11
|
-
# 在绑定前拿不到,会被误判成本地。保留为附加条件只为兼容,不影响判定结果。
|
|
7
|
+
# 显式想跑本地路径可用 `npm run dev:local`(绕过 SANDBOX_ID 判断)。
|
|
12
8
|
set -euo pipefail
|
|
13
9
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
10
|
|
|
15
|
-
if [ -n "${
|
|
11
|
+
if [ -n "${SANDBOX_ID:-}" ]; then
|
|
16
12
|
exec node "$SCRIPT_DIR/dev.js" "$@"
|
|
17
13
|
fi
|
|
18
14
|
|
|
@@ -22,7 +18,7 @@ if [ ! -f "$SCRIPT_DIR/dev-local.js" ]; then
|
|
|
22
18
|
fi
|
|
23
19
|
|
|
24
20
|
# 本地启动前先跑一次 miaoda app sync:同步 platform-controlled 内容 + 升 @lark-apaas/* 到
|
|
25
|
-
# latest + 迁移老 npm scripts
|
|
21
|
+
# latest + 迁移老 npm scripts。沙箱不走这里(SANDBOX_ID 分支已经 exec return)。
|
|
26
22
|
npx -y @lark-apaas/miaoda-cli@latest app sync || echo "[dev] miaoda app sync 失败,按现状继续" >&2
|
|
27
23
|
|
|
28
24
|
exec node "$SCRIPT_DIR/dev-local.js" "$@"
|
|
@@ -0,0 +1,164 @@
|
|
|
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 } = 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 MARKER_FILE = '.miaoda-dev-boot';
|
|
21
|
+
|
|
22
|
+
describe('preserveDevCache', () => {
|
|
23
|
+
let tmpDir: string;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'preserve-dev-cache-'));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
try {
|
|
31
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
32
|
+
} catch {
|
|
33
|
+
// ignore
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('FORCE_CLEAN_DIST escape hatch', () => {
|
|
38
|
+
it('cleans dist and writes marker regardless of state', () => {
|
|
39
|
+
fs.mkdirSync(path.join(tmpDir, 'dist', 'server'), { recursive: true });
|
|
40
|
+
fs.writeFileSync(path.join(tmpDir, 'yarn.lock'), 'lock-content');
|
|
41
|
+
const logs: LogEntry[] = [];
|
|
42
|
+
|
|
43
|
+
const r = preserveDevCache(tmpDir, {
|
|
44
|
+
log: makeLogger(logs),
|
|
45
|
+
forceClean: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(r.action).toBe('force_cleaned');
|
|
49
|
+
expect(fs.existsSync(path.join(tmpDir, 'dist'))).toBe(false);
|
|
50
|
+
expect(fs.existsSync(path.join(tmpDir, MARKER_FILE))).toBe(true);
|
|
51
|
+
expect(logs[0].msg).toContain('FORCE_CLEAN_DIST');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('first boot (no marker)', () => {
|
|
56
|
+
it('preserves existing dist and writes marker', () => {
|
|
57
|
+
fs.mkdirSync(path.join(tmpDir, 'dist', 'server'), { recursive: true });
|
|
58
|
+
fs.writeFileSync(
|
|
59
|
+
path.join(tmpDir, 'dist', 'server', 'main.js'),
|
|
60
|
+
'compiled-content'
|
|
61
|
+
);
|
|
62
|
+
const logs: LogEntry[] = [];
|
|
63
|
+
|
|
64
|
+
const r = preserveDevCache(tmpDir, { log: makeLogger(logs), forceClean: false });
|
|
65
|
+
|
|
66
|
+
expect(r.action).toBe('first_boot_preserved');
|
|
67
|
+
expect(fs.existsSync(path.join(tmpDir, 'dist', 'server', 'main.js'))).toBe(true);
|
|
68
|
+
expect(fs.existsSync(path.join(tmpDir, MARKER_FILE))).toBe(true);
|
|
69
|
+
expect(logs[0].msg).toContain('first boot');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('handles no dist as first_boot_no_dist', () => {
|
|
73
|
+
const logs: LogEntry[] = [];
|
|
74
|
+
|
|
75
|
+
const r = preserveDevCache(tmpDir, { log: makeLogger(logs), forceClean: false });
|
|
76
|
+
|
|
77
|
+
expect(r.action).toBe('first_boot_no_dist');
|
|
78
|
+
expect(fs.existsSync(path.join(tmpDir, MARKER_FILE))).toBe(true);
|
|
79
|
+
expect(logs[0].msg).toContain('no dist/');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('marker stores current lockfile hash', () => {
|
|
83
|
+
fs.writeFileSync(path.join(tmpDir, 'yarn.lock'), 'initial-lock');
|
|
84
|
+
|
|
85
|
+
preserveDevCache(tmpDir, { log: makeLogger([]), forceClean: false });
|
|
86
|
+
|
|
87
|
+
const raw = fs.readFileSync(path.join(tmpDir, MARKER_FILE), 'utf-8');
|
|
88
|
+
const data = JSON.parse(raw);
|
|
89
|
+
expect(typeof data.lockfileHash).toBe('string');
|
|
90
|
+
expect(data.lockfileHash.length).toBeGreaterThan(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('subsequent boot (marker exists)', () => {
|
|
95
|
+
it('cleans dist and rewrites marker when lockfile unchanged', () => {
|
|
96
|
+
fs.writeFileSync(path.join(tmpDir, 'yarn.lock'), 'lock-content');
|
|
97
|
+
// 先做一次首启建 marker
|
|
98
|
+
preserveDevCache(tmpDir, { log: makeLogger([]), forceClean: false });
|
|
99
|
+
|
|
100
|
+
fs.mkdirSync(path.join(tmpDir, 'dist', 'server'), { recursive: true });
|
|
101
|
+
fs.writeFileSync(path.join(tmpDir, 'dist', 'server', 'stale.js'), 'stale');
|
|
102
|
+
|
|
103
|
+
const logs: LogEntry[] = [];
|
|
104
|
+
const r = preserveDevCache(tmpDir, { log: makeLogger(logs), forceClean: false });
|
|
105
|
+
|
|
106
|
+
expect(r.action).toBe('subsequent_boot_cleaned');
|
|
107
|
+
expect(fs.existsSync(path.join(tmpDir, 'dist'))).toBe(false);
|
|
108
|
+
expect(fs.existsSync(path.join(tmpDir, MARKER_FILE))).toBe(true);
|
|
109
|
+
expect(logs[0].msg).toContain('subsequent boot');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('reports deps_changed when lockfile hash mismatches marker', () => {
|
|
113
|
+
fs.writeFileSync(path.join(tmpDir, 'yarn.lock'), 'lock-v1');
|
|
114
|
+
preserveDevCache(tmpDir, { log: makeLogger([]), forceClean: false });
|
|
115
|
+
|
|
116
|
+
// 依赖升级: lockfile 变了
|
|
117
|
+
fs.writeFileSync(path.join(tmpDir, 'yarn.lock'), 'lock-v2');
|
|
118
|
+
fs.mkdirSync(path.join(tmpDir, 'dist', 'server'), { recursive: true });
|
|
119
|
+
|
|
120
|
+
const logs: LogEntry[] = [];
|
|
121
|
+
const r = preserveDevCache(tmpDir, { log: makeLogger(logs), forceClean: false });
|
|
122
|
+
|
|
123
|
+
expect(r.action).toBe('deps_changed_cleaned');
|
|
124
|
+
expect(fs.existsSync(path.join(tmpDir, 'dist'))).toBe(false);
|
|
125
|
+
expect(logs[0].msg).toContain('deps changed');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('reports subsequent_boot_no_dist when marker exists but no dist', () => {
|
|
129
|
+
preserveDevCache(tmpDir, { log: makeLogger([]), forceClean: false });
|
|
130
|
+
|
|
131
|
+
const logs: LogEntry[] = [];
|
|
132
|
+
const r = preserveDevCache(tmpDir, { log: makeLogger(logs), forceClean: false });
|
|
133
|
+
|
|
134
|
+
expect(r.action).toBe('subsequent_boot_no_dist');
|
|
135
|
+
expect(fs.existsSync(path.join(tmpDir, MARKER_FILE))).toBe(true);
|
|
136
|
+
expect(logs[0].msg).toContain('no dist/');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('lockfile detection', () => {
|
|
141
|
+
it('supports package-lock.json', () => {
|
|
142
|
+
fs.writeFileSync(path.join(tmpDir, 'package-lock.json'), '{}');
|
|
143
|
+
preserveDevCache(tmpDir, { log: makeLogger([]), forceClean: false });
|
|
144
|
+
|
|
145
|
+
const raw = fs.readFileSync(path.join(tmpDir, MARKER_FILE), 'utf-8');
|
|
146
|
+
expect(JSON.parse(raw).lockfileHash).not.toBeNull();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('supports pnpm-lock.yaml', () => {
|
|
150
|
+
fs.writeFileSync(path.join(tmpDir, 'pnpm-lock.yaml'), 'lockfileVersion: 9');
|
|
151
|
+
preserveDevCache(tmpDir, { log: makeLogger([]), forceClean: false });
|
|
152
|
+
|
|
153
|
+
const raw = fs.readFileSync(path.join(tmpDir, MARKER_FILE), 'utf-8');
|
|
154
|
+
expect(JSON.parse(raw).lockfileHash).not.toBeNull();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('stores null lockfileHash when no lockfile present', () => {
|
|
158
|
+
preserveDevCache(tmpDir, { log: makeLogger([]), forceClean: false });
|
|
159
|
+
|
|
160
|
+
const raw = fs.readFileSync(path.join(tmpDir, MARKER_FILE), 'utf-8');
|
|
161
|
+
expect(JSON.parse(raw).lockfileHash).toBeNull();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// dev.js 启动时的 dist 缓存策略:
|
|
4
|
+
// - 沙箱首次启动 (marker 不存在): 保留 dist/, 让 nest --watch 复用上次的编译产物
|
|
5
|
+
// - 后续启动 (agent 主动触发 npm run dev): 全清 dist/, 让模型面对新鲜产物避免 stale
|
|
6
|
+
// - 依赖升级 (lockfile hash 变): 后续启动分支自然覆盖 (清 dist), log 里点出原因
|
|
7
|
+
// - FORCE_CLEAN_DIST=true: escape hatch, 强清
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
|
|
13
|
+
const MARKER_FILE = '.miaoda-dev-boot';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} projectRoot 项目根目录
|
|
17
|
+
* @param {object} [options]
|
|
18
|
+
* @param {(level: string, msg: string) => void} [options.log]
|
|
19
|
+
* @param {boolean} [options.forceClean]
|
|
20
|
+
* @returns {{ action: 'force_cleaned' | 'first_boot_preserved' | 'first_boot_no_dist' | 'subsequent_boot_cleaned' | 'deps_changed_cleaned' | 'subsequent_boot_no_dist' }}
|
|
21
|
+
*/
|
|
22
|
+
function preserveDevCache(projectRoot, options) {
|
|
23
|
+
const log = (options && options.log) || defaultLogger;
|
|
24
|
+
const forceClean =
|
|
25
|
+
options && typeof options.forceClean === 'boolean'
|
|
26
|
+
? options.forceClean
|
|
27
|
+
: process.env.FORCE_CLEAN_DIST === 'true';
|
|
28
|
+
|
|
29
|
+
const distPath = path.join(projectRoot, 'dist');
|
|
30
|
+
const markerPath = path.join(projectRoot, MARKER_FILE);
|
|
31
|
+
const currentLockfileHash = computeLockfileHash(projectRoot);
|
|
32
|
+
|
|
33
|
+
if (forceClean) {
|
|
34
|
+
if (fs.existsSync(distPath)) {
|
|
35
|
+
fs.rmSync(distPath, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
writeMarker(markerPath, currentLockfileHash);
|
|
38
|
+
log('INFO', 'preserveDevCache: FORCE_CLEAN_DIST=true, cleaned dist/');
|
|
39
|
+
return { action: 'force_cleaned' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const markerExists = fs.existsSync(markerPath);
|
|
43
|
+
const distExists = fs.existsSync(distPath);
|
|
44
|
+
|
|
45
|
+
if (!markerExists) {
|
|
46
|
+
writeMarker(markerPath, currentLockfileHash);
|
|
47
|
+
if (distExists) {
|
|
48
|
+
log('INFO', 'preserveDevCache: first boot, dist/ preserved');
|
|
49
|
+
return { action: 'first_boot_preserved' };
|
|
50
|
+
}
|
|
51
|
+
log('INFO', 'preserveDevCache: first boot, no dist/ (will be created)');
|
|
52
|
+
return { action: 'first_boot_no_dist' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 后续启动: 无脑清 dist
|
|
56
|
+
const prevLockfileHash = readMarkerHash(markerPath);
|
|
57
|
+
const depsChanged =
|
|
58
|
+
prevLockfileHash !== null &&
|
|
59
|
+
currentLockfileHash !== null &&
|
|
60
|
+
prevLockfileHash !== currentLockfileHash;
|
|
61
|
+
|
|
62
|
+
if (distExists) {
|
|
63
|
+
fs.rmSync(distPath, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
writeMarker(markerPath, currentLockfileHash);
|
|
66
|
+
|
|
67
|
+
if (depsChanged) {
|
|
68
|
+
log('INFO', 'preserveDevCache: deps changed (lockfile hash), cleaned dist/');
|
|
69
|
+
return { action: 'deps_changed_cleaned' };
|
|
70
|
+
}
|
|
71
|
+
if (distExists) {
|
|
72
|
+
log('INFO', 'preserveDevCache: subsequent boot, cleaned dist/');
|
|
73
|
+
return { action: 'subsequent_boot_cleaned' };
|
|
74
|
+
}
|
|
75
|
+
log('INFO', 'preserveDevCache: subsequent boot, no dist/ to clean');
|
|
76
|
+
return { action: 'subsequent_boot_no_dist' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function computeLockfileHash(projectRoot) {
|
|
80
|
+
const candidates = ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'];
|
|
81
|
+
for (const name of candidates) {
|
|
82
|
+
const p = path.join(projectRoot, name);
|
|
83
|
+
if (fs.existsSync(p)) {
|
|
84
|
+
try {
|
|
85
|
+
const content = fs.readFileSync(p);
|
|
86
|
+
return crypto.createHash('sha1').update(content).digest('hex');
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function readMarkerHash(markerPath) {
|
|
96
|
+
try {
|
|
97
|
+
const raw = fs.readFileSync(markerPath, 'utf-8');
|
|
98
|
+
const data = JSON.parse(raw);
|
|
99
|
+
return typeof data.lockfileHash === 'string' ? data.lockfileHash : null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function writeMarker(markerPath, lockfileHash) {
|
|
106
|
+
try {
|
|
107
|
+
fs.writeFileSync(
|
|
108
|
+
markerPath,
|
|
109
|
+
JSON.stringify({ lockfileHash: lockfileHash || null }),
|
|
110
|
+
'utf-8'
|
|
111
|
+
);
|
|
112
|
+
} catch {
|
|
113
|
+
// marker 写失败下次会重试, 不阻塞 dev 启动
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function defaultLogger(level, msg) {
|
|
118
|
+
// eslint-disable-next-line no-console
|
|
119
|
+
console.log(`[${level}] [main] ${msg}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { preserveDevCache };
|
|
@@ -76,49 +76,13 @@ 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
|
-
|
|
110
79
|
async function runDefaultLint() {
|
|
111
80
|
const taskSpecs = [
|
|
112
81
|
[getBinName('npm'), ['run', 'eslint']],
|
|
113
82
|
[getBinName('npm'), ['run', 'type:check']],
|
|
83
|
+
[getBinName('npm'), ['run', 'stylelint']],
|
|
114
84
|
];
|
|
115
85
|
|
|
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
|
-
|
|
122
86
|
process.exit(await runTasksSerially(taskSpecs));
|
|
123
87
|
}
|
|
124
88
|
|
|
@@ -156,9 +120,7 @@ async function runSelectiveLint(inputFiles) {
|
|
|
156
120
|
taskSpecs.push([getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]]);
|
|
157
121
|
}
|
|
158
122
|
|
|
159
|
-
if (stylelintFiles.length > 0
|
|
160
|
-
console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
|
|
161
|
-
} else if (stylelintFiles.length > 0) {
|
|
123
|
+
if (stylelintFiles.length > 0) {
|
|
162
124
|
taskSpecs.push([getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]]);
|
|
163
125
|
}
|
|
164
126
|
|