@lark-apaas/miaoda-cli 0.1.37 → 0.1.39-alpha.24bc5ff
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/README.md +10 -0
- package/dist/api/deploy/approval.js +77 -0
- package/dist/api/deploy/index.js +7 -1
- package/dist/api/deploy/schemas.js +3 -0
- package/dist/api/deploy/types.js +19 -1
- package/dist/cli/commands/deploy/index.js +30 -6
- package/dist/cli/commands/deploy/modern.js +34 -11
- package/dist/cli/commands/skills/index.js +1 -1
- package/dist/cli/handlers/app/export.js +3 -3
- package/dist/cli/handlers/app/migrate.js +4 -1
- package/dist/cli/handlers/app/sync.js +2 -2
- package/dist/cli/handlers/db/sql.js +8 -6
- package/dist/cli/handlers/deploy/deploy.js +57 -4
- package/dist/cli/handlers/deploy/format.js +13 -0
- package/dist/cli/handlers/deploy/get.js +24 -1
- package/dist/cli/handlers/deploy/helpers.js +36 -2
- package/dist/cli/handlers/deploy/modern.js +39 -3
- package/dist/cli/handlers/deploy/polling.js +13 -9
- package/dist/cli/handlers/plugin/plugin-local.js +5 -4
- package/dist/config/migrate-configs/vite-react-to-nestjs-react-fullstack.js +13 -2
- package/dist/config/sync-configs/index.js +3 -1
- package/dist/config/sync-configs/nestjs-react-fullstack.js +6 -0
- package/dist/config/sync-configs/vite-react.js +20 -0
- package/dist/services/app/init/async-install.js +13 -2
- package/dist/services/app/init/install.js +4 -3
- package/dist/services/deploy/modern/atoms/build.js +3 -3
- package/dist/services/deploy/modern/atoms/local-release.js +3 -0
- package/dist/services/deploy/modern/atoms/tosutil.js +6 -8
- package/dist/services/deploy/modern/patch/actions.js +7 -1
- package/dist/services/deploy/modern/patch/index.js +2 -1
- package/dist/services/deploy/modern/pipelines/design-local.js +7 -1
- package/dist/services/deploy/modern/pipelines/local.js +7 -1
- package/dist/utils/archive.js +103 -0
- package/dist/utils/coding-steering.js +30 -2
- package/dist/utils/file-ops.js +28 -8
- package/dist/utils/http.js +6 -6
- package/dist/utils/npm-pack.js +6 -7
- package/dist/utils/run-npm.js +102 -0
- package/dist/utils/sync-rule.js +6 -0
- package/dist/utils/typescript-exclude.js +36 -0
- package/dist/utils/which.js +27 -0
- package/package.json +6 -2
- package/upgrade/templates/nestjs-react-fullstack/templates/nest-cli.json +1 -10
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/build.sh +5 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/prune-smart.js +41 -1
- package/upgrade/templates/vite-react/templates/scripts/build.sh +61 -0
|
@@ -3,18 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseStatusFlag = parseStatusFlag;
|
|
4
4
|
exports.validateLimit = validateLimit;
|
|
5
5
|
exports.parseDeployId = parseDeployId;
|
|
6
|
+
exports.validateApplyReason = validateApplyReason;
|
|
6
7
|
const args_1 = require("../../../utils/args");
|
|
7
8
|
const index_1 = require("../../../api/deploy/index");
|
|
8
9
|
/**
|
|
9
10
|
* 把 CLI --status 字符串映射成 BAM NodeStatus 数字;非法值抛 ARGS_INVALID。
|
|
10
11
|
*
|
|
11
12
|
* 历史接口(CliListPipelineInstances)返回 SimpleInstance,状态语义是 pipeline 节点状态,
|
|
12
|
-
* 取值 todo / running / success / failed / canceled / hold_on
|
|
13
|
+
* 取值 todo / running / success / failed / canceled / rejected / pending / skipped / hold_on(已废弃)。
|
|
13
14
|
*/
|
|
14
15
|
function parseStatusFlag(text) {
|
|
15
16
|
const value = (0, index_1.nodeStatusFromText)(text);
|
|
16
17
|
if (value === undefined) {
|
|
17
|
-
(0, args_1.failArgs)(`--status 无效:'${text}'。可选值:todo | running | success | failed | canceled | hold_on`);
|
|
18
|
+
(0, args_1.failArgs)(`--status 无效:'${text}'。可选值:todo | running | success | failed | canceled | rejected | pending | skipped | hold_on`);
|
|
18
19
|
}
|
|
19
20
|
return value;
|
|
20
21
|
}
|
|
@@ -39,3 +40,36 @@ function parseDeployId(text) {
|
|
|
39
40
|
}
|
|
40
41
|
return n;
|
|
41
42
|
}
|
|
43
|
+
/** 发布理由上限,按 Unicode code point 计,对齐 lark-cli `apps +release-create` */
|
|
44
|
+
const MAX_APPLY_REASON_CODE_POINTS = 1000;
|
|
45
|
+
/** C0 / C1 控制字符(含换行、制表符) */
|
|
46
|
+
function isControlCodePoint(cp) {
|
|
47
|
+
return cp <= 0x1f || (cp >= 0x7f && cp <= 0x9f);
|
|
48
|
+
}
|
|
49
|
+
/** 零宽、BOM、双向嵌入/覆盖/隔离、行/段分隔符:肉眼不可见,却会改变审批单里的展示 */
|
|
50
|
+
function isDangerousCodePoint(cp) {
|
|
51
|
+
return ((cp >= 0x200b && cp <= 0x200d) ||
|
|
52
|
+
cp === 0xfeff ||
|
|
53
|
+
(cp >= 0x202a && cp <= 0x202e) ||
|
|
54
|
+
(cp >= 0x2066 && cp <= 0x2069) ||
|
|
55
|
+
cp === 0x2028 ||
|
|
56
|
+
cp === 0x2029);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 校验 --apply-reason 的格式;通过后调用方原样透传,不 trim。
|
|
60
|
+
* 是否必填由服务端按应用类型判定,这里不管。
|
|
61
|
+
*/
|
|
62
|
+
function validateApplyReason(value) {
|
|
63
|
+
if (value.trim() === '')
|
|
64
|
+
(0, args_1.failArgs)('--apply-reason 不能为空');
|
|
65
|
+
const codePoints = Array.from(value, (ch) => ch.codePointAt(0) ?? 0);
|
|
66
|
+
if (codePoints.some(isControlCodePoint)) {
|
|
67
|
+
(0, args_1.failArgs)('--apply-reason 不能包含换行、制表符等控制字符,需为单行文本');
|
|
68
|
+
}
|
|
69
|
+
if (codePoints.some(isDangerousCodePoint)) {
|
|
70
|
+
(0, args_1.failArgs)('--apply-reason 不能包含零宽字符、双向控制字符或行/段分隔符');
|
|
71
|
+
}
|
|
72
|
+
if (codePoints.length > MAX_APPLY_REASON_CODE_POINTS) {
|
|
73
|
+
(0, args_1.failArgs)(`--apply-reason 最多 ${String(MAX_APPLY_REASON_CODE_POINTS)} 个字符`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -4,14 +4,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.parseDeployConf = parseDeployConf;
|
|
7
|
+
exports.parseChangedFiles = parseChangedFiles;
|
|
7
8
|
exports.handleDeployModern = handleDeployModern;
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const output_1 = require("../../../utils/output");
|
|
10
11
|
const error_1 = require("../../../utils/error");
|
|
11
12
|
const index_1 = require("../../../services/deploy/modern/index");
|
|
12
|
-
const
|
|
13
|
+
const index_2 = require("../../../services/deploy/modern/patch/index");
|
|
14
|
+
const logger_1 = require("../../../utils/logger");
|
|
15
|
+
const CONF_EXPECTED = '期望形如 {"checkPointVersion":"...","commitID":"...","sessionID":"...","marker":"...","versionLevel":"major"} 的 JSON 对象';
|
|
13
16
|
/**
|
|
14
|
-
* 解析 `--conf` JSON string,只取 checkPointVersion / commitID
|
|
17
|
+
* 解析 `--conf` JSON string,只取 checkPointVersion / commitID / sessionID / marker / versionLevel 五个白名单字段。
|
|
15
18
|
* - 未传 → 返回 {}(行为不变)。
|
|
16
19
|
* - 非法 JSON / 非 object / 字段值非 string → 抛 DEPLOY_CONF_INVALID。
|
|
17
20
|
* - 空串字段视为未提供(不进 body,保持可选语义);未知 key 忽略。
|
|
@@ -31,7 +34,13 @@ function parseDeployConf(raw) {
|
|
|
31
34
|
}
|
|
32
35
|
const source = parsed;
|
|
33
36
|
const conf = {};
|
|
34
|
-
for (const key of [
|
|
37
|
+
for (const key of [
|
|
38
|
+
'checkPointVersion',
|
|
39
|
+
'commitID',
|
|
40
|
+
'sessionID',
|
|
41
|
+
'marker',
|
|
42
|
+
'versionLevel',
|
|
43
|
+
]) {
|
|
35
44
|
const value = source[key];
|
|
36
45
|
if (value === undefined)
|
|
37
46
|
continue;
|
|
@@ -43,6 +52,29 @@ function parseDeployConf(raw) {
|
|
|
43
52
|
}
|
|
44
53
|
return conf;
|
|
45
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* 解析 `--create` / `--update` / `--delete`,校验路径后组成 PatchChangeSet。
|
|
57
|
+
*
|
|
58
|
+
* 复用 deploy patch 的 normalizeRel(同一套校验:禁绝对路径、禁 ".."、统一为 posix 相对路径),
|
|
59
|
+
* 保证两条链路对「变更文件」的接受范围完全一致,调用方从 patch 迁移不会遇到收紧或放宽。
|
|
60
|
+
*
|
|
61
|
+
* 当前是预留口子:deploy 仍然全量上传,这里只校验并记一行日志,不参与上传决策。
|
|
62
|
+
* 三个参数都没传 → 返回 undefined(与不传时行为完全一致,不产生日志)。
|
|
63
|
+
*/
|
|
64
|
+
function parseChangedFiles(opts) {
|
|
65
|
+
const cs = {
|
|
66
|
+
creates: (opts.creates ?? []).map((rel) => (0, index_2.normalizeRel)(rel)),
|
|
67
|
+
updates: (opts.updates ?? []).map((rel) => (0, index_2.normalizeRel)(rel)),
|
|
68
|
+
deletes: (opts.deletes ?? []).map((rel) => (0, index_2.normalizeRel)(rel)),
|
|
69
|
+
};
|
|
70
|
+
const total = cs.creates.length + cs.updates.length + cs.deletes.length;
|
|
71
|
+
if (total === 0)
|
|
72
|
+
return undefined;
|
|
73
|
+
(0, logger_1.log)('deploy', `收到变更文件提示 ${String(total)} 个(create=${String(cs.creates.length)} ` +
|
|
74
|
+
`update=${String(cs.updates.length)} delete=${String(cs.deletes.length)}):` +
|
|
75
|
+
'当前仍为全量上传,未启用增量');
|
|
76
|
+
return cs;
|
|
77
|
+
}
|
|
46
78
|
/**
|
|
47
79
|
* miaoda deploy(modern scene 专用,CLI 表面对齐 openclaw-cli)
|
|
48
80
|
*
|
|
@@ -58,6 +90,10 @@ async function handleDeployModern(opts) {
|
|
|
58
90
|
skipRelease: opts.skipRelease ?? false,
|
|
59
91
|
checkPointVersion: conf.checkPointVersion,
|
|
60
92
|
commitID: conf.commitID,
|
|
93
|
+
sessionID: conf.sessionID,
|
|
94
|
+
marker: conf.marker,
|
|
95
|
+
versionLevel: conf.versionLevel,
|
|
96
|
+
changedFiles: parseChangedFiles(opts),
|
|
61
97
|
});
|
|
62
98
|
(0, output_1.emit)({
|
|
63
99
|
data: {
|
|
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.DEFAULT_POLL_INTERVAL_MS = void 0;
|
|
40
40
|
exports.waitForPipeline = waitForPipeline;
|
|
41
|
+
exports.normalizePipelineInstance = normalizePipelineInstance;
|
|
41
42
|
const ora_1 = __importDefault(require("ora"));
|
|
42
43
|
const api = __importStar(require("../../../api/index"));
|
|
43
44
|
const error_1 = require("../../../utils/error");
|
|
@@ -45,16 +46,11 @@ const output_1 = require("../../../utils/output");
|
|
|
45
46
|
const index_1 = require("../../../api/deploy/index");
|
|
46
47
|
/** 默认轮询间隔(毫秒) */
|
|
47
48
|
exports.DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
48
|
-
const TERMINAL_STATUSES = [
|
|
49
|
-
index_1.NodeStatus.SUCCESS,
|
|
50
|
-
index_1.NodeStatus.FAILED,
|
|
51
|
-
index_1.NodeStatus.CANCELED,
|
|
52
|
-
];
|
|
53
49
|
function defaultSleep(ms) {
|
|
54
50
|
return new Promise((res) => setTimeout(res, ms));
|
|
55
51
|
}
|
|
56
52
|
/**
|
|
57
|
-
* 轮询 pipeline
|
|
53
|
+
* 轮询 pipeline 状态直到终态、进入人工审批或超时。
|
|
58
54
|
*
|
|
59
55
|
* - JSON 模式:轮询期间静默;终态后由 caller 输出最终信封。
|
|
60
56
|
* - Pretty 非 TTY:同 JSON。
|
|
@@ -85,7 +81,11 @@ async function waitForPipeline(opts) {
|
|
|
85
81
|
const detail = normalizePipelineInstance(resp);
|
|
86
82
|
if (spinner)
|
|
87
83
|
advanceProgress(detail, printedStages, spinner);
|
|
88
|
-
if (
|
|
84
|
+
if (index_1.TERMINAL_NODE_STATUSES.includes(detail.status)) {
|
|
85
|
+
return detail;
|
|
86
|
+
}
|
|
87
|
+
// 等待人工审批(顶层 PENDING):可能持续数小时,立即交还给调用方,不再轮询到超时
|
|
88
|
+
if (detail.status === index_1.NodeStatus.PENDING) {
|
|
89
89
|
return detail;
|
|
90
90
|
}
|
|
91
91
|
if (now() - start >= timeoutMs) {
|
|
@@ -113,7 +113,7 @@ function advanceProgress(detail, printed, spinner) {
|
|
|
113
113
|
for (const stage of stages) {
|
|
114
114
|
if (printed.has(stage.id))
|
|
115
115
|
continue;
|
|
116
|
-
if (!
|
|
116
|
+
if (!index_1.TERMINAL_NODE_STATUSES.includes(stage.status))
|
|
117
117
|
continue;
|
|
118
118
|
spinner.stopAndPersist({
|
|
119
119
|
symbol: stageSymbol(stage),
|
|
@@ -153,7 +153,11 @@ function isPipelineInstance(value) {
|
|
|
153
153
|
return typeof item.status === 'number';
|
|
154
154
|
}
|
|
155
155
|
function stageSymbol(stage) {
|
|
156
|
-
|
|
156
|
+
if (stage.status === index_1.NodeStatus.SUCCESS)
|
|
157
|
+
return '✓';
|
|
158
|
+
if (stage.status === index_1.NodeStatus.FAILED || stage.status === index_1.NodeStatus.REJECTED)
|
|
159
|
+
return '✗';
|
|
160
|
+
return '•';
|
|
157
161
|
}
|
|
158
162
|
function formatStageBody(stage) {
|
|
159
163
|
const cost = stage.costTime >= 0 ? `(${(stage.costTime / 1000).toFixed(1)}s)` : '';
|
|
@@ -56,10 +56,11 @@ exports.readAllCapabilities = readAllCapabilities;
|
|
|
56
56
|
exports.hydrateCapability = hydrateCapability;
|
|
57
57
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
58
58
|
const node_path_1 = __importDefault(require("node:path"));
|
|
59
|
-
const node_child_process_1 = require("node:child_process");
|
|
60
59
|
const node_module_1 = require("node:module");
|
|
60
|
+
const archive_1 = require("../../../utils/archive");
|
|
61
61
|
const error_1 = require("../../../utils/error");
|
|
62
62
|
const logger_1 = require("../../../utils/logger");
|
|
63
|
+
const run_npm_1 = require("../../../utils/run-npm");
|
|
63
64
|
// ── Project paths ──
|
|
64
65
|
function getProjectRoot() {
|
|
65
66
|
return process.cwd();
|
|
@@ -152,7 +153,7 @@ function extractTgzToNodeModules(tgzPath, pluginName) {
|
|
|
152
153
|
}
|
|
153
154
|
node_fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
154
155
|
try {
|
|
155
|
-
(0,
|
|
156
|
+
(0, archive_1.extractTarGz)(tgzPath, tempDir);
|
|
156
157
|
const extractedDir = node_path_1.default.join(tempDir, 'package');
|
|
157
158
|
if (node_fs_1.default.existsSync(extractedDir)) {
|
|
158
159
|
node_fs_1.default.renameSync(extractedDir, targetDir);
|
|
@@ -191,7 +192,7 @@ function installMissingDeps(deps) {
|
|
|
191
192
|
if (deps.length === 0)
|
|
192
193
|
return;
|
|
193
194
|
(0, logger_1.log)('plugin', `Installing missing dependencies: ${deps.join(', ')}`);
|
|
194
|
-
const result = (0,
|
|
195
|
+
const result = (0, run_npm_1.runNpmSync)(['install', ...deps, '--no-save', '--no-package-lock'], {
|
|
195
196
|
cwd: getProjectRoot(),
|
|
196
197
|
stdio: 'inherit',
|
|
197
198
|
});
|
|
@@ -205,7 +206,7 @@ function installMissingDeps(deps) {
|
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
function npmInstall(tgzPath) {
|
|
208
|
-
const result = (0,
|
|
209
|
+
const result = (0, run_npm_1.runNpmSync)(['install', tgzPath, '--no-save', '--no-package-lock', '--ignore-scripts'], { cwd: getProjectRoot(), stdio: 'inherit' });
|
|
209
210
|
if (result.error) {
|
|
210
211
|
throw new error_1.AppError('INTERNAL_NPM_FAILED', `npm install failed: ${result.error.message}`, {
|
|
211
212
|
next_actions: ['确认本机已安装 npm,可 --verbose 查看执行详情'],
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* + client/index.html 等 fullstack 形态新增的资产)
|
|
15
15
|
* 5. 把 fullstack 必需的 scripts 加到 package.json(dev / build / type:check 等)
|
|
16
16
|
* 6. 从 fullstack template/package.json 取版本号,精确加 NestJS 强依赖白名单
|
|
17
|
-
* (client-toolkit + 几个 @nestjs/* + fullstack-nestjs-core + hbs
|
|
17
|
+
* (client-toolkit + 几个 @nestjs/* + SWC 工具链 + fullstack-nestjs-core + hbs 等;
|
|
18
|
+
* 不全量 merge)
|
|
18
19
|
* 7. codemod 业务代码:@lark-apaas/client-toolkit-lite → @lark-apaas/client-toolkit
|
|
19
20
|
* 8. codemod vite.config:defineConfig(...) → defineConfig(..., { fullstack: true }),
|
|
20
21
|
* 让 coding-preset-vite-react 启用 fullstack dev-proxy / html-output / basename /
|
|
@@ -58,7 +59,9 @@ exports.MIGRATE_CONFIG = {
|
|
|
58
59
|
// ===== 4. fullstack 形态必需资产(覆盖 user 已有同名文件) =====
|
|
59
60
|
// server/ 整目录:main.ts / app.module.ts / common/ / modules/view/ 等(fullstack 必需)
|
|
60
61
|
{ type: 'directory', from: 'server', to: 'server', overwrite: true },
|
|
61
|
-
// nest-cli.json
|
|
62
|
+
// nest-cli.json:整文件采用当前 fullstack template,除 Nest build 入口外还必须带上
|
|
63
|
+
// compilerOptions.builder.type=swc 及配套 typeCheck/options。若这里只保留旧的空配置
|
|
64
|
+
// 或字段级残缺合并,即使 package.json 已补齐 SWC 依赖,Nest 仍会回退到 TSC 构建。
|
|
62
65
|
{ type: 'file', from: 'nest-cli.json', to: 'nest-cli.json', overwrite: true },
|
|
63
66
|
// tsconfig.* —— vite-react 时代 tsconfig 配 include: ["src"],迁完代码搬到 client/src,
|
|
64
67
|
// include 路径不对 + 老 tsconfig.app 还 extends @lark-apaas/coding-presets-react;
|
|
@@ -272,6 +275,14 @@ exports.MIGRATE_CONFIG = {
|
|
|
272
275
|
// nest start / nest build 必需
|
|
273
276
|
'@nestjs/cli',
|
|
274
277
|
'@nestjs/testing',
|
|
278
|
+
// nest-cli.json 会被上面的 file rule 覆盖成 builder.type=swc。Nest CLI 只负责
|
|
279
|
+
// 调度 builder,不会自带 SWC 可执行入口和 native compiler;两者必须一起从
|
|
280
|
+
// 当前目标模板取版本写入迁移后 package.json。若漏掉,前端应用升级成全栈后
|
|
281
|
+
// 配置已经选择 SWC,但全新的依赖安装树里找不到 @swc/cli/@swc/core,冷启动和
|
|
282
|
+
// nest build 会失败;开发机旧 node_modules 偶然残留 SWC 时还可能掩盖该问题。
|
|
283
|
+
// 这里不硬编码版本,保持升级应用与同版本新建全栈应用使用完全相同的 SWC 组合。
|
|
284
|
+
'@swc/cli',
|
|
285
|
+
'@swc/core',
|
|
275
286
|
// server ts 编译
|
|
276
287
|
'ts-node',
|
|
277
288
|
'@types/express',
|
|
@@ -23,13 +23,15 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
24
|
const nestjs_react_fullstack_1 = __importDefault(require("./nestjs-react-fullstack"));
|
|
25
25
|
const design_stack_1 = __importDefault(require("./design-stack"));
|
|
26
|
+
const vite_react_1 = __importDefault(require("./vite-react"));
|
|
26
27
|
/**
|
|
27
|
-
* 已纳入新 sync 机制的 stack 注册表。未在表里的 stack
|
|
28
|
+
* 已纳入新 sync 机制的 stack 注册表。未在表里的 stack(如 html / design-html)走旧的
|
|
28
29
|
* `upgrade/templates/<stack>/{files,patches}` 机制,由 sync handler 兜底。
|
|
29
30
|
*/
|
|
30
31
|
const STACK_REGISTRY = {
|
|
31
32
|
'nestjs-react-fullstack': nestjs_react_fullstack_1.default,
|
|
32
33
|
'design-stack': design_stack_1.default,
|
|
34
|
+
'vite-react': vite_react_1.default,
|
|
33
35
|
};
|
|
34
36
|
/** 返回该 stack 的 SyncConfig;表里没有时返回 null。 */
|
|
35
37
|
function getSyncConfig(stack) {
|
|
@@ -159,6 +159,12 @@ exports.SYNC_CONFIG = {
|
|
|
159
159
|
to: `npx -y ${fullstack_cli_pin_1.FULLSTACK_CLI_PIN_SPEC} sync --disable-gen-openapi`,
|
|
160
160
|
ifStartsWith: 'npx -y @lark-apaas/fullstack-cli sync',
|
|
161
161
|
},
|
|
162
|
+
// MCP UI 使用独立浏览器配置;只补服务端排除项,不生成 UI 目录和配置。
|
|
163
|
+
{
|
|
164
|
+
type: 'typescript-exclude',
|
|
165
|
+
to: 'tsconfig.node.json',
|
|
166
|
+
path: 'server/mcp/ui',
|
|
167
|
+
},
|
|
162
168
|
// ===== miaoda-cli 本地开发新增规则(fullstack-cli sync 不会执行) =====
|
|
163
169
|
// M1. scripts.dev:local —— 本地用户绕过沙箱判定直接跑本地链路(npm run dev:local)。
|
|
164
170
|
// dev.sh 在 MIAODA_DEP_CACHE_DIR 非空时跑 dev.js(沙箱保活)、否则 exec dev-local.js;显式 dev:local
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* vite-react sync 规则(镜像 fullstack-cli 的 viteReactProfile):只同步 build.sh 一支,
|
|
4
|
+
* 回溯 public/* → dist/output/ 修复。
|
|
5
|
+
* ⚠️ build.sh 源须与 miaoda-coding 模板、fullstack-cli `templates/vite-react/build.sh` 逐字一致。
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.SYNC_CONFIG = void 0;
|
|
9
|
+
exports.SYNC_CONFIG = {
|
|
10
|
+
sync: [
|
|
11
|
+
// 覆盖 scripts/build.sh:回溯 public/* → dist/output/(同源根 /app/<appId>/*)修复
|
|
12
|
+
{
|
|
13
|
+
type: 'file',
|
|
14
|
+
from: 'scripts/build.sh',
|
|
15
|
+
to: 'scripts/build.sh',
|
|
16
|
+
overwrite: true,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
exports.default = exports.SYNC_CONFIG;
|
|
@@ -11,13 +11,24 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
11
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
12
|
const env_1 = require("../../../utils/env");
|
|
13
13
|
const install_1 = require("./install");
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* event marker 目录,写死 `/tmp/event`;写前 mkdir -p。沙箱里 code server 轮询此目录,
|
|
16
|
+
* 路径是与平台约定的契约,**不能换成 os.tmpdir()**。所有读写它的代码路径都被
|
|
17
|
+
* isSandboxEnv() 守着(沙箱恒为 Linux),非沙箱环境不会碰。
|
|
18
|
+
*/
|
|
15
19
|
const EVENT_DIR = '/tmp/event';
|
|
16
20
|
/** 安装成功 marker 名 */
|
|
17
21
|
const MARKER_READY = 'WORKSPACE_READY';
|
|
18
22
|
/** 安装失败 marker 名 */
|
|
19
23
|
const MARKER_FAILED = 'WORKSPACE_FAILED';
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* 后台 worker stdout/stderr 落盘路径,写死 `/tmp/...`。
|
|
26
|
+
*
|
|
27
|
+
* 刻意**不**换成 `os.tmpdir()`:这个路径已经是对外契约 —— 写在 async-install 设计
|
|
28
|
+
* spec 里、`app init` 的 help 文案里、失败 marker 的 `logPath` 字段里,用户与平台
|
|
29
|
+
* 都按它 cat 日志。Windows 上 Node 会把 `/tmp` 解析到当前盘符根下的 `\tmp\`,
|
|
30
|
+
* 目录可写、内容可读,功能不受影响,不值得为此破坏契约。
|
|
31
|
+
*/
|
|
21
32
|
exports.ASYNC_INSTALL_LOG = '/tmp/async_install_dep.std.log';
|
|
22
33
|
function nowIso() {
|
|
23
34
|
return new Date().toISOString();
|
|
@@ -5,12 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.installDependencies = installDependencies;
|
|
7
7
|
exports.resolveNpmInstallRegistry = resolveNpmInstallRegistry;
|
|
8
|
-
const node_child_process_1 = require("node:child_process");
|
|
9
8
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const archive_1 = require("../../../utils/archive");
|
|
12
12
|
const error_1 = require("../../../utils/error");
|
|
13
13
|
const logger_1 = require("../../../utils/logger");
|
|
14
|
+
const run_npm_1 = require("../../../utils/run-npm");
|
|
14
15
|
const DEP_CACHE_ENV = 'MIAODA_DEP_CACHE_DIR';
|
|
15
16
|
/**
|
|
16
17
|
* 依赖安装。
|
|
@@ -94,7 +95,7 @@ function md5File(filePath) {
|
|
|
94
95
|
}
|
|
95
96
|
function extractZip(zipPath, targetDir, stdio) {
|
|
96
97
|
try {
|
|
97
|
-
(0,
|
|
98
|
+
(0, archive_1.extractZip)(zipPath, targetDir, { stdio });
|
|
98
99
|
}
|
|
99
100
|
catch (err) {
|
|
100
101
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -129,5 +130,5 @@ function runNpmInstall(targetDir, stdio, extraPackages) {
|
|
|
129
130
|
args.push(...extraPackages);
|
|
130
131
|
}
|
|
131
132
|
(0, logger_1.log)('init', `npm ${args.join(' ')} in ${targetDir}...`);
|
|
132
|
-
(0,
|
|
133
|
+
(0, run_npm_1.execNpmSync)(args, { cwd: targetDir, stdio });
|
|
133
134
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runBuild = runBuild;
|
|
4
|
-
const node_child_process_1 = require("node:child_process");
|
|
5
4
|
const error_1 = require("../../../../utils/error");
|
|
6
5
|
const logger_1 = require("../../../../utils/logger");
|
|
6
|
+
const run_npm_1 = require("../../../../utils/run-npm");
|
|
7
7
|
const protocol_1 = require("../protocol");
|
|
8
8
|
/**
|
|
9
9
|
* 给 RESOURCE_CDN_PREFIX 补 https:// 协议头:
|
|
@@ -33,7 +33,7 @@ function ensureHttpsScheme(v) {
|
|
|
33
33
|
* 是 runtime 同域相对路径(例如 /app/<appId>/runtime/api/v1/storage/object/<bucket>/),
|
|
34
34
|
* 不补 https scheme,原样注入。顶层 static_cdn_prefix 不再下发。
|
|
35
35
|
*
|
|
36
|
-
* build 失败抛 AppError(
|
|
36
|
+
* build 失败抛 AppError(execNpmSync 自身会 throw,捕获后包一层加错误码)。
|
|
37
37
|
*/
|
|
38
38
|
function runBuild(opts) {
|
|
39
39
|
const staticCred = (0, protocol_1.parsePaasStorageCredential)((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL), protocol_1.DataKey.OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL);
|
|
@@ -47,7 +47,7 @@ function runBuild(opts) {
|
|
|
47
47
|
};
|
|
48
48
|
(0, logger_1.log)('deploy', 'Building...');
|
|
49
49
|
try {
|
|
50
|
-
(0,
|
|
50
|
+
(0, run_npm_1.execNpmSync)(['run', 'build'], {
|
|
51
51
|
cwd: opts.projectDir,
|
|
52
52
|
stdio: 'inherit',
|
|
53
53
|
env: buildEnv,
|
|
@@ -16,6 +16,9 @@ async function createLocalRelease(appId, version, extra) {
|
|
|
16
16
|
version,
|
|
17
17
|
checkPointVersion: extra?.checkPointVersion,
|
|
18
18
|
commitID: extra?.commitID,
|
|
19
|
+
sessionID: extra?.sessionID,
|
|
20
|
+
description: extra?.marker,
|
|
21
|
+
versionLevel: extra?.versionLevel,
|
|
19
22
|
});
|
|
20
23
|
}
|
|
21
24
|
/**
|
|
@@ -18,15 +18,13 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
18
18
|
const node_child_process_1 = require("node:child_process");
|
|
19
19
|
const error_1 = require("../../../../utils/error");
|
|
20
20
|
const logger_1 = require("../../../../utils/logger");
|
|
21
|
+
const which_1 = require("../../../../utils/which");
|
|
21
22
|
function resolveTosutilPath() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
catch {
|
|
28
|
-
/* fallthrough */
|
|
29
|
-
}
|
|
23
|
+
// 用 whichSync 而不是 shell out `which`:Windows 上没有 `which`(对应 `where`),
|
|
24
|
+
// 且查 PATH 本身不需要子进程。
|
|
25
|
+
const resolved = (0, which_1.whichSync)('tosutil');
|
|
26
|
+
if (resolved !== undefined)
|
|
27
|
+
return resolved;
|
|
30
28
|
throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
|
|
31
29
|
}
|
|
32
30
|
function tosutilUploadFromTos(cred) {
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizeRel = normalizeRel;
|
|
6
7
|
exports.buildTosActions = buildTosActions;
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -10,7 +11,12 @@ const error_1 = require("../../../../utils/error");
|
|
|
10
11
|
const index_1 = require("../../../../api/deploy/index");
|
|
11
12
|
const content_1 = require("./content");
|
|
12
13
|
const routes_1 = require("./routes");
|
|
13
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* 校验相对路径:禁绝对路径、禁含 ".." 段;normalize 成 posix 相对。
|
|
16
|
+
*
|
|
17
|
+
* 导出供 `deploy` 的 `--create` / `--update` / `--delete` 复用同一套校验,
|
|
18
|
+
* 让两条链路对「变更文件」的接受范围一致,避免两处实现漂移。
|
|
19
|
+
*/
|
|
14
20
|
function normalizeRel(rel) {
|
|
15
21
|
if (node_path_1.default.isAbsolute(rel) || rel.startsWith('/')) {
|
|
16
22
|
throw new error_1.AppError('ARGS_INVALID', `文件路径不能是绝对路径:${rel}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildTosActions = void 0;
|
|
3
|
+
exports.normalizeRel = exports.buildTosActions = void 0;
|
|
4
4
|
exports.patchDesignDeploy = patchDesignDeploy;
|
|
5
5
|
const spark_meta_1 = require("../../../../utils/spark-meta");
|
|
6
6
|
const error_1 = require("../../../../utils/error");
|
|
@@ -10,6 +10,7 @@ const template_key_map_1 = require("../template-key-map");
|
|
|
10
10
|
const actions_1 = require("./actions");
|
|
11
11
|
var actions_2 = require("./actions");
|
|
12
12
|
Object.defineProperty(exports, "buildTosActions", { enumerable: true, get: function () { return actions_2.buildTosActions; } });
|
|
13
|
+
Object.defineProperty(exports, "normalizeRel", { enumerable: true, get: function () { return actions_2.normalizeRel; } });
|
|
13
14
|
/**
|
|
14
15
|
* design-html 增量发布:读本地文件组 TosFileAction(改动含 .html 时带重算的
|
|
15
16
|
* routes.json),调后端 applyTosDiff 应用到 latest。scope 限 design_local_deploy。
|
|
@@ -28,7 +28,13 @@ async function designLocalPublishPipeline(opts) {
|
|
|
28
28
|
skipRelease: opts.skipRelease ?? false,
|
|
29
29
|
appId: ctx.appId,
|
|
30
30
|
version: pre.version,
|
|
31
|
-
extra: {
|
|
31
|
+
extra: {
|
|
32
|
+
checkPointVersion: opts.checkPointVersion,
|
|
33
|
+
commitID: opts.commitID,
|
|
34
|
+
sessionID: opts.sessionID,
|
|
35
|
+
marker: opts.marker,
|
|
36
|
+
versionLevel: opts.versionLevel,
|
|
37
|
+
},
|
|
32
38
|
});
|
|
33
39
|
try {
|
|
34
40
|
await (0, index_1.uploadDesignArtifacts)({ projectDir: ctx.projectDir, data });
|
|
@@ -45,7 +45,13 @@ async function localBuildLocalPublishPipeline(opts) {
|
|
|
45
45
|
skipRelease: opts.skipRelease ?? false,
|
|
46
46
|
appId: ctx.appId,
|
|
47
47
|
version: pre.version,
|
|
48
|
-
extra: {
|
|
48
|
+
extra: {
|
|
49
|
+
checkPointVersion: opts.checkPointVersion,
|
|
50
|
+
commitID: opts.commitID,
|
|
51
|
+
sessionID: opts.sessionID,
|
|
52
|
+
marker: opts.marker,
|
|
53
|
+
versionLevel: opts.versionLevel,
|
|
54
|
+
},
|
|
49
55
|
});
|
|
50
56
|
try {
|
|
51
57
|
(0, logger_1.log)('deploy', 'Uploading artifacts...');
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resetArchiveProbeCache = resetArchiveProbeCache;
|
|
4
|
+
exports.extractTarGz = extractTarGz;
|
|
5
|
+
exports.extractZip = extractZip;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const error_1 = require("../utils/error");
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
9
|
+
const which_1 = require("../utils/which");
|
|
10
|
+
/**
|
|
11
|
+
* 归档解压的跨平台入口 —— 仓库内所有解压必须走这里,禁止再裸写
|
|
12
|
+
* `execFileSync('unzip', ...)` / `execSync('tar -xzf ...')`。
|
|
13
|
+
*
|
|
14
|
+
* 平台差异:
|
|
15
|
+
* - `tar`:Linux(GNU tar)、macOS(bsdtar)、Windows 10 1803+(内置 bsdtar)都有,
|
|
16
|
+
* 且都支持 `-xzf` 解 .tar.gz,所以 tar.gz 一条路走到底。
|
|
17
|
+
* - `unzip`:Linux / macOS 常见,**Windows 完全没有**。但 bsdtar 能解 zip,
|
|
18
|
+
* 所以 zip 走 `unzip → tar` 两级探测:Linux 命中 unzip,Windows 命中 tar。
|
|
19
|
+
* (GNU tar 不支持 zip,所以顺序不能反 —— Linux 上必须优先 unzip。)
|
|
20
|
+
*
|
|
21
|
+
* 一律用 execFileSync 传参数数组、不过 shell:路径含空格 / 中文 / `&` 时,
|
|
22
|
+
* 拼字符串交给 cmd.exe 或 sh 重新分词的行为在各平台不一致,且是注入面。
|
|
23
|
+
*
|
|
24
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
* 为什么**不**换成纯 JS 解压库(adm-zip / yauzl / node-tar)—— 别再尝试了:
|
|
26
|
+
*
|
|
27
|
+
* `extractZip` 的主要用途是解依赖缓存 zip(`MIAODA_DEP_CACHE_DIR`),内容是整个
|
|
28
|
+
* `node_modules/`。实测对比同一个 zip:
|
|
29
|
+
*
|
|
30
|
+
* | | Unix mode | 软链 |
|
|
31
|
+
* | unzip | 755 保留 | 保留 |
|
|
32
|
+
* | adm-zip | 666(执行位丢) | 被当普通文件解开 |
|
|
33
|
+
*
|
|
34
|
+
* `node_modules/.bin/*` 既需要执行位、又大量是软链 —— 用 adm-zip 解出来的缓存,
|
|
35
|
+
* 沙箱里 `npm run dev` 会直接挂。yauzl 更低层,mode 得自己从
|
|
36
|
+
* `externalFileAttributes` 还原、软链也要自己判,等于把 unzip 重写一遍。
|
|
37
|
+
*
|
|
38
|
+
* node-tar 倒是保权限 + 软链(npm 自己用它装包),但装出来 2.9MB、连传递依赖
|
|
39
|
+
* +3.7MB —— 而本仓生产依赖总共才 1.7MB,翻 3 倍多。CLI 是 `npx -y` 拉起的
|
|
40
|
+
* (模板 `scripts/dev.sh` 每次本地启动都会跑 `app sync`),冷启动下载量直接可感。
|
|
41
|
+
* 外部 tar 在 Linux / macOS / Windows 10 1803+ 都有,不值得为此付这个代价。
|
|
42
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
*/
|
|
44
|
+
/** tar 可用性探测结果缓存(单次 CLI 执行期内 PATH 不变) */
|
|
45
|
+
let tarAvailable;
|
|
46
|
+
let unzipAvailable;
|
|
47
|
+
function hasTar() {
|
|
48
|
+
tarAvailable ??= (0, which_1.whichSync)('tar') !== undefined;
|
|
49
|
+
return tarAvailable;
|
|
50
|
+
}
|
|
51
|
+
function hasUnzip() {
|
|
52
|
+
unzipAvailable ??= (0, which_1.whichSync)('unzip') !== undefined;
|
|
53
|
+
return unzipAvailable;
|
|
54
|
+
}
|
|
55
|
+
/** 仅测试用:清掉可用性探测缓存 */
|
|
56
|
+
function resetArchiveProbeCache() {
|
|
57
|
+
tarAvailable = undefined;
|
|
58
|
+
unzipAvailable = undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 解 .tar.gz / .tgz 到 `destDir`(须已存在)。失败抛 `INTERNAL_EXTRACT_FAILED`。
|
|
62
|
+
*/
|
|
63
|
+
function extractTarGz(archivePath, destDir, options = {}) {
|
|
64
|
+
if (!hasTar()) {
|
|
65
|
+
throw new error_1.AppError('INTERNAL_EXTRACT_FAILED', `解压 ${archivePath} 需要 tar,但当前环境 PATH 中找不到`, {
|
|
66
|
+
next_actions: [
|
|
67
|
+
'Linux/macOS:安装 tar(通常已内置)',
|
|
68
|
+
'Windows:升级到 Windows 10 1803+(内置 tar),或安装 Git for Windows 并把其 usr/bin 加入 PATH',
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
(0, logger_1.debug)(`archive: tar -xzf ${archivePath} -C ${destDir}`);
|
|
73
|
+
(0, node_child_process_1.execFileSync)('tar', ['-xzf', archivePath, '-C', destDir], {
|
|
74
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
75
|
+
...options,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 解 .zip 到 `destDir`(须已存在),同名文件覆盖。失败抛 `INTERNAL_EXTRACT_FAILED`。
|
|
80
|
+
*
|
|
81
|
+
* unzip 优先(Linux 上唯一可用项),退化到 bsdtar(Windows 上唯一可用项)。
|
|
82
|
+
*/
|
|
83
|
+
function extractZip(archivePath, destDir, options = {}) {
|
|
84
|
+
const stdio = ['ignore', 'pipe', 'pipe'];
|
|
85
|
+
if (hasUnzip()) {
|
|
86
|
+
(0, logger_1.debug)(`archive: unzip -q -o ${archivePath} -d ${destDir}`);
|
|
87
|
+
(0, node_child_process_1.execFileSync)('unzip', ['-q', '-o', archivePath, '-d', destDir], { stdio, ...options });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (hasTar()) {
|
|
91
|
+
// bsdtar 按后缀自动识别 zip,故用 -xf 而非 -xzf(zip 不是 gzip 流)。
|
|
92
|
+
// 覆盖行为与 `unzip -o` 一致:tar 默认覆盖已存在文件。
|
|
93
|
+
(0, logger_1.debug)(`archive: tar -xf ${archivePath} -C ${destDir}(unzip 不可用,走 bsdtar)`);
|
|
94
|
+
(0, node_child_process_1.execFileSync)('tar', ['-xf', archivePath, '-C', destDir], { stdio, ...options });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
throw new error_1.AppError('INTERNAL_EXTRACT_FAILED', `解压 ${archivePath} 需要 unzip 或 tar,但当前环境 PATH 中两者都找不到`, {
|
|
98
|
+
next_actions: [
|
|
99
|
+
'Linux:apt-get install unzip / yum install unzip',
|
|
100
|
+
'Windows:升级到 Windows 10 1803+(内置 tar 可解 zip)',
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
}
|