@lark-apaas/miaoda-cli 0.1.4-alpha.abaa17f → 0.1.4-alpha.b2fdfff
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api/deploy/index.js +2 -1
- package/dist/api/deploy/modern.js +8 -0
- package/dist/api/deploy/plugin-instances-types.js +2 -2
- package/dist/api/deploy/plugin-instances.js +9 -17
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/skills/index.js +63 -0
- package/dist/cli/handlers/app/init.js +5 -7
- package/dist/cli/handlers/skills/index.js +7 -0
- package/dist/cli/handlers/skills/status.js +31 -0
- package/dist/cli/handlers/skills/sync.js +38 -0
- package/dist/services/app/init/index.js +1 -3
- package/dist/services/app/init/template.js +8 -6
- package/dist/services/deploy/modern/atoms/build.js +8 -3
- package/dist/services/deploy/modern/atoms/context.js +5 -5
- package/dist/services/deploy/modern/atoms/upload.js +103 -49
- package/dist/services/deploy/modern/check.js +2 -2
- package/dist/services/deploy/modern/pipelines/local.js +4 -4
- package/dist/services/deploy/modern/protocol.js +83 -4
- package/dist/services/deploy/modern/template-key-map.js +8 -15
- package/dist/services/skills/index.js +5 -0
- package/dist/services/skills/status.js +37 -0
- package/dist/{services/app/init/steering.js → utils/coding-steering.js} +19 -8
- package/dist/utils/http.js +14 -10
- package/dist/utils/spark-meta.js +2 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ miaoda file ls --output json
|
|
|
54
54
|
## 环境变量
|
|
55
55
|
|
|
56
56
|
- `MIAODA_APP_ID`:默认应用 ID,等价于 `--app-id`。
|
|
57
|
-
- `MIAODA_CANARY_HEADER
|
|
57
|
+
- `MIAODA_CANARY_HEADER`:HTTP 请求的 `x-tt-env` 小流量头取值。未设置时默认 `boe_miaoda_doubao`;设为空字符串则不带该头(例:`export MIAODA_CANARY_HEADER=boe_xxx`)。值形如 `ppe_xxx` 时额外带上 `x-use-ppe: 1`。
|
|
58
58
|
|
|
59
59
|
## 输出契约
|
|
60
60
|
|
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.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.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; } });
|
|
@@ -10,6 +10,7 @@ var modern_1 = require("./modern");
|
|
|
10
10
|
Object.defineProperty(exports, "preRelease", { enumerable: true, get: function () { return modern_1.preRelease; } });
|
|
11
11
|
Object.defineProperty(exports, "createLocalRelease", { enumerable: true, get: function () { return modern_1.createLocalRelease; } });
|
|
12
12
|
Object.defineProperty(exports, "updateLocalRelease", { enumerable: true, get: function () { return modern_1.updateLocalRelease; } });
|
|
13
|
+
Object.defineProperty(exports, "callbackStatic", { enumerable: true, get: function () { return modern_1.callbackStatic; } });
|
|
13
14
|
Object.defineProperty(exports, "createModernRelease", { enumerable: true, get: function () { return modern_1.createRelease; } });
|
|
14
15
|
Object.defineProperty(exports, "getModernReleaseStatus", { enumerable: true, get: function () { return modern_1.getReleaseStatus; } });
|
|
15
16
|
Object.defineProperty(exports, "getModernLastPublishedVersion", { enumerable: true, get: function () { return modern_1.getLastPublishedVersion; } });
|
|
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
exports.preRelease = preRelease;
|
|
14
14
|
exports.createLocalRelease = createLocalRelease;
|
|
15
15
|
exports.updateLocalRelease = updateLocalRelease;
|
|
16
|
+
exports.callbackStatic = callbackStatic;
|
|
16
17
|
exports.createRelease = createRelease;
|
|
17
18
|
exports.getReleaseStatus = getReleaseStatus;
|
|
18
19
|
exports.getLastPublishedVersion = getLastPublishedVersion;
|
|
@@ -48,6 +49,13 @@ async function updateLocalRelease(req) {
|
|
|
48
49
|
const url = `/v1/devops/app/${encodeURIComponent(appID)}/local_publish/update_release`;
|
|
49
50
|
await (0, http_1.postInnerApi)(url, body, envelopeOpts('Failed to update local release'));
|
|
50
51
|
}
|
|
52
|
+
// ── callbackStatic(PaaS Storage 静态资源上传完成回调) ──
|
|
53
|
+
/** POST /v1/storage/app/:appID/bucket/:bucketID/object/callbackStatic */
|
|
54
|
+
async function callbackStatic(req) {
|
|
55
|
+
const { appID, bucketID, uploadID } = req;
|
|
56
|
+
const url = `/v1/storage/app/${encodeURIComponent(appID)}/bucket/${encodeURIComponent(bucketID)}/object/callbackStatic`;
|
|
57
|
+
return (0, http_1.postInnerApi)(url, { uploadID }, envelopeOpts('Failed to callback static upload'));
|
|
58
|
+
}
|
|
51
59
|
// ── release(远端部署,B / C 链路;本期不导出,类型骨架已就绪) ──
|
|
52
60
|
/** POST /v1/devops/app/:appID/remote_publish/release */
|
|
53
61
|
async function createRelease(req) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// plugin_instances batch_save 接口的请求 / 响应类型。
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// 后端已统一到 InnerEnvelope(`{data, status_code, message}`),与 modern deploy
|
|
5
|
+
// 其它接口一致;旧的 BaseResp 形态已下线,类型也不再保留。
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// studio_server 的 plugin_instances 批量保存接口。
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
//
|
|
4
|
+
// 后端已统一到 InnerEnvelope(`{data, status_code, message}`)信封;不再手撸
|
|
5
|
+
// 旧 BaseResp 解析,直接复用 utils/http.ts 的 postInnerApi,verbose 下也能拿到
|
|
6
|
+
// `→ POST` / `body:` / `resp:` 的完整日志。
|
|
5
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
8
|
exports.batchSavePluginInstances = batchSavePluginInstances;
|
|
7
9
|
const http_1 = require("../../utils/http");
|
|
8
|
-
const error_1 = require("../../utils/error");
|
|
9
10
|
const URL_PATH = '/api/v1/studio/innerapi/plugin_instances/batch_save';
|
|
10
11
|
/**
|
|
11
12
|
* POST /api/v1/studio/innerapi/plugin_instances/batch_save
|
|
12
13
|
*
|
|
13
14
|
* 把发布版本的 capability 集合注册到 studio_server。
|
|
14
|
-
* 失败时抛 AppError("DEPLOY_PLUGIN_BATCH_SAVE_FAILED")
|
|
15
|
+
* 失败时抛 AppError("DEPLOY_PLUGIN_BATCH_SAVE_FAILED"),错误信息来自 envelope.message。
|
|
15
16
|
*/
|
|
16
17
|
async function batchSavePluginInstances(req) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
}
|
|
18
|
+
await (0, http_1.postInnerApi)(URL_PATH, req, {
|
|
19
|
+
errPrefix: 'Failed to save plugin instances',
|
|
20
|
+
defaultErrCode: 'DEPLOY_PLUGIN_BATCH_SAVE_FAILED',
|
|
21
|
+
});
|
|
30
22
|
}
|
|
@@ -8,6 +8,7 @@ const index_3 = require("../../cli/commands/observability/index");
|
|
|
8
8
|
const index_4 = require("../../cli/commands/app/index");
|
|
9
9
|
const index_5 = require("../../cli/commands/deploy/index");
|
|
10
10
|
const modern_1 = require("../../cli/commands/deploy/modern");
|
|
11
|
+
const index_6 = require("../../cli/commands/skills/index");
|
|
11
12
|
function resolveScene(appType, _archType) {
|
|
12
13
|
if (appType === '7')
|
|
13
14
|
return 'modern';
|
|
@@ -24,6 +25,7 @@ const SCENE_REGISTRARS = {
|
|
|
24
25
|
modern: (p) => {
|
|
25
26
|
(0, index_4.registerAppCommands)(p, { includeInit: true });
|
|
26
27
|
(0, modern_1.registerDeployCommandsModern)(p);
|
|
28
|
+
(0, index_6.registerSkillsCommands)(p);
|
|
27
29
|
},
|
|
28
30
|
};
|
|
29
31
|
function readEnv(name) {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSkillsCommands = registerSkillsCommands;
|
|
4
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
5
|
+
const index_1 = require("../../../cli/handlers/skills/index");
|
|
6
|
+
/**
|
|
7
|
+
* miaoda skills:本地 agent skills 维护。
|
|
8
|
+
*
|
|
9
|
+
* 当前仅 steering 一类子集(coding-steering 包内的 _common/skills + <stack>/skills + tech.md);
|
|
10
|
+
* 后续如有更多 skill 类型再加 --type。`app init` 会一次性同步装上,独立 `skills sync` 用于
|
|
11
|
+
* 后续单独升级 / pin 版本,不必走完整 init。
|
|
12
|
+
*/
|
|
13
|
+
function registerSkillsCommands(program) {
|
|
14
|
+
const skillsCmd = program.command('skills').description('本地 agent skills(含 steering)维护');
|
|
15
|
+
skillsCmd.action(() => {
|
|
16
|
+
skillsCmd.outputHelp();
|
|
17
|
+
});
|
|
18
|
+
registerSkillsSync(skillsCmd);
|
|
19
|
+
registerSkillsStatus(skillsCmd);
|
|
20
|
+
}
|
|
21
|
+
function registerSkillsSync(parent) {
|
|
22
|
+
const cmd = parent
|
|
23
|
+
.command('sync')
|
|
24
|
+
.description('同步 coding-steering(latest 或指定版本)到 .agent/steering/')
|
|
25
|
+
.argument('[version]', 'coding-steering 包版本或 dist-tag,缺省 latest')
|
|
26
|
+
.option('--dir <path>', '项目目录,默认当前目录', '.')
|
|
27
|
+
.addHelpText('after', `
|
|
28
|
+
前置要求
|
|
29
|
+
当前目录(或 --dir)已走过 'miaoda app init'(.spark/meta.json 含 stack)
|
|
30
|
+
|
|
31
|
+
JSON 输出
|
|
32
|
+
{"data": {"stack": "...", "version": "...", "syncedSkills": [...], "techSynced": true|false}}
|
|
33
|
+
|
|
34
|
+
示例
|
|
35
|
+
$ miaoda skills sync
|
|
36
|
+
$ miaoda skills sync 0.2.0
|
|
37
|
+
$ miaoda skills sync --dir ./my-app
|
|
38
|
+
`);
|
|
39
|
+
cmd.action((0, shared_1.withHelp)(cmd, async (version, rawOpts) => {
|
|
40
|
+
await (0, index_1.handleSkillsSync)({
|
|
41
|
+
dir: rawOpts.dir,
|
|
42
|
+
version,
|
|
43
|
+
});
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
function registerSkillsStatus(parent) {
|
|
47
|
+
const cmd = parent
|
|
48
|
+
.command('status')
|
|
49
|
+
.description('查看本地 .agent/steering/ 的当前状态(不联网)')
|
|
50
|
+
.option('--dir <path>', '项目目录,默认当前目录', '.')
|
|
51
|
+
.addHelpText('after', `
|
|
52
|
+
JSON 输出
|
|
53
|
+
{"data": {"present": true|false, "skills": [...], "techSynced": true|false}}
|
|
54
|
+
|
|
55
|
+
示例
|
|
56
|
+
$ miaoda skills status
|
|
57
|
+
`);
|
|
58
|
+
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
59
|
+
await (0, index_1.handleSkillsStatus)({
|
|
60
|
+
dir: rawOpts.dir,
|
|
61
|
+
});
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
@@ -7,6 +7,7 @@ exports.handleAppInit = handleAppInit;
|
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const index_1 = require("../../../services/app/init/index");
|
|
10
|
+
const coding_steering_1 = require("../../../utils/coding-steering");
|
|
10
11
|
const error_1 = require("../../../utils/error");
|
|
11
12
|
const output_1 = require("../../../utils/output");
|
|
12
13
|
/** miaoda app init [--template <stack>] [--conf <json>] */
|
|
@@ -43,10 +44,11 @@ async function handleAppInit(opts) {
|
|
|
43
44
|
const version = opts.conf?.version;
|
|
44
45
|
const projectName = node_path_1.default.basename(targetDir);
|
|
45
46
|
const tplResult = (0, index_1.renderTemplate)({ stack, version, targetDir, projectName });
|
|
46
|
-
const steeringResult = (0,
|
|
47
|
+
const steeringResult = (0, coding_steering_1.syncCodingSteering)({
|
|
47
48
|
stack,
|
|
48
49
|
targetDir,
|
|
49
50
|
version: opts.conf?.steeringVersion,
|
|
51
|
+
logPrefix: 'init',
|
|
50
52
|
});
|
|
51
53
|
const installResult = (0, index_1.installDependencies)({
|
|
52
54
|
targetDir,
|
|
@@ -55,13 +57,9 @@ async function handleAppInit(opts) {
|
|
|
55
57
|
});
|
|
56
58
|
(0, index_1.writeSparkMeta)(targetDir, {
|
|
57
59
|
appId: opts.appId,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
templateVersion: tplResult.version,
|
|
61
|
-
steeringVersion: steeringResult.version,
|
|
60
|
+
stack,
|
|
61
|
+
version: tplResult.version,
|
|
62
62
|
archType: tplResult.archType,
|
|
63
|
-
installSource: installResult.source,
|
|
64
|
-
installHash: installResult.hash,
|
|
65
63
|
});
|
|
66
64
|
if (!(0, output_1.isJsonMode)()) {
|
|
67
65
|
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install ${installResult.source}) in ${targetDir}\n`);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleSkillsStatus = exports.handleSkillsSync = void 0;
|
|
4
|
+
var sync_1 = require("./sync");
|
|
5
|
+
Object.defineProperty(exports, "handleSkillsSync", { enumerable: true, get: function () { return sync_1.handleSkillsSync; } });
|
|
6
|
+
var status_1 = require("./status");
|
|
7
|
+
Object.defineProperty(exports, "handleSkillsStatus", { enumerable: true, get: function () { return status_1.handleSkillsStatus; } });
|
|
@@ -0,0 +1,31 @@
|
|
|
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.handleSkillsStatus = handleSkillsStatus;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const index_1 = require("../../../services/skills/index");
|
|
9
|
+
const spark_meta_1 = require("../../../utils/spark-meta");
|
|
10
|
+
const output_1 = require("../../../utils/output");
|
|
11
|
+
/**
|
|
12
|
+
* miaoda skills status [--dir <path>]
|
|
13
|
+
*
|
|
14
|
+
* 从 .spark/meta.json 读 stack,扫描 <dir>/.agent/steering/<stack>/,
|
|
15
|
+
* 给出当前装了哪些 skills、tech.md 是否在。纯本地操作,不联网。
|
|
16
|
+
* meta 缺 stack 时返回 present=false(不抛错,方便和 init 的引导分开)。
|
|
17
|
+
*/
|
|
18
|
+
async function handleSkillsStatus(opts) {
|
|
19
|
+
await Promise.resolve();
|
|
20
|
+
const targetDir = node_path_1.default.resolve(opts.dir ?? process.cwd());
|
|
21
|
+
const meta = (0, spark_meta_1.readSparkMeta)(targetDir);
|
|
22
|
+
const status = (0, index_1.readSkillsStatus)({ targetDir, stack: meta.stack });
|
|
23
|
+
(0, output_1.emit)({
|
|
24
|
+
data: {
|
|
25
|
+
stack: meta.stack ?? null,
|
|
26
|
+
present: status.present,
|
|
27
|
+
skills: status.skills,
|
|
28
|
+
techSynced: status.techSynced,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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.handleSkillsSync = handleSkillsSync;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const coding_steering_1 = require("../../../utils/coding-steering");
|
|
9
|
+
const spark_meta_1 = require("../../../utils/spark-meta");
|
|
10
|
+
const error_1 = require("../../../utils/error");
|
|
11
|
+
const output_1 = require("../../../utils/output");
|
|
12
|
+
/**
|
|
13
|
+
* miaoda skills sync [--dir <path>] [--version <ver>]
|
|
14
|
+
*
|
|
15
|
+
* 从 .spark/meta.json 读 stack,把 coding-steering 包内对应 stack 的 skills + tech.md
|
|
16
|
+
* 同步到 <dir>/.agent/steering/。需要先跑过 `miaoda app init`。
|
|
17
|
+
*/
|
|
18
|
+
async function handleSkillsSync(opts) {
|
|
19
|
+
await Promise.resolve();
|
|
20
|
+
const targetDir = node_path_1.default.resolve(opts.dir ?? process.cwd());
|
|
21
|
+
const meta = (0, spark_meta_1.readSparkMeta)(targetDir);
|
|
22
|
+
if (meta.stack === undefined || meta.stack === '') {
|
|
23
|
+
throw new error_1.AppError('SKILLS_META_INCOMPLETE', '.spark/meta.json missing stack — run `miaoda app init` first');
|
|
24
|
+
}
|
|
25
|
+
const result = (0, coding_steering_1.syncCodingSteering)({
|
|
26
|
+
stack: meta.stack,
|
|
27
|
+
targetDir,
|
|
28
|
+
version: opts.version,
|
|
29
|
+
});
|
|
30
|
+
(0, output_1.emit)({
|
|
31
|
+
data: {
|
|
32
|
+
stack: meta.stack,
|
|
33
|
+
version: result.version,
|
|
34
|
+
syncedSkills: result.syncedSkills,
|
|
35
|
+
techSynced: result.techSynced,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.
|
|
3
|
+
exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.TEMPLATE_PACKAGE_BY_STACK = exports.SUPPORTED_STACKS = exports.renderTemplate = void 0;
|
|
4
4
|
var template_1 = require("./template");
|
|
5
5
|
Object.defineProperty(exports, "renderTemplate", { enumerable: true, get: function () { return template_1.renderTemplate; } });
|
|
6
6
|
Object.defineProperty(exports, "SUPPORTED_STACKS", { enumerable: true, get: function () { return template_1.SUPPORTED_STACKS; } });
|
|
7
7
|
Object.defineProperty(exports, "TEMPLATE_PACKAGE_BY_STACK", { enumerable: true, get: function () { return template_1.TEMPLATE_PACKAGE_BY_STACK; } });
|
|
8
|
-
var steering_1 = require("./steering");
|
|
9
|
-
Object.defineProperty(exports, "syncSteering", { enumerable: true, get: function () { return steering_1.syncSteering; } });
|
|
10
8
|
var spark_meta_1 = require("../../../utils/spark-meta");
|
|
11
9
|
Object.defineProperty(exports, "readSparkMeta", { enumerable: true, get: function () { return spark_meta_1.readSparkMeta; } });
|
|
12
10
|
Object.defineProperty(exports, "writeSparkMeta", { enumerable: true, get: function () { return spark_meta_1.writeSparkMeta; } });
|
|
@@ -10,17 +10,18 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
10
10
|
const npm_pack_1 = require("../../../utils/npm-pack");
|
|
11
11
|
const error_1 = require("../../../utils/error");
|
|
12
12
|
const logger_1 = require("../../../utils/logger");
|
|
13
|
-
/** 短名 → 包名映射。新增 stack 时改这里。 */
|
|
13
|
+
/** 短名 → 包名映射。新增 stack 时改这里。archType 由 template 包自报,CLI 不维护。 */
|
|
14
14
|
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
|
};
|
|
18
18
|
exports.SUPPORTED_STACKS = Object.keys(exports.TEMPLATE_PACKAGE_BY_STACK);
|
|
19
|
-
// template/ 内以下划线开头的占位文件名在渲染时改名(npm publish 会把 .gitignore 吞掉,
|
|
20
|
-
// 模板里用 _gitignore 提交,渲染时再改回)。
|
|
19
|
+
// template/ 内以下划线开头的占位文件名在渲染时改名(npm publish 会把 .gitignore / .npmrc 吞掉,
|
|
20
|
+
// 模板里用 _gitignore / _npmrc 提交,渲染时再改回)。
|
|
21
21
|
const RENAME_FILES = {
|
|
22
22
|
_gitignore: '.gitignore',
|
|
23
23
|
'_env.local': '.env.local',
|
|
24
|
+
_npmrc: '.npmrc',
|
|
24
25
|
};
|
|
25
26
|
function renderTemplate(opts) {
|
|
26
27
|
const packageName = exports.TEMPLATE_PACKAGE_BY_STACK[opts.stack];
|
|
@@ -37,12 +38,13 @@ function renderTemplate(opts) {
|
|
|
37
38
|
throw new error_1.AppError('TEMPLATE_INVALID', `包 ${packageName}@${fetched.version} 缺少 template/ 目录`);
|
|
38
39
|
}
|
|
39
40
|
const pkgJsonPath = node_path_1.default.join(fetched.extractDir, 'package.json');
|
|
40
|
-
let archType
|
|
41
|
+
let archType;
|
|
41
42
|
if (node_fs_1.default.existsSync(pkgJsonPath)) {
|
|
42
43
|
const pkgJson = JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
|
|
43
|
-
archType = pkgJson.miaodaTemplate?.archType
|
|
44
|
+
archType = pkgJson.miaodaTemplate?.archType;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
// 用显式 null/undefined/'' 判,避免把合法值 0 / false 当成"缺失"误抛
|
|
47
|
+
if (archType === undefined || archType === null || archType === '') {
|
|
46
48
|
throw new error_1.AppError('TEMPLATE_INVALID', `包 ${packageName}@${fetched.version} 缺少 package.json.miaodaTemplate.archType`);
|
|
47
49
|
}
|
|
48
50
|
(0, logger_1.log)('init', `Rendering template to ${opts.targetDir}...`);
|
|
@@ -6,13 +6,14 @@ const error_1 = require("../../../../utils/error");
|
|
|
6
6
|
const logger_1 = require("../../../../utils/logger");
|
|
7
7
|
const protocol_1 = require("../protocol");
|
|
8
8
|
/**
|
|
9
|
-
* 给
|
|
9
|
+
* 给 RESOURCE_CDN_PREFIX 补 https:// 协议头:
|
|
10
10
|
* - 已带 http:// 或 https:// → 原样返回
|
|
11
11
|
* - 以 / 开头(绝对路径)→ 原样返回
|
|
12
12
|
* - 不含 dot(看起来不像域名,比如纯路径 app_xxx/2)→ 原样返回
|
|
13
13
|
* - 其它(典型:lf-xxx.bytetos.com/...)→ 前缀 https://
|
|
14
14
|
*
|
|
15
15
|
* template 的 build script 直接拿来当 vite base / asset URL 用,必须是合法 URL。
|
|
16
|
+
* STATIC_CDN_PREFIX 由 runtime 同域提供(相对路径),不走这里。
|
|
16
17
|
*/
|
|
17
18
|
function ensureHttpsScheme(v) {
|
|
18
19
|
if (/^https?:\/\//i.test(v))
|
|
@@ -28,17 +29,21 @@ function ensureHttpsScheme(v) {
|
|
|
28
29
|
* MIAODA_APP_ID / MIAODA_VERSION / MIAODA_STACK
|
|
29
30
|
* MIAODA_RESOURCE_CDN_PREFIX / MIAODA_STATIC_CDN_PREFIX
|
|
30
31
|
*
|
|
31
|
-
*
|
|
32
|
+
* MIAODA_STATIC_CDN_PREFIX 取自 output_static_paas_storage_credential.downloadURLPrefix,
|
|
33
|
+
* 是 runtime 同域相对路径(例如 /app/<appId>/runtime/api/v1/storage/object/<bucket>/),
|
|
34
|
+
* 不补 https scheme,原样注入。顶层 static_cdn_prefix 不再下发。
|
|
35
|
+
*
|
|
32
36
|
* build 失败抛 AppError(execSync 自身会 throw,捕获后包一层加错误码)。
|
|
33
37
|
*/
|
|
34
38
|
function runBuild(opts) {
|
|
39
|
+
const staticCred = (0, protocol_1.parsePaasStorageCredential)((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL), protocol_1.DataKey.OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL);
|
|
35
40
|
const buildEnv = {
|
|
36
41
|
...process.env,
|
|
37
42
|
MIAODA_APP_ID: opts.appId,
|
|
38
43
|
MIAODA_VERSION: opts.version,
|
|
39
44
|
MIAODA_STACK: opts.templateKey,
|
|
40
45
|
MIAODA_RESOURCE_CDN_PREFIX: ensureHttpsScheme((0, protocol_1.requireDataKey)(opts.data, protocol_1.DataKey.RESOURCE_CDN_PREFIX)),
|
|
41
|
-
MIAODA_STATIC_CDN_PREFIX:
|
|
46
|
+
MIAODA_STATIC_CDN_PREFIX: staticCred.downloadURLPrefix,
|
|
42
47
|
};
|
|
43
48
|
(0, logger_1.log)('deploy', 'Building...');
|
|
44
49
|
try {
|
|
@@ -6,7 +6,7 @@ const spark_meta_1 = require("../../../../utils/spark-meta");
|
|
|
6
6
|
const check_1 = require("../check");
|
|
7
7
|
const template_key_map_1 = require("../template-key-map");
|
|
8
8
|
/**
|
|
9
|
-
* 准备 deploy 上下文:跑前置检查、读 .spark/meta.json、抽出 appId /
|
|
9
|
+
* 准备 deploy 上下文:跑前置检查、读 .spark/meta.json、抽出 appId / stack,
|
|
10
10
|
* 并把 stack 短名映射成后端 templateKey。
|
|
11
11
|
*
|
|
12
12
|
* 任何前置失败统一抛 AppError。返回值是后续 atom 唯一信任的入参源。
|
|
@@ -17,14 +17,14 @@ function prepareDeployContext(projectDir) {
|
|
|
17
17
|
if (meta.appId === undefined || meta.appId === '') {
|
|
18
18
|
throw new error_1.AppError('DEPLOY_META_INCOMPLETE', '.spark/meta.json missing appId — run `miaoda app init` first');
|
|
19
19
|
}
|
|
20
|
-
if (meta.
|
|
21
|
-
throw new error_1.AppError('DEPLOY_META_INCOMPLETE', '.spark/meta.json missing
|
|
20
|
+
if (meta.stack === undefined || meta.stack === '') {
|
|
21
|
+
throw new error_1.AppError('DEPLOY_META_INCOMPLETE', '.spark/meta.json missing stack — run `miaoda app init` first');
|
|
22
22
|
}
|
|
23
23
|
return {
|
|
24
24
|
projectDir,
|
|
25
25
|
appId: meta.appId,
|
|
26
|
-
|
|
27
|
-
templateKey: (0, template_key_map_1.resolveTemplateKey)(meta.
|
|
26
|
+
stack: meta.stack,
|
|
27
|
+
templateKey: (0, template_key_map_1.resolveTemplateKey)(meta.stack),
|
|
28
28
|
meta,
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -8,21 +8,28 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
10
|
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const index_1 = require("../../../../api/deploy/index");
|
|
11
12
|
const error_1 = require("../../../../utils/error");
|
|
12
13
|
const logger_1 = require("../../../../utils/logger");
|
|
13
14
|
const constants_1 = require("../constants");
|
|
14
15
|
const protocol_1 = require("../protocol");
|
|
15
16
|
const UPLOAD_PLAN = [
|
|
16
|
-
{ dir: constants_1.OUTPUT_DIR, dataKey: protocol_1.DataKey.OUTPUT_TOS_UPLOAD_CREDENTIAL, required: true },
|
|
17
17
|
{
|
|
18
|
+
kind: 'tos',
|
|
19
|
+
dir: constants_1.OUTPUT_DIR,
|
|
20
|
+
dataKey: protocol_1.DataKey.OUTPUT_TOS_UPLOAD_CREDENTIAL,
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
kind: 'tos',
|
|
18
25
|
dir: constants_1.OUTPUT_RESOURCE_DIR,
|
|
19
26
|
dataKey: protocol_1.DataKey.OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL,
|
|
20
27
|
required: false,
|
|
21
28
|
},
|
|
22
29
|
{
|
|
30
|
+
kind: 'paas_storage',
|
|
23
31
|
dir: constants_1.OUTPUT_STATIC_DIR,
|
|
24
|
-
dataKey: protocol_1.DataKey.
|
|
25
|
-
required: false,
|
|
32
|
+
dataKey: protocol_1.DataKey.OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL,
|
|
26
33
|
},
|
|
27
34
|
];
|
|
28
35
|
function resolveTosutilPath() {
|
|
@@ -36,33 +43,28 @@ function resolveTosutilPath() {
|
|
|
36
43
|
}
|
|
37
44
|
throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
|
|
38
45
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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;
|
|
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
|
+
function tosutilUploadFromPaasStorage(cred, dataKey) {
|
|
58
|
+
const { bucket, prefix } = (0, protocol_1.parseTosUploadPrefix)(cred.uploadPrefix, dataKey);
|
|
59
|
+
return {
|
|
60
|
+
accessKeyID: cred.accessKeyID,
|
|
61
|
+
secretAccessKey: cred.secretAccessKey,
|
|
62
|
+
sessionToken: cred.sessionToken,
|
|
63
|
+
endpoint: cred.endpoint,
|
|
64
|
+
region: cred.region,
|
|
65
|
+
bucket,
|
|
66
|
+
prefix,
|
|
67
|
+
};
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
70
|
* 写一个 tosutil 临时 config 文件,注入 STS 凭证 + endpoint + region。
|
|
@@ -88,6 +90,16 @@ function writeTosutilConfig(tosutilPath, cred, label) {
|
|
|
88
90
|
], { stdio: 'pipe' });
|
|
89
91
|
return confPath;
|
|
90
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
|
+
}
|
|
91
103
|
function normalizeTosDest(bucket, prefix) {
|
|
92
104
|
const bucketPart = bucket.startsWith('tos://') ? bucket : `tos://${bucket}`;
|
|
93
105
|
const normalized = prefix.replace(/^\/+/, '');
|
|
@@ -123,11 +135,14 @@ function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
|
|
|
123
135
|
'-conf',
|
|
124
136
|
confPath,
|
|
125
137
|
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
138
|
+
streamTosutilOutput(output);
|
|
126
139
|
}
|
|
127
140
|
catch (err) {
|
|
128
141
|
const e = err;
|
|
129
142
|
const stderr = (e.stderr ?? '').trim();
|
|
130
143
|
const stdout = (e.stdout ?? '').trim();
|
|
144
|
+
streamTosutilOutput(stdout);
|
|
145
|
+
streamTosutilOutput(stderr);
|
|
131
146
|
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `tosutil cp exited with status ${String(e.status ?? -1)} for ${sourceDir}\n` +
|
|
132
147
|
` stderr: ${stderr || '(empty)'}\n` +
|
|
133
148
|
` stdout: ${stdout.slice(0, 500) || '(empty)'}`);
|
|
@@ -151,42 +166,81 @@ function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
|
|
|
151
166
|
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
|
|
152
167
|
}
|
|
153
168
|
}
|
|
169
|
+
function uploadTosTarget(tosutilPath, target, localPath, data) {
|
|
170
|
+
const credRaw = target.required
|
|
171
|
+
? (0, protocol_1.requireDataKey)(data, target.dataKey)
|
|
172
|
+
: (0, protocol_1.optionalDataKey)(data, target.dataKey);
|
|
173
|
+
if (credRaw === undefined) {
|
|
174
|
+
(0, logger_1.debug)(`upload: ${target.dir} exists but no credential configured, skipping`);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const cred = (0, protocol_1.parseTosUploadCredential)(credRaw, target.dataKey);
|
|
178
|
+
(0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
|
|
179
|
+
uploadDirWithCredential(tosutilPath, localPath, tosutilUploadFromTos(cred), target.dir);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
function uploadPaasStorageTarget(tosutilPath, target, localPath, data) {
|
|
183
|
+
// 空目录:直接跳过;callbackStatic 也不能调,否则会让后端把"未发生的上传"
|
|
184
|
+
// 误以为已完成。凭证本身 build 阶段已经消费过 downloadURLPrefix,这里不重复要求。
|
|
185
|
+
if (node_fs_1.default.readdirSync(localPath).length === 0) {
|
|
186
|
+
(0, logger_1.debug)(`upload: ${target.dir} is empty, skipping`);
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const cred = (0, protocol_1.parsePaasStorageCredential)((0, protocol_1.requireDataKey)(data, target.dataKey), target.dataKey);
|
|
190
|
+
(0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
|
|
191
|
+
uploadDirWithCredential(tosutilPath, localPath, tosutilUploadFromPaasStorage(cred, target.dataKey), target.dir);
|
|
192
|
+
return cred;
|
|
193
|
+
}
|
|
154
194
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
195
|
+
* 上传三类产物。output / output_resource 走 TOS STS 凭证;output_static 走 PaaS Storage
|
|
196
|
+
* 凭证(uploadPrefix 解出 tosutil cp 目的,上传完后再调 callbackStatic 把 uploadID
|
|
197
|
+
* 提交给后端核销):
|
|
157
198
|
* - dist/output (必传) → OUTPUT_TOS_UPLOAD_CREDENTIAL
|
|
158
199
|
* - dist/output_resource (可选) → OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL
|
|
159
|
-
* - dist/output_static (可选) →
|
|
200
|
+
* - dist/output_static (可选) → OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL + callbackStatic
|
|
160
201
|
*
|
|
161
202
|
* 目录不存在的可选项跳过;output 不存在或凭证缺失抛错。
|
|
162
203
|
*/
|
|
163
|
-
function uploadArtifacts(opts) {
|
|
204
|
+
async function uploadArtifacts(opts) {
|
|
164
205
|
const tosutilPath = resolveTosutilPath();
|
|
165
206
|
const distDir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR);
|
|
166
|
-
|
|
207
|
+
const outcome = { uploaded: 0 };
|
|
167
208
|
for (const target of UPLOAD_PLAN) {
|
|
168
209
|
const localPath = node_path_1.default.join(distDir, target.dir);
|
|
169
210
|
const exists = node_fs_1.default.existsSync(localPath);
|
|
170
|
-
if (
|
|
171
|
-
if (
|
|
172
|
-
|
|
211
|
+
if (target.kind === 'tos') {
|
|
212
|
+
if (!exists) {
|
|
213
|
+
if (target.required) {
|
|
214
|
+
throw new error_1.AppError('DEPLOY_NO_BUILD_OUTPUT', `Required directory missing: ${constants_1.DIST_DIR}/${target.dir}`);
|
|
215
|
+
}
|
|
216
|
+
(0, logger_1.debug)(`upload: dir not present, skipping ${target.dir}`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (uploadTosTarget(tosutilPath, target, localPath, opts.data)) {
|
|
220
|
+
outcome.uploaded++;
|
|
173
221
|
}
|
|
174
|
-
(0, logger_1.debug)(`upload: dir not present, skipping ${target.dir}`);
|
|
175
222
|
continue;
|
|
176
223
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (credRaw === undefined) {
|
|
181
|
-
(0, logger_1.debug)(`upload: ${target.dir} exists but no credential configured, skipping`);
|
|
224
|
+
// PaaS Storage 分支
|
|
225
|
+
if (!exists) {
|
|
226
|
+
(0, logger_1.debug)(`upload: dir not present, skipping ${target.dir}`);
|
|
182
227
|
continue;
|
|
183
228
|
}
|
|
184
|
-
const cred =
|
|
185
|
-
(
|
|
186
|
-
|
|
187
|
-
|
|
229
|
+
const cred = uploadPaasStorageTarget(tosutilPath, target, localPath, opts.data);
|
|
230
|
+
if (cred) {
|
|
231
|
+
outcome.staticUploaded = cred;
|
|
232
|
+
outcome.uploaded++;
|
|
233
|
+
}
|
|
188
234
|
}
|
|
189
|
-
if (uploaded === 0) {
|
|
235
|
+
if (outcome.uploaded === 0) {
|
|
190
236
|
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', 'No artifacts uploaded — check build output.');
|
|
191
237
|
}
|
|
238
|
+
if (outcome.staticUploaded) {
|
|
239
|
+
(0, logger_1.log)('deploy', 'Callbacking static upload...');
|
|
240
|
+
await (0, index_1.callbackStatic)({
|
|
241
|
+
appID: opts.appId,
|
|
242
|
+
bucketID: outcome.staticUploaded.bucketID,
|
|
243
|
+
uploadID: outcome.staticUploaded.uploadID,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
192
246
|
}
|
|
@@ -27,8 +27,8 @@ function runDeployChecks(projectDir) {
|
|
|
27
27
|
const missing = [];
|
|
28
28
|
if (!meta.appId)
|
|
29
29
|
missing.push('appId');
|
|
30
|
-
if (!meta.
|
|
31
|
-
missing.push('
|
|
30
|
+
if (!meta.stack)
|
|
31
|
+
missing.push('stack');
|
|
32
32
|
if (missing.length > 0) {
|
|
33
33
|
issues.push(`.spark/meta.json missing fields: ${missing.join(', ')}`);
|
|
34
34
|
}
|
|
@@ -46,7 +46,7 @@ async function localBuildLocalPublishPipeline(opts) {
|
|
|
46
46
|
const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version);
|
|
47
47
|
try {
|
|
48
48
|
(0, logger_1.log)('deploy', 'Uploading artifacts...');
|
|
49
|
-
(0, index_1.uploadArtifacts)({ projectDir: ctx.projectDir, data });
|
|
49
|
+
await (0, index_1.uploadArtifacts)({ projectDir: ctx.projectDir, appId: ctx.appId, data });
|
|
50
50
|
const saveResult = await (0, index_1.savePluginInstances)({
|
|
51
51
|
projectDir: ctx.projectDir,
|
|
52
52
|
appId: ctx.appId,
|
|
@@ -61,14 +61,14 @@ async function localBuildLocalPublishPipeline(opts) {
|
|
|
61
61
|
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Failed);
|
|
62
62
|
throw err;
|
|
63
63
|
}
|
|
64
|
-
if (release.
|
|
65
|
-
(0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { ...ctx.meta, appUrl: release.
|
|
64
|
+
if (release.onlineUrl !== undefined && release.onlineUrl !== '') {
|
|
65
|
+
(0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { ...ctx.meta, appUrl: release.onlineUrl });
|
|
66
66
|
}
|
|
67
67
|
(0, logger_1.log)('deploy', 'Deployed successfully');
|
|
68
68
|
return {
|
|
69
69
|
appId: ctx.appId,
|
|
70
70
|
version: pre.version,
|
|
71
|
-
url: release.
|
|
71
|
+
url: release.onlineUrl ?? '',
|
|
72
72
|
releaseID: release.releaseID,
|
|
73
73
|
preReleaseID: pre.preReleaseID,
|
|
74
74
|
};
|
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DataKey = void 0;
|
|
4
4
|
exports.requireDataKey = requireDataKey;
|
|
5
5
|
exports.optionalDataKey = optionalDataKey;
|
|
6
|
+
exports.parseTosUploadCredential = parseTosUploadCredential;
|
|
7
|
+
exports.parsePaasStorageCredential = parsePaasStorageCredential;
|
|
8
|
+
exports.parseTosUploadPrefix = parseTosUploadPrefix;
|
|
6
9
|
const error_1 = require("../../../utils/error");
|
|
7
10
|
/**
|
|
8
11
|
* modern deploy 对外协议常量集中地(SSOT)。
|
|
@@ -15,9 +18,7 @@ exports.DataKey = {
|
|
|
15
18
|
// ── CDN 前缀(构建期注入到 build env,用于客户端资源访问) ──
|
|
16
19
|
/** dist/output_resource 对应的 CDN 域名前缀 */
|
|
17
20
|
RESOURCE_CDN_PREFIX: 'resource_cdn_prefix',
|
|
18
|
-
|
|
19
|
-
STATIC_CDN_PREFIX: 'static_cdn_prefix',
|
|
20
|
-
// ── TOS 上传凭证(与三个产物目录一一对应,value 为 JSON.stringify 的凭证对象) ──
|
|
21
|
+
// ── TOS 上传凭证(output / output_resource 两条线,value 为 JSON.stringify 的凭证对象) ──
|
|
21
22
|
// 凭证对象形态(camelCase):
|
|
22
23
|
// { accessKeyID, secretAccessKey, sessionToken, endpoint, region, bucket, prefix }
|
|
23
24
|
// CLI 端 JSON.parse 后写 tosutil 临时 config,cp 到 tos://bucket/prefix。
|
|
@@ -25,8 +26,13 @@ exports.DataKey = {
|
|
|
25
26
|
OUTPUT_TOS_UPLOAD_CREDENTIAL: 'output_tos_upload_credential',
|
|
26
27
|
/** dist/output_resource 上传凭证(仅当目录存在时使用) */
|
|
27
28
|
OUTPUT_RESOURCE_TOS_UPLOAD_CREDENTIAL: 'output_resource_tos_upload_credential',
|
|
29
|
+
// ── PaaS Storage 静态资源凭证(dist/output_static 专用) ──
|
|
30
|
+
// 形态:JSON.stringify 后的 PaasStorageCredential(见下方 interface)。
|
|
31
|
+
// 上传走 tosutil(从 uploadPrefix 解析出 bucket/prefix);上传完成后再调
|
|
32
|
+
// callbackStatic(appID, bucketID, uploadID) 把本次上传核销给后端;
|
|
33
|
+
// downloadURLPrefix 替代原 static_cdn_prefix 注入 build env。
|
|
28
34
|
/** dist/output_static 上传凭证(仅当目录存在时使用) */
|
|
29
|
-
|
|
35
|
+
OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL: 'output_static_paas_storage_credential',
|
|
30
36
|
};
|
|
31
37
|
/** 取必传 key,缺失抛 AppError(带 key 名,便于后端 tcc 配置排查) */
|
|
32
38
|
function requireDataKey(data, key) {
|
|
@@ -41,3 +47,76 @@ function optionalDataKey(data, key) {
|
|
|
41
47
|
const v = data[key];
|
|
42
48
|
return v === undefined || v === '' ? undefined : v;
|
|
43
49
|
}
|
|
50
|
+
const TOS_CREDENTIAL_REQUIRED_FIELDS = [
|
|
51
|
+
'accessKeyID',
|
|
52
|
+
'secretAccessKey',
|
|
53
|
+
'sessionToken',
|
|
54
|
+
'endpoint',
|
|
55
|
+
'region',
|
|
56
|
+
'bucket',
|
|
57
|
+
'prefix',
|
|
58
|
+
];
|
|
59
|
+
const PAAS_STORAGE_CREDENTIAL_REQUIRED_FIELDS = [
|
|
60
|
+
'accessKeyID',
|
|
61
|
+
'secretAccessKey',
|
|
62
|
+
'sessionToken',
|
|
63
|
+
'endpoint',
|
|
64
|
+
'region',
|
|
65
|
+
'uploadPrefix',
|
|
66
|
+
'downloadURLPrefix',
|
|
67
|
+
'uploadID',
|
|
68
|
+
'bucketID',
|
|
69
|
+
];
|
|
70
|
+
function parseCredentialObject(raw, dataKey) {
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(raw);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
throw new error_1.AppError('DEPLOY_CRED_INVALID_JSON', `preRelease.data["${dataKey}"] is not valid JSON: ${err.message}`);
|
|
77
|
+
}
|
|
78
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
79
|
+
throw new error_1.AppError('DEPLOY_CRED_INVALID_JSON', `preRelease.data["${dataKey}"] is not an object`);
|
|
80
|
+
}
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
function assertStringFields(obj, required, dataKey) {
|
|
84
|
+
const missing = required.filter((k) => {
|
|
85
|
+
const v = obj[k];
|
|
86
|
+
return typeof v !== 'string' || v === '';
|
|
87
|
+
});
|
|
88
|
+
if (missing.length > 0) {
|
|
89
|
+
throw new error_1.AppError('DEPLOY_CRED_INCOMPLETE', `preRelease.data["${dataKey}"] credential missing fields: ${missing.join(', ')}`);
|
|
90
|
+
}
|
|
91
|
+
return obj;
|
|
92
|
+
}
|
|
93
|
+
/** JSON.parse + 字段完整性校验:output / output_resource 凭证 */
|
|
94
|
+
function parseTosUploadCredential(raw, dataKey) {
|
|
95
|
+
const obj = parseCredentialObject(raw, dataKey);
|
|
96
|
+
return assertStringFields(obj, TOS_CREDENTIAL_REQUIRED_FIELDS, dataKey);
|
|
97
|
+
}
|
|
98
|
+
/** JSON.parse + 字段完整性校验:output_static PaaS Storage 凭证 */
|
|
99
|
+
function parsePaasStorageCredential(raw, dataKey) {
|
|
100
|
+
const obj = parseCredentialObject(raw, dataKey);
|
|
101
|
+
return assertStringFields(obj, PAAS_STORAGE_CREDENTIAL_REQUIRED_FIELDS, dataKey);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 解析 `tos://<storage-bucket>/<segments...>/` 形态的 uploadPrefix。
|
|
105
|
+
* 返回的 bucket 是 tosutil cp 视角的 TOS 物理 bucket(uploadPrefix 第一段),
|
|
106
|
+
* prefix 是其余路径(不含开头斜杠,保留可能存在的尾部斜杠以贴合 tosutil 习惯)。
|
|
107
|
+
*
|
|
108
|
+
* 注意:这里返回的 bucket 与 PaasStorageCredential.bucketID 不是同一个概念
|
|
109
|
+
* (bucketID 是 PaaS Storage 逻辑 bucket,给 callbackStatic 用)。
|
|
110
|
+
*/
|
|
111
|
+
function parseTosUploadPrefix(uploadPrefix, dataKey) {
|
|
112
|
+
const match = /^tos:\/\/([^/]+)\/(.*)$/.exec(uploadPrefix);
|
|
113
|
+
if (!match) {
|
|
114
|
+
throw new error_1.AppError('DEPLOY_CRED_INCOMPLETE', `preRelease.data["${dataKey}"].uploadPrefix must look like "tos://<bucket>/<prefix>/", got: ${uploadPrefix}`);
|
|
115
|
+
}
|
|
116
|
+
const bucket = match[1];
|
|
117
|
+
const prefix = match[2];
|
|
118
|
+
if (bucket === '' || prefix === '') {
|
|
119
|
+
throw new error_1.AppError('DEPLOY_CRED_INCOMPLETE', `preRelease.data["${dataKey}"].uploadPrefix missing bucket or prefix segment: ${uploadPrefix}`);
|
|
120
|
+
}
|
|
121
|
+
return { bucket, prefix };
|
|
122
|
+
}
|
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveTemplateKey = resolveTemplateKey;
|
|
4
|
-
const error_1 = require("../../../utils/error");
|
|
5
4
|
/**
|
|
6
5
|
* 本地 stack 短名 → 后端发布模板 key 的映射。
|
|
7
6
|
*
|
|
8
|
-
* .spark/meta.json.
|
|
7
|
+
* .spark/meta.json.stack 存的是脚手架模板短名(vite-react / html ...),
|
|
9
8
|
* 而服务端 preRelease 接受的 templateKey 是"部署类别"维度,由后端 tcc 注册。
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 加新 stack:在表里加一行;新增类别同步跟后端 tcc 配置对齐。
|
|
9
|
+
* 当前所有支持的 stack 都属于纯前端类别,默认映射到 client_local_deploy;
|
|
10
|
+
* 未来出现需要走别的 templateKey 的 stack,再在表里加一行覆盖默认。
|
|
13
11
|
*/
|
|
12
|
+
const DEFAULT_TEMPLATE_KEY = 'client_local_deploy';
|
|
14
13
|
const TEMPLATE_KEY_MAP = {
|
|
15
|
-
'vite-react':
|
|
16
|
-
|
|
14
|
+
'vite-react': DEFAULT_TEMPLATE_KEY,
|
|
15
|
+
html: DEFAULT_TEMPLATE_KEY,
|
|
17
16
|
};
|
|
18
17
|
/**
|
|
19
|
-
* 把本地 stack 短名映射到后端 templateKey
|
|
20
|
-
* 避免拿原始短名直接打过去导致后端报"empty"或"出错了"等含糊错误。
|
|
18
|
+
* 把本地 stack 短名映射到后端 templateKey;未配置的 stack 走默认 client_local_deploy。
|
|
21
19
|
*/
|
|
22
20
|
function resolveTemplateKey(template) {
|
|
23
|
-
|
|
24
|
-
if (key === undefined) {
|
|
25
|
-
throw new error_1.AppError('DEPLOY_TEMPLATE_KEY_UNMAPPED', `Template "${template}" has no mapping to backend templateKey. ` +
|
|
26
|
-
`Known: ${Object.keys(TEMPLATE_KEY_MAP).join(', ')}`);
|
|
27
|
-
}
|
|
28
|
-
return key;
|
|
21
|
+
return TEMPLATE_KEY_MAP[template] ?? DEFAULT_TEMPLATE_KEY;
|
|
29
22
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readSkillsStatus = void 0;
|
|
4
|
+
var status_1 = require("./status");
|
|
5
|
+
Object.defineProperty(exports, "readSkillsStatus", { enumerable: true, get: function () { return status_1.readSkillsStatus; } });
|
|
@@ -0,0 +1,37 @@
|
|
|
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.readSkillsStatus = readSkillsStatus;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
/**
|
|
10
|
+
* 扫描 <targetDir>/.agent/skills/steering/<stack>/,给出"当前装了什么"的快照。
|
|
11
|
+
* 不联网、不解析任何 SKILL.md 内容;只列目录名 + tech.md 是否在。
|
|
12
|
+
*
|
|
13
|
+
* stack 缺省(meta.json 缺 stack)时直接返回 present=false,
|
|
14
|
+
* 让 handler 把"先 init"的引导留给用户。
|
|
15
|
+
*/
|
|
16
|
+
function readSkillsStatus(opts) {
|
|
17
|
+
if (opts.stack === undefined || opts.stack === '') {
|
|
18
|
+
return { present: false, skills: [], techSynced: false };
|
|
19
|
+
}
|
|
20
|
+
const root = node_path_1.default.join(opts.targetDir, '.agent', 'skills', 'steering', opts.stack);
|
|
21
|
+
if (!node_fs_1.default.existsSync(root)) {
|
|
22
|
+
return { present: false, skills: [], techSynced: false };
|
|
23
|
+
}
|
|
24
|
+
const skillsDir = node_path_1.default.join(root, 'skills');
|
|
25
|
+
const skills = node_fs_1.default.existsSync(skillsDir)
|
|
26
|
+
? node_fs_1.default
|
|
27
|
+
.readdirSync(skillsDir, { withFileTypes: true })
|
|
28
|
+
.filter((e) => e.isDirectory())
|
|
29
|
+
.map((e) => e.name)
|
|
30
|
+
.sort()
|
|
31
|
+
: [];
|
|
32
|
+
return {
|
|
33
|
+
present: true,
|
|
34
|
+
skills,
|
|
35
|
+
techSynced: node_fs_1.default.existsSync(node_path_1.default.join(root, 'tech.md')),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -3,14 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.syncCodingSteering = syncCodingSteering;
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const npm_pack_1 = require("
|
|
10
|
-
const logger_1 = require("
|
|
9
|
+
const npm_pack_1 = require("../utils/npm-pack");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
11
|
const STEERING_PACKAGE = '@lark-apaas/coding-steering';
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* 把 coding-steering 包内 _common/skills + <stack>/skills 同步到 <targetDir>/.agent/steering/skills/,
|
|
14
|
+
* 同名 stack 子目录覆盖 _common。tech.md 存在时也同步到 .agent/steering/tech.md。
|
|
15
|
+
*
|
|
16
|
+
* 纯本地 atom(pull npm package + 拷贝文件),不属于任何 cli 域;放在 utils 让
|
|
17
|
+
* app init handler 与独立 skills sync handler 都能复用。
|
|
18
|
+
*/
|
|
19
|
+
function syncCodingSteering(opts) {
|
|
20
|
+
const logPrefix = opts.logPrefix ?? 'skills';
|
|
21
|
+
(0, logger_1.log)(logPrefix, `Fetching ${STEERING_PACKAGE}@${opts.version ?? 'latest'}...`);
|
|
14
22
|
const fetched = (0, npm_pack_1.fetchNpmPackage)({
|
|
15
23
|
packageName: STEERING_PACKAGE,
|
|
16
24
|
version: opts.version,
|
|
@@ -19,7 +27,10 @@ function syncSteering(opts) {
|
|
|
19
27
|
const steeringRoot = node_path_1.default.join(fetched.extractDir, 'steering');
|
|
20
28
|
const stackDir = node_path_1.default.join(steeringRoot, opts.stack);
|
|
21
29
|
const commonSkillsDir = node_path_1.default.join(steeringRoot, '_common', 'skills');
|
|
22
|
-
|
|
30
|
+
// steering 落到 .agent/skills/steering/<stack>/。.agent/skills/ 是 agent skills 的
|
|
31
|
+
// 公共容器,steering 是其中一个 producer,<stack> 再做命名空间隔离。内部仍保留
|
|
32
|
+
// 包内 steering/<stack>/skills/ 的结构,便于按"包内子目录 → 本地子目录"一一对应排查。
|
|
33
|
+
const dstRoot = node_path_1.default.join(opts.targetDir, '.agent', 'skills', 'steering', opts.stack);
|
|
23
34
|
const dstSkillsDir = node_path_1.default.join(dstRoot, 'skills');
|
|
24
35
|
node_fs_1.default.mkdirSync(dstSkillsDir, { recursive: true });
|
|
25
36
|
let techSynced = false;
|
|
@@ -29,7 +40,7 @@ function syncSteering(opts) {
|
|
|
29
40
|
techSynced = true;
|
|
30
41
|
}
|
|
31
42
|
const synced = [];
|
|
32
|
-
// 先拷 _common/skills/,再拷 <stack>/skills/。stack 同名覆盖 common
|
|
43
|
+
// 先拷 _common/skills/,再拷 <stack>/skills/。stack 同名覆盖 common
|
|
33
44
|
if (node_fs_1.default.existsSync(commonSkillsDir)) {
|
|
34
45
|
for (const name of node_fs_1.default.readdirSync(commonSkillsDir)) {
|
|
35
46
|
const src = node_path_1.default.join(commonSkillsDir, name);
|
|
@@ -52,7 +63,7 @@ function syncSteering(opts) {
|
|
|
52
63
|
synced.push(`${opts.stack}/skills/${name}`);
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
|
-
(0, logger_1.log)(
|
|
66
|
+
(0, logger_1.log)(logPrefix, `Synced ${String(synced.length)} skill(s), tech.md ${techSynced ? 'yes' : 'no'}`);
|
|
56
67
|
return { version: fetched.version, syncedSkills: synced, techSynced };
|
|
57
68
|
}
|
|
58
69
|
finally {
|
package/dist/utils/http.js
CHANGED
|
@@ -65,7 +65,12 @@ function createClient(opts) {
|
|
|
65
65
|
* 缺省走这个。MIAODA_CANARY_HEADER 可显式覆盖;设为空字符串则去掉头。
|
|
66
66
|
*/
|
|
67
67
|
const DEFAULT_CANARY_HEADER = 'boe_miaoda_doubao';
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* 注入 x-tt-env 小流量头:env 显式设值 > DEFAULT_CANARY_HEADER。
|
|
70
|
+
*
|
|
71
|
+
* 当泳道形如 `ppe_xxx`(PPE 预发环境)时,额外加 `x-use-ppe: 1`;
|
|
72
|
+
* BOE 泳道(`boe_xxx`)只带 x-tt-env,不加该头。
|
|
73
|
+
*/
|
|
69
74
|
function applyCanaryHeader(reqConfig) {
|
|
70
75
|
// env 显式设为空字符串视为"去掉灰度头";未设置时用 DEFAULT_CANARY_HEADER
|
|
71
76
|
const canary = process.env.MIAODA_CANARY_HEADER ?? DEFAULT_CANARY_HEADER;
|
|
@@ -73,6 +78,9 @@ function applyCanaryHeader(reqConfig) {
|
|
|
73
78
|
return reqConfig;
|
|
74
79
|
const headers = new Headers(reqConfig.headers);
|
|
75
80
|
headers.set('x-tt-env', canary);
|
|
81
|
+
if (canary.startsWith('ppe_')) {
|
|
82
|
+
headers.set('x-use-ppe', '1');
|
|
83
|
+
}
|
|
76
84
|
reqConfig.headers = headers;
|
|
77
85
|
return reqConfig;
|
|
78
86
|
}
|
|
@@ -109,14 +117,14 @@ async function handleInnerEnvelope(response, url, opts) {
|
|
|
109
117
|
throw new error_1.HttpError(response.status, url, `${opts.errPrefix}: ${String(response.status)} ${response.statusText}`);
|
|
110
118
|
}
|
|
111
119
|
const result = await response.json();
|
|
120
|
+
// verbose: 把完整响应体打到 stderr,便于排查(业务错误 / 字段缺失等)
|
|
121
|
+
if ((0, config_1.getConfig)().verbose) {
|
|
122
|
+
(0, logger_1.debug)(` resp: ${truncateForLog(safeStringify(result), 2000)}`);
|
|
123
|
+
}
|
|
112
124
|
if (typeof result === 'object' && result !== null && 'status_code' in result) {
|
|
113
125
|
const env = result;
|
|
114
126
|
if (env.status_code !== undefined && env.status_code !== '0') {
|
|
115
127
|
const msg = env.message ?? env.error_msg ?? 'unknown error';
|
|
116
|
-
// verbose: 把完整业务错误信封打到 stderr,便于追溯
|
|
117
|
-
if ((0, config_1.getConfig)().verbose) {
|
|
118
|
-
(0, logger_1.debug)(` envelope: ${truncateForLog(safeStringify(env), 1000)}`);
|
|
119
|
-
}
|
|
120
128
|
const mapped = opts.mapErr?.(env.status_code, msg);
|
|
121
129
|
if (mapped)
|
|
122
130
|
throw mapped;
|
|
@@ -169,12 +177,8 @@ function logResponseFailure(method, url, err, startMs) {
|
|
|
169
177
|
const msgPart = e.message ? ` (${e.message})` : '';
|
|
170
178
|
(0, logger_1.debug)(`✗ ${method} ${url} ${statusPart} ${String(elapsedMs)}ms${logidPart}${msgPart}`);
|
|
171
179
|
}
|
|
172
|
-
/** BAM gateway 在不同 PSM/版本上 logid 落在不同 header;按命中顺序取第一个非空。 */
|
|
173
180
|
function pickLogid(headers) {
|
|
174
|
-
return
|
|
175
|
-
headers.get('x-logid') ??
|
|
176
|
-
headers.get('logid') ??
|
|
177
|
-
headers.get('x-tt-trace-tag'));
|
|
181
|
+
return headers.get('x-tt-logid');
|
|
178
182
|
}
|
|
179
183
|
function safeStringify(v) {
|
|
180
184
|
try {
|
package/dist/utils/spark-meta.js
CHANGED
|
@@ -21,7 +21,7 @@ function readSparkMeta(appDir) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* merge
|
|
24
|
+
* merge 写入:已有字段保留,undefined 不覆盖;不再维护时间戳。
|
|
25
25
|
*/
|
|
26
26
|
function writeSparkMeta(appDir, meta) {
|
|
27
27
|
const sparkDir = node_path_1.default.join(appDir, SPARK_DIR);
|
|
@@ -37,12 +37,6 @@ function writeSparkMeta(appDir, meta) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
const defined = Object.fromEntries(Object.entries(meta).filter(([, v]) => v !== undefined));
|
|
40
|
-
const
|
|
41
|
-
const merged = {
|
|
42
|
-
...existing,
|
|
43
|
-
...defined,
|
|
44
|
-
createdAt: existing.createdAt ?? now,
|
|
45
|
-
updatedAt: now,
|
|
46
|
-
};
|
|
40
|
+
const merged = { ...existing, ...defined };
|
|
47
41
|
node_fs_1.default.writeFileSync(metaPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
48
42
|
}
|