@lark-apaas/miaoda-cli 0.1.3 → 0.1.4-alpha.abaa17f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/app/api.js +3 -3
- package/dist/api/app/schemas.js +43 -43
- package/dist/api/db/api.js +398 -55
- package/dist/api/db/client.js +155 -28
- package/dist/api/db/index.js +12 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +5 -5
- package/dist/api/deploy/index.js +12 -1
- package/dist/api/deploy/modern-types.js +23 -0
- package/dist/api/deploy/modern.js +70 -0
- package/dist/api/deploy/plugin-instances-types.js +6 -0
- package/dist/api/deploy/plugin-instances.js +30 -0
- package/dist/api/deploy/schemas.js +32 -32
- package/dist/api/file/api.js +89 -87
- package/dist/api/file/client.js +62 -22
- package/dist/api/file/detect.js +3 -3
- package/dist/api/file/index.js +2 -1
- package/dist/api/file/parsers.js +18 -7
- package/dist/api/observability/api.js +6 -6
- package/dist/api/observability/schemas.js +14 -14
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +94 -13
- package/dist/cli/commands/db/index.js +602 -54
- package/dist/cli/commands/deploy/index.js +28 -28
- package/dist/cli/commands/deploy/modern.js +48 -0
- package/dist/cli/commands/file/index.js +85 -58
- package/dist/cli/commands/index.js +31 -5
- package/dist/cli/commands/observability/index.js +69 -69
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +10 -10
- package/dist/cli/handlers/app/index.js +6 -1
- package/dist/cli/handlers/app/init.js +86 -0
- package/dist/cli/handlers/app/update.js +2 -2
- package/dist/cli/handlers/db/_destructive.js +67 -0
- package/dist/cli/handlers/db/_env.js +26 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +383 -0
- package/dist/cli/handlers/db/changelog.js +160 -0
- package/dist/cli/handlers/db/data.js +32 -31
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +234 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +413 -0
- package/dist/cli/handlers/db/schema.js +33 -33
- package/dist/cli/handlers/db/sql.js +69 -69
- package/dist/cli/handlers/deploy/deploy.js +4 -4
- package/dist/cli/handlers/deploy/error-log.js +1 -1
- package/dist/cli/handlers/deploy/get.js +3 -3
- package/dist/cli/handlers/deploy/modern.js +32 -0
- package/dist/cli/handlers/deploy/polling.js +11 -11
- package/dist/cli/handlers/file/cp.js +30 -30
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +5 -5
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +32 -30
- package/dist/cli/handlers/file/sign.js +3 -3
- package/dist/cli/handlers/file/stat.js +10 -9
- package/dist/cli/handlers/observability/analytics.js +47 -47
- package/dist/cli/handlers/observability/helpers.js +2 -2
- package/dist/cli/handlers/observability/log.js +9 -9
- package/dist/cli/handlers/observability/metric.js +26 -26
- package/dist/cli/handlers/observability/trace.js +5 -5
- package/dist/cli/handlers/plugin/plugin-local.js +53 -53
- package/dist/cli/handlers/plugin/plugin.js +15 -15
- package/dist/cli/help.js +16 -16
- package/dist/main.js +12 -12
- package/dist/services/app/init/index.js +14 -0
- package/dist/services/app/init/install.js +101 -0
- package/dist/services/app/init/steering.js +74 -0
- package/dist/services/app/init/template.js +85 -0
- package/dist/services/deploy/modern/atoms/build.js +54 -0
- package/dist/services/deploy/modern/atoms/context.js +30 -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 +192 -0
- package/dist/services/deploy/modern/check.js +58 -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 +43 -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 +29 -0
- package/dist/utils/args.js +1 -1
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +9 -9
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +4 -4
- package/dist/utils/http.js +27 -21
- package/dist/utils/index.js +3 -1
- package/dist/utils/npm-pack.js +55 -0
- package/dist/utils/output.js +67 -45
- package/dist/utils/poll.js +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spark-meta.js +48 -0
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +47 -42
- package/package.json +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
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.uploadArtifacts = uploadArtifacts;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const error_1 = require("../../../../utils/error");
|
|
12
|
+
const logger_1 = require("../../../../utils/logger");
|
|
13
|
+
const constants_1 = require("../constants");
|
|
14
|
+
const protocol_1 = require("../protocol");
|
|
15
|
+
const UPLOAD_PLAN = [
|
|
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
|
+
},
|
|
27
|
+
];
|
|
28
|
+
function resolveTosutilPath() {
|
|
29
|
+
try {
|
|
30
|
+
const resolved = (0, node_child_process_1.execFileSync)('which', ['tosutil'], { encoding: 'utf-8' }).trim();
|
|
31
|
+
if (resolved)
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* fallthrough */
|
|
36
|
+
}
|
|
37
|
+
throw new error_1.AppError('DEPLOY_TOSUTIL_MISSING', 'tosutil is not installed or not in PATH. modern deploy requires sandbox preinstalled tosutil.');
|
|
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
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 用 tosutil 把目录批量上传到 tos://bucket/prefix(STS 凭证从 config 文件读取)。
|
|
98
|
+
*/
|
|
99
|
+
function uploadDirWithCredential(tosutilPath, sourceDir, cred, label) {
|
|
100
|
+
const entries = node_fs_1.default.readdirSync(sourceDir);
|
|
101
|
+
if (entries.length === 0) {
|
|
102
|
+
(0, logger_1.debug)(`upload: skip empty dir ${sourceDir}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
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
|
+
}
|
|
143
|
+
const succeedMatch = /Succeed count is:\s*(\d+)/.exec(output);
|
|
144
|
+
const failedMatch = /Failed count is:\s*(\d+)/.exec(output);
|
|
145
|
+
const succeedCount = succeedMatch ? Number.parseInt(succeedMatch[1], 10) : 0;
|
|
146
|
+
const failedCount = failedMatch ? Number.parseInt(failedMatch[1], 10) : 0;
|
|
147
|
+
if (failedCount > 0) {
|
|
148
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_FAILED', `Upload failed: ${String(failedCount)} files failed, ${String(succeedCount)} succeeded. source=${sourceDir}`);
|
|
149
|
+
}
|
|
150
|
+
if (succeedCount === 0) {
|
|
151
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', `Upload failed: 0 files uploaded. source=${sourceDir}. Check tosutil credentials and bucket permissions.`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
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
|
|
160
|
+
*
|
|
161
|
+
* 目录不存在的可选项跳过;output 不存在或凭证缺失抛错。
|
|
162
|
+
*/
|
|
163
|
+
function uploadArtifacts(opts) {
|
|
164
|
+
const tosutilPath = resolveTosutilPath();
|
|
165
|
+
const distDir = node_path_1.default.join(opts.projectDir, constants_1.DIST_DIR);
|
|
166
|
+
let uploaded = 0;
|
|
167
|
+
for (const target of UPLOAD_PLAN) {
|
|
168
|
+
const localPath = node_path_1.default.join(distDir, target.dir);
|
|
169
|
+
const exists = node_fs_1.default.existsSync(localPath);
|
|
170
|
+
if (!exists) {
|
|
171
|
+
if (target.required) {
|
|
172
|
+
throw new error_1.AppError('DEPLOY_NO_BUILD_OUTPUT', `Required directory missing: ${constants_1.DIST_DIR}/${target.dir}`);
|
|
173
|
+
}
|
|
174
|
+
(0, logger_1.debug)(`upload: dir not present, skipping ${target.dir}`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const credRaw = target.required
|
|
178
|
+
? (0, protocol_1.requireDataKey)(opts.data, target.dataKey)
|
|
179
|
+
: (0, protocol_1.optionalDataKey)(opts.data, target.dataKey);
|
|
180
|
+
if (credRaw === undefined) {
|
|
181
|
+
(0, logger_1.debug)(`upload: ${target.dir} exists but no credential configured, skipping`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const cred = parseCredential(credRaw, target.dataKey);
|
|
185
|
+
(0, logger_1.log)('deploy', `Uploading ${target.dir}...`);
|
|
186
|
+
uploadDirWithCredential(tosutilPath, localPath, cred, target.dir);
|
|
187
|
+
uploaded++;
|
|
188
|
+
}
|
|
189
|
+
if (uploaded === 0) {
|
|
190
|
+
throw new error_1.AppError('DEPLOY_UPLOAD_EMPTY', 'No artifacts uploaded — check build output.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
const missing = [];
|
|
28
|
+
if (!meta.appId)
|
|
29
|
+
missing.push('appId');
|
|
30
|
+
if (!meta.template)
|
|
31
|
+
missing.push('template');
|
|
32
|
+
if (missing.length > 0) {
|
|
33
|
+
issues.push(`.spark/meta.json missing fields: ${missing.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
issues.push('.spark/meta.json is not valid JSON.');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const pkgPath = node_path_1.default.join(projectDir, 'package.json');
|
|
41
|
+
if (!node_fs_1.default.existsSync(pkgPath)) {
|
|
42
|
+
issues.push('package.json not found.');
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
try {
|
|
46
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
47
|
+
if (!pkg.scripts?.build) {
|
|
48
|
+
issues.push('package.json missing "build" script.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
issues.push('package.json is not valid JSON.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (issues.length > 0) {
|
|
56
|
+
throw new error_1.AppError('DEPLOY_PROJECT_CHECK_FAILED', `Project check failed:\n${issues.map((i) => ` - ${i}`).join('\n')}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -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);
|
|
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
|
+
(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
|
+
}
|
|
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, { ...ctx.meta, 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,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DataKey = void 0;
|
|
4
|
+
exports.requireDataKey = requireDataKey;
|
|
5
|
+
exports.optionalDataKey = optionalDataKey;
|
|
6
|
+
const error_1 = require("../../../utils/error");
|
|
7
|
+
/**
|
|
8
|
+
* modern deploy 对外协议常量集中地(SSOT)。
|
|
9
|
+
*
|
|
10
|
+
* preRelease 接口返回 `data` 动态配置 map,其 key 由后端按 template_key 在 tcc
|
|
11
|
+
* 中下发。CLI 端通过本文件枚举的 DataKey 访问 `data`,避免拼写错误散布,
|
|
12
|
+
* 同时让协议演进(新增 key / 重命名)只需要改这一个文件。
|
|
13
|
+
*/
|
|
14
|
+
exports.DataKey = {
|
|
15
|
+
// ── CDN 前缀(构建期注入到 build env,用于客户端资源访问) ──
|
|
16
|
+
/** dist/output_resource 对应的 CDN 域名前缀 */
|
|
17
|
+
RESOURCE_CDN_PREFIX: 'resource_cdn_prefix',
|
|
18
|
+
/** dist/output_static 对应的 CDN 域名前缀 */
|
|
19
|
+
STATIC_CDN_PREFIX: 'static_cdn_prefix',
|
|
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',
|
|
30
|
+
};
|
|
31
|
+
/** 取必传 key,缺失抛 AppError(带 key 名,便于后端 tcc 配置排查) */
|
|
32
|
+
function requireDataKey(data, key) {
|
|
33
|
+
const v = data[key];
|
|
34
|
+
if (v === undefined || v === '') {
|
|
35
|
+
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`);
|
|
36
|
+
}
|
|
37
|
+
return v;
|
|
38
|
+
}
|
|
39
|
+
/** 取可选 key,缺失返回 undefined(不抛错) */
|
|
40
|
+
function optionalDataKey(data, key) {
|
|
41
|
+
const v = data[key];
|
|
42
|
+
return v === undefined || v === '' ? undefined : v;
|
|
43
|
+
}
|
|
@@ -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,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveTemplateKey = resolveTemplateKey;
|
|
4
|
+
const error_1 = require("../../../utils/error");
|
|
5
|
+
/**
|
|
6
|
+
* 本地 stack 短名 → 后端发布模板 key 的映射。
|
|
7
|
+
*
|
|
8
|
+
* .spark/meta.json.template 存的是脚手架模板短名(vite-react / vue-react / html ...),
|
|
9
|
+
* 而服务端 preRelease 接受的 templateKey 是"部署类别"维度,由后端 tcc 注册。
|
|
10
|
+
* 同一类别的多个脚手架共享一个部署 key(典型:所有纯前端 stack → client_local_deploy)。
|
|
11
|
+
*
|
|
12
|
+
* 加新 stack:在表里加一行;新增类别同步跟后端 tcc 配置对齐。
|
|
13
|
+
*/
|
|
14
|
+
const TEMPLATE_KEY_MAP = {
|
|
15
|
+
'vite-react': 'client_local_deploy',
|
|
16
|
+
'vue-react': 'client_local_deploy',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* 把本地 stack 短名映射到后端 templateKey。未配置的 stack 抛 AppError,
|
|
20
|
+
* 避免拿原始短名直接打过去导致后端报"empty"或"出错了"等含糊错误。
|
|
21
|
+
*/
|
|
22
|
+
function resolveTemplateKey(template) {
|
|
23
|
+
const key = TEMPLATE_KEY_MAP[template];
|
|
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;
|
|
29
|
+
}
|
package/dist/utils/args.js
CHANGED
package/dist/utils/colors.js
CHANGED
|
@@ -46,8 +46,8 @@ const picocolors_1 = require("picocolors");
|
|
|
46
46
|
*/
|
|
47
47
|
function shouldColorize() {
|
|
48
48
|
const env = process.env;
|
|
49
|
-
const noColor = env.NO_COLOR != null && env.NO_COLOR !==
|
|
50
|
-
const forceColor = env.FORCE_COLOR != null && env.FORCE_COLOR !==
|
|
49
|
+
const noColor = env.NO_COLOR != null && env.NO_COLOR !== '';
|
|
50
|
+
const forceColor = env.FORCE_COLOR != null && env.FORCE_COLOR !== '';
|
|
51
51
|
const isTTY = process.stdout.isTTY === true;
|
|
52
52
|
return forceColor || (!noColor && isTTY);
|
|
53
53
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -6,7 +6,7 @@ exports.resetConfig = resetConfig;
|
|
|
6
6
|
exports.initConfigFromOpts = initConfigFromOpts;
|
|
7
7
|
const defaults = {
|
|
8
8
|
json: false,
|
|
9
|
-
output:
|
|
9
|
+
output: 'pretty',
|
|
10
10
|
verbose: false,
|
|
11
11
|
};
|
|
12
12
|
let config = { ...defaults };
|
|
@@ -25,7 +25,7 @@ function initConfigFromOpts(opts) {
|
|
|
25
25
|
const output = opts.output;
|
|
26
26
|
setConfig({
|
|
27
27
|
json: json ?? defaults.json,
|
|
28
|
-
output: output ===
|
|
28
|
+
output: output === 'json' || json ? 'json' : defaults.output,
|
|
29
29
|
verbose: opts.verbose ?? defaults.verbose,
|
|
30
30
|
});
|
|
31
31
|
}
|
|
@@ -11,16 +11,16 @@ const error_1 = require("./error");
|
|
|
11
11
|
*/
|
|
12
12
|
function mapDevopsError(bizCode, message) {
|
|
13
13
|
switch (bizCode) {
|
|
14
|
-
case
|
|
15
|
-
case
|
|
16
|
-
return new error_1.AppError(
|
|
17
|
-
next_actions: [
|
|
14
|
+
case '400002577':
|
|
15
|
+
case '400002569':
|
|
16
|
+
return new error_1.AppError('APP_NOT_FOUND', message, {
|
|
17
|
+
next_actions: ['Run `miaoda app list` to see available apps'],
|
|
18
18
|
});
|
|
19
|
-
case
|
|
20
|
-
return new error_1.AppError(
|
|
21
|
-
case
|
|
22
|
-
return new error_1.AppError(
|
|
23
|
-
next_actions: [
|
|
19
|
+
case '400002579':
|
|
20
|
+
return new error_1.AppError('APP_PERMISSION_DENIED', message);
|
|
21
|
+
case '400002575':
|
|
22
|
+
return new error_1.AppError('INVALID_ARG_VALUE', message, {
|
|
23
|
+
next_actions: ['缩短 --name / --description 后重试(后端有长度限制)'],
|
|
24
24
|
});
|
|
25
25
|
default:
|
|
26
26
|
return undefined;
|
package/dist/utils/error.js
CHANGED
|
@@ -16,7 +16,7 @@ class AppError extends Error {
|
|
|
16
16
|
rolled_back;
|
|
17
17
|
constructor(code, message, opts) {
|
|
18
18
|
super(message);
|
|
19
|
-
this.name =
|
|
19
|
+
this.name = 'AppError';
|
|
20
20
|
this.code = code;
|
|
21
21
|
this.retryable = opts?.retryable ?? false;
|
|
22
22
|
this.next_actions = opts?.next_actions ?? [];
|
|
@@ -41,7 +41,7 @@ class HttpError extends AppError {
|
|
|
41
41
|
url;
|
|
42
42
|
constructor(status, url, message, opts) {
|
|
43
43
|
super(`HTTP_${String(status)}`, message, opts ?? { retryable: status >= 500 || status === 429 });
|
|
44
|
-
this.name =
|
|
44
|
+
this.name = 'HttpError';
|
|
45
45
|
this.status = status;
|
|
46
46
|
this.url = url;
|
|
47
47
|
}
|
package/dist/utils/git.js
CHANGED
|
@@ -11,10 +11,10 @@ const node_child_process_1 = require("node:child_process");
|
|
|
11
11
|
function getCurrentGitBranch(cwd = process.cwd()) {
|
|
12
12
|
let result;
|
|
13
13
|
try {
|
|
14
|
-
result = (0, node_child_process_1.spawnSync)(
|
|
14
|
+
result = (0, node_child_process_1.spawnSync)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
15
15
|
cwd,
|
|
16
|
-
encoding:
|
|
17
|
-
stdio: [
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
catch {
|
|
@@ -23,7 +23,7 @@ function getCurrentGitBranch(cwd = process.cwd()) {
|
|
|
23
23
|
if (result.status !== 0)
|
|
24
24
|
return undefined;
|
|
25
25
|
const branch = result.stdout.trim();
|
|
26
|
-
if (!branch || branch ===
|
|
26
|
+
if (!branch || branch === 'HEAD')
|
|
27
27
|
return undefined;
|
|
28
28
|
return branch;
|
|
29
29
|
}
|