@lark-apaas/miaoda-cli 0.1.17-alpha.ddef3e9 → 0.1.17
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/commands/app/index.js +0 -16
- package/dist/cli/handlers/app/init.js +12 -41
- package/dist/cli/handlers/app/migrate.js +1 -5
- package/dist/services/app/init/index.js +1 -6
- package/package.json +1 -1
- package/upgrade/templates/design-stack/templates/scripts/dev-local.js +7 -16
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev-local.js +10 -19
- package/dist/services/app/init/async-install.js +0 -116
|
@@ -166,7 +166,6 @@ function registerAppInit(parent) {
|
|
|
166
166
|
.option('--template <stack>', `技术栈短名(${index_1.SUPPORTED_STACKS.join(' / ')})`)
|
|
167
167
|
.option('--conf <json>', 'init 配置 JSON。支持 {"version": "<template 版本>"},默认 latest')
|
|
168
168
|
.option('--skip-install', '跳过依赖安装', false)
|
|
169
|
-
.option('--async-install', '派发后台进程装依赖并立即返回(与 --skip-install 互斥)', false)
|
|
170
169
|
.addOption((0, shared_1.appIdOption)())
|
|
171
170
|
.addHelpText('after', `
|
|
172
171
|
幂等
|
|
@@ -195,31 +194,17 @@ function registerAppInit(parent) {
|
|
|
195
194
|
为空都会 fallback npm install。
|
|
196
195
|
JSON 模式下子进程 stdout 重定向到 stderr,避免污染最终 emit 的 JSON。
|
|
197
196
|
|
|
198
|
-
异步安装(--async-install)
|
|
199
|
-
跳过同步 npm install,派发一个 detached 后台进程装依赖后立即返回。
|
|
200
|
-
.spark/meta.json 在返回前写出(= 脚手架就绪),install 真实结果由 event marker 表达。
|
|
201
|
-
仅沙箱(SANDBOX_ID 非空)写 marker(presence 定成败,content 给排障):
|
|
202
|
-
成功 → /tmp/event/WORKSPACE_READY
|
|
203
|
-
失败 → /tmp/event/WORKSPACE_FAILED
|
|
204
|
-
安装日志:/tmp/async_install_dep.std.log
|
|
205
|
-
失败恢复:靠后续 'miaoda app sync'(dev.sh 入口 / 沙箱 pod 启动会跑)兜底,init 不重试。
|
|
206
|
-
与 --skip-install 互斥。
|
|
207
|
-
|
|
208
197
|
JSON 输出
|
|
209
198
|
已初始化:{"data": {"initialized": false, "reason": "already_initialized", "targetDir": "..."}}
|
|
210
199
|
新初始化:{"data": {"initialized": true, "template": "...", "templateVersion": "...", "steeringVersion": "...",
|
|
211
200
|
"appId": "...", "platformStackFound": true, "platformSyncedFiles": [...],
|
|
212
201
|
"installed": true, "installSource": "cache|npm|skipped", "installHash": "...", ...}}
|
|
213
|
-
async 模式:{"data": {"initialized": true, "asyncInstall": true, "installed": false,
|
|
214
|
-
"installSource": "async", "installPid": 123, "installLogPath": "...",
|
|
215
|
-
"eventReadyPath": "...", "eventFailedPath": "..."}}
|
|
216
202
|
|
|
217
203
|
示例
|
|
218
204
|
$ miaoda app init --template vite-react
|
|
219
205
|
$ miaoda app init --template nestjs-react-fullstack --app-id app_xxx
|
|
220
206
|
$ miaoda app init --template vite-react --conf '{"version": "0.1.0"}'
|
|
221
207
|
$ miaoda app init --template vite-react --skip-install
|
|
222
|
-
$ miaoda app init --template vite-react --async-install
|
|
223
208
|
$ MIAODA_DEP_CACHE_DIR=/tmp/dep-cache miaoda app init --template vite-react
|
|
224
209
|
`);
|
|
225
210
|
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
@@ -229,7 +214,6 @@ JSON 输出
|
|
|
229
214
|
conf,
|
|
230
215
|
skipInstall: rawOpts.skipInstall,
|
|
231
216
|
appId: rawOpts.appId,
|
|
232
|
-
asyncInstall: rawOpts.asyncInstall,
|
|
233
217
|
});
|
|
234
218
|
}));
|
|
235
219
|
}
|
|
@@ -60,9 +60,6 @@ async function handleAppInit(opts) {
|
|
|
60
60
|
next_actions: [`可用 stack:${index_1.SUPPORTED_STACKS.join(', ')}`],
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
-
if (opts.asyncInstall && opts.skipInstall) {
|
|
64
|
-
throw new error_1.AppError('ARGS_INVALID', '--async-install 与 --skip-install 互斥');
|
|
65
|
-
}
|
|
66
63
|
const version = opts.conf?.version;
|
|
67
64
|
const projectName = node_path_1.default.basename(targetDir);
|
|
68
65
|
const tplResult = (0, index_1.renderTemplate)({ stack, version, targetDir, projectName });
|
|
@@ -89,17 +86,13 @@ async function handleAppInit(opts) {
|
|
|
89
86
|
(0, logger_1.log)('init', `⚠ skills sync failed (continuing init): ${steeringError}`);
|
|
90
87
|
steeringResult = { version: 'unknown', syncedSkills: [], techSynced: false };
|
|
91
88
|
}
|
|
92
|
-
//
|
|
93
|
-
// 同步:装模板钉死的依赖。不带 <pkg>@latest 位置参数,因此 npm 不会改写 package.json/lockfile
|
|
89
|
+
// 装模板钉死的依赖。不带 <pkg>@latest 位置参数,因此 npm 不会改写 package.json/lockfile
|
|
94
90
|
// —— "升管控包到 latest" 是 sync 的职责,在 dev.sh 入口跑 `miaoda app sync` 时触发。
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
quietStdout: (0, output_1.isJsonMode)(),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
91
|
+
const installResult = (0, index_1.installDependencies)({
|
|
92
|
+
targetDir,
|
|
93
|
+
skip: opts.skipInstall,
|
|
94
|
+
quietStdout: (0, output_1.isJsonMode)(),
|
|
95
|
+
});
|
|
103
96
|
// template 自带 .githooks/pre-commit;如果用户项目已 git init,顺便设上 core.hooksPath。
|
|
104
97
|
const hookActivation = (0, githooks_1.activateGitHooks)(targetDir);
|
|
105
98
|
(0, index_1.writeSparkMeta)(targetDir, {
|
|
@@ -108,18 +101,8 @@ async function handleAppInit(opts) {
|
|
|
108
101
|
archType: tplResult.archType,
|
|
109
102
|
app_id: opts.appId,
|
|
110
103
|
});
|
|
111
|
-
// async 模式:meta 已落盘(= 脚手架就绪),再派发后台安装并立即返回(不等装完)。
|
|
112
|
-
let asyncDispatch;
|
|
113
|
-
if (opts.asyncInstall) {
|
|
114
|
-
asyncDispatch = (0, index_1.dispatchAsyncInstall)({ targetDir });
|
|
115
|
-
}
|
|
116
104
|
if (!(0, output_1.isJsonMode)()) {
|
|
117
|
-
|
|
118
|
-
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install dispatched in background pid ${String(asyncDispatch.pid)}) in ${targetDir}\n`);
|
|
119
|
-
}
|
|
120
|
-
else if (installResult) {
|
|
121
|
-
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install ${installResult.source}) in ${targetDir}\n`);
|
|
122
|
-
}
|
|
105
|
+
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install ${installResult.source}) in ${targetDir}\n`);
|
|
123
106
|
}
|
|
124
107
|
(0, output_1.emit)({
|
|
125
108
|
data: {
|
|
@@ -134,23 +117,11 @@ async function handleAppInit(opts) {
|
|
|
134
117
|
techSynced: steeringResult.techSynced,
|
|
135
118
|
steeringError,
|
|
136
119
|
gitHooks: hookActivation.action,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
installPid: asyncDispatch.pid,
|
|
143
|
-
installLogPath: asyncDispatch.logPath,
|
|
144
|
-
eventReadyPath: asyncDispatch.eventReadyPath,
|
|
145
|
-
eventFailedPath: asyncDispatch.eventFailedPath,
|
|
146
|
-
}
|
|
147
|
-
: {
|
|
148
|
-
installed: installResult?.installed,
|
|
149
|
-
installSource: installResult?.source,
|
|
150
|
-
installHash: installResult?.hash,
|
|
151
|
-
cacheZip: installResult?.cacheZip,
|
|
152
|
-
installError: installResult?.error,
|
|
153
|
-
}),
|
|
120
|
+
installed: installResult.installed,
|
|
121
|
+
installSource: installResult.source,
|
|
122
|
+
installHash: installResult.hash,
|
|
123
|
+
cacheZip: installResult.cacheZip,
|
|
124
|
+
installError: installResult.error,
|
|
154
125
|
},
|
|
155
126
|
});
|
|
156
127
|
}
|
|
@@ -139,13 +139,9 @@ async function handleAppMigrate(opts) {
|
|
|
139
139
|
// 通过 pkill 杀掉 dev.js 主进程,依赖沙箱平台 supervisor 自动重新 exec dev.sh → dev.js,
|
|
140
140
|
// 新进程用新 deps + 新 scripts,正常进入 fullstack 模式。
|
|
141
141
|
// - 只在 SANDBOX_ID 非空时做(本地环境用户进程混杂,主动 pkill 风险大)
|
|
142
|
-
// - install 挂了不做 —— 新依赖不全, 杀旧 dev 后新 dev 起不来反而更糟,
|
|
143
|
-
// 留着旧进程让用户先处理 installError
|
|
144
142
|
// - 软失败:pkill 无匹配进程退出 1,catch 吞掉
|
|
145
143
|
let devRestarted = false;
|
|
146
|
-
if (
|
|
147
|
-
process.env.SANDBOX_ID !== undefined &&
|
|
148
|
-
process.env.SANDBOX_ID !== '') {
|
|
144
|
+
if (process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== '') {
|
|
149
145
|
(0, logger_1.log)('migrate', '沙箱环境,重启 dev process(平台 supervisor 会自动拉起)...');
|
|
150
146
|
try {
|
|
151
147
|
(0, node_child_process_1.execFileSync)('pkill', ['-f', 'node.*scripts/dev\\.js'], { stdio: 'ignore' });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.resolveNpmInstallRegistry = exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.TEMPLATE_PACKAGE_BY_STACK = exports.SUPPORTED_STACKS = exports.renderTemplate = void 0;
|
|
4
4
|
var template_1 = require("./template");
|
|
5
5
|
Object.defineProperty(exports, "renderTemplate", { enumerable: true, get: function () { return template_1.renderTemplate; } });
|
|
6
6
|
Object.defineProperty(exports, "SUPPORTED_STACKS", { enumerable: true, get: function () { return template_1.SUPPORTED_STACKS; } });
|
|
@@ -11,8 +11,3 @@ Object.defineProperty(exports, "writeSparkMeta", { enumerable: true, get: functi
|
|
|
11
11
|
var install_1 = require("./install");
|
|
12
12
|
Object.defineProperty(exports, "installDependencies", { enumerable: true, get: function () { return install_1.installDependencies; } });
|
|
13
13
|
Object.defineProperty(exports, "resolveNpmInstallRegistry", { enumerable: true, get: function () { return install_1.resolveNpmInstallRegistry; } });
|
|
14
|
-
var async_install_1 = require("./async-install");
|
|
15
|
-
Object.defineProperty(exports, "dispatchAsyncInstall", { enumerable: true, get: function () { return async_install_1.dispatchAsyncInstall; } });
|
|
16
|
-
Object.defineProperty(exports, "runAsyncInstallWorker", { enumerable: true, get: function () { return async_install_1.runAsyncInstallWorker; } });
|
|
17
|
-
Object.defineProperty(exports, "isSandboxEnv", { enumerable: true, get: function () { return async_install_1.isSandboxEnv; } });
|
|
18
|
-
Object.defineProperty(exports, "ASYNC_INSTALL_LOG", { enumerable: true, get: function () { return async_install_1.ASYNC_INSTALL_LOG; } });
|
package/package.json
CHANGED
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
//
|
|
6
6
|
// 流程:
|
|
7
7
|
// 1. env pull —— 拉沙箱身份/凭证到 .env.local
|
|
8
|
-
// 2.
|
|
9
|
-
// 3.
|
|
10
|
-
// 4.
|
|
11
|
-
// 5. 起单进程 dev server(design-stack 单进程,无 server/client 拆分)
|
|
8
|
+
// 2. skills sync —— 同步当前 stack 的 agent skills
|
|
9
|
+
// 3. dotenv 加载 .env / .env.local 到 process.env(含 SUDA_WEBUSER 适配)
|
|
10
|
+
// 4. 起单进程 dev server(design-stack 单进程,无 server/client 拆分)
|
|
12
11
|
//
|
|
13
12
|
// 设计同 nestjs-react-fullstack/dev-local.js,SDK 包不需要自己 require('dotenv'),
|
|
14
13
|
// env 加载收敛在启动脚本单点。SUDA_WEBUSER 适配同 nrf 那份。
|
|
@@ -27,7 +26,7 @@ function warn(msg) {
|
|
|
27
26
|
if (!process.env.MIAODA_APP_TYPE) process.env.MIAODA_APP_TYPE = '4';
|
|
28
27
|
process.env.MIAODA_LOCAL_DEV = '1';
|
|
29
28
|
|
|
30
|
-
console.log('[dev-local] (1/
|
|
29
|
+
console.log('[dev-local] (1/4) env pull...');
|
|
31
30
|
const hasLarkCli = spawnSync('command', ['-v', 'lark-cli'], { shell: true, stdio: 'ignore' }).status === 0;
|
|
32
31
|
if (hasLarkCli) {
|
|
33
32
|
let appId = '';
|
|
@@ -48,23 +47,15 @@ if (hasLarkCli) {
|
|
|
48
47
|
warn('lark-cli 未安装,跳过 env pull;请确保 .env.local 已就绪');
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
// action-plugin init —— 装 user app 在 package.json.actionPlugins 里声明的插件。
|
|
52
|
-
console.log('[dev-local] (2/5) action-plugin init...');
|
|
53
|
-
try {
|
|
54
|
-
execSync('npx -y @lark-apaas/fullstack-cli@latest action-plugin init', { stdio: 'inherit' });
|
|
55
|
-
} catch {
|
|
56
|
-
warn('action-plugin init 失败,继续启动');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
50
|
// skills sync —— --local 切 flat layout;不传 --version,handler 默认 coding-steering@latest。
|
|
60
|
-
console.log('[dev-local] (
|
|
51
|
+
console.log('[dev-local] (2/4) miaoda skills sync...');
|
|
61
52
|
try {
|
|
62
53
|
execSync('npx -y @lark-apaas/miaoda-cli@latest skills sync --local', { stdio: 'inherit' });
|
|
63
54
|
} catch {
|
|
64
55
|
console.log(' (skills sync 失败,继续启动)');
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
console.log('[dev-local] (4
|
|
58
|
+
console.log('[dev-local] (3/4) loading .env / .env.local...');
|
|
68
59
|
const dotenv = require('dotenv');
|
|
69
60
|
dotenv.config({ path: '.env.local' });
|
|
70
61
|
dotenv.config({ path: '.env' });
|
|
@@ -84,7 +75,7 @@ if (process.env.SUDA_WEBUSER) {
|
|
|
84
75
|
}
|
|
85
76
|
}
|
|
86
77
|
|
|
87
|
-
console.log('[dev-local] (
|
|
78
|
+
console.log('[dev-local] (4/4) npm run dev');
|
|
88
79
|
const child = spawn('npm', ['run', 'dev'], { stdio: 'inherit', env: process.env });
|
|
89
80
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
90
81
|
child.on('error', (err) => {
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
//
|
|
6
6
|
// 流程:
|
|
7
7
|
// 1. env pull —— 拉沙箱身份/凭证到 .env.local
|
|
8
|
-
// 2.
|
|
9
|
-
// 3.
|
|
10
|
-
// 4.
|
|
11
|
-
// 5. 并发起 dev:server + dev:client(子进程继承 process.env)
|
|
8
|
+
// 2. skills sync —— 同步当前 stack 的 agent skills
|
|
9
|
+
// 3. dotenv 加载 .env / .env.local 到 process.env(含 SUDA_WEBUSER 适配)
|
|
10
|
+
// 4. 并发起 dev:server + dev:client(子进程继承 process.env)
|
|
12
11
|
//
|
|
13
12
|
// 关键设计:本脚本在 spawn 子进程之前先把 .env / .env.local 加载到 process.env,
|
|
14
13
|
// 然后 spawn 的 server / client 进程通过 env 继承直接拿到——SDK(fullstack-nestjs-core
|
|
@@ -35,7 +34,7 @@ if (!process.env.MIAODA_APP_TYPE) process.env.MIAODA_APP_TYPE = '3';
|
|
|
35
34
|
process.env.MIAODA_LOCAL_DEV = '1';
|
|
36
35
|
|
|
37
36
|
// 1. env pull
|
|
38
|
-
console.log('[dev-local] (1/
|
|
37
|
+
console.log('[dev-local] (1/4) env pull...');
|
|
39
38
|
const hasLarkCli = spawnSync('command', ['-v', 'lark-cli'], { shell: true, stdio: 'ignore' }).status === 0;
|
|
40
39
|
if (hasLarkCli) {
|
|
41
40
|
let appId = '';
|
|
@@ -56,28 +55,20 @@ if (hasLarkCli) {
|
|
|
56
55
|
warn('lark-cli 未安装,跳过 env pull;请确保 .env.local 已就绪');
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
// 2.
|
|
60
|
-
console.log('[dev-local] (2/5) action-plugin init...');
|
|
61
|
-
try {
|
|
62
|
-
execSync('npx -y @lark-apaas/fullstack-cli@latest action-plugin init', { stdio: 'inherit' });
|
|
63
|
-
} catch {
|
|
64
|
-
warn('action-plugin init 失败,继续启动');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 3. skills sync —— --local 切到 flat layout (.agents/skills + .claude/skills 软链),
|
|
58
|
+
// 2. skills sync —— --local 切到 flat layout (.agents/skills + .claude/skills 软链),
|
|
68
59
|
// 跟沙箱 nested layout 区分。不传 --version,handler 默认拉 coding-steering@latest,
|
|
69
60
|
// 保证每次本地 npm run dev 都把 skills 升到最新。
|
|
70
|
-
console.log('[dev-local] (
|
|
61
|
+
console.log('[dev-local] (2/4) miaoda skills sync...');
|
|
71
62
|
try {
|
|
72
63
|
execSync('npx -y @lark-apaas/miaoda-cli@latest skills sync --local', { stdio: 'inherit' });
|
|
73
64
|
} catch {
|
|
74
65
|
console.log(' (skills sync 失败,继续启动)');
|
|
75
66
|
}
|
|
76
67
|
|
|
77
|
-
//
|
|
68
|
+
// 3. 加载 .env / .env.local 到 process.env
|
|
78
69
|
// dotenv 默认 override:false,先到先得 → 先 .env.local 让它优先于 .env;
|
|
79
70
|
// shell env 已在 process.env,两次 config 都不会覆盖。
|
|
80
|
-
console.log('[dev-local] (4
|
|
71
|
+
console.log('[dev-local] (3/4) loading .env / .env.local...');
|
|
81
72
|
const dotenv = require('dotenv');
|
|
82
73
|
dotenv.config({ path: '.env.local' });
|
|
83
74
|
dotenv.config({ path: '.env' });
|
|
@@ -98,8 +89,8 @@ if (process.env.SUDA_WEBUSER) {
|
|
|
98
89
|
}
|
|
99
90
|
}
|
|
100
91
|
|
|
101
|
-
//
|
|
102
|
-
console.log('[dev-local] (
|
|
92
|
+
// 4. 并发起前后端 dev server
|
|
93
|
+
console.log('[dev-local] (4/4) 并发起 dev:server + dev:client');
|
|
103
94
|
const child = spawn(
|
|
104
95
|
'npx',
|
|
105
96
|
[
|
|
@@ -1,116 +0,0 @@
|
|
|
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.ASYNC_INSTALL_LOG = void 0;
|
|
7
|
-
exports.isSandboxEnv = isSandboxEnv;
|
|
8
|
-
exports.runAsyncInstallWorker = runAsyncInstallWorker;
|
|
9
|
-
exports.dispatchAsyncInstall = dispatchAsyncInstall;
|
|
10
|
-
const node_child_process_1 = require("node:child_process");
|
|
11
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
-
const install_1 = require("./install");
|
|
14
|
-
/** event marker 目录,写死;写前 mkdir -p。沙箱里 code server 轮询此目录。 */
|
|
15
|
-
const EVENT_DIR = '/tmp/event';
|
|
16
|
-
/** 安装成功 marker 名 */
|
|
17
|
-
const MARKER_READY = 'WORKSPACE_READY';
|
|
18
|
-
/** 安装失败 marker 名 */
|
|
19
|
-
const MARKER_FAILED = 'WORKSPACE_FAILED';
|
|
20
|
-
/** 后台 worker stdout/stderr 落盘路径 */
|
|
21
|
-
exports.ASYNC_INSTALL_LOG = '/tmp/async_install_dep.std.log';
|
|
22
|
-
/** 沙箱判定:SANDBOX_ID 非空。与 init handler 的 outputLayout 分流口径一致。 */
|
|
23
|
-
function isSandboxEnv() {
|
|
24
|
-
return process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== '';
|
|
25
|
-
}
|
|
26
|
-
function nowIso() {
|
|
27
|
-
return new Date().toISOString();
|
|
28
|
-
}
|
|
29
|
-
function clearStaleMarkers(eventDir) {
|
|
30
|
-
for (const name of [MARKER_READY, MARKER_FAILED]) {
|
|
31
|
-
const p = node_path_1.default.join(eventDir, name);
|
|
32
|
-
if (node_fs_1.default.existsSync(p))
|
|
33
|
-
node_fs_1.default.rmSync(p, { force: true });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function writeMarker(eventDir, name, content) {
|
|
37
|
-
node_fs_1.default.mkdirSync(eventDir, { recursive: true });
|
|
38
|
-
node_fs_1.default.writeFileSync(node_path_1.default.join(eventDir, name), JSON.stringify(content) + '\n', 'utf-8');
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* 后台 install worker 体:跑 installDependencies,按结果写 marker(仅沙箱)。
|
|
42
|
-
* 由 dispatchAsyncInstall 通过 `node -e` 在 detached 子进程里调用(见下)。
|
|
43
|
-
* presence 定成败:成功写 WORKSPACE_READY,失败写 WORKSPACE_FAILED;content 给排障。
|
|
44
|
-
*/
|
|
45
|
-
function runAsyncInstallWorker(opts) {
|
|
46
|
-
const eventDir = opts.eventDir ?? EVENT_DIR;
|
|
47
|
-
const sandbox = isSandboxEnv();
|
|
48
|
-
if (sandbox)
|
|
49
|
-
clearStaleMarkers(eventDir);
|
|
50
|
-
const start = Date.now();
|
|
51
|
-
let result;
|
|
52
|
-
try {
|
|
53
|
-
result = (0, install_1.installDependencies)({ targetDir: opts.targetDir, quietStdout: false });
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
57
|
-
if (sandbox) {
|
|
58
|
-
writeMarker(eventDir, MARKER_FAILED, {
|
|
59
|
-
ts: nowIso(),
|
|
60
|
-
error: msg,
|
|
61
|
-
logPath: exports.ASYNC_INSTALL_LOG,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (!sandbox)
|
|
67
|
-
return;
|
|
68
|
-
if (result.source === 'failed') {
|
|
69
|
-
writeMarker(eventDir, MARKER_FAILED, {
|
|
70
|
-
ts: nowIso(),
|
|
71
|
-
error: result.error ?? 'npm install failed',
|
|
72
|
-
logPath: exports.ASYNC_INSTALL_LOG,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
writeMarker(eventDir, MARKER_READY, {
|
|
77
|
-
ts: nowIso(),
|
|
78
|
-
durationMs: Date.now() - start,
|
|
79
|
-
source: result.source,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* 派发 detached 后台进程跑依赖安装并立即返回。
|
|
85
|
-
* 用 `node -e` 直接 require 本模块(编译产物)调 runAsyncInstallWorker,
|
|
86
|
-
* 不挂任何 CLI 子命令、对外不暴露;worker 内复用 installDependencies(cache / registry / fallback)。
|
|
87
|
-
* 沙箱下派发前清旧 marker;stdout/stderr 重定向到 logPath。
|
|
88
|
-
*/
|
|
89
|
-
function dispatchAsyncInstall(opts) {
|
|
90
|
-
const eventDir = opts.eventDir ?? EVENT_DIR;
|
|
91
|
-
const logPath = opts.logPath ?? exports.ASYNC_INSTALL_LOG;
|
|
92
|
-
const sandbox = isSandboxEnv();
|
|
93
|
-
if (sandbox)
|
|
94
|
-
clearStaleMarkers(eventDir);
|
|
95
|
-
// targetDir 走 argv 传,不拼进代码串(免转义);只把本模块路径 JSON 内联进去。
|
|
96
|
-
const workerCode = `require(${JSON.stringify(__filename)}).runAsyncInstallWorker({ targetDir: process.argv[1] })`;
|
|
97
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(logPath), { recursive: true });
|
|
98
|
-
const fd = node_fs_1.default.openSync(logPath, 'w');
|
|
99
|
-
try {
|
|
100
|
-
const child = (0, node_child_process_1.spawn)(process.execPath, ['-e', workerCode, opts.targetDir], {
|
|
101
|
-
detached: true,
|
|
102
|
-
stdio: ['ignore', fd, fd],
|
|
103
|
-
});
|
|
104
|
-
child.unref();
|
|
105
|
-
return {
|
|
106
|
-
pid: child.pid,
|
|
107
|
-
logPath,
|
|
108
|
-
eventReadyPath: sandbox ? node_path_1.default.join(eventDir, MARKER_READY) : undefined,
|
|
109
|
-
eventFailedPath: sandbox ? node_path_1.default.join(eventDir, MARKER_FAILED) : undefined,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
// 父进程关掉自己那份 fd;子进程已通过 stdio dup 持有独立副本。
|
|
114
|
-
node_fs_1.default.closeSync(fd);
|
|
115
|
-
}
|
|
116
|
-
}
|