@lark-apaas/miaoda-cli 0.1.18-alpha.b094028 → 0.1.18-alpha.b809ecc
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/api/deploy/index.js +3 -1
- package/dist/api/deploy/modern-types.js +4 -1
- package/dist/api/deploy/modern.js +8 -0
- package/dist/cli/commands/deploy/modern.js +30 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/handlers/app/migrate.js +0 -37
- package/dist/cli/handlers/deploy/index.js +3 -1
- package/dist/cli/handlers/deploy/patch.js +16 -0
- package/dist/services/app/init/template.js +1 -0
- package/dist/services/deploy/modern/atoms/design-build.js +41 -0
- package/dist/services/deploy/modern/atoms/design-upload.js +74 -0
- package/dist/services/deploy/modern/atoms/index.js +5 -1
- package/dist/services/deploy/modern/atoms/tosutil.js +246 -0
- package/dist/services/deploy/modern/atoms/upload.js +4 -127
- package/dist/services/deploy/modern/patch/actions.js +60 -0
- package/dist/services/deploy/modern/patch/content.js +18 -0
- package/dist/services/deploy/modern/patch/index.js +41 -0
- package/dist/services/deploy/modern/patch/routes.js +28 -0
- package/dist/services/deploy/modern/patch/source-scan.js +56 -0
- package/dist/services/deploy/modern/pipelines/design-local.js +47 -0
- package/dist/services/deploy/modern/pipelines/index.js +3 -1
- package/dist/services/deploy/modern/protocol.js +7 -0
- package/dist/services/deploy/modern/run.js +10 -4
- package/dist/services/deploy/modern/template-key-map.js +4 -0
- package/package.json +1 -1
package/dist/api/deploy/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NodeStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.batchSavePluginInstances = exports.LocalReleaseStatus = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = exports.callbackStatic = exports.updateLocalRelease = exports.createLocalRelease = exports.preRelease = exports.queryPipelineInstance = exports.getErrorLog = exports.listPipelineInstances = exports.createRelease = void 0;
|
|
3
|
+
exports.NodeStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.batchSavePluginInstances = exports.TosActionType = exports.LocalReleaseStatus = exports.applyTosDiff = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = exports.callbackStatic = exports.updateLocalRelease = exports.createLocalRelease = exports.preRelease = exports.queryPipelineInstance = exports.getErrorLog = exports.listPipelineInstances = exports.createRelease = void 0;
|
|
4
4
|
var api_1 = require("./api");
|
|
5
5
|
Object.defineProperty(exports, "createRelease", { enumerable: true, get: function () { return api_1.createRelease; } });
|
|
6
6
|
Object.defineProperty(exports, "listPipelineInstances", { enumerable: true, get: function () { return api_1.listPipelineInstances; } });
|
|
@@ -14,8 +14,10 @@ Object.defineProperty(exports, "callbackStatic", { enumerable: true, get: functi
|
|
|
14
14
|
Object.defineProperty(exports, "createModernRelease", { enumerable: true, get: function () { return modern_1.createRelease; } });
|
|
15
15
|
Object.defineProperty(exports, "getModernReleaseStatus", { enumerable: true, get: function () { return modern_1.getReleaseStatus; } });
|
|
16
16
|
Object.defineProperty(exports, "getModernLastPublishedVersion", { enumerable: true, get: function () { return modern_1.getLastPublishedVersion; } });
|
|
17
|
+
Object.defineProperty(exports, "applyTosDiff", { enumerable: true, get: function () { return modern_1.applyTosDiff; } });
|
|
17
18
|
var modern_types_1 = require("./modern-types");
|
|
18
19
|
Object.defineProperty(exports, "LocalReleaseStatus", { enumerable: true, get: function () { return modern_types_1.LocalReleaseStatus; } });
|
|
20
|
+
Object.defineProperty(exports, "TosActionType", { enumerable: true, get: function () { return modern_types_1.TosActionType; } });
|
|
19
21
|
var plugin_instances_1 = require("./plugin-instances");
|
|
20
22
|
Object.defineProperty(exports, "batchSavePluginInstances", { enumerable: true, get: function () { return plugin_instances_1.batchSavePluginInstances; } });
|
|
21
23
|
var schemas_1 = require("./schemas");
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//
|
|
9
9
|
// 远端部署链路(B/C)所需的 release 接口先预留类型骨架,待 B/C 落地时启用。
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.LocalReleaseStatus = void 0;
|
|
11
|
+
exports.TosActionType = exports.LocalReleaseStatus = void 0;
|
|
12
12
|
/**
|
|
13
13
|
* localPublish 状态枚举(数值,与服务端 IDL 对齐)。
|
|
14
14
|
* CLI updateLocalRelease 只翻 Finished / Failed 两个终态;其它由后端流转。
|
|
@@ -21,3 +21,6 @@ exports.LocalReleaseStatus = {
|
|
|
21
21
|
Canceled: 4,
|
|
22
22
|
Rollback: 5,
|
|
23
23
|
};
|
|
24
|
+
// ── applyTosDiff(design-html 增量发布;后端应用 TOS diff) ──
|
|
25
|
+
/** 对齐 IDL publish.TosActionType(UNSPECIFIED=0 不用) */
|
|
26
|
+
exports.TosActionType = { CREATE: 1, UPDATE: 2, DELETE: 3 };
|
|
@@ -17,6 +17,7 @@ exports.callbackStatic = callbackStatic;
|
|
|
17
17
|
exports.createRelease = createRelease;
|
|
18
18
|
exports.getReleaseStatus = getReleaseStatus;
|
|
19
19
|
exports.getLastPublishedVersion = getLastPublishedVersion;
|
|
20
|
+
exports.applyTosDiff = applyTosDiff;
|
|
20
21
|
const http_1 = require("../../utils/http");
|
|
21
22
|
const devops_error_1 = require("../../utils/devops-error");
|
|
22
23
|
const DEFAULT_ERR_CODE = 'INTERNAL_DEVOPS_ERROR';
|
|
@@ -76,3 +77,10 @@ async function getLastPublishedVersion(req) {
|
|
|
76
77
|
const url = `/v1/devops/app/${encodeURIComponent(appID)}/last_published_version`;
|
|
77
78
|
return (0, http_1.postInnerApi)(url, {}, envelopeOpts('Failed to get last published version'));
|
|
78
79
|
}
|
|
80
|
+
// ── applyTosDiff ──
|
|
81
|
+
/** POST /v1/devops/app/:appID/apply_tos_diff —— 增量应用 TOS 文件 diff */
|
|
82
|
+
async function applyTosDiff(req) {
|
|
83
|
+
const { appID, ...body } = req;
|
|
84
|
+
const url = `/v1/devops/app/${encodeURIComponent(appID)}/apply_tos_diff`;
|
|
85
|
+
return (0, http_1.postInnerApi)(url, body, envelopeOpts('Failed to apply tos diff'));
|
|
86
|
+
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerDeployCommandsModern = registerDeployCommandsModern;
|
|
4
4
|
const shared_1 = require("../../../cli/commands/shared");
|
|
5
5
|
const modern_1 = require("../../../cli/handlers/deploy/modern");
|
|
6
|
+
const index_1 = require("../../../cli/handlers/deploy/index");
|
|
6
7
|
/**
|
|
7
8
|
* modern scene (MIAODA_APP_TYPE=7) 专用 deploy 注册。
|
|
8
9
|
* 仅披露顶层 `miaoda deploy`,CLI 表面对齐 openclaw-cli:`--dir / --skip-build`。
|
|
@@ -56,4 +57,33 @@ JSON 输出(stdout)
|
|
|
56
57
|
conf: rawOpts.conf,
|
|
57
58
|
});
|
|
58
59
|
}));
|
|
60
|
+
const patchCmd = deployCmd
|
|
61
|
+
.command('patch')
|
|
62
|
+
.description('design-html 增量发布:按文件 create/update/delete,.html 变化时同步 routes.json')
|
|
63
|
+
.option('--create <relpath>', '新增文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
64
|
+
.option('--update <relpath>', '覆盖已有文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
65
|
+
.option('--delete <relpath>', '删除文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
66
|
+
.addHelpText('after', `
|
|
67
|
+
--dir 为项目目录(与 deploy 同名参数,默认当前目录)。
|
|
68
|
+
仅支持 design-html(design_local_deploy)。路径为工程根相对路径(= 服务端 latest 下 key),
|
|
69
|
+
禁绝对路径与 ".."。--create/--update 的本地文件必须存在。改动集含 .html 时自动重算并随包
|
|
70
|
+
更新 routes.json。
|
|
71
|
+
|
|
72
|
+
JSON 输出
|
|
73
|
+
{"data": {"upsertCount": <n>, "deleteCount": <n>, "actionsSent": <n>, "routesRegenerated": <bool>}}
|
|
74
|
+
|
|
75
|
+
示例
|
|
76
|
+
$ miaoda deploy patch --update index.html
|
|
77
|
+
$ miaoda deploy patch --create blog/post.html --delete blog/old.html
|
|
78
|
+
$ miaoda deploy patch --update assets/app.js --update assets/app.css
|
|
79
|
+
`);
|
|
80
|
+
patchCmd.action((0, shared_1.withHelp)(patchCmd, async (rawOpts) => {
|
|
81
|
+
await (0, index_1.handleDeployPatch)({
|
|
82
|
+
appId: (0, shared_1.resolveAppId)({}),
|
|
83
|
+
dir: deployCmd.opts().dir ?? '.',
|
|
84
|
+
creates: rawOpts.create,
|
|
85
|
+
updates: rawOpts.update,
|
|
86
|
+
deletes: rawOpts.delete,
|
|
87
|
+
});
|
|
88
|
+
}));
|
|
59
89
|
}
|
|
@@ -14,6 +14,7 @@ const index_6 = require("../../cli/commands/skills/index");
|
|
|
14
14
|
// 3 = AppType_APPLICATION(全栈应用,当前仅 nestjs-react-fullstack 一个 stack)
|
|
15
15
|
// 4 = AppType_DESIGN(design-stack,SSR 渲染、无业务逻辑、无数据库)
|
|
16
16
|
// 7 = miaoda-cli 自定义 modern 占位(后端枚举无 7,沙箱目前不传)
|
|
17
|
+
// 8 = AppType_DESIGN_HTML(design-html,buildless 静态 HTML,命令集同 modern)
|
|
17
18
|
// 其它(0/1/2/5/6 / 未设)→ default(命令全开,本地 dev / CI / 兼容回退)
|
|
18
19
|
// stack 维度(nestjs-react-fullstack / vite-react / ...)是正交的,
|
|
19
20
|
// skills sync 内部按 .spark/meta.json.stack 拉对应 coding-steering/steering/<stack>/ 子目录。
|
|
@@ -24,6 +25,8 @@ function resolveScene(appType, _archType) {
|
|
|
24
25
|
return 'application';
|
|
25
26
|
if (appType === '4')
|
|
26
27
|
return 'design';
|
|
28
|
+
if (appType === '8')
|
|
29
|
+
return 'design-html';
|
|
27
30
|
return 'default';
|
|
28
31
|
}
|
|
29
32
|
const SCENE_REGISTRARS = {
|
|
@@ -62,6 +65,13 @@ const SCENE_REGISTRARS = {
|
|
|
62
65
|
(0, index_3.registerObservabilityCommands)(p);
|
|
63
66
|
(0, index_6.registerSkillsCommands)(p);
|
|
64
67
|
},
|
|
68
|
+
// design-html scene(AppType_DESIGN_HTML=8):buildless 静态 HTML,
|
|
69
|
+
// 命令集与 modern 一致(app init/sync + modern 拆分版 deploy + skills),不挂 db/file/observability。
|
|
70
|
+
'design-html': (p) => {
|
|
71
|
+
(0, index_4.registerAppCommands)(p, { includeInit: true });
|
|
72
|
+
(0, modern_1.registerDeployCommandsModern)(p);
|
|
73
|
+
(0, index_6.registerSkillsCommands)(p);
|
|
74
|
+
},
|
|
65
75
|
};
|
|
66
76
|
function readEnv(name) {
|
|
67
77
|
const v = process.env[name]?.trim();
|
|
@@ -134,42 +134,6 @@ async function handleAppMigrate(opts) {
|
|
|
134
134
|
installError = err instanceof Error ? err.message : String(err);
|
|
135
135
|
(0, logger_1.log)('migrate', `⚠ npm install failed (continuing): ${installError}`);
|
|
136
136
|
}
|
|
137
|
-
// 预热 vite deps cache —— 绕开 dev server 内嵌 optimizer 在沙箱里的 silent hang。
|
|
138
|
-
//
|
|
139
|
-
// 背景:migrate 跑完 npm install 后,fullstack user app 的 lockfile 是新的,但
|
|
140
|
-
// node_modules/.vite/deps/_metadata.json 还是旧/不存在。下一次 dev:client 启动时
|
|
141
|
-
// vite 看到 lockfile hash 跟 metadata 不一致,触发内嵌 optimizer 重跑 prebundle。
|
|
142
|
-
// 偶发(看起来跟沙箱 CPU / esbuild worker 状态有关) optimizer 静默 hang —— bundle 不
|
|
143
|
-
// 完成、不写产物、不报错,但 vite middleware 仍 serve 请求:transform 写一个 hash、
|
|
144
|
-
// load 又拿到另一个 hash, 持续 504。详见
|
|
145
|
-
// ~/docs/miaoda-fullstack-vite-deps-504-20260624.md
|
|
146
|
-
//
|
|
147
|
-
// 解法:这里 standalone `npx vite optimize --force` 跑一次,只用 vite optimizer 子流程
|
|
148
|
-
// (不加载 preset 那一堆 plugin 的 dev server lifecycle hook),沙箱里实测 1~2 秒就能
|
|
149
|
-
// 完成,写出 _metadata.json + 当下 lockfileHash。下一次 dev:client 启动时 vite 看
|
|
150
|
-
// hash 跟 cache 一致就跳过 Re-optimize, 直接复用 cache,绕开 hang 路径。
|
|
151
|
-
//
|
|
152
|
-
// - 只在 SANDBOX_ID 非空时做(本地环境 vite 自带 optimizer 不卡, 没必要拖慢 migrate)
|
|
153
|
-
// - 只在 install 成功时做(install 失败时 node_modules 状态不对, optimize 也会挂)
|
|
154
|
-
// - 软失败:optimize 挂了不阻断后续 pkill —— 即使没预热成功, dev server 自己跑
|
|
155
|
-
// optimizer 偶尔也能通过, 不要因为这一步丢掉重启 dev 的机会
|
|
156
|
-
let optimizeError;
|
|
157
|
-
if (installError === undefined &&
|
|
158
|
-
process.env.SANDBOX_ID !== undefined &&
|
|
159
|
-
process.env.SANDBOX_ID !== '') {
|
|
160
|
-
(0, logger_1.log)('migrate', 'Pre-warming vite deps cache (vite optimize --force)...');
|
|
161
|
-
try {
|
|
162
|
-
(0, node_child_process_1.execFileSync)('npx', ['-y', 'vite', 'optimize', '--force'], {
|
|
163
|
-
cwd: targetDir,
|
|
164
|
-
stdio: (0, output_1.isJsonMode)() ? ['ignore', 'ignore', 'inherit'] : 'inherit',
|
|
165
|
-
timeout: 120_000,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
catch (err) {
|
|
169
|
-
optimizeError = err instanceof Error ? err.message : String(err);
|
|
170
|
-
(0, logger_1.log)('migrate', `⚠ vite optimize prewarm failed (continuing): ${optimizeError}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
137
|
// 沙箱环境下重启 dev process —— scripts/dev.js 被 fullstack 版本覆盖了,但当前正在
|
|
174
138
|
// 跑的 node 进程仍按老逻辑工作(只起 vite,没起 nest),整体没切到 fullstack 模式。
|
|
175
139
|
// 通过 pkill 杀掉 dev.js 主进程,依赖沙箱平台 supervisor 自动重新 exec dev.sh → dev.js,
|
|
@@ -205,7 +169,6 @@ async function handleAppMigrate(opts) {
|
|
|
205
169
|
...summarizeResults(results),
|
|
206
170
|
followLatestPackages: followLatest,
|
|
207
171
|
installError,
|
|
208
|
-
optimizeError,
|
|
209
172
|
devRestarted,
|
|
210
173
|
nextActions: process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== ''
|
|
211
174
|
? [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_POLL_INTERVAL_MS = exports.waitForPipeline = exports.handleDeployErrorLog = exports.handleDeployHistory = exports.handleDeployGet = exports.handleDeploy = void 0;
|
|
3
|
+
exports.handleDeployPatch = exports.DEFAULT_POLL_INTERVAL_MS = exports.waitForPipeline = exports.handleDeployErrorLog = exports.handleDeployHistory = exports.handleDeployGet = exports.handleDeploy = void 0;
|
|
4
4
|
var deploy_1 = require("./deploy");
|
|
5
5
|
Object.defineProperty(exports, "handleDeploy", { enumerable: true, get: function () { return deploy_1.handleDeploy; } });
|
|
6
6
|
var get_1 = require("./get");
|
|
@@ -12,3 +12,5 @@ Object.defineProperty(exports, "handleDeployErrorLog", { enumerable: true, get:
|
|
|
12
12
|
var polling_1 = require("./polling");
|
|
13
13
|
Object.defineProperty(exports, "waitForPipeline", { enumerable: true, get: function () { return polling_1.waitForPipeline; } });
|
|
14
14
|
Object.defineProperty(exports, "DEFAULT_POLL_INTERVAL_MS", { enumerable: true, get: function () { return polling_1.DEFAULT_POLL_INTERVAL_MS; } });
|
|
15
|
+
var patch_1 = require("./patch");
|
|
16
|
+
Object.defineProperty(exports, "handleDeployPatch", { enumerable: true, get: function () { return patch_1.handleDeployPatch; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleDeployPatch = handleDeployPatch;
|
|
4
|
+
const index_1 = require("../../../services/deploy/modern/patch/index");
|
|
5
|
+
const output_1 = require("../../../utils/output");
|
|
6
|
+
/** miaoda deploy patch —— design-html 文件级增量发布 */
|
|
7
|
+
async function handleDeployPatch(opts) {
|
|
8
|
+
const result = await (0, index_1.patchDesignDeploy)({
|
|
9
|
+
appId: opts.appId,
|
|
10
|
+
projectDir: opts.dir ?? process.cwd(),
|
|
11
|
+
creates: opts.creates,
|
|
12
|
+
updates: opts.updates,
|
|
13
|
+
deletes: opts.deletes,
|
|
14
|
+
});
|
|
15
|
+
(0, output_1.emit)({ data: result });
|
|
16
|
+
}
|
|
@@ -15,6 +15,7 @@ exports.TEMPLATE_PACKAGE_BY_STACK = {
|
|
|
15
15
|
'vite-react': '@lark-apaas/coding-template-vite-react',
|
|
16
16
|
html: '@lark-apaas/coding-template-html',
|
|
17
17
|
'nestjs-react-fullstack': '@lark-apaas/coding-template-nestjs-react-fullstack',
|
|
18
|
+
'design-html': '@lark-apaas/coding-template-design-html',
|
|
18
19
|
};
|
|
19
20
|
/**
|
|
20
21
|
* 短名 → template 包钉版表。renderTemplate 默认按这张表取版本号,表外 stack 跟 npm latest。
|
|
@@ -0,0 +1,41 @@
|
|
|
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.buildDesignOutput = buildDesignOutput;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const error_1 = require("../../../../utils/error");
|
|
10
|
+
const logger_1 = require("../../../../utils/logger");
|
|
11
|
+
const constants_1 = require("../constants");
|
|
12
|
+
const source_scan_1 = require("../patch/source-scan");
|
|
13
|
+
const routes_1 = require("../patch/routes");
|
|
14
|
+
/**
|
|
15
|
+
* design-html buildless 闭环 build(替代模板 scripts/build.sh):
|
|
16
|
+
* 1. rm -rf <projectDir>/dist
|
|
17
|
+
* 2. 拷源码 → dist/output(套 EXCLUDES,与 routes 同一份 listSourceFiles)
|
|
18
|
+
* 3. 写 dist/output/routes.json = generateRoutes(projectDir)
|
|
19
|
+
* 不跑 npm run build、不依赖技术栈内 build.sh。
|
|
20
|
+
* fs / scan / routes 任何失败统一包成 AppError('DEPLOY_BUILD_FAILED'),与旧 runDesignBuild 对齐。
|
|
21
|
+
*/
|
|
22
|
+
function buildDesignOutput(opts) {
|
|
23
|
+
(0, logger_1.log)('deploy', 'Building (design, CLI)...');
|
|
24
|
+
try {
|
|
25
|
+
const distDir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR);
|
|
26
|
+
const outputDir = node_path_1.default.join(distDir, constants_1.OUTPUT_DIR);
|
|
27
|
+
node_fs_1.default.rmSync(distDir, { recursive: true, force: true });
|
|
28
|
+
node_fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
29
|
+
for (const rel of (0, source_scan_1.listSourceFiles)(opts.projectDir)) {
|
|
30
|
+
const dest = node_path_1.default.join(outputDir, rel);
|
|
31
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(dest), { recursive: true });
|
|
32
|
+
node_fs_1.default.copyFileSync(node_path_1.default.join(opts.projectDir, rel), dest);
|
|
33
|
+
}
|
|
34
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(outputDir, 'routes.json'), (0, routes_1.generateRoutes)(opts.projectDir), 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof error_1.AppError)
|
|
38
|
+
throw err;
|
|
39
|
+
throw new error_1.AppError('DEPLOY_BUILD_FAILED', `design build failed: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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.uploadDesignArtifacts = uploadDesignArtifacts;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const error_1 = require("../../../../utils/error");
|
|
10
|
+
const logger_1 = require("../../../../utils/logger");
|
|
11
|
+
const constants_1 = require("../constants");
|
|
12
|
+
const protocol_1 = require("../protocol");
|
|
13
|
+
const tosutil_1 = require("./tosutil");
|
|
14
|
+
/** 递归收集 dir 下相对路径(posix 分隔),与 -flat 上传后远端 key 对齐。 */
|
|
15
|
+
function listLocalRelKeys(dir) {
|
|
16
|
+
const out = new Set();
|
|
17
|
+
const walk = (cur, rel) => {
|
|
18
|
+
for (const entry of node_fs_1.default.readdirSync(cur, { withFileTypes: true })) {
|
|
19
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
20
|
+
if (entry.isDirectory())
|
|
21
|
+
walk(node_path_1.default.join(cur, entry.name), childRel);
|
|
22
|
+
else
|
|
23
|
+
out.add(childRel);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
walk(dir, '');
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* design 部署上传(省带宽):
|
|
31
|
+
* 1. 本地 dist/output ──cp(local→tos)──▶ output_backup_path/ (唯一一次上传)
|
|
32
|
+
* 2. output_backup_path ──cp(tos→tos,同凭证同桶)──▶ output_latest_path (服务端复制)
|
|
33
|
+
* 3. ls latest → 跟本地 diff → rm latest 里源端已无的对象 (精确镜像)
|
|
34
|
+
* design-html 只产 dist/output,无 output_resource / output_static / capability。
|
|
35
|
+
*/
|
|
36
|
+
async function uploadDesignArtifacts(opts) {
|
|
37
|
+
await Promise.resolve();
|
|
38
|
+
const tosutilPath = (0, tosutil_1.resolveTosutilPath)();
|
|
39
|
+
const outputDir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR, constants_1.OUTPUT_DIR);
|
|
40
|
+
if (!node_fs_1.default.existsSync(outputDir)) {
|
|
41
|
+
throw new error_1.AppError('DEPLOY_NO_BUILD_OUTPUT', `Required directory missing: ${constants_1.DIST_DIR}/${constants_1.OUTPUT_DIR}`);
|
|
42
|
+
}
|
|
43
|
+
// 空产物防呆:本地一个文件都没有时,绝不继续(否则 latest 会被整体 prune 清空)。
|
|
44
|
+
const localKeys = listLocalRelKeys(outputDir);
|
|
45
|
+
if (localKeys.size === 0) {
|
|
46
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `No files under ${constants_1.DIST_DIR}/${constants_1.OUTPUT_DIR} to upload — check build output.`);
|
|
47
|
+
}
|
|
48
|
+
const cred = (0, protocol_1.parseTosUploadCredential)((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_ALL_TOS_UPLOAD_CREDENTIAL), protocol_1.DataKey.OUTPUT_ALL_TOS_UPLOAD_CREDENTIAL);
|
|
49
|
+
const backupPath = (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_BACKUP_PATH);
|
|
50
|
+
const latestPath = (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_LATEST_PATH);
|
|
51
|
+
const base = (0, tosutil_1.tosutilUploadFromTos)(cred); // 鉴权 + bucket
|
|
52
|
+
// 1. 本地 → backup(version):唯一一次本地上传。
|
|
53
|
+
// -ddo(noDirObjects):不上传文件夹占位对象,避免 latest 残留 `src/` 这类空目录。
|
|
54
|
+
(0, logger_1.log)('deploy', `Uploading ${constants_1.OUTPUT_DIR} → ${backupPath}...`);
|
|
55
|
+
(0, tosutil_1.uploadDirWithCredential)(tosutilPath, outputDir, { ...base, prefix: backupPath }, 'output_backup', true);
|
|
56
|
+
// 2. backup → latest:服务端 tos→tos cp(不占带宽)
|
|
57
|
+
(0, logger_1.log)('deploy', `Server-side copy ${backupPath} → ${latestPath}...`);
|
|
58
|
+
(0, tosutil_1.copyTosToTos)(tosutilPath, base, backupPath, latestPath);
|
|
59
|
+
// 3. latest 精确镜像:删源端已不存在的对象。
|
|
60
|
+
// 安全阀:远端有对象但跟本地零重合时,判定为 key 形态/ls 解析不匹配,跳过 prune——
|
|
61
|
+
// 宁可漏删(latest 残留旧文件)也绝不误删整目录(数据丢失)。design-html 的 build.sh
|
|
62
|
+
// 必产稳定名的 routes.json / *.html,正常重合不会为空,零重合即异常信号。
|
|
63
|
+
const remoteKeys = (0, tosutil_1.listRemoteKeys)(tosutilPath, { ...base, prefix: latestPath });
|
|
64
|
+
const overlap = remoteKeys.filter((k) => localKeys.has(k));
|
|
65
|
+
if (remoteKeys.length > 0 && overlap.length === 0) {
|
|
66
|
+
(0, logger_1.log)('deploy', `⚠ latest 远端 ${String(remoteKeys.length)} 个对象与本地零重合,疑似 key 形态不匹配,跳过 prune(避免误删)`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const staleKeys = remoteKeys.filter((k) => !localKeys.has(k));
|
|
70
|
+
if (staleKeys.length > 0) {
|
|
71
|
+
(0, logger_1.log)('deploy', `Pruning ${String(staleKeys.length)} stale object(s) from latest...`);
|
|
72
|
+
(0, tosutil_1.removeRemoteKeys)(tosutilPath, { ...base, prefix: latestPath }, staleKeys);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.savePluginInstances = exports.LocalReleaseStatus = exports.finalizeLocalRelease = exports.createLocalRelease = exports.uploadArtifacts = exports.runBuild = exports.preRelease = exports.prepareDeployContext = void 0;
|
|
3
|
+
exports.savePluginInstances = exports.LocalReleaseStatus = exports.finalizeLocalRelease = exports.createLocalRelease = exports.uploadArtifacts = exports.uploadDesignArtifacts = exports.buildDesignOutput = exports.runBuild = exports.preRelease = exports.prepareDeployContext = void 0;
|
|
4
4
|
var context_1 = require("./context");
|
|
5
5
|
Object.defineProperty(exports, "prepareDeployContext", { enumerable: true, get: function () { return context_1.prepareDeployContext; } });
|
|
6
6
|
var pre_release_1 = require("./pre-release");
|
|
7
7
|
Object.defineProperty(exports, "preRelease", { enumerable: true, get: function () { return pre_release_1.preRelease; } });
|
|
8
8
|
var build_1 = require("./build");
|
|
9
9
|
Object.defineProperty(exports, "runBuild", { enumerable: true, get: function () { return build_1.runBuild; } });
|
|
10
|
+
var design_build_1 = require("./design-build");
|
|
11
|
+
Object.defineProperty(exports, "buildDesignOutput", { enumerable: true, get: function () { return design_build_1.buildDesignOutput; } });
|
|
12
|
+
var design_upload_1 = require("./design-upload");
|
|
13
|
+
Object.defineProperty(exports, "uploadDesignArtifacts", { enumerable: true, get: function () { return design_upload_1.uploadDesignArtifacts; } });
|
|
10
14
|
var upload_1 = require("./upload");
|
|
11
15
|
Object.defineProperty(exports, "uploadArtifacts", { enumerable: true, get: function () { return upload_1.uploadArtifacts; } });
|
|
12
16
|
var local_release_1 = require("./local-release");
|
|
@@ -0,0 +1,246 @@
|
|
|
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.resolveTosutilPath = resolveTosutilPath;
|
|
7
|
+
exports.tosutilUploadFromTos = tosutilUploadFromTos;
|
|
8
|
+
exports.writeTosutilConfig = writeTosutilConfig;
|
|
9
|
+
exports.streamTosutilOutput = streamTosutilOutput;
|
|
10
|
+
exports.normalizeTosDest = normalizeTosDest;
|
|
11
|
+
exports.uploadDirWithCredential = uploadDirWithCredential;
|
|
12
|
+
exports.copyTosToTos = copyTosToTos;
|
|
13
|
+
exports.listRemoteKeys = listRemoteKeys;
|
|
14
|
+
exports.removeRemoteKeys = removeRemoteKeys;
|
|
15
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
16
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const node_child_process_1 = require("node:child_process");
|
|
19
|
+
const error_1 = require("../../../../utils/error");
|
|
20
|
+
const logger_1 = require("../../../../utils/logger");
|
|
21
|
+
function resolveTosutilPath() {
|
|
22
|
+
try {
|
|
23
|
+
const resolved = (0, node_child_process_1.execFileSync)('which', ['tosutil'], { encoding: 'utf-8' }).trim();
|
|
24
|
+
if (resolved)
|
|
25
|
+
return resolved;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
/* fallthrough */
|
|
29
|
+
}
|
|
30
|
+
throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
|
|
31
|
+
}
|
|
32
|
+
function tosutilUploadFromTos(cred) {
|
|
33
|
+
return {
|
|
34
|
+
accessKeyID: cred.accessKeyID,
|
|
35
|
+
secretAccessKey: cred.secretAccessKey,
|
|
36
|
+
sessionToken: cred.sessionToken,
|
|
37
|
+
endpoint: cred.endpoint,
|
|
38
|
+
region: cred.region,
|
|
39
|
+
bucket: cred.bucket,
|
|
40
|
+
prefix: cred.prefix,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 写一个 tosutil 临时 config 文件,注入 STS 凭证 + endpoint + region。
|
|
45
|
+
* 返回 config 路径,调用方负责清理。
|
|
46
|
+
*/
|
|
47
|
+
function writeTosutilConfig(tosutilPath, cred, label) {
|
|
48
|
+
const confPath = node_path_1.default.join(node_os_1.default.tmpdir(), `.tosutilconfig-miaoda-${label}-${String(process.pid)}-${String(Date.now())}`);
|
|
49
|
+
node_fs_1.default.writeFileSync(confPath, '');
|
|
50
|
+
(0, node_child_process_1.execFileSync)(tosutilPath, [
|
|
51
|
+
'config',
|
|
52
|
+
'-conf',
|
|
53
|
+
confPath,
|
|
54
|
+
'-e',
|
|
55
|
+
cred.endpoint,
|
|
56
|
+
'-i',
|
|
57
|
+
cred.accessKeyID,
|
|
58
|
+
'-k',
|
|
59
|
+
cred.secretAccessKey,
|
|
60
|
+
'-t',
|
|
61
|
+
cred.sessionToken,
|
|
62
|
+
'-re',
|
|
63
|
+
cred.region,
|
|
64
|
+
], { stdio: 'pipe' });
|
|
65
|
+
return confPath;
|
|
66
|
+
}
|
|
67
|
+
/** 把 tosutil 进程的输出按行回放到 stderr,便于线上排查(沙箱里无法 attach 进程)。 */
|
|
68
|
+
function streamTosutilOutput(output) {
|
|
69
|
+
if (!output)
|
|
70
|
+
return;
|
|
71
|
+
for (const line of output.split('\n')) {
|
|
72
|
+
if (line.length === 0)
|
|
73
|
+
continue;
|
|
74
|
+
process.stderr.write(`[tosutil] ${line}\n`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function normalizeTosDest(bucket, prefix) {
|
|
78
|
+
const bucketPart = bucket.startsWith('tos://') ? bucket : `tos://${bucket}`;
|
|
79
|
+
const normalized = prefix.replace(/^\/+/, '');
|
|
80
|
+
return `${bucketPart.replace(/\/+$/, '')}/${normalized}`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
|
|
84
|
+
*/
|
|
85
|
+
function uploadDirWithCredential(tosutilPath, sourceDir, cred, label,
|
|
86
|
+
/**
|
|
87
|
+
* 传 true 时加 tosutil `-ddo`:上传时不把文件夹本身作为单独对象上传,
|
|
88
|
+
* 避免在 TOS 留下 `src/` 这类零字节目录占位对象(删空目录下最后一个文件后“文件夹”仍残留)。
|
|
89
|
+
* 仅 design 链路传 true;client/nrf 上传保持原行为(不传)。
|
|
90
|
+
*/
|
|
91
|
+
noDirObjects = false) {
|
|
92
|
+
const entries = node_fs_1.default.readdirSync(sourceDir);
|
|
93
|
+
if (entries.length === 0) {
|
|
94
|
+
(0, logger_1.debug)(`upload: skip empty dir ${sourceDir}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const dest = normalizeTosDest(cred.bucket, cred.prefix);
|
|
98
|
+
(0, logger_1.debug)(`upload: ${sourceDir} -> ${dest} (${String(entries.length)} entries)`);
|
|
99
|
+
const confPath = writeTosutilConfig(tosutilPath, cred, label);
|
|
100
|
+
let output;
|
|
101
|
+
try {
|
|
102
|
+
output = (0, node_child_process_1.execFileSync)(tosutilPath, [
|
|
103
|
+
'cp',
|
|
104
|
+
sourceDir,
|
|
105
|
+
dest,
|
|
106
|
+
'-r',
|
|
107
|
+
'-flat',
|
|
108
|
+
'-j',
|
|
109
|
+
'5',
|
|
110
|
+
'-p',
|
|
111
|
+
'3',
|
|
112
|
+
'-ps',
|
|
113
|
+
'10485760',
|
|
114
|
+
'-f',
|
|
115
|
+
...(noDirObjects ? ['-ddo'] : []),
|
|
116
|
+
'-conf',
|
|
117
|
+
confPath,
|
|
118
|
+
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
119
|
+
streamTosutilOutput(output);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const e = err;
|
|
123
|
+
const stderr = (e.stderr ?? '').trim();
|
|
124
|
+
const stdout = (e.stdout ?? '').trim();
|
|
125
|
+
streamTosutilOutput(stdout);
|
|
126
|
+
streamTosutilOutput(stderr);
|
|
127
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp exited with status ${String(e.status ?? -1)} for ${sourceDir}\n` +
|
|
128
|
+
` stderr: ${stderr || '(empty)'}\n` +
|
|
129
|
+
` stdout: ${stdout.slice(0, 500) || '(empty)'}`);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
try {
|
|
133
|
+
node_fs_1.default.unlinkSync(confPath);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* ignore */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const succeedMatch = /Succeed count is:\s*(\d+)/.exec(output);
|
|
140
|
+
const failedMatch = /Failed count is:\s*(\d+)/.exec(output);
|
|
141
|
+
const succeedCount = succeedMatch ? Number.parseInt(succeedMatch[1], 10) : 0;
|
|
142
|
+
const failedCount = failedMatch ? Number.parseInt(failedMatch[1], 10) : 0;
|
|
143
|
+
if (failedCount > 0) {
|
|
144
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `Upload failed: ${String(failedCount)} files failed, ${String(succeedCount)} succeeded. source=${sourceDir}`);
|
|
145
|
+
}
|
|
146
|
+
if (succeedCount === 0) {
|
|
147
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** version→latest 同桶同凭证服务端复制:tosutil cp tos://src tos://dst -r -flat -f */
|
|
151
|
+
function copyTosToTos(tosutilPath, cred, srcPrefix, dstPrefix) {
|
|
152
|
+
const src = normalizeTosDest(cred.bucket, srcPrefix);
|
|
153
|
+
const dst = normalizeTosDest(cred.bucket, dstPrefix);
|
|
154
|
+
const confPath = writeTosutilConfig(tosutilPath, cred, 'tos2tos');
|
|
155
|
+
try {
|
|
156
|
+
const out = (0, node_child_process_1.execFileSync)(tosutilPath, ['cp', src, dst, '-r', '-flat', '-f', '-j', '5', '-conf', confPath], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
157
|
+
streamTosutilOutput(out);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const e = err;
|
|
161
|
+
streamTosutilOutput((e.stdout ?? '').trim());
|
|
162
|
+
streamTosutilOutput((e.stderr ?? '').trim());
|
|
163
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp(tos→tos) exited status ${String(e.status ?? -1)}: ${src} → ${dst}`);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
try {
|
|
167
|
+
node_fs_1.default.unlinkSync(confPath);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
/* ignore */
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** 列 tos://bucket/prefix 下所有对象,返回相对 prefix 的 key 集合(用 ls -s 精简格式,ls 默认递归)。 */
|
|
175
|
+
function listRemoteKeys(tosutilPath, cred) {
|
|
176
|
+
const base = normalizeTosDest(cred.bucket, cred.prefix); // tos://bucket/prefix
|
|
177
|
+
const confPath = writeTosutilConfig(tosutilPath, cred, 'ls');
|
|
178
|
+
let out;
|
|
179
|
+
try {
|
|
180
|
+
// 注意:tosutil ls 没有 -r(默认就递归,-d 才是只列一层),别套 cp/rm 的 -r。
|
|
181
|
+
out = (0, node_child_process_1.execFileSync)(tosutilPath, ['ls', base, '-s', '-conf', confPath], {
|
|
182
|
+
encoding: 'utf-8',
|
|
183
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
const e = err;
|
|
188
|
+
// tosutil 把参数类错误打到 stdout,故 stderr/stdout 都带上,避免错误信息为空难排查。
|
|
189
|
+
const detail = [(e.stderr ?? '').trim(), (e.stdout ?? '').trim()].filter(Boolean).join(' | ');
|
|
190
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil ls exited status ${String(e.status ?? -1)} for ${base}: ${detail || '(no output)'}`);
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
try {
|
|
194
|
+
node_fs_1.default.unlinkSync(confPath);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
/* ignore */
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const prefixUri = base.replace(/\/+$/, '') + '/';
|
|
201
|
+
const keys = [];
|
|
202
|
+
for (const line of out.split('\n')) {
|
|
203
|
+
const t = line.trim();
|
|
204
|
+
if (!t.startsWith(prefixUri))
|
|
205
|
+
continue;
|
|
206
|
+
const rel = t.slice(prefixUri.length);
|
|
207
|
+
// 含以 / 结尾的目录占位对象(如 `src/`):本地文件集永远不含此类 key,
|
|
208
|
+
// 故它们必落入镜像 prune 的“stale”集合被删除——清掉历史 -ddo 之前留下的空目录残留。
|
|
209
|
+
if (rel.length > 0)
|
|
210
|
+
keys.push(rel);
|
|
211
|
+
}
|
|
212
|
+
return keys;
|
|
213
|
+
}
|
|
214
|
+
/** 删除 tos://bucket/prefix 下指定相对 key(逐个 rm -f)。 */
|
|
215
|
+
function removeRemoteKeys(tosutilPath, cred, relKeys) {
|
|
216
|
+
if (relKeys.length === 0)
|
|
217
|
+
return;
|
|
218
|
+
const confPath = writeTosutilConfig(tosutilPath, cred, 'rm');
|
|
219
|
+
const prefix = cred.prefix.replace(/\/+$/, '');
|
|
220
|
+
const failed = [];
|
|
221
|
+
try {
|
|
222
|
+
for (const rel of relKeys) {
|
|
223
|
+
// 用与 cp/ls 同一份 normalizeTosDest 拼目标 URI,避免删除路径与其它操作漂移。
|
|
224
|
+
const dest = normalizeTosDest(cred.bucket, `${prefix}/${rel}`);
|
|
225
|
+
try {
|
|
226
|
+
(0, node_child_process_1.execFileSync)(tosutilPath, ['rm', dest, '-f', '-conf', confPath], {
|
|
227
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
failed.push(rel);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
try {
|
|
237
|
+
node_fs_1.default.unlinkSync(confPath);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
/* ignore */
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (failed.length > 0) {
|
|
244
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `prune latest failed for: ${failed.join(', ')}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -5,14 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.uploadArtifacts = uploadArtifacts;
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
9
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
const node_child_process_1 = require("node:child_process");
|
|
11
9
|
const index_1 = require("../../../../api/deploy/index");
|
|
12
10
|
const error_1 = require("../../../../utils/error");
|
|
13
11
|
const logger_1 = require("../../../../utils/logger");
|
|
14
12
|
const constants_1 = require("../constants");
|
|
15
13
|
const protocol_1 = require("../protocol");
|
|
14
|
+
const tosutil_1 = require("./tosutil");
|
|
16
15
|
const UPLOAD_PLAN = [
|
|
17
16
|
{
|
|
18
17
|
kind: 'tos',
|
|
@@ -32,28 +31,6 @@ const UPLOAD_PLAN = [
|
|
|
32
31
|
dataKey: protocol_1.DataKey.OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL,
|
|
33
32
|
},
|
|
34
33
|
];
|
|
35
|
-
function resolveTosutilPath() {
|
|
36
|
-
try {
|
|
37
|
-
const resolved = (0, node_child_process_1.execFileSync)('which', ['tosutil'], { encoding: 'utf-8' }).trim();
|
|
38
|
-
if (resolved)
|
|
39
|
-
return resolved;
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
/* fallthrough */
|
|
43
|
-
}
|
|
44
|
-
throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
|
|
45
|
-
}
|
|
46
|
-
function tosutilUploadFromTos(cred) {
|
|
47
|
-
return {
|
|
48
|
-
accessKeyID: cred.accessKeyID,
|
|
49
|
-
secretAccessKey: cred.secretAccessKey,
|
|
50
|
-
sessionToken: cred.sessionToken,
|
|
51
|
-
endpoint: cred.endpoint,
|
|
52
|
-
region: cred.region,
|
|
53
|
-
bucket: cred.bucket,
|
|
54
|
-
prefix: cred.prefix,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
34
|
function tosutilUploadFromPaasStorage(cred, dataKey) {
|
|
58
35
|
const { bucket, prefix } = (0, protocol_1.parseTosUploadPrefix)(cred.uploadPrefix, dataKey);
|
|
59
36
|
return {
|
|
@@ -66,106 +43,6 @@ function tosutilUploadFromPaasStorage(cred, dataKey) {
|
|
|
66
43
|
prefix,
|
|
67
44
|
};
|
|
68
45
|
}
|
|
69
|
-
/**
|
|
70
|
-
* 写一个 tosutil 临时 config 文件,注入 STS 凭证 + endpoint + region。
|
|
71
|
-
* 返回 config 路径,调用方负责清理。
|
|
72
|
-
*/
|
|
73
|
-
function writeTosutilConfig(tosutilPath, cred, label) {
|
|
74
|
-
const confPath = node_path_1.default.join(node_os_1.default.tmpdir(), `.tosutilconfig-miaoda-${label}-${String(process.pid)}-${String(Date.now())}`);
|
|
75
|
-
node_fs_1.default.writeFileSync(confPath, '');
|
|
76
|
-
(0, node_child_process_1.execFileSync)(tosutilPath, [
|
|
77
|
-
'config',
|
|
78
|
-
'-conf',
|
|
79
|
-
confPath,
|
|
80
|
-
'-e',
|
|
81
|
-
cred.endpoint,
|
|
82
|
-
'-i',
|
|
83
|
-
cred.accessKeyID,
|
|
84
|
-
'-k',
|
|
85
|
-
cred.secretAccessKey,
|
|
86
|
-
'-t',
|
|
87
|
-
cred.sessionToken,
|
|
88
|
-
'-re',
|
|
89
|
-
cred.region,
|
|
90
|
-
], { stdio: 'pipe' });
|
|
91
|
-
return confPath;
|
|
92
|
-
}
|
|
93
|
-
/** 把 tosutil 进程的输出按行回放到 stderr,便于线上排查(沙箱里无法 attach 进程)。 */
|
|
94
|
-
function streamTosutilOutput(output) {
|
|
95
|
-
if (!output)
|
|
96
|
-
return;
|
|
97
|
-
for (const line of output.split('\n')) {
|
|
98
|
-
if (line.length === 0)
|
|
99
|
-
continue;
|
|
100
|
-
process.stderr.write(`[tosutil] ${line}\n`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function normalizeTosDest(bucket, prefix) {
|
|
104
|
-
const bucketPart = bucket.startsWith('tos://') ? bucket : `tos://${bucket}`;
|
|
105
|
-
const normalized = prefix.replace(/^\/+/, '');
|
|
106
|
-
return `${bucketPart.replace(/\/+$/, '')}/${normalized}`;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
|
|
110
|
-
*/
|
|
111
|
-
function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
|
|
112
|
-
const entries = node_fs_1.default.readdirSync(sourceDir);
|
|
113
|
-
if (entries.length === 0) {
|
|
114
|
-
(0, logger_1.debug)(`upload: skip empty dir ${sourceDir}`);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const dest = normalizeTosDest(cred.bucket, cred.prefix);
|
|
118
|
-
(0, logger_1.debug)(`upload: ${sourceDir} -> ${dest} (${String(entries.length)} entries)`);
|
|
119
|
-
const confPath = writeTosutilConfig(tosutilPath, cred, label);
|
|
120
|
-
let output;
|
|
121
|
-
try {
|
|
122
|
-
output = (0, node_child_process_1.execFileSync)(tosutilPath, [
|
|
123
|
-
'cp',
|
|
124
|
-
sourceDir,
|
|
125
|
-
dest,
|
|
126
|
-
'-r',
|
|
127
|
-
'-flat',
|
|
128
|
-
'-j',
|
|
129
|
-
'5',
|
|
130
|
-
'-p',
|
|
131
|
-
'3',
|
|
132
|
-
'-ps',
|
|
133
|
-
'10485760',
|
|
134
|
-
'-f',
|
|
135
|
-
'-conf',
|
|
136
|
-
confPath,
|
|
137
|
-
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
138
|
-
streamTosutilOutput(output);
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
const e = err;
|
|
142
|
-
const stderr = (e.stderr ?? '').trim();
|
|
143
|
-
const stdout = (e.stdout ?? '').trim();
|
|
144
|
-
streamTosutilOutput(stdout);
|
|
145
|
-
streamTosutilOutput(stderr);
|
|
146
|
-
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp exited with status ${String(e.status ?? -1)} for ${sourceDir}\n` +
|
|
147
|
-
` stderr: ${stderr || '(empty)'}\n` +
|
|
148
|
-
` stdout: ${stdout.slice(0, 500) || '(empty)'}`);
|
|
149
|
-
}
|
|
150
|
-
finally {
|
|
151
|
-
try {
|
|
152
|
-
node_fs_1.default.unlinkSync(confPath);
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
/* ignore */
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const succeedMatch = /Succeed count is:\s*(\d+)/.exec(output);
|
|
159
|
-
const failedMatch = /Failed count is:\s*(\d+)/.exec(output);
|
|
160
|
-
const succeedCount = succeedMatch ? Number.parseInt(succeedMatch[1], 10) : 0;
|
|
161
|
-
const failedCount = failedMatch ? Number.parseInt(failedMatch[1], 10) : 0;
|
|
162
|
-
if (failedCount > 0) {
|
|
163
|
-
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `Upload failed: ${String(failedCount)} files failed, ${String(succeedCount)} succeeded. source=${sourceDir}`);
|
|
164
|
-
}
|
|
165
|
-
if (succeedCount === 0) {
|
|
166
|
-
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
46
|
function uploadTosTarget(tosutilPath, target, localPath, data) {
|
|
170
47
|
const credRaw = target.required
|
|
171
48
|
? (0, protocol_1.requireDataKey)(data, target.dataKey)
|
|
@@ -176,7 +53,7 @@ function uploadTosTarget(tosutilPath, target, localPath, data) {
|
|
|
176
53
|
}
|
|
177
54
|
const cred = (0, protocol_1.parseTosUploadCredential)(credRaw, target.dataKey);
|
|
178
55
|
(0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
|
|
179
|
-
uploadDirWithCredential(tosutilPath, localPath, tosutilUploadFromTos(cred), target.dir);
|
|
56
|
+
(0, tosutil_1.uploadDirWithCredential)(tosutilPath, localPath, (0, tosutil_1.tosutilUploadFromTos)(cred), target.dir);
|
|
180
57
|
return true;
|
|
181
58
|
}
|
|
182
59
|
function uploadPaasStorageTarget(tosutilPath, target, localPath, data) {
|
|
@@ -188,7 +65,7 @@ function uploadPaasStorageTarget(tosutilPath, target, localPath, data) {
|
|
|
188
65
|
}
|
|
189
66
|
const cred = (0, protocol_1.parsePaasStorageCredential)((0, protocol_1.requireDataKey)(data, target.dataKey), target.dataKey);
|
|
190
67
|
(0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
|
|
191
|
-
uploadDirWithCredential(tosutilPath, localPath, tosutilUploadFromPaasStorage(cred, target.dataKey), target.dir);
|
|
68
|
+
(0, tosutil_1.uploadDirWithCredential)(tosutilPath, localPath, tosutilUploadFromPaasStorage(cred, target.dataKey), target.dir);
|
|
192
69
|
return cred;
|
|
193
70
|
}
|
|
194
71
|
/**
|
|
@@ -202,7 +79,7 @@ function uploadPaasStorageTarget(tosutilPath, target, localPath, data) {
|
|
|
202
79
|
* 目录不存在的可选项跳过;output 不存在或凭证缺失抛错。
|
|
203
80
|
*/
|
|
204
81
|
async function uploadArtifacts(opts) {
|
|
205
|
-
const tosutilPath = resolveTosutilPath();
|
|
82
|
+
const tosutilPath = (0, tosutil_1.resolveTosutilPath)();
|
|
206
83
|
const distDir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR);
|
|
207
84
|
const outcome = { uploaded: 0 };
|
|
208
85
|
for (const target of UPLOAD_PLAN) {
|
|
@@ -0,0 +1,60 @@
|
|
|
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.buildTosActions = buildTosActions;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const error_1 = require("../../../../utils/error");
|
|
10
|
+
const index_1 = require("../../../../api/deploy/index");
|
|
11
|
+
const content_1 = require("./content");
|
|
12
|
+
const routes_1 = require("./routes");
|
|
13
|
+
/** 校验相对路径:禁绝对路径、禁含 ".." 段;normalize 成 posix 相对。 */
|
|
14
|
+
function normalizeRel(rel) {
|
|
15
|
+
if (node_path_1.default.isAbsolute(rel) || rel.startsWith('/')) {
|
|
16
|
+
throw new error_1.AppError('ARGS_INVALID', `文件路径不能是绝对路径:${rel}`);
|
|
17
|
+
}
|
|
18
|
+
const posix = rel.split(node_path_1.default.sep).join('/').replace(/^\.\//, '');
|
|
19
|
+
if (posix.split('/').includes('..')) {
|
|
20
|
+
throw new error_1.AppError('ARGS_INVALID', `文件路径不能包含 "..":${rel}`);
|
|
21
|
+
}
|
|
22
|
+
return posix;
|
|
23
|
+
}
|
|
24
|
+
function readAction(projectDir, rel, actionType) {
|
|
25
|
+
const filePath = normalizeRel(rel);
|
|
26
|
+
const abs = node_path_1.default.join(projectDir, filePath);
|
|
27
|
+
if (!node_fs_1.default.existsSync(abs) || !node_fs_1.default.statSync(abs).isFile()) {
|
|
28
|
+
throw new error_1.AppError('ARGS_INVALID', `文件不存在:${filePath}`);
|
|
29
|
+
}
|
|
30
|
+
const { content, base64Content } = (0, content_1.encodeContent)(node_fs_1.default.readFileSync(abs));
|
|
31
|
+
return { actionType, filePath, content, base64Content };
|
|
32
|
+
}
|
|
33
|
+
function isHtml(rel) {
|
|
34
|
+
return rel.toLowerCase().endsWith('.html');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 把 changeset 组成 TosFileAction[]:
|
|
38
|
+
* - create/update 读本地文件、自动编码;delete 仅 FilePath。
|
|
39
|
+
* - 任一改动是 .html → 追加 routes.json(TS 重算)的 UPDATE action。
|
|
40
|
+
*/
|
|
41
|
+
function buildTosActions(projectDir, cs) {
|
|
42
|
+
const actions = [];
|
|
43
|
+
for (const rel of cs.creates)
|
|
44
|
+
actions.push(readAction(projectDir, rel, index_1.TosActionType.CREATE));
|
|
45
|
+
for (const rel of cs.updates)
|
|
46
|
+
actions.push(readAction(projectDir, rel, index_1.TosActionType.UPDATE));
|
|
47
|
+
for (const rel of cs.deletes) {
|
|
48
|
+
actions.push({ actionType: index_1.TosActionType.DELETE, filePath: normalizeRel(rel) });
|
|
49
|
+
}
|
|
50
|
+
const touchesHtml = [...cs.creates, ...cs.updates, ...cs.deletes].some(isHtml);
|
|
51
|
+
if (touchesHtml) {
|
|
52
|
+
actions.push({
|
|
53
|
+
actionType: index_1.TosActionType.UPDATE,
|
|
54
|
+
filePath: 'routes.json',
|
|
55
|
+
content: (0, routes_1.generateRoutes)(projectDir),
|
|
56
|
+
base64Content: false,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return actions;
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encodeContent = encodeContent;
|
|
4
|
+
/**
|
|
5
|
+
* 自动判别文件内容编码:
|
|
6
|
+
* - 含 0x00 字节 → 二进制 → base64
|
|
7
|
+
* - 否则 round-trip 校验 UTF-8:能无损 decode→encode → 文本直传;否则 base64
|
|
8
|
+
*/
|
|
9
|
+
function encodeContent(buf) {
|
|
10
|
+
if (buf.includes(0x00)) {
|
|
11
|
+
return { content: buf.toString('base64'), base64Content: true };
|
|
12
|
+
}
|
|
13
|
+
const text = buf.toString('utf8');
|
|
14
|
+
if (Buffer.from(text, 'utf8').equals(buf)) {
|
|
15
|
+
return { content: text, base64Content: false };
|
|
16
|
+
}
|
|
17
|
+
return { content: buf.toString('base64'), base64Content: true };
|
|
18
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTosActions = void 0;
|
|
4
|
+
exports.patchDesignDeploy = patchDesignDeploy;
|
|
5
|
+
const spark_meta_1 = require("../../../../utils/spark-meta");
|
|
6
|
+
const error_1 = require("../../../../utils/error");
|
|
7
|
+
const logger_1 = require("../../../../utils/logger");
|
|
8
|
+
const index_1 = require("../../../../api/deploy/index");
|
|
9
|
+
const template_key_map_1 = require("../template-key-map");
|
|
10
|
+
const actions_1 = require("./actions");
|
|
11
|
+
var actions_2 = require("./actions");
|
|
12
|
+
Object.defineProperty(exports, "buildTosActions", { enumerable: true, get: function () { return actions_2.buildTosActions; } });
|
|
13
|
+
/**
|
|
14
|
+
* design-html 增量发布:读本地文件组 TosFileAction(含 .html 时带 routes.json),
|
|
15
|
+
* 调后端 applyTosDiff 应用到 latest。scope 限 design_local_deploy。
|
|
16
|
+
*/
|
|
17
|
+
async function patchDesignDeploy(opts) {
|
|
18
|
+
if (opts.creates.length + opts.updates.length + opts.deletes.length === 0) {
|
|
19
|
+
throw new error_1.AppError('ARGS_INVALID', '至少指定一个 --create / --update / --delete');
|
|
20
|
+
}
|
|
21
|
+
const stack = (0, spark_meta_1.readSparkMeta)(opts.projectDir).stack ?? '';
|
|
22
|
+
const templateKey = (0, template_key_map_1.resolveTemplateKey)(stack);
|
|
23
|
+
if (templateKey !== template_key_map_1.DESIGN_LOCAL_DEPLOY) {
|
|
24
|
+
throw new error_1.AppError('ARGS_INVALID', `deploy patch 仅支持 design-html(design_local_deploy),当前 stack=${stack || '(空)'}`);
|
|
25
|
+
}
|
|
26
|
+
const actions = (0, actions_1.buildTosActions)(opts.projectDir, {
|
|
27
|
+
creates: opts.creates,
|
|
28
|
+
updates: opts.updates,
|
|
29
|
+
deletes: opts.deletes,
|
|
30
|
+
});
|
|
31
|
+
const routesRegenerated = actions.some((a) => a.filePath === 'routes.json');
|
|
32
|
+
(0, logger_1.log)('deploy', `Applying ${String(actions.length)} file action(s)...`);
|
|
33
|
+
const res = await (0, index_1.applyTosDiff)({ appID: opts.appId, templateKey, actions });
|
|
34
|
+
(0, logger_1.log)('deploy', `Patched: upsert=${String(res.upsertCount)} delete=${String(res.deleteCount)}`);
|
|
35
|
+
return {
|
|
36
|
+
upsertCount: res.upsertCount,
|
|
37
|
+
deleteCount: res.deleteCount,
|
|
38
|
+
actionsSent: actions.length,
|
|
39
|
+
routesRegenerated,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRoutes = generateRoutes;
|
|
4
|
+
const source_scan_1 = require("./source-scan");
|
|
5
|
+
/** 与 build.sh 一致的 .html → route path 推导。rel 为 posix 相对路径。 */
|
|
6
|
+
function toRoute(rel, base) {
|
|
7
|
+
let r = '/' + rel;
|
|
8
|
+
r = r.replace(/\.html$/i, '');
|
|
9
|
+
r = r.replace(/(^|\/)index$/, '$1');
|
|
10
|
+
if (r.length > 1)
|
|
11
|
+
r = r.replace(/\/$/, '');
|
|
12
|
+
const full = base + r;
|
|
13
|
+
return full === '' ? '/' : full;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 扫描 projectDir 下所有 .html(套 source-scan 的 EXCLUDES),生成与 build.sh 同款 routes.json
|
|
17
|
+
* 文本(含尾换行)。CLIENT_BASE_PATH 取自 process.env(平台预置),去尾斜杠后作前缀。
|
|
18
|
+
*/
|
|
19
|
+
function generateRoutes(projectDir) {
|
|
20
|
+
const base = (process.env.CLIENT_BASE_PATH ?? '').replace(/\/+$/, '');
|
|
21
|
+
const htmls = (0, source_scan_1.listSourceFiles)(projectDir).filter((f) => f.toLowerCase().endsWith('.html'));
|
|
22
|
+
const routes = [...new Set(htmls.map((rel) => toRoute(rel, base)))]
|
|
23
|
+
.sort()
|
|
24
|
+
.map((p) => ({ path: p }));
|
|
25
|
+
if (routes.length === 0)
|
|
26
|
+
routes.push({ path: base ? `${base}/` : '/' });
|
|
27
|
+
return JSON.stringify(routes, null, 2) + '\n';
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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.EXCLUDES = void 0;
|
|
7
|
+
exports.listSourceFiles = listSourceFiles;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
// 与模板 scripts/build.sh 的 rsync EXCLUDES 对齐:这些名字(任意层级 basename)不进产物/不计入路由。
|
|
11
|
+
exports.EXCLUDES = new Set([
|
|
12
|
+
'.git',
|
|
13
|
+
'node_modules',
|
|
14
|
+
'dist',
|
|
15
|
+
'scripts',
|
|
16
|
+
'package.json',
|
|
17
|
+
'package-lock.json',
|
|
18
|
+
'pnpm-lock.yaml',
|
|
19
|
+
'yarn.lock',
|
|
20
|
+
'.gitignore',
|
|
21
|
+
'.npmrc',
|
|
22
|
+
'.agent',
|
|
23
|
+
'.env',
|
|
24
|
+
'.env.local',
|
|
25
|
+
'README.md',
|
|
26
|
+
'.DS_Store',
|
|
27
|
+
'.spark',
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* 递归列出 root 下所有文件的相对 posix 路径,跳过 EXCLUDES(任意层级 basename),
|
|
31
|
+
* 用 statSync 判定目录/文件以 follow 符号链接(含指向目录的软链),断链等无法 stat 的条目跳过。
|
|
32
|
+
*/
|
|
33
|
+
function listSourceFiles(root) {
|
|
34
|
+
const out = [];
|
|
35
|
+
const walk = (dir, rel) => {
|
|
36
|
+
for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
+
if (exports.EXCLUDES.has(entry.name))
|
|
38
|
+
continue;
|
|
39
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
40
|
+
const abs = node_path_1.default.join(dir, entry.name);
|
|
41
|
+
let isDir;
|
|
42
|
+
try {
|
|
43
|
+
isDir = node_fs_1.default.statSync(abs).isDirectory();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (isDir)
|
|
49
|
+
walk(abs, childRel);
|
|
50
|
+
else
|
|
51
|
+
out.push(childRel);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
walk(root, '');
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.designLocalPublishPipeline = designLocalPublishPipeline;
|
|
4
|
+
const spark_meta_1 = require("../../../../utils/spark-meta");
|
|
5
|
+
const error_1 = require("../../../../utils/error");
|
|
6
|
+
const logger_1 = require("../../../../utils/logger");
|
|
7
|
+
const index_1 = require("../atoms/index");
|
|
8
|
+
/**
|
|
9
|
+
* design(design_local_deploy)本地构建 + 本地部署 pipeline。
|
|
10
|
+
*
|
|
11
|
+
* 与 client 版差异:build 走 buildDesignOutput(CLI 闭环,不跑 npm run build)、upload 走 uploadDesignArtifacts(version+latest
|
|
12
|
+
* 双写、latest 精确镜像)、不注册插件能力(design-html 无 capability)。其余复用同一套 atom。
|
|
13
|
+
*/
|
|
14
|
+
async function designLocalPublishPipeline(opts) {
|
|
15
|
+
const ctx = (0, index_1.prepareDeployContext)(opts.projectDir, opts.appId);
|
|
16
|
+
(0, logger_1.log)('deploy', 'Pre-releasing...');
|
|
17
|
+
const pre = await (0, index_1.preRelease)(ctx.appId, ctx.templateKey);
|
|
18
|
+
(0, logger_1.log)('deploy', `pre_release_id=${pre.preReleaseID} version=${pre.version}`);
|
|
19
|
+
if (pre.data === undefined) {
|
|
20
|
+
throw new error_1.AppError('DEPLOY_PRE_RELEASE_DATA_MISSING', 'preRelease did not return a `data` map — backend tcc may not be configured for current template_key');
|
|
21
|
+
}
|
|
22
|
+
const data = pre.data;
|
|
23
|
+
if (!opts.skipBuild) {
|
|
24
|
+
(0, index_1.buildDesignOutput)({ projectDir: ctx.projectDir });
|
|
25
|
+
}
|
|
26
|
+
(0, logger_1.log)('deploy', 'Creating local release...');
|
|
27
|
+
const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version);
|
|
28
|
+
try {
|
|
29
|
+
await (0, index_1.uploadDesignArtifacts)({ projectDir: ctx.projectDir, data });
|
|
30
|
+
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Finished);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Failed);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
if (release.onlineUrl !== undefined && release.onlineUrl !== '') {
|
|
37
|
+
(0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { appUrl: release.onlineUrl });
|
|
38
|
+
}
|
|
39
|
+
(0, logger_1.log)('deploy', 'Deployed successfully');
|
|
40
|
+
return {
|
|
41
|
+
appId: ctx.appId,
|
|
42
|
+
version: pre.version,
|
|
43
|
+
url: release.onlineUrl ?? '',
|
|
44
|
+
releaseID: release.releaseID,
|
|
45
|
+
preReleaseID: pre.preReleaseID,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.localBuildLocalPublishPipeline = void 0;
|
|
3
|
+
exports.designLocalPublishPipeline = exports.localBuildLocalPublishPipeline = void 0;
|
|
4
4
|
var local_1 = require("./local");
|
|
5
5
|
Object.defineProperty(exports, "localBuildLocalPublishPipeline", { enumerable: true, get: function () { return local_1.localBuildLocalPublishPipeline; } });
|
|
6
|
+
var design_local_1 = require("./design-local");
|
|
7
|
+
Object.defineProperty(exports, "designLocalPublishPipeline", { enumerable: true, get: function () { return design_local_1.designLocalPublishPipeline; } });
|
|
@@ -33,6 +33,13 @@ exports.DataKey = {
|
|
|
33
33
|
// downloadURLPrefix 替代原 static_cdn_prefix 注入 build env。
|
|
34
34
|
/** dist/output_static 上传凭证(仅当目录存在时使用) */
|
|
35
35
|
OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL: 'output_static_paas_storage_credential',
|
|
36
|
+
// ── design 部署专用(design_local_deploy) ──
|
|
37
|
+
/** 覆盖整个 {app_id} 文件夹的上传凭证(形态同 TosUploadCredential) */
|
|
38
|
+
OUTPUT_ALL_TOS_UPLOAD_CREDENTIAL: 'output_all_tos_upload_credential',
|
|
39
|
+
/** version/备份份真实路径(后端已替换 {app_id}/{version}) */
|
|
40
|
+
OUTPUT_BACKUP_PATH: 'output_backup_path',
|
|
41
|
+
/** latest 份真实路径(后端已替换 {app_id}/latest) */
|
|
42
|
+
OUTPUT_LATEST_PATH: 'output_latest_path',
|
|
36
43
|
};
|
|
37
44
|
/** 取必传 key,缺失抛 AppError(带 key 名,便于后端 tcc 配置排查) */
|
|
38
45
|
function requireDataKey(data, key) {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// modern deploy 主入口(dispatch 层)。
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// 按 templateKey 选 pipeline:design_local_deploy → design pipeline,其余 → client pipeline。
|
|
5
|
+
// 仅为路由轻量解析 templateKey(读 meta.stack + 映射),不跑前置检查——前置检查在各
|
|
6
|
+
// pipeline 内部的 prepareDeployContext 里做。client 分支与原行为完全一致。
|
|
7
|
+
// pipeline 之间彼此独立、不共享中间状态。
|
|
7
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
9
|
exports.runModernDeploy = runModernDeploy;
|
|
10
|
+
const spark_meta_1 = require("../../../utils/spark-meta");
|
|
11
|
+
const template_key_map_1 = require("./template-key-map");
|
|
9
12
|
const index_1 = require("./pipelines/index");
|
|
10
13
|
async function runModernDeploy(opts) {
|
|
11
|
-
|
|
14
|
+
const stack = (0, spark_meta_1.readSparkMeta)(opts.projectDir).stack ?? '';
|
|
15
|
+
if ((0, template_key_map_1.resolveTemplateKey)(stack) === template_key_map_1.DESIGN_LOCAL_DEPLOY) {
|
|
16
|
+
return (0, index_1.designLocalPublishPipeline)(opts);
|
|
17
|
+
}
|
|
12
18
|
return (0, index_1.localBuildLocalPublishPipeline)(opts);
|
|
13
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DESIGN_LOCAL_DEPLOY = void 0;
|
|
3
4
|
exports.resolveTemplateKey = resolveTemplateKey;
|
|
4
5
|
/**
|
|
5
6
|
* 本地 stack 短名 → 后端发布模板 key 的映射。
|
|
@@ -10,9 +11,12 @@ exports.resolveTemplateKey = resolveTemplateKey;
|
|
|
10
11
|
* 未来出现需要走别的 templateKey 的 stack,再在表里加一行覆盖默认。
|
|
11
12
|
*/
|
|
12
13
|
const DEFAULT_TEMPLATE_KEY = 'client_local_deploy';
|
|
14
|
+
/** design 部署 templateKey(run.ts 据此分流到 design pipeline) */
|
|
15
|
+
exports.DESIGN_LOCAL_DEPLOY = 'design_local_deploy';
|
|
13
16
|
const TEMPLATE_KEY_MAP = {
|
|
14
17
|
'vite-react': DEFAULT_TEMPLATE_KEY,
|
|
15
18
|
html: DEFAULT_TEMPLATE_KEY,
|
|
19
|
+
'design-html': exports.DESIGN_LOCAL_DEPLOY,
|
|
16
20
|
};
|
|
17
21
|
/**
|
|
18
22
|
* 把本地 stack 短名映射到后端 templateKey;未配置的 stack 走默认 client_local_deploy。
|