@cloudbase/manager-node 5.8.2 → 5.8.4
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/CHANGELOG.md +15 -0
- package/lib/database/index.js +11 -6
- package/lib/deploy/DeployOrchestrator.js +170 -278
- package/lib/deploy/FunctionDeployer.js +310 -52
- package/lib/deploy/StaticDeployer.js +7 -1
- package/lib/deploy/function/artifact.js +55 -41
- package/lib/deploy/function/builders/cloud.js +58 -359
- package/lib/deploy/function/cam-preflight.js +2 -1
- package/lib/deploy/function/cloud-build-service.js +2 -16
- package/lib/deploy/function/config-guard.js +210 -55
- package/lib/deploy/function/enterprise-tcr.js +195 -0
- package/lib/deploy/function/image-tag.js +31 -0
- package/lib/deploy/function/local-builder.js +178 -32
- package/lib/deploy/function/personal-tcr.js +153 -0
- package/lib/deploy/function/preflight.js +3 -2
- package/lib/deploy/types.js +11 -0
- package/lib/hosting/index.js +27 -0
- package/lib/projectValidator/index.js +22 -23
- package/package.json +2 -2
- package/types/database/index.d.ts +1 -0
- package/types/deploy/DeployOrchestrator.d.ts +19 -25
- package/types/deploy/FunctionDeployer.d.ts +35 -3
- package/types/deploy/StaticDeployer.d.ts +11 -0
- package/types/deploy/function/artifact.d.ts +0 -5
- package/types/deploy/function/builders/cloud.d.ts +6 -39
- package/types/deploy/function/config-guard.d.ts +18 -0
- package/types/deploy/function/enterprise-tcr.d.ts +32 -0
- package/types/deploy/function/image-tag.d.ts +10 -0
- package/types/deploy/function/local-builder.d.ts +35 -6
- package/types/deploy/function/personal-tcr.d.ts +35 -0
- package/types/deploy/types.d.ts +110 -28
- package/types/hosting/index.d.ts +13 -0
- package/types/projectValidator/index.d.ts +3 -2
- package/types/projectValidator/types.d.ts +0 -1
- package/lib/deploy/StateStore.js +0 -328
- package/types/deploy/StateStore.d.ts +0 -174
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## @cloudbase/manager-node v5.8.3 版本发布 🚀(2026-08-28)
|
|
2
|
+
|
|
3
|
+
- 新增 完善云函数镜像部署闭环
|
|
4
|
+
- 新增 support consistency deploy options in hosting declarative deploy
|
|
5
|
+
- 修复 仅 cdn/空 domainType 调用 CDN 接口
|
|
6
|
+
- 修复 handle missing document database config
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## @cloudbase/manager-node v5.8.2 版本发布 🚀(2026-08-25)
|
|
11
|
+
|
|
12
|
+
- 新增 support requestFn injection to bypass TC3 signing
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
1
16
|
## @cloudbase/manager-node v5.8.1 版本发布 🚀(2026-08-21)
|
|
2
17
|
|
|
3
18
|
- 新增 支持一致性发布
|
package/lib/database/index.js
CHANGED
|
@@ -40,17 +40,13 @@ class DatabaseService {
|
|
|
40
40
|
return this.environment;
|
|
41
41
|
}
|
|
42
42
|
getDatabaseConfig() {
|
|
43
|
-
const currEnv = this.environment;
|
|
44
|
-
const { Databases } = currEnv.lazyEnvironmentConfig;
|
|
45
43
|
return {
|
|
46
|
-
Tag:
|
|
44
|
+
Tag: this.getDefaultDatabaseInstanceId()
|
|
47
45
|
};
|
|
48
46
|
}
|
|
49
47
|
getMongoConnector() {
|
|
50
|
-
const currEnv = this.environment;
|
|
51
|
-
const { Databases } = currEnv.lazyEnvironmentConfig;
|
|
52
48
|
return {
|
|
53
|
-
DatabaseName:
|
|
49
|
+
DatabaseName: this.getDefaultDatabaseInstanceId(),
|
|
54
50
|
InstanceId: 'flexdb'
|
|
55
51
|
};
|
|
56
52
|
}
|
|
@@ -500,6 +496,15 @@ class DatabaseService {
|
|
|
500
496
|
TaskId
|
|
501
497
|
});
|
|
502
498
|
}
|
|
499
|
+
getDefaultDatabaseInstanceId() {
|
|
500
|
+
var _a, _b, _c;
|
|
501
|
+
const currEnv = this.environment;
|
|
502
|
+
const instanceId = (_c = (_b = (_a = currEnv.lazyEnvironmentConfig) === null || _a === void 0 ? void 0 : _a.Databases) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.InstanceId;
|
|
503
|
+
if (!instanceId) {
|
|
504
|
+
throw new error_1.CloudBaseError('当前环境未开通文档型数据库');
|
|
505
|
+
}
|
|
506
|
+
return instanceId;
|
|
507
|
+
}
|
|
503
508
|
}
|
|
504
509
|
exports.DatabaseService = DatabaseService;
|
|
505
510
|
DatabaseService.tcbServiceVersion = {
|
|
@@ -10,8 +10,7 @@ const DatabaseDeployer_1 = require("./DatabaseDeployer");
|
|
|
10
10
|
const FunctionDeployer_1 = require("./FunctionDeployer");
|
|
11
11
|
const StaticDeployer_1 = require("./StaticDeployer");
|
|
12
12
|
const GatewayDeployer_1 = require("./GatewayDeployer");
|
|
13
|
-
const
|
|
14
|
-
const framework_1 = require("./framework");
|
|
13
|
+
const config_guard_1 = require("./function/config-guard");
|
|
15
14
|
const parallel_1 = require("../utils/parallel");
|
|
16
15
|
/** 部署顺序(按依赖:database 最先,函数依赖新 Schema;gateway 最后,依赖函数/hosting) */
|
|
17
16
|
const DEPLOY_ORDER = ['database', 'functions', 'app', 'hosting', 'gateway'];
|
|
@@ -32,7 +31,8 @@ class DeployOrchestrator {
|
|
|
32
31
|
* 计算部署计划(dry-run 输出)
|
|
33
32
|
*/
|
|
34
33
|
async deployPlan(options) {
|
|
35
|
-
|
|
34
|
+
const config = this.normalizeFunctionConfigs(options.config, options.envId, options.cwd);
|
|
35
|
+
return this.buildPlan(config, options);
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* 执行部署
|
|
@@ -44,8 +44,9 @@ class DeployOrchestrator {
|
|
|
44
44
|
* - database 失败始终强制中断(无论是否 continueOnError)
|
|
45
45
|
*/
|
|
46
46
|
async deploy(options) {
|
|
47
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
48
|
-
const { config, envId, dryRun, cwd = process.cwd(), log, concurrency =
|
|
47
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
48
|
+
const { config: inputConfig, envId, dryRun, cwd = process.cwd(), log, concurrency = 5, continueOnError = false } = options;
|
|
49
|
+
const config = this.normalizeFunctionConfigs(inputConfig, envId, cwd);
|
|
49
50
|
// 1. 轻量 inline 守卫(validateProject 未接入时的兜底)
|
|
50
51
|
this.guardConfig(config);
|
|
51
52
|
const plan = await this.buildPlan(config, options);
|
|
@@ -59,114 +60,157 @@ class DeployOrchestrator {
|
|
|
59
60
|
// gateway 段强制串行:executeStep 的 gateway 分支部署 config.gateway.routes 全部路由
|
|
60
61
|
// (逐路由 plan item 仅用于展示 diff),并发执行会对同一批路由发起重复全量部署(竞态 create/modify 路由、bindCustomDomain)
|
|
61
62
|
const globalLimit = Math.max(1, Math.min(Number(concurrency) || 1, 20));
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
(_d = log === null || log === void 0 ? void 0 : log.info) === null || _d === void 0 ? void 0 : _d.call(log, `[${item.type}] ${item.name}: ${item.action}`);
|
|
78
|
-
// conflict:数据库迁移冲突 → 中断整个部署(后续资源可能依赖新 Schema)
|
|
79
|
-
if (item.status === 'conflict') {
|
|
80
|
-
const message = '检测到冲突,已中断部署';
|
|
81
|
-
(_e = log === null || log === void 0 ? void 0 : log.error) === null || _e === void 0 ? void 0 : _e.call(log, `[${item.type}] ${item.name} ${message}`);
|
|
82
|
-
results.push({ type: item.type, name: item.name, ok: false, error: message });
|
|
83
|
-
if (item.type === 'database') {
|
|
84
|
-
return { plan, results };
|
|
85
|
-
}
|
|
86
|
-
segmentFailed = true;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
// 覆盖确认:仅函数已存在(update)时触发,对齐 tcb fn deploy 的 FunctionConflictChecker 语义
|
|
90
|
-
// yes=true 直接放行;注入 confirmUpdate 按回调决定;两者都无 → 保守跳过(no-confirm)
|
|
91
|
-
if (item.type === 'functions' && item.status === 'update') {
|
|
92
|
-
let confirmed = false;
|
|
93
|
-
if (options.yes) {
|
|
94
|
-
confirmed = true;
|
|
95
|
-
}
|
|
96
|
-
else if (options.confirmUpdate) {
|
|
97
|
-
confirmed = await options.confirmUpdate(item);
|
|
98
|
-
}
|
|
99
|
-
if (!confirmed) {
|
|
100
|
-
const reason = options.confirmUpdate
|
|
101
|
-
? 'skipped-by-user'
|
|
102
|
-
: 'no-confirm';
|
|
103
|
-
(_f = log === null || log === void 0 ? void 0 : log.warn) === null || _f === void 0 ? void 0 : _f.call(log, `[${item.type}] ${item.name} 跳过覆盖更新(${reason})`);
|
|
104
|
-
results.push({ type: item.type, name: item.name, ok: false, reason });
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
toExecute.push(item);
|
|
63
|
+
for (const segment of segments) {
|
|
64
|
+
// 段内并行度:gateway 段固定 1(全量部署语义);其余段用全局 concurrency
|
|
65
|
+
const limit = ((_b = segment[0]) === null || _b === void 0 ? void 0 : _b.type) === 'gateway' ? 1 : globalLimit;
|
|
66
|
+
// 段内预判定(顺序执行,保留交互/冲突中断语义)
|
|
67
|
+
const toExecute = [];
|
|
68
|
+
let segmentFailed = false;
|
|
69
|
+
let segmentDbFailed = false;
|
|
70
|
+
for (const item of segment) {
|
|
71
|
+
// skip:产物/配置未变更,静默跳过(不执行、不询问)
|
|
72
|
+
if (item.status === 'skip') {
|
|
73
|
+
(_c = log === null || log === void 0 ? void 0 : log.info) === null || _c === void 0 ? void 0 : _c.call(log, `[${item.type}] ${item.name}: ${item.action}`);
|
|
74
|
+
results.push({ type: item.type, name: item.name, ok: true, reason: 'skip' });
|
|
75
|
+
continue;
|
|
109
76
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
77
|
+
(_d = log === null || log === void 0 ? void 0 : log.info) === null || _d === void 0 ? void 0 : _d.call(log, `[${item.type}] ${item.name}: ${item.action}`);
|
|
78
|
+
// conflict:数据库迁移冲突 → 中断整个部署(后续资源可能依赖新 Schema)
|
|
79
|
+
if (item.status === 'conflict') {
|
|
80
|
+
const message = '检测到冲突,已中断部署';
|
|
81
|
+
(_e = log === null || log === void 0 ? void 0 : log.error) === null || _e === void 0 ? void 0 : _e.call(log, `[${item.type}] ${item.name} ${message}`);
|
|
82
|
+
results.push({ type: item.type, name: item.name, ok: false, error: message });
|
|
83
|
+
if (item.type === 'database') {
|
|
113
84
|
return { plan, results };
|
|
114
85
|
}
|
|
86
|
+
segmentFailed = true;
|
|
115
87
|
continue;
|
|
116
88
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
results.push({ type: item.type, name: item.name, ok: true, url: stepResult });
|
|
124
|
-
}
|
|
125
|
-
catch (e) {
|
|
126
|
-
const message = (e === null || e === void 0 ? void 0 : e.message) || String(e);
|
|
127
|
-
const requestId = (e === null || e === void 0 ? void 0 : e.requestId) ||
|
|
128
|
-
(e === null || e === void 0 ? void 0 : e.RequestId) ||
|
|
129
|
-
((_a = e === null || e === void 0 ? void 0 : e.original) === null || _a === void 0 ? void 0 : _a.requestId) ||
|
|
130
|
-
((_b = e === null || e === void 0 ? void 0 : e.original) === null || _b === void 0 ? void 0 : _b.RequestId) ||
|
|
131
|
-
((_c = e === null || e === void 0 ? void 0 : e.response) === null || _c === void 0 ? void 0 : _c.requestId) ||
|
|
132
|
-
((_d = e === null || e === void 0 ? void 0 : e.response) === null || _d === void 0 ? void 0 : _d.RequestId);
|
|
133
|
-
(_e = log === null || log === void 0 ? void 0 : log.error) === null || _e === void 0 ? void 0 : _e.call(log, `[${item.type}] ${item.name} 部署失败: ${message}`);
|
|
134
|
-
results.push(Object.assign({ type: item.type, name: item.name, ok: false, error: message }, (requestId ? { requestId } : {})));
|
|
135
|
-
segmentFailed = true;
|
|
136
|
-
if (item.type === 'database') {
|
|
137
|
-
segmentDbFailed = true;
|
|
138
|
-
}
|
|
89
|
+
// 覆盖确认:仅函数已存在(update)时触发,对齐 tcb fn deploy 的 FunctionConflictChecker 语义
|
|
90
|
+
// yes=true 直接放行;注入 confirmUpdate 按回调决定;两者都无 → 保守跳过(no-confirm)
|
|
91
|
+
if (item.type === 'functions' && item.status === 'update') {
|
|
92
|
+
let confirmed = false;
|
|
93
|
+
if (options.yes) {
|
|
94
|
+
confirmed = true;
|
|
139
95
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
await controller.run();
|
|
143
|
-
// 段内失败中断决策
|
|
144
|
-
if (segmentFailed) {
|
|
145
|
-
if (segmentDbFailed) {
|
|
146
|
-
(_g = log === null || log === void 0 ? void 0 : log.error) === null || _g === void 0 ? void 0 : _g.call(log, '数据库迁移失败,中断部署');
|
|
147
|
-
return { plan, results };
|
|
96
|
+
else if (options.confirmUpdate) {
|
|
97
|
+
confirmed = await options.confirmUpdate(item);
|
|
148
98
|
}
|
|
149
|
-
if (!
|
|
150
|
-
|
|
151
|
-
|
|
99
|
+
if (!confirmed) {
|
|
100
|
+
const reason = options.confirmUpdate
|
|
101
|
+
? 'skipped-by-user'
|
|
102
|
+
: 'no-confirm';
|
|
103
|
+
(_f = log === null || log === void 0 ? void 0 : log.warn) === null || _f === void 0 ? void 0 : _f.call(log, `[${item.type}] ${item.name} 跳过覆盖更新(${reason})`);
|
|
104
|
+
results.push({ type: item.type, name: item.name, ok: false, reason });
|
|
105
|
+
continue;
|
|
152
106
|
}
|
|
153
107
|
}
|
|
108
|
+
toExecute.push(item);
|
|
154
109
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
110
|
+
if (toExecute.length === 0) {
|
|
111
|
+
// 段内无实际执行项:若本段已因 conflict 标记失败且 fail-fast,则中断
|
|
112
|
+
if (segmentFailed && !continueOnError) {
|
|
113
|
+
return { plan, results };
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// 段内并行执行
|
|
118
|
+
const controller = new parallel_1.AsyncTaskParallelController(limit);
|
|
119
|
+
const tasks = toExecute.map((item) => async () => {
|
|
120
|
+
var _a, _b, _c, _d, _e;
|
|
160
121
|
try {
|
|
161
|
-
await this.
|
|
122
|
+
const stepResult = await this.executeStep({
|
|
123
|
+
item,
|
|
124
|
+
config,
|
|
125
|
+
envId,
|
|
126
|
+
cwd,
|
|
127
|
+
onProgress: options.onProgress
|
|
128
|
+
});
|
|
129
|
+
if (item.type === 'functions') {
|
|
130
|
+
results.push({
|
|
131
|
+
type: item.type,
|
|
132
|
+
name: item.name,
|
|
133
|
+
ok: true,
|
|
134
|
+
url: stepResult === null || stepResult === void 0 ? void 0 : stepResult.url,
|
|
135
|
+
detail: stepResult
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
results.push({ type: item.type, name: item.name, ok: true, url: stepResult });
|
|
140
|
+
}
|
|
162
141
|
}
|
|
163
142
|
catch (e) {
|
|
164
|
-
|
|
143
|
+
const message = (e === null || e === void 0 ? void 0 : e.message) || String(e);
|
|
144
|
+
const requestId = (e === null || e === void 0 ? void 0 : e.requestId) ||
|
|
145
|
+
(e === null || e === void 0 ? void 0 : e.RequestId) ||
|
|
146
|
+
((_a = e === null || e === void 0 ? void 0 : e.original) === null || _a === void 0 ? void 0 : _a.requestId) ||
|
|
147
|
+
((_b = e === null || e === void 0 ? void 0 : e.original) === null || _b === void 0 ? void 0 : _b.RequestId) ||
|
|
148
|
+
((_c = e === null || e === void 0 ? void 0 : e.response) === null || _c === void 0 ? void 0 : _c.requestId) ||
|
|
149
|
+
((_d = e === null || e === void 0 ? void 0 : e.response) === null || _d === void 0 ? void 0 : _d.RequestId);
|
|
150
|
+
if (!(item.type === 'functions' && options.onProgress)) {
|
|
151
|
+
(_e = log === null || log === void 0 ? void 0 : log.error) === null || _e === void 0 ? void 0 : _e.call(log, `[${item.type}] ${item.name} 部署失败: ${message}`);
|
|
152
|
+
}
|
|
153
|
+
results.push(Object.assign(Object.assign(Object.assign({ type: item.type, name: item.name, ok: false, error: message }, ((e === null || e === void 0 ? void 0 : e.code) ? { errorCode: e.code } : {})), ((e === null || e === void 0 ? void 0 : e.failedStage) ? { failedStage: e.failedStage } : {})), (requestId ? { requestId } : {})));
|
|
154
|
+
segmentFailed = true;
|
|
155
|
+
if (item.type === 'database') {
|
|
156
|
+
segmentDbFailed = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
controller.loadTasks(tasks);
|
|
161
|
+
await controller.run();
|
|
162
|
+
// 段内失败中断决策
|
|
163
|
+
if (segmentFailed) {
|
|
164
|
+
if (segmentDbFailed) {
|
|
165
|
+
(_g = log === null || log === void 0 ? void 0 : log.error) === null || _g === void 0 ? void 0 : _g.call(log, '数据库迁移失败,中断部署');
|
|
166
|
+
return { plan, results };
|
|
167
|
+
}
|
|
168
|
+
if (!continueOnError) {
|
|
169
|
+
(_h = log === null || log === void 0 ? void 0 : log.error) === null || _h === void 0 ? void 0 : _h.call(log, '存在资源部署失败,中断部署');
|
|
170
|
+
return { plan, results };
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
174
|
return { plan, results };
|
|
169
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* 归一化声明式函数配置:直接消费 cloudbaserc 的嵌套形状(imageConfig.build),
|
|
178
|
+
* 不做字段平铺映射;非 HTTP 函数原样返回。
|
|
179
|
+
*/
|
|
180
|
+
normalizeFunctionConfigs(config, envId, cwd = process.cwd()) {
|
|
181
|
+
if (!Array.isArray(config === null || config === void 0 ? void 0 : config.functions)) {
|
|
182
|
+
return config;
|
|
183
|
+
}
|
|
184
|
+
const functionRootPath = path_1.default.resolve(cwd, config.functionRoot || 'functions');
|
|
185
|
+
return Object.assign(Object.assign({}, config), { functions: config.functions.map((func) => {
|
|
186
|
+
var _a, _b;
|
|
187
|
+
// 直接消费 cloudbaserc 的嵌套形状(imageConfig.build),不再做字段平铺映射。
|
|
188
|
+
// 非镜像/构建策略由 prepareMinimalDeployConfig 内部识别并原样返回。
|
|
189
|
+
if ((func === null || func === void 0 ? void 0 : func.type) !== 'HTTP') {
|
|
190
|
+
return func;
|
|
191
|
+
}
|
|
192
|
+
const funcAny = func;
|
|
193
|
+
const explicitCwd = (_b = (_a = funcAny.imageConfig) === null || _a === void 0 ? void 0 : _a.build) === null || _b === void 0 ? void 0 : _b.cwd;
|
|
194
|
+
const dir = funcAny.dir;
|
|
195
|
+
// 构建上下文根解析优先级:显式 build.cwd > dir > functionRoot/name。
|
|
196
|
+
// 相对路径一律以部署传入的项目 cwd 为基准解析为绝对路径,避免 preflight
|
|
197
|
+
// 与云端构建(builders/cloud.ts)以进程 CWD 兜底导致目录错位。
|
|
198
|
+
const resolvedContextRoot = typeof explicitCwd === 'string' && explicitCwd.trim()
|
|
199
|
+
? path_1.default.resolve(cwd, explicitCwd)
|
|
200
|
+
: typeof dir === 'string' && dir.trim()
|
|
201
|
+
? path_1.default.resolve(cwd, dir)
|
|
202
|
+
: undefined;
|
|
203
|
+
// 显式 build.cwd 归一为绝对路径写回,避免 applyMinimalDeployConfigDefaults
|
|
204
|
+
// 因 build.cwd 已存在而保留原始相对值。
|
|
205
|
+
const normalizedFunc = resolvedContextRoot && typeof explicitCwd === 'string' && explicitCwd.trim()
|
|
206
|
+
? Object.assign(Object.assign({}, funcAny), { imageConfig: Object.assign(Object.assign({}, funcAny.imageConfig), { build: Object.assign(Object.assign({}, funcAny.imageConfig.build), { cwd: resolvedContextRoot }) }) }) : func;
|
|
207
|
+
return (0, config_guard_1.prepareMinimalDeployConfig)(normalizedFunc, {
|
|
208
|
+
envId,
|
|
209
|
+
functionPath: resolvedContextRoot,
|
|
210
|
+
functionRootPath
|
|
211
|
+
}).config;
|
|
212
|
+
}) });
|
|
213
|
+
}
|
|
170
214
|
/**
|
|
171
215
|
* 将扁平 plan 按「连续相同资源类型」切成有序 segments
|
|
172
216
|
*
|
|
@@ -194,91 +238,19 @@ class DeployOrchestrator {
|
|
|
194
238
|
}
|
|
195
239
|
return segments;
|
|
196
240
|
}
|
|
197
|
-
/**
|
|
198
|
-
* 部署成功后写回本地 state 快照
|
|
199
|
-
*
|
|
200
|
-
* - hosting:成功部署/未变更(skip)的项重算产物指纹;失败项保留旧快照
|
|
201
|
-
* - app:成功部署写 buildAppConfig 快照;skip 复用旧快照
|
|
202
|
-
* - gateway:保留旧快照(本轮不做路由级实时快照)
|
|
203
|
-
*/
|
|
204
|
-
async persistState(options) {
|
|
205
|
-
var _a, _b, _c, _d, _e, _f;
|
|
206
|
-
const { config, plan, results, cwd, envId } = options;
|
|
207
|
-
const stateStore = new StateStore_1.StateStore({ cwd });
|
|
208
|
-
const prev = stateStore.read();
|
|
209
|
-
// hosting
|
|
210
|
-
const hostingEntries = [];
|
|
211
|
-
for (const h of config.hosting || []) {
|
|
212
|
-
const name = h.name || 'default';
|
|
213
|
-
const planItem = plan.find(p => p.type === 'hosting' && p.name === name);
|
|
214
|
-
const result = results.find(r => r.type === 'hosting' && r.name === name);
|
|
215
|
-
if (!result || !result.ok) {
|
|
216
|
-
// 未执行(fail-fast 中断,results 中无对应条目)或部署失败:
|
|
217
|
-
// 保留旧快照(下次 diff 仍会检测到差异)
|
|
218
|
-
const prevEntry = (_b = (_a = prev === null || prev === void 0 ? void 0 : prev.resources) === null || _a === void 0 ? void 0 : _a.hosting) === null || _b === void 0 ? void 0 : _b.find(s => s.name === name);
|
|
219
|
-
if (prevEntry) {
|
|
220
|
-
hostingEntries.push(prevEntry);
|
|
221
|
-
}
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
if ((planItem === null || planItem === void 0 ? void 0 : planItem.status) === 'skip') {
|
|
225
|
-
const prevEntry = (_d = (_c = prev === null || prev === void 0 ? void 0 : prev.resources) === null || _c === void 0 ? void 0 : _c.hosting) === null || _d === void 0 ? void 0 : _d.find(s => s.name === name);
|
|
226
|
-
if (prevEntry) {
|
|
227
|
-
hostingEntries.push(prevEntry);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// 成功部署:重算当前指纹(含 size,供下次 diff 做 size 预判)
|
|
232
|
-
// 注意:deployHosting 可能执行本地构建,产物已更新,必须独立重算而非复用 plan 指纹
|
|
233
|
-
const root = path_1.default.resolve(cwd, h.root || '.');
|
|
234
|
-
// outputDir 与 StaticDeployer.deployHosting 完全复用同一解析逻辑
|
|
235
|
-
const outputDir = (0, framework_1.resolveHostingOutputDir)(h, root);
|
|
236
|
-
try {
|
|
237
|
-
const files = await stateStore.computeHostingFingerprints(outputDir, h.ignore || []);
|
|
238
|
-
hostingEntries.push({ name, deployPath: h.deployPath, files });
|
|
239
|
-
}
|
|
240
|
-
catch (_g) {
|
|
241
|
-
// 目录缺失 / 文件数超限:不写入(下次 diff 走云端兜底或全量部署)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// app
|
|
245
|
-
let appEntry;
|
|
246
|
-
if (config.app) {
|
|
247
|
-
const appResult = results.find(r => r.type === 'app');
|
|
248
|
-
if (appResult === null || appResult === void 0 ? void 0 : appResult.ok) {
|
|
249
|
-
appEntry = stateStore.buildAppConfig(config.app);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
appEntry = (_e = prev === null || prev === void 0 ? void 0 : prev.resources) === null || _e === void 0 ? void 0 : _e.app;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
const resources = {};
|
|
256
|
-
if (hostingEntries.length) {
|
|
257
|
-
resources.hosting = hostingEntries;
|
|
258
|
-
}
|
|
259
|
-
if (appEntry) {
|
|
260
|
-
resources.app = appEntry;
|
|
261
|
-
}
|
|
262
|
-
if ((_f = prev === null || prev === void 0 ? void 0 : prev.resources) === null || _f === void 0 ? void 0 : _f.gateway) {
|
|
263
|
-
resources.gateway = prev.resources.gateway;
|
|
264
|
-
}
|
|
265
|
-
if (Object.keys(resources).length) {
|
|
266
|
-
stateStore.write({ resources, envId });
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
241
|
/**
|
|
270
242
|
* 构建完整部署计划(按依赖顺序 + only/skip 过滤)
|
|
271
243
|
*
|
|
272
244
|
* 通过查询云端状态判定每个资源的动作:
|
|
273
245
|
* - database:先按迁移文件做 preview,再聚合成单条资源级计划项(pending→create,存在 conflict→conflict,全部 applied→不出现在 plan)
|
|
274
246
|
* - functions:ListFunctions 判断是否已存在 → create / update(不做本地 hash / 字段 diff)
|
|
275
|
-
* - app:describeAppInfo 判断是否已存在 → create / update
|
|
276
|
-
* - hosting
|
|
247
|
+
* - app:describeAppInfo 判断是否已存在 → create / update
|
|
248
|
+
* - hosting:直传覆盖(无存在性概念)
|
|
277
249
|
* - gateway:planRoutes 对比云端路由 → create / update / skip(含变更字段明细)
|
|
278
250
|
* 云端查询失败时降级为保守判定(全 create),不影响 dry-run 展示。
|
|
279
251
|
*/
|
|
280
252
|
async buildPlan(config, options) {
|
|
281
|
-
const { only, skip
|
|
253
|
+
const { only, skip } = options;
|
|
282
254
|
const cwd = options.cwd || process.cwd();
|
|
283
255
|
const envId = options.envId;
|
|
284
256
|
const plan = [];
|
|
@@ -289,14 +261,7 @@ class DeployOrchestrator {
|
|
|
289
261
|
return false;
|
|
290
262
|
return true;
|
|
291
263
|
};
|
|
292
|
-
|
|
293
|
-
const stateStore = new StateStore_1.StateStore({ cwd });
|
|
294
|
-
const rawState = refresh ? null : stateStore.read();
|
|
295
|
-
// 跨环境保护:快照记录的 envId 与目标环境不一致(或旧格式快照无 envId)时,
|
|
296
|
-
// 快照是其他环境部署后的指纹,不可作为 skip 依据 → 视为无快照,强制走全量部署,
|
|
297
|
-
// 避免切换环境后误判「产物未变更」导致新环境 COS 404
|
|
298
|
-
const state = rawState && rawState.envId === envId ? rawState : null;
|
|
299
|
-
const planCtx = { config, envId, stateStore, state, refresh, cwd };
|
|
264
|
+
const planCtx = { config, envId, cwd };
|
|
300
265
|
for (const type of DEPLOY_ORDER) {
|
|
301
266
|
if (!isEnabled(type))
|
|
302
267
|
continue;
|
|
@@ -309,7 +274,7 @@ class DeployOrchestrator {
|
|
|
309
274
|
* 按资源类型构建计划(拆分自 buildPlan,控制圈复杂度)
|
|
310
275
|
*/
|
|
311
276
|
async buildPlanForType(type, ctx) {
|
|
312
|
-
const { config, envId,
|
|
277
|
+
const { config, envId, cwd } = ctx;
|
|
313
278
|
if (type === 'database') {
|
|
314
279
|
return this.buildDatabasePlan(config, cwd, envId);
|
|
315
280
|
}
|
|
@@ -347,10 +312,10 @@ class DeployOrchestrator {
|
|
|
347
312
|
});
|
|
348
313
|
}
|
|
349
314
|
if (type === 'app') {
|
|
350
|
-
return this.buildAppPlan({ config, envId
|
|
315
|
+
return this.buildAppPlan({ config, envId });
|
|
351
316
|
}
|
|
352
317
|
if (type === 'hosting') {
|
|
353
|
-
return this.buildHostingPlans(config
|
|
318
|
+
return this.buildHostingPlans(config);
|
|
354
319
|
}
|
|
355
320
|
return this.buildGatewayPlans(config, envId);
|
|
356
321
|
}
|
|
@@ -407,100 +372,51 @@ class DeployOrchestrator {
|
|
|
407
372
|
return [];
|
|
408
373
|
}
|
|
409
374
|
/**
|
|
410
|
-
* app
|
|
375
|
+
* app 计划:按云端存在性 → create / update
|
|
411
376
|
*/
|
|
412
377
|
async buildAppPlan(options) {
|
|
413
|
-
|
|
414
|
-
const { config, envId, stateStore, state, refresh } = options;
|
|
378
|
+
const { config, envId } = options;
|
|
415
379
|
if (!config.app)
|
|
416
380
|
return [];
|
|
417
381
|
const name = config.app.serviceName || 'app';
|
|
418
|
-
// 本地 state 配置一致 → skip(真增量,不重复构建版本)
|
|
419
|
-
const appEntry = stateStore.buildAppConfig(config.app);
|
|
420
|
-
const stateApp = (_a = state === null || state === void 0 ? void 0 : state.resources) === null || _a === void 0 ? void 0 : _a.app;
|
|
421
|
-
const appUnchanged = !refresh &&
|
|
422
|
-
stateApp !== undefined &&
|
|
423
|
-
appEntry !== undefined &&
|
|
424
|
-
this.deepEqual(stateApp, appEntry);
|
|
425
|
-
if (appUnchanged) {
|
|
426
|
-
return [
|
|
427
|
-
{
|
|
428
|
-
type: 'app',
|
|
429
|
-
name,
|
|
430
|
-
status: 'skip',
|
|
431
|
-
action: `云应用 ${name} 配置未变更,跳过`
|
|
432
|
-
}
|
|
433
|
-
];
|
|
434
|
-
}
|
|
435
382
|
const appExists = await this.appExists(name);
|
|
436
|
-
const changes = this.appConfigChanges(stateApp, appEntry);
|
|
437
383
|
return [
|
|
438
|
-
|
|
384
|
+
{
|
|
385
|
+
type: 'app',
|
|
386
|
+
name,
|
|
387
|
+
status: appExists ? 'update' : 'create',
|
|
388
|
+
action: appExists
|
|
439
389
|
? `覆盖更新云应用 ${name}`
|
|
440
|
-
: `新建云应用 ${name}`
|
|
390
|
+
: `新建云应用 ${name}`
|
|
391
|
+
}
|
|
441
392
|
];
|
|
442
393
|
}
|
|
443
394
|
/**
|
|
444
|
-
* hosting
|
|
395
|
+
* hosting 计划:每次直传覆盖(无本地指纹 skip)
|
|
445
396
|
*/
|
|
446
|
-
|
|
447
|
-
var _a, _b;
|
|
448
|
-
const { stateStore, state, refresh, cwd } = ctx;
|
|
397
|
+
buildHostingPlans(config) {
|
|
449
398
|
const plans = [];
|
|
450
399
|
for (const h of config.hosting || []) {
|
|
451
400
|
const name = h.name || 'default';
|
|
452
|
-
const root = path_1.default.resolve(cwd, h.root || '.');
|
|
453
|
-
const buildCommand = (0, framework_1.resolveBuildCommand)(h, root);
|
|
454
|
-
// outputDir 与 StaticDeployer.deployHosting 完全复用同一解析逻辑
|
|
455
|
-
const outputDir = (0, framework_1.resolveHostingOutputDir)(h, root);
|
|
456
|
-
const stateHosting = (_b = (_a = state === null || state === void 0 ? void 0 : state.resources) === null || _a === void 0 ? void 0 : _a.hosting) === null || _b === void 0 ? void 0 : _b.find(s => s.name === name);
|
|
457
|
-
// 懒加载 diff:size 预判 + 按需 hash(目录缺失 / 文件数超限 → 保守部署)
|
|
458
|
-
let fileDiff = null;
|
|
459
|
-
try {
|
|
460
|
-
fileDiff = await stateStore.diffHostingFiles(outputDir, stateHosting === null || stateHosting === void 0 ? void 0 : stateHosting.files, {
|
|
461
|
-
ignore: h.ignore || []
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
catch (_c) {
|
|
465
|
-
fileDiff = null;
|
|
466
|
-
}
|
|
467
|
-
const unchanged = !refresh && fileDiff !== null && fileDiff.unchanged;
|
|
468
|
-
if (unchanged) {
|
|
469
|
-
plans.push({
|
|
470
|
-
type: 'hosting',
|
|
471
|
-
name,
|
|
472
|
-
status: 'skip',
|
|
473
|
-
action: `静态托管 ${name} 产物未变更,跳过`
|
|
474
|
-
});
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
401
|
const hasBuild = !!h.buildCommand ||
|
|
478
402
|
(!!h.framework && h.framework !== 'static' && h.framework !== 'custom');
|
|
479
403
|
const details = [
|
|
480
404
|
{ label: '框架', value: h.framework || 'static' },
|
|
481
405
|
{
|
|
482
406
|
label: '构建',
|
|
483
|
-
value: hasBuild
|
|
484
|
-
? `本地构建(${buildCommand || 'npm run build'})→ 上传 ${outputDir}`
|
|
485
|
-
: '无构建,直接上传'
|
|
407
|
+
value: hasBuild ? '本地构建后上传' : '无构建,直接上传'
|
|
486
408
|
},
|
|
487
409
|
{ label: '部署路径', value: h.deployPath || '/' }
|
|
488
410
|
];
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
411
|
+
plans.push({
|
|
412
|
+
type: 'hosting',
|
|
413
|
+
name,
|
|
414
|
+
status: 'deploy',
|
|
415
|
+
action: hasBuild
|
|
493
416
|
? `本地构建后直传静态托管 ${name}`
|
|
494
|
-
: `直传覆盖静态托管 ${name}`,
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
added: fileDiff.added,
|
|
498
|
-
modified: fileDiff.modified,
|
|
499
|
-
deleted: fileDiff.deleted,
|
|
500
|
-
totalChanged: fileDiff.totalChanged
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
: {})));
|
|
417
|
+
: `直传覆盖静态托管 ${name}`,
|
|
418
|
+
details
|
|
419
|
+
});
|
|
504
420
|
}
|
|
505
421
|
return plans;
|
|
506
422
|
}
|
|
@@ -566,7 +482,7 @@ class DeployOrchestrator {
|
|
|
566
482
|
* 执行单个计划步骤
|
|
567
483
|
*/
|
|
568
484
|
async executeStep(options) {
|
|
569
|
-
const { item, config, envId, cwd } = options;
|
|
485
|
+
const { item, config, envId, cwd, onProgress } = options;
|
|
570
486
|
switch (item.type) {
|
|
571
487
|
case 'database': {
|
|
572
488
|
const dbConfig = config.database;
|
|
@@ -585,14 +501,10 @@ class DeployOrchestrator {
|
|
|
585
501
|
const functionPath = fn.dir
|
|
586
502
|
? path_1.default.resolve(cwd, fn.dir)
|
|
587
503
|
: path_1.default.join(functionRootPath, fn.name);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
functionRootPath,
|
|
592
|
-
functionPath,
|
|
593
|
-
force: true
|
|
504
|
+
const deployConfig = Object.assign(Object.assign({}, fn), { functionPath });
|
|
505
|
+
return this.functionDeployer.deployFunction(deployConfig, {
|
|
506
|
+
onProgress: event => onProgress === null || onProgress === void 0 ? void 0 : onProgress(Object.assign(Object.assign({}, event), { resourceType: 'functions', resourceName: item.name }))
|
|
594
507
|
});
|
|
595
|
-
return result.url;
|
|
596
508
|
}
|
|
597
509
|
case 'app': {
|
|
598
510
|
const result = await this.staticDeployer.deployApp({ config: config.app, cwd });
|
|
@@ -614,26 +526,6 @@ class DeployOrchestrator {
|
|
|
614
526
|
}
|
|
615
527
|
}
|
|
616
528
|
}
|
|
617
|
-
/** 深比较(JSON 快照语义):仅用于 app 配置快照等简单对象 */
|
|
618
|
-
deepEqual(a, b) {
|
|
619
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
620
|
-
}
|
|
621
|
-
/** app 配置差异明细(from → to) */
|
|
622
|
-
appConfigChanges(prev, curr) {
|
|
623
|
-
const changes = [];
|
|
624
|
-
const keys = new Set([
|
|
625
|
-
...Object.keys(prev || {}),
|
|
626
|
-
...Object.keys(curr || {})
|
|
627
|
-
]);
|
|
628
|
-
for (const key of keys) {
|
|
629
|
-
const from = prev === null || prev === void 0 ? void 0 : prev[key];
|
|
630
|
-
const to = curr === null || curr === void 0 ? void 0 : curr[key];
|
|
631
|
-
if (from !== to) {
|
|
632
|
-
changes.push({ field: String(key), from, to });
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return changes;
|
|
636
|
-
}
|
|
637
529
|
/**
|
|
638
530
|
* 轻量 inline 守卫:配置合法 + 目录存在(validateProject 接线点)
|
|
639
531
|
*/
|