@lark-apaas/miaoda-cli 0.1.17-alpha.ddef3e9 → 0.1.17-beta.45b48d2
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/cli/handlers/app/init.js +4 -0
- package/dist/cli/handlers/app/sync.js +5 -0
- package/dist/utils/logs-dir.js +19 -0
- package/package.json +1 -1
- package/upgrade/templates/design-stack/templates/.githooks/pre-commit +1 -0
- package/upgrade/templates/design-stack/templates/scripts/dev-local.js +54 -4
- package/upgrade/templates/design-stack/templates/scripts/hooks/run-precommit.js +36 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.githooks/pre-commit +1 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.spark_project +2 -2
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev-local.js +54 -5
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/hooks/run-precommit.js +36 -0
|
@@ -10,6 +10,7 @@ const index_1 = require("../../../services/app/init/index");
|
|
|
10
10
|
const coding_steering_1 = require("../../../utils/coding-steering");
|
|
11
11
|
const logger_1 = require("../../../utils/logger");
|
|
12
12
|
const githooks_1 = require("../../../utils/githooks");
|
|
13
|
+
const logs_dir_1 = require("../../../utils/logs-dir");
|
|
13
14
|
const error_1 = require("../../../utils/error");
|
|
14
15
|
const output_1 = require("../../../utils/output");
|
|
15
16
|
/**
|
|
@@ -66,6 +67,9 @@ async function handleAppInit(opts) {
|
|
|
66
67
|
const version = opts.conf?.version;
|
|
67
68
|
const projectName = node_path_1.default.basename(targetDir);
|
|
68
69
|
const tplResult = (0, index_1.renderTemplate)({ stack, version, targetDir, projectName });
|
|
70
|
+
// 先建 logs/,防止 user 跑 dev 之前 AI/工具 redirect 到 logs/*.log 因为父目录不存在直接挂
|
|
71
|
+
// (dev.js / dev-local.js 自己也建 logs/,但只在它启动后;启动前的 shell redirect 会先于此)
|
|
72
|
+
(0, logs_dir_1.ensureLogsDir)(targetDir);
|
|
69
73
|
// skills 同步软失败:拉不到 coding-steering 包不该阻断 writeSparkMeta /
|
|
70
74
|
// activateGitHooks(之前会让 .spark/meta.json 没写,下次 init 半渲染状态又得重跑全套)。
|
|
71
75
|
// 按运行环境(SANDBOX_ID)分流 outputLayout,不绑 stack:
|
|
@@ -16,6 +16,7 @@ const sync_configs_1 = require("../../../config/sync-configs");
|
|
|
16
16
|
const sync_rule_1 = require("../../../utils/sync-rule");
|
|
17
17
|
const platform_sync_1 = require("../../../utils/platform-sync");
|
|
18
18
|
const githooks_1 = require("../../../utils/githooks");
|
|
19
|
+
const logs_dir_1 = require("../../../utils/logs-dir");
|
|
19
20
|
const install_1 = require("../../../services/app/init/install");
|
|
20
21
|
const spark_meta_1 = require("../../../utils/spark-meta");
|
|
21
22
|
const error_1 = require("../../../utils/error");
|
|
@@ -81,6 +82,10 @@ async function handleAppSync(opts) {
|
|
|
81
82
|
}
|
|
82
83
|
// 2. activate git hooks(template 自带 .githooks/pre-commit,core.hooksPath 一次性设置)
|
|
83
84
|
const hookActivation = (0, githooks_1.activateGitHooks)(targetDir);
|
|
85
|
+
// logs/ 兜底:dev.js / dev-local.js 启动后才 mkdir,启动前任何想 redirect 到 logs/*.log 的
|
|
86
|
+
// shell 命令都会因父目录不存在直接 ENOENT 挂掉。sync 也建一次,长期使用的 user app 老仓库
|
|
87
|
+
// 第一次升级也能拿到这个目录。
|
|
88
|
+
(0, logs_dir_1.ensureLogsDir)(targetDir);
|
|
84
89
|
// 3. snapshot 当前 user app 装的管控包版本,供 install 后 diff 用
|
|
85
90
|
const beforeSpecs = snapshotManagedDepSpecs(targetDir);
|
|
86
91
|
// 4. npm install —— 跟 init 一样软失败,install 挂了不该阻断 emit / sync 总结输出。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureLogsDir = ensureLogsDir;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
/**
|
|
10
|
+
* 确保 user app 根下存在 `logs/` 目录(幂等)。
|
|
11
|
+
*
|
|
12
|
+
* 模板的 dev.js / dev-local.js 自己也会 mkdir LOG_DIR,但只在进程启动后才执行 —— AI / 用户
|
|
13
|
+
* 在 dev 启动前跑 `npm run dev > logs/dev.log 2>&1` 这种 shell redirect 时,shell 先 open
|
|
14
|
+
* 目标文件,父目录不存在直接 ENOENT,子进程根本不会启动。init / sync 落地阶段先把空目录建出来,
|
|
15
|
+
* 避免这种「目录不存在导致 redirect 失败」的低级问题。
|
|
16
|
+
*/
|
|
17
|
+
function ensureLogsDir(targetDir) {
|
|
18
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(targetDir, 'logs'), { recursive: true });
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// 2. action-plugin init —— 装 user app 在 package.json.actionPlugins 里声明的插件
|
|
9
9
|
// 3. skills sync —— 同步当前 stack 的 agent skills
|
|
10
10
|
// 4. dotenv 加载 .env / .env.local 到 process.env(含 SUDA_WEBUSER 适配)
|
|
11
|
-
// 5. 起单进程 dev server(design-stack 单进程,无 server/client
|
|
11
|
+
// 5. 起单进程 dev server(design-stack 单进程,无 server/client 拆分),
|
|
12
|
+
// stdout/stderr 整体 tee 到 logs/dev.std.log
|
|
12
13
|
//
|
|
13
14
|
// 设计同 nestjs-react-fullstack/dev-local.js,SDK 包不需要自己 require('dotenv'),
|
|
14
15
|
// env 加载收敛在启动脚本单点。SUDA_WEBUSER 适配同 nrf 那份。
|
|
@@ -27,6 +28,10 @@ function warn(msg) {
|
|
|
27
28
|
if (!process.env.MIAODA_APP_TYPE) process.env.MIAODA_APP_TYPE = '4';
|
|
28
29
|
process.env.MIAODA_LOCAL_DEV = '1';
|
|
29
30
|
|
|
31
|
+
// 先建 logs/,防止任何步骤(尤其是 spawn 子进程前的 shell redirect)因父目录不存在挂掉
|
|
32
|
+
const LOG_DIR = process.env.LOG_DIR || 'logs';
|
|
33
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
34
|
+
|
|
30
35
|
console.log('[dev-local] (1/5) env pull...');
|
|
31
36
|
const hasLarkCli = spawnSync('command', ['-v', 'lark-cli'], { shell: true, stdio: 'ignore' }).status === 0;
|
|
32
37
|
if (hasLarkCli) {
|
|
@@ -84,10 +89,55 @@ if (process.env.SUDA_WEBUSER) {
|
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
const devLogPath = path.join(LOG_DIR, 'dev.std.log');
|
|
87
93
|
console.log('[dev-local] (5/5) npm run dev');
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
console.log(`[dev-local] 日志: ${devLogPath}`);
|
|
95
|
+
|
|
96
|
+
const logFd = fs.openSync(devLogPath, 'a');
|
|
97
|
+
const child = spawn('npm', ['run', 'dev'], {
|
|
98
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
99
|
+
env: process.env,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const tee = (src) =>
|
|
103
|
+
src.on('data', (chunk) => {
|
|
104
|
+
try {
|
|
105
|
+
process.stdout.write(chunk);
|
|
106
|
+
} catch {
|
|
107
|
+
/* terminal gone */
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
fs.writeSync(logFd, chunk);
|
|
111
|
+
} catch {
|
|
112
|
+
/* log fd closed */
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
tee(child.stdout);
|
|
116
|
+
tee(child.stderr);
|
|
117
|
+
|
|
118
|
+
// 外部 SIGTERM/SIGHUP 转发给 npm,避免本进程死了 nest 变孤儿
|
|
119
|
+
// (SIGINT 在 TTY 下 shell 直接发给整个前台进程组,不需要转发)
|
|
120
|
+
const forward = (sig) => () => {
|
|
121
|
+
try {
|
|
122
|
+
child.kill(sig);
|
|
123
|
+
} catch {
|
|
124
|
+
/* already gone */
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
process.on('SIGTERM', forward('SIGTERM'));
|
|
128
|
+
process.on('SIGHUP', forward('SIGHUP'));
|
|
129
|
+
|
|
130
|
+
// 'close' 而非 'exit':等 child 的 stdio stream drain 完才触发,
|
|
131
|
+
// 保证 tee 把最后一批 chunk 写进 logFd 再关闭,不丢尾。
|
|
132
|
+
child.on('close', (code) => {
|
|
133
|
+
try {
|
|
134
|
+
fs.closeSync(logFd);
|
|
135
|
+
} catch {
|
|
136
|
+
/* already closed */
|
|
137
|
+
}
|
|
138
|
+
process.exit(code ?? 0);
|
|
139
|
+
});
|
|
90
140
|
child.on('error', (err) => {
|
|
91
|
-
console.error(err);
|
|
141
|
+
console.error('[dev-local] 启动失败:', err.message);
|
|
92
142
|
process.exit(1);
|
|
93
143
|
});
|
|
@@ -6,6 +6,10 @@ const { spawnSync } = require('node:child_process');
|
|
|
6
6
|
|
|
7
7
|
const SEP = ' ' + '─'.repeat(36);
|
|
8
8
|
|
|
9
|
+
// package-lock.json 锁内网镜像源 → 线上构建无法访问,改用公共镜像源。
|
|
10
|
+
// 后续如有其它内网域名需要拦截,在这里加 pattern 即可。
|
|
11
|
+
const INTERNAL_REGISTRY_PATTERNS = [/bnpm\.byted\.org/];
|
|
12
|
+
|
|
9
13
|
function failAndExit(step, body) {
|
|
10
14
|
process.stderr.write('\n✗ pre-commit failed: ' + step + '\n');
|
|
11
15
|
process.stderr.write(SEP + '\n');
|
|
@@ -17,6 +21,37 @@ function failAndExit(step, body) {
|
|
|
17
21
|
process.exit(1);
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
function checkLockfileRegistry() {
|
|
25
|
+
const res = spawnSync(
|
|
26
|
+
'git',
|
|
27
|
+
['diff', '--cached', '--diff-filter=ACMR', '--', 'package-lock.json'],
|
|
28
|
+
{ stdio: ['ignore', 'pipe', 'pipe'], env: process.env },
|
|
29
|
+
);
|
|
30
|
+
// git 不可用 / 不在 git 仓库 → 静默放行,交给 lint 步骤报错
|
|
31
|
+
if (res.error || res.status !== 0) return;
|
|
32
|
+
const diff = res.stdout ? res.stdout.toString() : '';
|
|
33
|
+
// 只看本次新增行(`+` 开头但排除 `+++` 文件头)
|
|
34
|
+
const hit = diff
|
|
35
|
+
.split('\n')
|
|
36
|
+
.some(
|
|
37
|
+
(line) =>
|
|
38
|
+
line.startsWith('+') &&
|
|
39
|
+
!line.startsWith('+++') &&
|
|
40
|
+
INTERNAL_REGISTRY_PATTERNS.some((p) => p.test(line)),
|
|
41
|
+
);
|
|
42
|
+
if (!hit) return;
|
|
43
|
+
failAndExit(
|
|
44
|
+
'package-lock.json 使用了内网镜像源',
|
|
45
|
+
[
|
|
46
|
+
'线上构建环境无法访问内网镜像源,将导致部署阶段 npm install 失败。',
|
|
47
|
+
'请使用公共镜像源重新生成 lockfile:',
|
|
48
|
+
'',
|
|
49
|
+
' rm -rf node_modules package-lock.json',
|
|
50
|
+
' npm install --registry=https://registry.npmmirror.com',
|
|
51
|
+
].join('\n'),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
20
55
|
function runLint() {
|
|
21
56
|
const cwd = process.cwd();
|
|
22
57
|
const res = spawnSync('npm', ['run', 'lint'], {
|
|
@@ -34,4 +69,5 @@ function runLint() {
|
|
|
34
69
|
}
|
|
35
70
|
}
|
|
36
71
|
|
|
72
|
+
checkLockfileRegistry();
|
|
37
73
|
runLint();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
run = ["npm", "run", "dev"] # 默认 spark-cli dev
|
|
2
|
-
hidden = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
2
|
+
hidden = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", ".claude", "tmp", ".spark_project", ".playwright-cli"]
|
|
3
3
|
lint = ["npm", "run", "lint"]
|
|
4
4
|
test = ["npm", "run", "test"]
|
|
5
5
|
genDbSchema = ["npm", "run", "gen:db-schema"]
|
|
@@ -13,4 +13,4 @@ run = ["npm", "run", "start"]
|
|
|
13
13
|
[files.restrict]
|
|
14
14
|
pathPatterns = ["client/src/api/gen", "package.json", ".spark_project", ".gitignore"]
|
|
15
15
|
[files.hidden]
|
|
16
|
-
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
16
|
+
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", ".claude", "tmp", ".spark_project", ".playwright-cli"]
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
// 2. action-plugin init —— 装 user app 在 package.json.actionPlugins 里声明的插件
|
|
9
9
|
// 3. skills sync —— 同步当前 stack 的 agent skills
|
|
10
10
|
// 4. dotenv 加载 .env / .env.local 到 process.env(含 SUDA_WEBUSER 适配)
|
|
11
|
-
// 5. 并发起 dev:server + dev:client
|
|
11
|
+
// 5. concurrently 并发起 dev:server + dev:client,整体 stdout/stderr tee 到
|
|
12
|
+
// logs/dev.std.log;server / client 输出靠 concurrently 自带 [server]/[client]
|
|
13
|
+
// 前缀区分,`grep '\[server\]' logs/dev.std.log` 拿单边日志
|
|
12
14
|
//
|
|
13
15
|
// 关键设计:本脚本在 spawn 子进程之前先把 .env / .env.local 加载到 process.env,
|
|
14
16
|
// 然后 spawn 的 server / client 进程通过 env 继承直接拿到——SDK(fullstack-nestjs-core
|
|
@@ -34,6 +36,10 @@ function warn(msg) {
|
|
|
34
36
|
if (!process.env.MIAODA_APP_TYPE) process.env.MIAODA_APP_TYPE = '3';
|
|
35
37
|
process.env.MIAODA_LOCAL_DEV = '1';
|
|
36
38
|
|
|
39
|
+
// 先建 logs/,防止任何步骤(尤其是 spawn 子进程前的 shell redirect)因父目录不存在挂掉
|
|
40
|
+
const LOG_DIR = process.env.LOG_DIR || 'logs';
|
|
41
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
42
|
+
|
|
37
43
|
// 1. env pull
|
|
38
44
|
console.log('[dev-local] (1/5) env pull...');
|
|
39
45
|
const hasLarkCli = spawnSync('command', ['-v', 'lark-cli'], { shell: true, stdio: 'ignore' }).status === 0;
|
|
@@ -98,8 +104,12 @@ if (process.env.SUDA_WEBUSER) {
|
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
// 5. 并发起前后端 dev server
|
|
107
|
+
// 5. 并发起前后端 dev server,整体 tee 到 logs/dev.std.log
|
|
108
|
+
const devLogPath = path.join(LOG_DIR, 'dev.std.log');
|
|
102
109
|
console.log('[dev-local] (5/5) 并发起 dev:server + dev:client');
|
|
110
|
+
console.log(`[dev-local] 日志: ${devLogPath}`);
|
|
111
|
+
|
|
112
|
+
const logFd = fs.openSync(devLogPath, 'a');
|
|
103
113
|
const child = spawn(
|
|
104
114
|
'npx',
|
|
105
115
|
[
|
|
@@ -113,10 +123,49 @@ const child = spawn(
|
|
|
113
123
|
'npm run dev:server',
|
|
114
124
|
'npm run dev:client',
|
|
115
125
|
],
|
|
116
|
-
{ stdio: '
|
|
126
|
+
{ stdio: ['ignore', 'pipe', 'pipe'], env: process.env },
|
|
117
127
|
);
|
|
118
|
-
|
|
128
|
+
|
|
129
|
+
const tee = (src) =>
|
|
130
|
+
src.on('data', (chunk) => {
|
|
131
|
+
try {
|
|
132
|
+
process.stdout.write(chunk);
|
|
133
|
+
} catch {
|
|
134
|
+
/* terminal gone */
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
fs.writeSync(logFd, chunk);
|
|
138
|
+
} catch {
|
|
139
|
+
/* log fd closed */
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
tee(child.stdout);
|
|
143
|
+
tee(child.stderr);
|
|
144
|
+
|
|
145
|
+
// 外部 SIGTERM/SIGHUP 转发给 concurrently,避免本进程死了 server/client 变孤儿
|
|
146
|
+
// (SIGINT 在 TTY 下 shell 直接发给整个前台进程组,不需要转发)
|
|
147
|
+
const forward = (sig) => () => {
|
|
148
|
+
try {
|
|
149
|
+
child.kill(sig);
|
|
150
|
+
} catch {
|
|
151
|
+
/* already gone */
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
process.on('SIGTERM', forward('SIGTERM'));
|
|
155
|
+
process.on('SIGHUP', forward('SIGHUP'));
|
|
156
|
+
|
|
157
|
+
// 'close' 而非 'exit':等 child 的 stdio stream drain 完才触发,
|
|
158
|
+
// 保证 tee 把最后一批 chunk 写进 logFd 再关闭,不丢尾。
|
|
159
|
+
child.on('close', (code) => {
|
|
160
|
+
try {
|
|
161
|
+
fs.closeSync(logFd);
|
|
162
|
+
} catch {
|
|
163
|
+
/* already closed */
|
|
164
|
+
}
|
|
165
|
+
process.exit(code ?? 0);
|
|
166
|
+
});
|
|
119
167
|
child.on('error', (err) => {
|
|
120
|
-
console.error(err);
|
|
168
|
+
console.error('[dev-local] 启动失败:', err.message);
|
|
169
|
+
console.error('[dev-local] 如缺 concurrently,运行: npm install');
|
|
121
170
|
process.exit(1);
|
|
122
171
|
});
|
|
@@ -6,6 +6,10 @@ const { spawnSync } = require('node:child_process');
|
|
|
6
6
|
|
|
7
7
|
const SEP = ' ' + '─'.repeat(36);
|
|
8
8
|
|
|
9
|
+
// package-lock.json 锁内网镜像源 → 线上构建无法访问,改用公共镜像源。
|
|
10
|
+
// 后续如有其它内网域名需要拦截,在这里加 pattern 即可。
|
|
11
|
+
const INTERNAL_REGISTRY_PATTERNS = [/bnpm\.byted\.org/];
|
|
12
|
+
|
|
9
13
|
function failAndExit(step, body) {
|
|
10
14
|
process.stderr.write('\n✗ pre-commit failed: ' + step + '\n');
|
|
11
15
|
process.stderr.write(SEP + '\n');
|
|
@@ -17,6 +21,37 @@ function failAndExit(step, body) {
|
|
|
17
21
|
process.exit(1);
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
function checkLockfileRegistry() {
|
|
25
|
+
const res = spawnSync(
|
|
26
|
+
'git',
|
|
27
|
+
['diff', '--cached', '--diff-filter=ACMR', '--', 'package-lock.json'],
|
|
28
|
+
{ stdio: ['ignore', 'pipe', 'pipe'], env: process.env },
|
|
29
|
+
);
|
|
30
|
+
// git 不可用 / 不在 git 仓库 → 静默放行,交给 lint 步骤报错
|
|
31
|
+
if (res.error || res.status !== 0) return;
|
|
32
|
+
const diff = res.stdout ? res.stdout.toString() : '';
|
|
33
|
+
// 只看本次新增行(`+` 开头但排除 `+++` 文件头)
|
|
34
|
+
const hit = diff
|
|
35
|
+
.split('\n')
|
|
36
|
+
.some(
|
|
37
|
+
(line) =>
|
|
38
|
+
line.startsWith('+') &&
|
|
39
|
+
!line.startsWith('+++') &&
|
|
40
|
+
INTERNAL_REGISTRY_PATTERNS.some((p) => p.test(line)),
|
|
41
|
+
);
|
|
42
|
+
if (!hit) return;
|
|
43
|
+
failAndExit(
|
|
44
|
+
'package-lock.json 使用了内网镜像源',
|
|
45
|
+
[
|
|
46
|
+
'线上构建环境无法访问内网镜像源,将导致部署阶段 npm install 失败。',
|
|
47
|
+
'请使用公共镜像源重新生成 lockfile:',
|
|
48
|
+
'',
|
|
49
|
+
' rm -rf node_modules package-lock.json',
|
|
50
|
+
' npm install --registry=https://registry.npmmirror.com',
|
|
51
|
+
].join('\n'),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
20
55
|
function runLint() {
|
|
21
56
|
const cwd = process.cwd();
|
|
22
57
|
const res = spawnSync('npm', ['run', 'lint'], {
|
|
@@ -34,4 +69,5 @@ function runLint() {
|
|
|
34
69
|
}
|
|
35
70
|
}
|
|
36
71
|
|
|
72
|
+
checkLockfileRegistry();
|
|
37
73
|
runLint();
|