@lark-apaas/miaoda-cli 0.1.4-alpha.9153b4c → 0.1.4-alpha.abaa17f
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/plugin-instances-types.js +6 -0
- package/dist/api/deploy/plugin-instances.js +30 -0
- package/dist/cli/commands/deploy/modern.js +2 -1
- package/dist/services/deploy/modern/atoms/build.js +26 -7
- package/dist/services/deploy/modern/atoms/index.js +3 -1
- package/dist/services/deploy/modern/atoms/local-release.js +2 -4
- package/dist/services/deploy/modern/atoms/save-plugin-instances.js +72 -0
- package/dist/services/deploy/modern/atoms/upload.js +121 -19
- package/dist/services/deploy/modern/constants.js +3 -1
- package/dist/services/deploy/modern/pipelines/local.js +17 -8
- package/dist/services/deploy/modern/protocol.js +10 -7
- 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.LocalReleaseStatus = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = 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.LocalReleaseStatus = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = 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; } });
|
|
@@ -15,6 +15,8 @@ Object.defineProperty(exports, "getModernReleaseStatus", { enumerable: true, get
|
|
|
15
15
|
Object.defineProperty(exports, "getModernLastPublishedVersion", { enumerable: true, get: function () { return modern_1.getLastPublishedVersion; } });
|
|
16
16
|
var modern_types_1 = require("./modern-types");
|
|
17
17
|
Object.defineProperty(exports, "LocalReleaseStatus", { enumerable: true, get: function () { return modern_types_1.LocalReleaseStatus; } });
|
|
18
|
+
var plugin_instances_1 = require("./plugin-instances");
|
|
19
|
+
Object.defineProperty(exports, "batchSavePluginInstances", { enumerable: true, get: function () { return plugin_instances_1.batchSavePluginInstances; } });
|
|
18
20
|
var schemas_1 = require("./schemas");
|
|
19
21
|
Object.defineProperty(exports, "deployHistorySchema", { enumerable: true, get: function () { return schemas_1.deployHistorySchema; } });
|
|
20
22
|
Object.defineProperty(exports, "deployGetSchema", { enumerable: true, get: function () { return schemas_1.deployGetSchema; } });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// studio_server 的 plugin_instances 批量保存接口。
|
|
3
|
+
// 与 modern deploy 其余接口(走 devops PSM、InnerEnvelope)信封不同,
|
|
4
|
+
// 这里走 studio_server PSM、BaseResp 信封,故自行解析。
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.batchSavePluginInstances = batchSavePluginInstances;
|
|
7
|
+
const http_1 = require("../../utils/http");
|
|
8
|
+
const error_1 = require("../../utils/error");
|
|
9
|
+
const URL_PATH = '/api/v1/studio/innerapi/plugin_instances/batch_save';
|
|
10
|
+
/**
|
|
11
|
+
* POST /api/v1/studio/innerapi/plugin_instances/batch_save
|
|
12
|
+
*
|
|
13
|
+
* 把发布版本的 capability 集合注册到 studio_server。
|
|
14
|
+
* 失败时抛 AppError("DEPLOY_PLUGIN_BATCH_SAVE_FAILED"),带 BaseResp 错误信息。
|
|
15
|
+
*/
|
|
16
|
+
async function batchSavePluginInstances(req) {
|
|
17
|
+
const response = await (0, http_1.getHttpClient)().post(URL_PATH, req);
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new error_1.HttpError(response.status, URL_PATH, `Failed to save plugin instances: ${String(response.status)} ${response.statusText}`);
|
|
20
|
+
}
|
|
21
|
+
const body = (await response.json());
|
|
22
|
+
const baseResp = body.BaseResp;
|
|
23
|
+
if (baseResp?.StatusCode !== 0) {
|
|
24
|
+
const msg = baseResp?.StatusMessage ?? baseResp?.KStatusMessage ?? 'unknown error';
|
|
25
|
+
const code = baseResp?.KStatusCode !== undefined && baseResp.KStatusCode !== ''
|
|
26
|
+
? baseResp.KStatusCode
|
|
27
|
+
: String(baseResp?.StatusCode ?? 'unknown');
|
|
28
|
+
throw new error_1.AppError('DEPLOY_PLUGIN_BATCH_SAVE_FAILED', `Failed to save plugin instances: status_code=${code} message=${msg}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -28,7 +28,8 @@ function registerDeployCommandsModern(program) {
|
|
|
28
28
|
流程概览(stderr 进度提示)
|
|
29
29
|
1. prepareContext 2. preRelease 3. build
|
|
30
30
|
4. createLocalRelease 5. uploadArtifacts
|
|
31
|
-
6.
|
|
31
|
+
6. savePluginInstances(扫描 dist/output_capabilities/*.json 批量注册)
|
|
32
|
+
7. finalizeLocalRelease(Finished|Failed)
|
|
32
33
|
|
|
33
34
|
JSON 输出(stdout)
|
|
34
35
|
{"data": {"appId": "...", "version": <n>, "url": "...", "releaseID": "...", "preReleaseID": "..."}}
|
|
@@ -5,21 +5,40 @@ const node_child_process_1 = require("node:child_process");
|
|
|
5
5
|
const error_1 = require("../../../../utils/error");
|
|
6
6
|
const logger_1 = require("../../../../utils/logger");
|
|
7
7
|
const protocol_1 = require("../protocol");
|
|
8
|
+
/**
|
|
9
|
+
* 给 CDN 前缀补 https:// 协议头:
|
|
10
|
+
* - 已带 http:// 或 https:// → 原样返回
|
|
11
|
+
* - 以 / 开头(绝对路径)→ 原样返回
|
|
12
|
+
* - 不含 dot(看起来不像域名,比如纯路径 app_xxx/2)→ 原样返回
|
|
13
|
+
* - 其它(典型:lf-xxx.bytetos.com/...)→ 前缀 https://
|
|
14
|
+
*
|
|
15
|
+
* template 的 build script 直接拿来当 vite base / asset URL 用,必须是合法 URL。
|
|
16
|
+
*/
|
|
17
|
+
function ensureHttpsScheme(v) {
|
|
18
|
+
if (/^https?:\/\//i.test(v))
|
|
19
|
+
return v;
|
|
20
|
+
if (v.startsWith('/'))
|
|
21
|
+
return v;
|
|
22
|
+
if (!v.includes('.'))
|
|
23
|
+
return v;
|
|
24
|
+
return `https://${v}`;
|
|
25
|
+
}
|
|
8
26
|
/**
|
|
9
27
|
* 跑 `npm run build`,把 preRelease 下发的动态配置注入 env:
|
|
10
|
-
*
|
|
11
|
-
*
|
|
28
|
+
* MIAODA_APP_ID / MIAODA_VERSION / MIAODA_STACK
|
|
29
|
+
* MIAODA_RESOURCE_CDN_PREFIX / MIAODA_STATIC_CDN_PREFIX
|
|
12
30
|
*
|
|
31
|
+
* CDN 前缀缺 scheme 时由 CLI 自动补 https://,template 不用关心。
|
|
13
32
|
* build 失败抛 AppError(execSync 自身会 throw,捕获后包一层加错误码)。
|
|
14
33
|
*/
|
|
15
34
|
function runBuild(opts) {
|
|
16
35
|
const buildEnv = {
|
|
17
36
|
...process.env,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
MIAODA_APP_ID: opts.appId,
|
|
38
|
+
MIAODA_VERSION: opts.version,
|
|
39
|
+
MIAODA_STACK: opts.templateKey,
|
|
40
|
+
MIAODA_RESOURCE_CDN_PREFIX: ensureHttpsScheme((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.RESOURCE_CDN_PREFIX)),
|
|
41
|
+
MIAODA_STATIC_CDN_PREFIX: ensureHttpsScheme((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.STATIC_CDN_PREFIX)),
|
|
23
42
|
};
|
|
24
43
|
(0, logger_1.log)('deploy', 'Building...');
|
|
25
44
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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.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");
|
|
@@ -13,3 +13,5 @@ var local_release_1 = require("./local-release");
|
|
|
13
13
|
Object.defineProperty(exports, "createLocalRelease", { enumerable: true, get: function () { return local_release_1.createLocalRelease; } });
|
|
14
14
|
Object.defineProperty(exports, "finalizeLocalRelease", { enumerable: true, get: function () { return local_release_1.finalizeLocalRelease; } });
|
|
15
15
|
Object.defineProperty(exports, "LocalReleaseStatus", { enumerable: true, get: function () { return local_release_1.LocalReleaseStatus; } });
|
|
16
|
+
var save_plugin_instances_1 = require("./save-plugin-instances");
|
|
17
|
+
Object.defineProperty(exports, "savePluginInstances", { enumerable: true, get: function () { return save_plugin_instances_1.savePluginInstances; } });
|
|
@@ -15,13 +15,11 @@ async function createLocalRelease(appId, version) {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* 把本地发布单翻为终态。Finished / Failed 由 pipeline 视上下游结果决定。
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* 失败不抛(避免覆盖上游真错误),但记一行 stderr 让排查可定位。releaseId 仅用于日志。
|
|
18
|
+
* 失败不抛(避免覆盖上游真错误),但记一行 stderr 让排查可定位。
|
|
21
19
|
*/
|
|
22
20
|
async function finalizeLocalRelease(appId, releaseId, status) {
|
|
23
21
|
try {
|
|
24
|
-
await (0, index_1.updateLocalRelease)({ appID: appId, status });
|
|
22
|
+
await (0, index_1.updateLocalRelease)({ appID: appId, releaseID: releaseId, status });
|
|
25
23
|
}
|
|
26
24
|
catch (err) {
|
|
27
25
|
(0, logger_1.log)('deploy', `finalize release(${releaseId}, status=${String(status)}) failed: ${err.message}`);
|
|
@@ -0,0 +1,72 @@
|
|
|
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.savePluginInstances = savePluginInstances;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const index_1 = require("../../../../api/deploy/index");
|
|
10
|
+
const logger_1 = require("../../../../utils/logger");
|
|
11
|
+
const constants_1 = require("../constants");
|
|
12
|
+
function isValidCapability(o) {
|
|
13
|
+
if (typeof o !== 'object' || o === null)
|
|
14
|
+
return false;
|
|
15
|
+
const id = o.id;
|
|
16
|
+
return typeof id === 'string' && id.length > 0;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 扫描 dist/output_capabilities/*.json,过滤出合法 capability,批量上报到 studio_server。
|
|
20
|
+
*
|
|
21
|
+
* 合法判定:能 JSON.parse + 顶层 id 是非空字符串。其它字段后端校验。
|
|
22
|
+
* 目录不存在 / 没有合法 capability → 直接返回 0,pipeline 当作 no-op。
|
|
23
|
+
* batchSave 调用失败 → 透传抛 AppError,pipeline 兜底 finalizeLocalRelease(Failed)。
|
|
24
|
+
*/
|
|
25
|
+
async function savePluginInstances(opts) {
|
|
26
|
+
const dir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR, constants_1.OUTPUT_CAPABILITIES_DIR);
|
|
27
|
+
if (!node_fs_1.default.existsSync(dir)) {
|
|
28
|
+
(0, logger_1.debug)(`save-plugin-instances: ${dir} not present, skipping`);
|
|
29
|
+
return { saved: 0, skipped: 0 };
|
|
30
|
+
}
|
|
31
|
+
const files = node_fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
32
|
+
const instances = [];
|
|
33
|
+
let skipped = 0;
|
|
34
|
+
for (const f of files) {
|
|
35
|
+
const full = node_path_1.default.join(dir, f);
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
raw = node_fs_1.default.readFileSync(full, 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
(0, logger_1.debug)(`save-plugin-instances: read ${f} failed: ${err.message}`);
|
|
42
|
+
skipped++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
(0, logger_1.debug)(`save-plugin-instances: ${f} is not valid JSON, skipping`);
|
|
51
|
+
skipped++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!isValidCapability(parsed)) {
|
|
55
|
+
(0, logger_1.debug)(`save-plugin-instances: ${f} missing 'id' string, skipping`);
|
|
56
|
+
skipped++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
instances.push({ id: parsed.id, content: JSON.stringify(parsed) });
|
|
60
|
+
}
|
|
61
|
+
if (instances.length === 0) {
|
|
62
|
+
(0, logger_1.log)('deploy', `No capability to register (${String(files.length)} files scanned, all skipped)`);
|
|
63
|
+
return { saved: 0, skipped };
|
|
64
|
+
}
|
|
65
|
+
(0, logger_1.log)('deploy', `Registering ${String(instances.length)} capability instance(s)...`);
|
|
66
|
+
await (0, index_1.batchSavePluginInstances)({
|
|
67
|
+
appID: opts.appId,
|
|
68
|
+
appVersion: opts.version,
|
|
69
|
+
pluginInstances: instances,
|
|
70
|
+
});
|
|
71
|
+
return { saved: instances.length, skipped };
|
|
72
|
+
}
|
|
@@ -5,6 +5,7 @@ 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"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const node_child_process_1 = require("node:child_process");
|
|
10
11
|
const error_1 = require("../../../../utils/error");
|
|
@@ -12,9 +13,17 @@ const logger_1 = require("../../../../utils/logger");
|
|
|
12
13
|
const constants_1 = require("../constants");
|
|
13
14
|
const protocol_1 = require("../protocol");
|
|
14
15
|
const UPLOAD_PLAN = [
|
|
15
|
-
{ dir: constants_1.OUTPUT_DIR, dataKey: protocol_1.DataKey.
|
|
16
|
-
{
|
|
17
|
-
|
|
16
|
+
{ dir: constants_1.OUTPUT_DIR, dataKey: protocol_1.DataKey.OUTPUT_TOS_UPLOAD_CREDENTIAL, required: true },
|
|
17
|
+
{
|
|
18
|
+
dir: constants_1.OUTPUT_RESOURCE_DIR,
|
|
19
|
+
dataKey: protocol_1.DataKey.OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL,
|
|
20
|
+
required: false,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
dir: constants_1.OUTPUT_STATIC_DIR,
|
|
24
|
+
dataKey: protocol_1.DataKey.OUTPUT_STATIC_TOS_UPLOAD_CREDENTIAL,
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
18
27
|
];
|
|
19
28
|
function resolveTosutilPath() {
|
|
20
29
|
try {
|
|
@@ -27,18 +36,110 @@ function resolveTosutilPath() {
|
|
|
27
36
|
}
|
|
28
37
|
throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
|
|
29
38
|
}
|
|
39
|
+
/** 把 data 里的凭证字段 JSON.parse 出来;非法 JSON / 缺字段都抛 AppError */
|
|
40
|
+
function parseCredential(raw, dataKey) {
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new error_1.AppError('DEPLOY_CRED_INVALID_JSON', `preRelease.data["${dataKey}"] is not valid JSON: ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
49
|
+
throw new error_1.AppError('DEPLOY_CRED_INVALID_JSON', `preRelease.data["${dataKey}"] is not an object`);
|
|
50
|
+
}
|
|
51
|
+
const c = parsed;
|
|
52
|
+
const required = [
|
|
53
|
+
'accessKeyID',
|
|
54
|
+
'secretAccessKey',
|
|
55
|
+
'sessionToken',
|
|
56
|
+
'endpoint',
|
|
57
|
+
'region',
|
|
58
|
+
'bucket',
|
|
59
|
+
'prefix',
|
|
60
|
+
];
|
|
61
|
+
const missing = required.filter((k) => typeof c[k] !== 'string' || c[k] === '');
|
|
62
|
+
if (missing.length > 0) {
|
|
63
|
+
throw new error_1.AppError('DEPLOY_CRED_INCOMPLETE', `preRelease.data["${dataKey}"] credential missing fields: ${missing.join(', ')}`);
|
|
64
|
+
}
|
|
65
|
+
return c;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 写一个 tosutil 临时 config 文件,注入 STS 凭证 + endpoint + region。
|
|
69
|
+
* 返回 config 路径,调用方负责清理。
|
|
70
|
+
*/
|
|
71
|
+
function writeTosutilConfig(tosutilPath, cred, label) {
|
|
72
|
+
const confPath = node_path_1.default.join(node_os_1.default.tmpdir(), `.tosutilconfig-miaoda-${label}-${String(process.pid)}-${String(Date.now())}`);
|
|
73
|
+
node_fs_1.default.writeFileSync(confPath, '');
|
|
74
|
+
(0, node_child_process_1.execFileSync)(tosutilPath, [
|
|
75
|
+
'config',
|
|
76
|
+
'-conf',
|
|
77
|
+
confPath,
|
|
78
|
+
'-e',
|
|
79
|
+
cred.endpoint,
|
|
80
|
+
'-i',
|
|
81
|
+
cred.accessKeyID,
|
|
82
|
+
'-k',
|
|
83
|
+
cred.secretAccessKey,
|
|
84
|
+
'-t',
|
|
85
|
+
cred.sessionToken,
|
|
86
|
+
'-re',
|
|
87
|
+
cred.region,
|
|
88
|
+
], { stdio: 'pipe' });
|
|
89
|
+
return confPath;
|
|
90
|
+
}
|
|
91
|
+
function normalizeTosDest(bucket, prefix) {
|
|
92
|
+
const bucketPart = bucket.startsWith('tos://') ? bucket : `tos://${bucket}`;
|
|
93
|
+
const normalized = prefix.replace(/^\/+/, '');
|
|
94
|
+
return `${bucketPart.replace(/\/+$/, '')}/${normalized}`;
|
|
95
|
+
}
|
|
30
96
|
/**
|
|
31
|
-
* 用 tosutil 把目录批量上传到
|
|
32
|
-
* 服务端返回的 URL 已带签名凭证,CLI 直接透传给 tosutil,不解析、不拆字段。
|
|
97
|
+
* 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
|
|
33
98
|
*/
|
|
34
|
-
function
|
|
99
|
+
function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
|
|
35
100
|
const entries = node_fs_1.default.readdirSync(sourceDir);
|
|
36
101
|
if (entries.length === 0) {
|
|
37
102
|
(0, logger_1.debug)(`upload: skip empty dir ${sourceDir}`);
|
|
38
103
|
return;
|
|
39
104
|
}
|
|
40
|
-
|
|
41
|
-
|
|
105
|
+
const dest = normalizeTosDest(cred.bucket, cred.prefix);
|
|
106
|
+
(0, logger_1.debug)(`upload: ${sourceDir} -> ${dest} (${String(entries.length)} entries)`);
|
|
107
|
+
const confPath = writeTosutilConfig(tosutilPath, cred, label);
|
|
108
|
+
let output;
|
|
109
|
+
try {
|
|
110
|
+
output = (0, node_child_process_1.execFileSync)(tosutilPath, [
|
|
111
|
+
'cp',
|
|
112
|
+
sourceDir,
|
|
113
|
+
dest,
|
|
114
|
+
'-r',
|
|
115
|
+
'-flat',
|
|
116
|
+
'-j',
|
|
117
|
+
'5',
|
|
118
|
+
'-p',
|
|
119
|
+
'3',
|
|
120
|
+
'-ps',
|
|
121
|
+
'10485760',
|
|
122
|
+
'-f',
|
|
123
|
+
'-conf',
|
|
124
|
+
confPath,
|
|
125
|
+
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
const e = err;
|
|
129
|
+
const stderr = (e.stderr ?? '').trim();
|
|
130
|
+
const stdout = (e.stdout ?? '').trim();
|
|
131
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp exited with status ${String(e.status ?? -1)} for ${sourceDir}\n` +
|
|
132
|
+
` stderr: ${stderr || '(empty)'}\n` +
|
|
133
|
+
` stdout: ${stdout.slice(0, 500) || '(empty)'}`);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
try {
|
|
137
|
+
node_fs_1.default.unlinkSync(confPath);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
/* ignore */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
42
143
|
const succeedMatch = /Succeed count is:\s*(\d+)/.exec(output);
|
|
43
144
|
const failedMatch = /Failed count is:\s*(\d+)/.exec(output);
|
|
44
145
|
const succeedCount = succeedMatch ? Number.parseInt(succeedMatch[1], 10) : 0;
|
|
@@ -47,16 +148,17 @@ function uploadDirToSignedUrl(tosutilPath, sourceDir, signedUrl) {
|
|
|
47
148
|
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `Upload failed: ${String(failedCount)} files failed, ${String(succeedCount)} succeeded. source=${sourceDir}`);
|
|
48
149
|
}
|
|
49
150
|
if (succeedCount === 0) {
|
|
50
|
-
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil
|
|
151
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
|
|
51
152
|
}
|
|
52
153
|
}
|
|
53
154
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* - dist/
|
|
57
|
-
* - dist/
|
|
155
|
+
* 上传三类产物。每类的凭证来自 preRelease.data 中对应 *_tos_upload_credential 字段
|
|
156
|
+
* (JSON.stringify 的 TosUploadCredential 对象):
|
|
157
|
+
* - dist/output (必传) → OUTPUT_TOS_UPLOAD_CREDENTIAL
|
|
158
|
+
* - dist/output_resource (可选) → OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL
|
|
159
|
+
* - dist/output_static (可选) → OUTPUT_STATIC_TOS_UPLOAD_CREDENTIAL
|
|
58
160
|
*
|
|
59
|
-
* 目录不存在的可选项跳过;output
|
|
161
|
+
* 目录不存在的可选项跳过;output 不存在或凭证缺失抛错。
|
|
60
162
|
*/
|
|
61
163
|
function uploadArtifacts(opts) {
|
|
62
164
|
const tosutilPath = resolveTosutilPath();
|
|
@@ -72,16 +174,16 @@ function uploadArtifacts(opts) {
|
|
|
72
174
|
(0, logger_1.debug)(`upload: dir not present, skipping ${target.dir}`);
|
|
73
175
|
continue;
|
|
74
176
|
}
|
|
75
|
-
const
|
|
177
|
+
const credRaw = target.required
|
|
76
178
|
? (0, protocol_1.requireDataKey)(opts.data, target.dataKey)
|
|
77
179
|
: (0, protocol_1.optionalDataKey)(opts.data, target.dataKey);
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
(0, logger_1.debug)(`upload: ${target.dir} exists but no upload URL configured, skipping`);
|
|
180
|
+
if (credRaw === undefined) {
|
|
181
|
+
(0, logger_1.debug)(`upload: ${target.dir} exists but no credential configured, skipping`);
|
|
81
182
|
continue;
|
|
82
183
|
}
|
|
184
|
+
const cred = parseCredential(credRaw, target.dataKey);
|
|
83
185
|
(0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
|
|
84
|
-
|
|
186
|
+
uploadDirWithCredential(tosutilPath, localPath, cred, target.dir);
|
|
85
187
|
uploaded++;
|
|
86
188
|
}
|
|
87
189
|
if (uploaded === 0) {
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
// (旧版的 STACK_CONFIGS / archType / FaaS 相关常量在新接口契约下不再需要——
|
|
4
4
|
// 发布模板差异完全由后端 preRelease.data 下发,CLI 不维护 stack 配置。)
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.OUTPUT_STATIC_DIR = exports.OUTPUT_RESOURCE_DIR = exports.OUTPUT_DIR = exports.DIST_DIR = void 0;
|
|
6
|
+
exports.OUTPUT_CAPABILITIES_DIR = exports.OUTPUT_STATIC_DIR = exports.OUTPUT_RESOURCE_DIR = exports.OUTPUT_DIR = exports.DIST_DIR = void 0;
|
|
7
7
|
/** 构建产物固定目录约定 */
|
|
8
8
|
exports.DIST_DIR = 'dist';
|
|
9
9
|
exports.OUTPUT_DIR = 'output';
|
|
10
10
|
exports.OUTPUT_RESOURCE_DIR = 'output_resource';
|
|
11
11
|
exports.OUTPUT_STATIC_DIR = 'output_static';
|
|
12
|
+
/** 发布插件能力声明目录:含若干 capability.json 文件,发布时批量注册到 plugin_instances */
|
|
13
|
+
exports.OUTPUT_CAPABILITIES_DIR = 'output_capabilities';
|
|
@@ -9,16 +9,17 @@ const index_1 = require("../atoms/index");
|
|
|
9
9
|
* 本地构建 + 本地部署 pipeline。
|
|
10
10
|
*
|
|
11
11
|
* 步骤顺序:
|
|
12
|
-
* 1. prepareDeployContext
|
|
13
|
-
* 2. preRelease
|
|
14
|
-
* 3. runBuild
|
|
15
|
-
* 4. createLocalRelease
|
|
16
|
-
* 5. uploadArtifacts
|
|
17
|
-
* 6.
|
|
12
|
+
* 1. prepareDeployContext — 校验项目 + 读 meta
|
|
13
|
+
* 2. preRelease — 签发 pre_release_id / version / 动态配置 data
|
|
14
|
+
* 3. runBuild — 本地 npm run build(注入 data 中的 CDN 前缀)
|
|
15
|
+
* 4. createLocalRelease — 创建发布单(加锁、拿 releaseID)
|
|
16
|
+
* 5. uploadArtifacts — 把产物推到 preRelease 下发的 pre-signed URL
|
|
17
|
+
* 6. savePluginInstances — 扫描 dist/output_capabilities/*.json,批量注册插件能力
|
|
18
|
+
* 7. finalizeLocalRelease — Finished / Failed 翻终态
|
|
18
19
|
*
|
|
19
20
|
* createLocalRelease 提前到 upload 之前:发布单先建出来,
|
|
20
|
-
* upload 在已知 releaseID 的前提下进行;
|
|
21
|
-
*
|
|
21
|
+
* upload / savePluginInstances 在已知 releaseID 的前提下进行;
|
|
22
|
+
* 任一步失败都会精准把该发布单标记为 Failed,避免遗留挂态。
|
|
22
23
|
*/
|
|
23
24
|
async function localBuildLocalPublishPipeline(opts) {
|
|
24
25
|
const ctx = (0, index_1.prepareDeployContext)(opts.projectDir);
|
|
@@ -46,6 +47,14 @@ async function localBuildLocalPublishPipeline(opts) {
|
|
|
46
47
|
try {
|
|
47
48
|
(0, logger_1.log)('deploy', 'Uploading artifacts...');
|
|
48
49
|
(0, index_1.uploadArtifacts)({ projectDir: ctx.projectDir, data });
|
|
50
|
+
const saveResult = await (0, index_1.savePluginInstances)({
|
|
51
|
+
projectDir: ctx.projectDir,
|
|
52
|
+
appId: ctx.appId,
|
|
53
|
+
version: pre.version,
|
|
54
|
+
});
|
|
55
|
+
if (saveResult.saved > 0) {
|
|
56
|
+
(0, logger_1.log)('deploy', `${String(saveResult.saved)} capability instance(s) registered`);
|
|
57
|
+
}
|
|
49
58
|
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Finished);
|
|
50
59
|
}
|
|
51
60
|
catch (err) {
|
|
@@ -17,13 +17,16 @@ exports.DataKey = {
|
|
|
17
17
|
RESOURCE_CDN_PREFIX: 'resource_cdn_prefix',
|
|
18
18
|
/** dist/output_static 对应的 CDN 域名前缀 */
|
|
19
19
|
STATIC_CDN_PREFIX: 'static_cdn_prefix',
|
|
20
|
-
// ── TOS
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
// ── TOS 上传凭证(与三个产物目录一一对应,value 为 JSON.stringify 的凭证对象) ──
|
|
21
|
+
// 凭证对象形态(camelCase):
|
|
22
|
+
// { accessKeyID, secretAccessKey, sessionToken, endpoint, region, bucket, prefix }
|
|
23
|
+
// CLI 端 JSON.parse 后写 tosutil 临时 config,cp 到 tos://bucket/prefix。
|
|
24
|
+
/** dist/output 上传凭证(必传) */
|
|
25
|
+
OUTPUT_TOS_UPLOAD_CREDENTIAL: 'output_tos_upload_credential',
|
|
26
|
+
/** dist/output_resource 上传凭证(仅当目录存在时使用) */
|
|
27
|
+
OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL: 'output_resource_tos_upload_credential',
|
|
28
|
+
/** dist/output_static 上传凭证(仅当目录存在时使用) */
|
|
29
|
+
OUTPUT_STATIC_TOS_UPLOAD_CREDENTIAL: 'output_static_tos_upload_credential',
|
|
27
30
|
};
|
|
28
31
|
/** 取必传 key,缺失抛 AppError(带 key 名,便于后端 tcc 配置排查) */
|
|
29
32
|
function requireDataKey(data, key) {
|