@lark-apaas/miaoda-cli 0.1.5 → 0.1.6
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 +13 -1
- package/dist/api/deploy/modern-types.js +23 -0
- package/dist/api/deploy/modern.js +78 -0
- package/dist/api/deploy/plugin-instances-types.js +6 -0
- package/dist/api/deploy/plugin-instances.js +22 -0
- package/dist/cli/commands/app/index.js +84 -2
- package/dist/cli/commands/deploy/modern.js +50 -0
- package/dist/cli/commands/index.js +33 -5
- package/dist/cli/commands/skills/index.js +63 -0
- package/dist/cli/handlers/app/index.js +6 -1
- package/dist/cli/handlers/app/init.js +88 -0
- package/dist/cli/handlers/deploy/modern.js +33 -0
- 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 +12 -0
- package/dist/services/app/init/install.js +101 -0
- package/dist/services/app/init/template.js +87 -0
- package/dist/services/deploy/modern/atoms/build.js +59 -0
- package/dist/services/deploy/modern/atoms/context.js +27 -0
- package/dist/services/deploy/modern/atoms/index.js +17 -0
- package/dist/services/deploy/modern/atoms/local-release.js +27 -0
- package/dist/services/deploy/modern/atoms/pre-release.js +13 -0
- package/dist/services/deploy/modern/atoms/save-plugin-instances.js +72 -0
- package/dist/services/deploy/modern/atoms/upload.js +246 -0
- package/dist/services/deploy/modern/check.js +53 -0
- package/dist/services/deploy/modern/constants.js +13 -0
- package/dist/services/deploy/modern/index.js +16 -0
- package/dist/services/deploy/modern/pipelines/index.js +5 -0
- package/dist/services/deploy/modern/pipelines/local.js +75 -0
- package/dist/services/deploy/modern/protocol.js +122 -0
- package/dist/services/deploy/modern/run-types.js +4 -0
- package/dist/services/deploy/modern/run.js +13 -0
- package/dist/services/deploy/modern/template-key-map.js +22 -0
- package/dist/services/skills/index.js +5 -0
- package/dist/services/skills/status.js +37 -0
- package/dist/utils/coding-steering.js +85 -0
- package/dist/utils/http.js +21 -11
- package/dist/utils/npm-pack.js +55 -0
- package/dist/utils/spark-meta.js +42 -0
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
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.runDeployChecks = runDeployChecks;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const error_1 = require("../../../utils/error");
|
|
10
|
+
/**
|
|
11
|
+
* 项目级 deploy 前置检查:node_modules / .spark/meta.json / package.json.scripts.build。
|
|
12
|
+
* 任一 error 级别失败抛 AppError,附错误码 DEPLOY_PROJECT_CHECK_FAILED。
|
|
13
|
+
*/
|
|
14
|
+
function runDeployChecks(projectDir) {
|
|
15
|
+
const issues = [];
|
|
16
|
+
const nodeModules = node_path_1.default.join(projectDir, 'node_modules');
|
|
17
|
+
if (!node_fs_1.default.existsSync(nodeModules)) {
|
|
18
|
+
issues.push('node_modules not found. Run `npm install` (or equivalent) first.');
|
|
19
|
+
}
|
|
20
|
+
const metaPath = node_path_1.default.join(projectDir, '.spark', 'meta.json');
|
|
21
|
+
if (!node_fs_1.default.existsSync(metaPath)) {
|
|
22
|
+
issues.push('.spark/meta.json not found. Run `miaoda app init` first.');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
try {
|
|
26
|
+
const meta = JSON.parse(node_fs_1.default.readFileSync(metaPath, 'utf-8'));
|
|
27
|
+
if (!meta.stack) {
|
|
28
|
+
issues.push('.spark/meta.json missing fields: stack');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
issues.push('.spark/meta.json is not valid JSON.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const pkgPath = node_path_1.default.join(projectDir, 'package.json');
|
|
36
|
+
if (!node_fs_1.default.existsSync(pkgPath)) {
|
|
37
|
+
issues.push('package.json not found.');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
try {
|
|
41
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
42
|
+
if (!pkg.scripts?.build) {
|
|
43
|
+
issues.push('package.json missing "build" script.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
issues.push('package.json is not valid JSON.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (issues.length > 0) {
|
|
51
|
+
throw new error_1.AppError('DEPLOY_PROJECT_CHECK_FAILED', `Project check failed:\n${issues.map((i) => ` - ${i}`).join('\n')}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// modern deploy 流程使用的目录常量。
|
|
3
|
+
// (旧版的 STACK_CONFIGS / archType / FaaS 相关常量在新接口契约下不再需要——
|
|
4
|
+
// 发布模板差异完全由后端 preRelease.data 下发,CLI 不维护 stack 配置。)
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OUTPUT_CAPABILITIES_DIR = exports.OUTPUT_STATIC_DIR = exports.OUTPUT_RESOURCE_DIR = exports.OUTPUT_DIR = exports.DIST_DIR = void 0;
|
|
7
|
+
/** 构建产物固定目录约定 */
|
|
8
|
+
exports.DIST_DIR = 'dist';
|
|
9
|
+
exports.OUTPUT_DIR = 'output';
|
|
10
|
+
exports.OUTPUT_RESOURCE_DIR = 'output_resource';
|
|
11
|
+
exports.OUTPUT_STATIC_DIR = 'output_static';
|
|
12
|
+
/** 发布插件能力声明目录:含若干 capability.json 文件,发布时批量注册到 plugin_instances */
|
|
13
|
+
exports.OUTPUT_CAPABILITIES_DIR = 'output_capabilities';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OUTPUT_STATIC_DIR = exports.OUTPUT_RESOURCE_DIR = exports.OUTPUT_DIR = exports.DIST_DIR = exports.optionalDataKey = exports.requireDataKey = exports.DataKey = exports.runDeployChecks = exports.runModernDeploy = void 0;
|
|
4
|
+
var run_1 = require("./run");
|
|
5
|
+
Object.defineProperty(exports, "runModernDeploy", { enumerable: true, get: function () { return run_1.runModernDeploy; } });
|
|
6
|
+
var check_1 = require("./check");
|
|
7
|
+
Object.defineProperty(exports, "runDeployChecks", { enumerable: true, get: function () { return check_1.runDeployChecks; } });
|
|
8
|
+
var protocol_1 = require("./protocol");
|
|
9
|
+
Object.defineProperty(exports, "DataKey", { enumerable: true, get: function () { return protocol_1.DataKey; } });
|
|
10
|
+
Object.defineProperty(exports, "requireDataKey", { enumerable: true, get: function () { return protocol_1.requireDataKey; } });
|
|
11
|
+
Object.defineProperty(exports, "optionalDataKey", { enumerable: true, get: function () { return protocol_1.optionalDataKey; } });
|
|
12
|
+
var constants_1 = require("./constants");
|
|
13
|
+
Object.defineProperty(exports, "DIST_DIR", { enumerable: true, get: function () { return constants_1.DIST_DIR; } });
|
|
14
|
+
Object.defineProperty(exports, "OUTPUT_DIR", { enumerable: true, get: function () { return constants_1.OUTPUT_DIR; } });
|
|
15
|
+
Object.defineProperty(exports, "OUTPUT_RESOURCE_DIR", { enumerable: true, get: function () { return constants_1.OUTPUT_RESOURCE_DIR; } });
|
|
16
|
+
Object.defineProperty(exports, "OUTPUT_STATIC_DIR", { enumerable: true, get: function () { return constants_1.OUTPUT_STATIC_DIR; } });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.localBuildLocalPublishPipeline = void 0;
|
|
4
|
+
var local_1 = require("./local");
|
|
5
|
+
Object.defineProperty(exports, "localBuildLocalPublishPipeline", { enumerable: true, get: function () { return local_1.localBuildLocalPublishPipeline; } });
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.localBuildLocalPublishPipeline = localBuildLocalPublishPipeline;
|
|
4
|
+
const spark_meta_1 = require("../../../../utils/spark-meta");
|
|
5
|
+
const error_1 = require("../../../../utils/error");
|
|
6
|
+
const logger_1 = require("../../../../utils/logger");
|
|
7
|
+
const index_1 = require("../atoms/index");
|
|
8
|
+
/**
|
|
9
|
+
* 本地构建 + 本地部署 pipeline。
|
|
10
|
+
*
|
|
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. savePluginInstances — 扫描 dist/output_capabilities/*.json,批量注册插件能力
|
|
18
|
+
* 7. finalizeLocalRelease — Finished / Failed 翻终态
|
|
19
|
+
*
|
|
20
|
+
* createLocalRelease 提前到 upload 之前:发布单先建出来,
|
|
21
|
+
* upload / savePluginInstances 在已知 releaseID 的前提下进行;
|
|
22
|
+
* 任一步失败都会精准把该发布单标记为 Failed,避免遗留挂态。
|
|
23
|
+
*/
|
|
24
|
+
async function localBuildLocalPublishPipeline(opts) {
|
|
25
|
+
const ctx = (0, index_1.prepareDeployContext)(opts.projectDir, opts.appId);
|
|
26
|
+
(0, logger_1.log)('deploy', 'Pre-releasing...');
|
|
27
|
+
const pre = await (0, index_1.preRelease)(ctx.appId, ctx.templateKey);
|
|
28
|
+
(0, logger_1.log)('deploy', `pre_release_id=${pre.preReleaseID} version=${pre.version}`);
|
|
29
|
+
// data 文档标注为选填,但本地构建链路所有 atom 都需要其中的 key;
|
|
30
|
+
// 此处早期检查,把"data 缺失"翻为明确错误码,比让 requireDataKey 在
|
|
31
|
+
// 各 atom 里散落抛错好排查。
|
|
32
|
+
if (pre.data === undefined) {
|
|
33
|
+
throw new error_1.AppError('DEPLOY_PRE_RELEASE_DATA_MISSING', 'preRelease did not return a `data` map — backend tcc may not be configured for current template_key');
|
|
34
|
+
}
|
|
35
|
+
const data = pre.data;
|
|
36
|
+
if (!opts.skipBuild) {
|
|
37
|
+
(0, index_1.runBuild)({
|
|
38
|
+
projectDir: ctx.projectDir,
|
|
39
|
+
appId: ctx.appId,
|
|
40
|
+
version: pre.version,
|
|
41
|
+
templateKey: ctx.templateKey,
|
|
42
|
+
data,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
(0, logger_1.log)('deploy', 'Creating local release...');
|
|
46
|
+
const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version);
|
|
47
|
+
try {
|
|
48
|
+
(0, logger_1.log)('deploy', 'Uploading artifacts...');
|
|
49
|
+
await (0, index_1.uploadArtifacts)({ projectDir: ctx.projectDir, appId: ctx.appId, 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
|
+
}
|
|
58
|
+
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Finished);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Failed);
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
if (release.onlineUrl !== undefined && release.onlineUrl !== '') {
|
|
65
|
+
(0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { appUrl: release.onlineUrl });
|
|
66
|
+
}
|
|
67
|
+
(0, logger_1.log)('deploy', 'Deployed successfully');
|
|
68
|
+
return {
|
|
69
|
+
appId: ctx.appId,
|
|
70
|
+
version: pre.version,
|
|
71
|
+
url: release.onlineUrl ?? '',
|
|
72
|
+
releaseID: release.releaseID,
|
|
73
|
+
preReleaseID: pre.preReleaseID,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DataKey = void 0;
|
|
4
|
+
exports.requireDataKey = requireDataKey;
|
|
5
|
+
exports.optionalDataKey = optionalDataKey;
|
|
6
|
+
exports.parseTosUploadCredential = parseTosUploadCredential;
|
|
7
|
+
exports.parsePaasStorageCredential = parsePaasStorageCredential;
|
|
8
|
+
exports.parseTosUploadPrefix = parseTosUploadPrefix;
|
|
9
|
+
const error_1 = require("../../../utils/error");
|
|
10
|
+
/**
|
|
11
|
+
* modern deploy 对外协议常量集中地(SSOT)。
|
|
12
|
+
*
|
|
13
|
+
* preRelease 接口返回 `data` 动态配置 map,其 key 由后端按 template_key 在 tcc
|
|
14
|
+
* 中下发。CLI 端通过本文件枚举的 DataKey 访问 `data`,避免拼写错误散布,
|
|
15
|
+
* 同时让协议演进(新增 key / 重命名)只需要改这一个文件。
|
|
16
|
+
*/
|
|
17
|
+
exports.DataKey = {
|
|
18
|
+
// ── CDN 前缀(构建期注入到 build env,用于客户端资源访问) ──
|
|
19
|
+
/** dist/output_resource 对应的 CDN 域名前缀 */
|
|
20
|
+
RESOURCE_CDN_PREFIX: 'resource_cdn_prefix',
|
|
21
|
+
// ── TOS 上传凭证(output / output_resource 两条线,value 为 JSON.stringify 的凭证对象) ──
|
|
22
|
+
// 凭证对象形态(camelCase):
|
|
23
|
+
// { accessKeyID, secretAccessKey, sessionToken, endpoint, region, bucket, prefix }
|
|
24
|
+
// CLI 端 JSON.parse 后写 tosutil 临时 config,cp 到 tos://bucket/prefix。
|
|
25
|
+
/** dist/output 上传凭证(必传) */
|
|
26
|
+
OUTPUT_TOS_UPLOAD_CREDENTIAL: 'output_tos_upload_credential',
|
|
27
|
+
/** dist/output_resource 上传凭证(仅当目录存在时使用) */
|
|
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。
|
|
34
|
+
/** dist/output_static 上传凭证(仅当目录存在时使用) */
|
|
35
|
+
OUTPUT_STATIC_PAAS_STORAGE_CREDENTIAL: 'output_static_paas_storage_credential',
|
|
36
|
+
};
|
|
37
|
+
/** 取必传 key,缺失抛 AppError(带 key 名,便于后端 tcc 配置排查) */
|
|
38
|
+
function requireDataKey(data, key) {
|
|
39
|
+
const v = data[key];
|
|
40
|
+
if (v === undefined || v === '') {
|
|
41
|
+
throw new error_1.AppError('DEPLOY_DATA_KEY_MISSING', `preRelease.data missing required key "${key}" — backend tcc may not be configured for the current template_key`);
|
|
42
|
+
}
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
45
|
+
/** 取可选 key,缺失返回 undefined(不抛错) */
|
|
46
|
+
function optionalDataKey(data, key) {
|
|
47
|
+
const v = data[key];
|
|
48
|
+
return v === undefined || v === '' ? undefined : v;
|
|
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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// modern deploy 主入口(dispatch 层)。
|
|
3
|
+
//
|
|
4
|
+
// 当前阶段(Scope A)只接入"本地构建 + 本地部署"一条 pipeline;
|
|
5
|
+
// 后续接入"本地构建 + 远端部署 / 远端构建 + 远端部署"时,在此处按 template_key
|
|
6
|
+
// 或显式 flag 选不同 pipeline。pipeline 之间彼此独立、不共享中间状态。
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.runModernDeploy = runModernDeploy;
|
|
9
|
+
const index_1 = require("./pipelines/index");
|
|
10
|
+
async function runModernDeploy(opts) {
|
|
11
|
+
// 暂只支持本地构建 + 本地部署
|
|
12
|
+
return (0, index_1.localBuildLocalPublishPipeline)(opts);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveTemplateKey = resolveTemplateKey;
|
|
4
|
+
/**
|
|
5
|
+
* 本地 stack 短名 → 后端发布模板 key 的映射。
|
|
6
|
+
*
|
|
7
|
+
* .spark/meta.json.stack 存的是脚手架模板短名(vite-react / html ...),
|
|
8
|
+
* 而服务端 preRelease 接受的 templateKey 是"部署类别"维度,由后端 tcc 注册。
|
|
9
|
+
* 当前所有支持的 stack 都属于纯前端类别,默认映射到 client_local_deploy;
|
|
10
|
+
* 未来出现需要走别的 templateKey 的 stack,再在表里加一行覆盖默认。
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_TEMPLATE_KEY = 'client_local_deploy';
|
|
13
|
+
const TEMPLATE_KEY_MAP = {
|
|
14
|
+
'vite-react': DEFAULT_TEMPLATE_KEY,
|
|
15
|
+
html: DEFAULT_TEMPLATE_KEY,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 把本地 stack 短名映射到后端 templateKey;未配置的 stack 走默认 client_local_deploy。
|
|
19
|
+
*/
|
|
20
|
+
function resolveTemplateKey(template) {
|
|
21
|
+
return TEMPLATE_KEY_MAP[template] ?? DEFAULT_TEMPLATE_KEY;
|
|
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
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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.syncCodingSteering = syncCodingSteering;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const npm_pack_1 = require("../utils/npm-pack");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
const STEERING_PACKAGE = '@lark-apaas/coding-steering';
|
|
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'}...`);
|
|
22
|
+
const fetched = (0, npm_pack_1.fetchNpmPackage)({
|
|
23
|
+
packageName: STEERING_PACKAGE,
|
|
24
|
+
version: opts.version,
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
const steeringRoot = node_path_1.default.join(fetched.extractDir, 'steering');
|
|
28
|
+
const stackDir = node_path_1.default.join(steeringRoot, opts.stack);
|
|
29
|
+
const commonSkillsDir = node_path_1.default.join(steeringRoot, '_common', 'skills');
|
|
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);
|
|
34
|
+
const dstSkillsDir = node_path_1.default.join(dstRoot, 'skills');
|
|
35
|
+
node_fs_1.default.mkdirSync(dstSkillsDir, { recursive: true });
|
|
36
|
+
let techSynced = false;
|
|
37
|
+
const techSrc = node_path_1.default.join(stackDir, 'tech.md');
|
|
38
|
+
if (node_fs_1.default.existsSync(techSrc)) {
|
|
39
|
+
node_fs_1.default.copyFileSync(techSrc, node_path_1.default.join(dstRoot, 'tech.md'));
|
|
40
|
+
techSynced = true;
|
|
41
|
+
}
|
|
42
|
+
const synced = [];
|
|
43
|
+
// 先拷 _common/skills/,再拷 <stack>/skills/。stack 同名覆盖 common
|
|
44
|
+
if (node_fs_1.default.existsSync(commonSkillsDir)) {
|
|
45
|
+
for (const name of node_fs_1.default.readdirSync(commonSkillsDir)) {
|
|
46
|
+
const src = node_path_1.default.join(commonSkillsDir, name);
|
|
47
|
+
if (!node_fs_1.default.statSync(src).isDirectory())
|
|
48
|
+
continue;
|
|
49
|
+
copyDir(src, node_path_1.default.join(dstSkillsDir, name));
|
|
50
|
+
synced.push(`_common/skills/${name}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const stackSkillsDir = node_path_1.default.join(stackDir, 'skills');
|
|
54
|
+
if (node_fs_1.default.existsSync(stackSkillsDir)) {
|
|
55
|
+
for (const name of node_fs_1.default.readdirSync(stackSkillsDir)) {
|
|
56
|
+
const src = node_path_1.default.join(stackSkillsDir, name);
|
|
57
|
+
if (!node_fs_1.default.statSync(src).isDirectory())
|
|
58
|
+
continue;
|
|
59
|
+
const dst = node_path_1.default.join(dstSkillsDir, name);
|
|
60
|
+
if (node_fs_1.default.existsSync(dst))
|
|
61
|
+
node_fs_1.default.rmSync(dst, { recursive: true, force: true });
|
|
62
|
+
copyDir(src, dst);
|
|
63
|
+
synced.push(`${opts.stack}/skills/${name}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
(0, logger_1.log)(logPrefix, `Synced ${String(synced.length)} skill(s), tech.md ${techSynced ? 'yes' : 'no'}`);
|
|
67
|
+
return { version: fetched.version, syncedSkills: synced, techSynced };
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
fetched.cleanup();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function copyDir(src, dest) {
|
|
74
|
+
node_fs_1.default.mkdirSync(dest, { recursive: true });
|
|
75
|
+
for (const entry of node_fs_1.default.readdirSync(src, { withFileTypes: true })) {
|
|
76
|
+
const srcPath = node_path_1.default.join(src, entry.name);
|
|
77
|
+
const destPath = node_path_1.default.join(dest, entry.name);
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
copyDir(srcPath, destPath);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
node_fs_1.default.copyFileSync(srcPath, destPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/utils/http.js
CHANGED
|
@@ -60,13 +60,27 @@ function createClient(opts) {
|
|
|
60
60
|
instance.interceptors.request.use(applyCanaryHeader);
|
|
61
61
|
return instance;
|
|
62
62
|
}
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* 默认灰度泳道。当前 miaoda CLI 接入的新接口(modern deploy 等)只在该泳道生效,
|
|
65
|
+
* 缺省走这个。MIAODA_CANARY_HEADER 可显式覆盖;设为空字符串则去掉头。
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_CANARY_HEADER = 'boe_miaoda_doubao';
|
|
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
|
+
*/
|
|
64
74
|
function applyCanaryHeader(reqConfig) {
|
|
65
|
-
|
|
75
|
+
// env 显式设为空字符串视为"去掉灰度头";未设置时用 DEFAULT_CANARY_HEADER
|
|
76
|
+
const canary = process.env.MIAODA_CANARY_HEADER ?? DEFAULT_CANARY_HEADER;
|
|
66
77
|
if (!canary)
|
|
67
78
|
return reqConfig;
|
|
68
79
|
const headers = new Headers(reqConfig.headers);
|
|
69
80
|
headers.set('x-tt-env', canary);
|
|
81
|
+
if (canary.startsWith('ppe_')) {
|
|
82
|
+
headers.set('x-use-ppe', '1');
|
|
83
|
+
}
|
|
70
84
|
reqConfig.headers = headers;
|
|
71
85
|
return reqConfig;
|
|
72
86
|
}
|
|
@@ -103,14 +117,14 @@ async function handleInnerEnvelope(response, url, opts) {
|
|
|
103
117
|
throw new error_1.HttpError(response.status, url, `${opts.errPrefix}: ${String(response.status)} ${response.statusText}`);
|
|
104
118
|
}
|
|
105
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
|
+
}
|
|
106
124
|
if (typeof result === 'object' && result !== null && 'status_code' in result) {
|
|
107
125
|
const env = result;
|
|
108
126
|
if (env.status_code !== undefined && env.status_code !== '0') {
|
|
109
127
|
const msg = env.message ?? env.error_msg ?? 'unknown error';
|
|
110
|
-
// verbose: 把完整业务错误信封打到 stderr,便于追溯
|
|
111
|
-
if ((0, config_1.getConfig)().verbose) {
|
|
112
|
-
(0, logger_1.debug)(` envelope: ${truncateForLog(safeStringify(env), 1000)}`);
|
|
113
|
-
}
|
|
114
128
|
const mapped = opts.mapErr?.(env.status_code, msg);
|
|
115
129
|
if (mapped)
|
|
116
130
|
throw mapped;
|
|
@@ -163,12 +177,8 @@ function logResponseFailure(method, url, err, startMs) {
|
|
|
163
177
|
const msgPart = e.message ? ` (${e.message})` : '';
|
|
164
178
|
(0, logger_1.debug)(`✗ ${method} ${url} ${statusPart} ${String(elapsedMs)}ms${logidPart}${msgPart}`);
|
|
165
179
|
}
|
|
166
|
-
/** BAM gateway 在不同 PSM/版本上 logid 落在不同 header;按命中顺序取第一个非空。 */
|
|
167
180
|
function pickLogid(headers) {
|
|
168
|
-
return
|
|
169
|
-
headers.get('x-logid') ??
|
|
170
|
-
headers.get('logid') ??
|
|
171
|
-
headers.get('x-tt-trace-tag'));
|
|
181
|
+
return headers.get('x-tt-logid');
|
|
172
182
|
}
|
|
173
183
|
function safeStringify(v) {
|
|
174
184
|
try {
|
|
@@ -0,0 +1,55 @@
|
|
|
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.fetchNpmPackage = fetchNpmPackage;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const error_1 = require("./error");
|
|
12
|
+
const logger_1 = require("./logger");
|
|
13
|
+
const DEFAULT_REGISTRY = 'https://registry.npmmirror.com/';
|
|
14
|
+
/**
|
|
15
|
+
* `npm pack <pkg>@<ver> --registry <r>` + `tar -xzf`,解到临时目录。
|
|
16
|
+
* extractDir 是包内容根(含 package.json)。使用完必须 cleanup()。
|
|
17
|
+
*/
|
|
18
|
+
function fetchNpmPackage(opts) {
|
|
19
|
+
const registry = opts.registry ?? process.env.MIAODA_NPM_REGISTRY ?? DEFAULT_REGISTRY;
|
|
20
|
+
const version = opts.version ?? 'latest';
|
|
21
|
+
const pkgSpec = `${opts.packageName}@${version}`;
|
|
22
|
+
const tmpDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'miaoda-pack-'));
|
|
23
|
+
try {
|
|
24
|
+
(0, logger_1.debug)(`npm pack ${pkgSpec} --registry ${registry} → ${tmpDir}`);
|
|
25
|
+
const stdout = (0, node_child_process_1.execFileSync)('npm', ['pack', pkgSpec, '--pack-destination', tmpDir, '--json', '--registry', registry], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
26
|
+
const packInfo = JSON.parse(stdout);
|
|
27
|
+
const tgzFilename = packInfo[0]?.filename;
|
|
28
|
+
const pkgVersion = packInfo[0]?.version ?? version;
|
|
29
|
+
if (!tgzFilename) {
|
|
30
|
+
throw new error_1.AppError('NPM_PACK_FAILED', `npm pack 未返回 filename:${pkgSpec}`);
|
|
31
|
+
}
|
|
32
|
+
const tgzPath = node_path_1.default.join(tmpDir, tgzFilename);
|
|
33
|
+
const extractDir = node_path_1.default.join(tmpDir, 'extracted');
|
|
34
|
+
node_fs_1.default.mkdirSync(extractDir, { recursive: true });
|
|
35
|
+
(0, node_child_process_1.execFileSync)('tar', ['-xzf', tgzPath, '-C', extractDir], {
|
|
36
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
extractDir: node_path_1.default.join(extractDir, 'package'),
|
|
40
|
+
version: pkgVersion,
|
|
41
|
+
cleanup: () => {
|
|
42
|
+
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
48
|
+
if (err instanceof error_1.AppError)
|
|
49
|
+
throw err;
|
|
50
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
51
|
+
throw new error_1.AppError('NPM_PACK_FAILED', `抓取 npm 包失败 (${pkgSpec}): ${msg}`, {
|
|
52
|
+
next_actions: [`确认包已发布到 registry:${registry}`, `检查版本号 / dist-tag 拼写`],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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.readSparkMeta = readSparkMeta;
|
|
7
|
+
exports.writeSparkMeta = writeSparkMeta;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const SPARK_DIR = '.spark';
|
|
11
|
+
const SPARK_META_FILE = 'meta.json';
|
|
12
|
+
function readSparkMeta(appDir) {
|
|
13
|
+
const metaPath = node_path_1.default.join(appDir, SPARK_DIR, SPARK_META_FILE);
|
|
14
|
+
if (!node_fs_1.default.existsSync(metaPath))
|
|
15
|
+
return {};
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(node_fs_1.default.readFileSync(metaPath, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* merge 写入:已有字段保留,undefined 不覆盖;不再维护时间戳。
|
|
25
|
+
*/
|
|
26
|
+
function writeSparkMeta(appDir, meta) {
|
|
27
|
+
const sparkDir = node_path_1.default.join(appDir, SPARK_DIR);
|
|
28
|
+
node_fs_1.default.mkdirSync(sparkDir, { recursive: true });
|
|
29
|
+
const metaPath = node_path_1.default.join(sparkDir, SPARK_META_FILE);
|
|
30
|
+
let existing = {};
|
|
31
|
+
if (node_fs_1.default.existsSync(metaPath)) {
|
|
32
|
+
try {
|
|
33
|
+
existing = JSON.parse(node_fs_1.default.readFileSync(metaPath, 'utf-8'));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* 读不出就当空 */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const defined = Object.fromEntries(Object.entries(meta).filter(([, v]) => v !== undefined));
|
|
40
|
+
const merged = { ...existing, ...defined };
|
|
41
|
+
node_fs_1.default.writeFileSync(metaPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
42
|
+
}
|