@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.
@@ -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,6 @@
1
+ "use strict";
2
+ // plugin_instances batch_save 接口的请求 / 响应类型。
3
+ //
4
+ // 与 modern deploy 其它接口走的 lark.apaas.devops PSM 不同,
5
+ // 本接口走 lark.apaas.studio_server,响应信封形态也不一样(BaseResp 而非 status_code)。
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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. finalizeLocalRelease(Success|Failed)
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
- * MCLAW_APP_ID / MCLAW_VERSION / MCLAW_STACK
11
- * MCLAW_RESOURCE_CDN_PREFIX / MCLAW_STATIC_CDN_PREFIX
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
- MCLAW_APP_ID: opts.appId,
19
- MCLAW_VERSION: opts.version,
20
- MCLAW_STACK: opts.templateKey,
21
- MCLAW_RESOURCE_CDN_PREFIX: (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.RESOURCE_CDN_PREFIX),
22
- MCLAW_STATIC_CDN_PREFIX: (0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.STATIC_CDN_PREFIX),
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
- * 后端按 appID 找当前 active release,CLI 无需透传 releaseID。
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.OUTPUT_TOS_UPLOAD_URL, required: true },
16
- { dir: constants_1.OUTPUT_RESOURCE_DIR, dataKey: protocol_1.DataKey.OUTPUT_RESOURCE_TOS_UPLOAD_URL, required: false },
17
- { dir: constants_1.OUTPUT_STATIC_DIR, dataKey: protocol_1.DataKey.OUTPUT_STATIC_TOS_UPLOAD_URL, required: false },
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 把目录批量上传到 pre-signed URL。
32
- * 服务端返回的 URL 已带签名凭证,CLI 直接透传给 tosutil,不解析、不拆字段。
97
+ * 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
33
98
  */
34
- function uploadDirToSignedUrl(tosutilPath, sourceDir, signedUrl) {
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
- (0, logger_1.debug)(`upload: ${sourceDir} -> ${signedUrl} (${String(entries.length)} entries)`);
41
- const output = (0, node_child_process_1.execFileSync)(tosutilPath, ['cp', sourceDir, signedUrl, '-r', '-flat', '-j', '5', '-p', '3', '-ps', '10485760', '-f'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
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 output and pre-signed URL validity.`);
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
- * 上传三类产物到 preRelease 下发的对应 pre-signed URL。
55
- * - dist/output (必传) DataKey.OUTPUT_TOS_UPLOAD_URL
56
- * - dist/output_resource (可选) → DataKey.OUTPUT_RESOURCE_TOS_UPLOAD_URL
57
- * - dist/output_static (可选) → DataKey.OUTPUT_STATIC_TOS_UPLOAD_URL
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 不存在或 url 缺失抛错。
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 signedUrl = target.required
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 (signedUrl === undefined) {
79
- // 目录有但后端没下发 URL 静默跳过(后端语义:这个产物不归该模板管)
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
- uploadDirToSignedUrl(tosutilPath, localPath, signedUrl);
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 — 校验项目 + 读 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. finalizeLocalRelease Success / Failed 翻终态
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 的前提下进行;upload 失败时也能精准把
21
- * 该发布单标记为 Failed,避免遗留挂态。
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 上传 URL(与三个产物目录一一对应) ──
21
- /** dist/output 上传地址(必传) */
22
- OUTPUT_TOS_UPLOAD_URL: 'output_tos_upload_url',
23
- /** dist/output_resource 上传地址(仅当目录存在时使用) */
24
- OUTPUT_RESOURCE_TOS_UPLOAD_URL: 'output_resource_tos_upload_url',
25
- /** dist/output_static 上传地址(仅当目录存在时使用) */
26
- OUTPUT_STATIC_TOS_UPLOAD_URL: 'output_static_tos_upload_url',
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.4-alpha.9153b4c",
3
+ "version": "0.1.4-alpha.abaa17f",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {