@cloudbase/manager-node 5.6.8 → 5.7.0-beta.0

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.
Files changed (62) hide show
  1. package/lib/cloudApp/index.js +12 -3
  2. package/lib/cloudrun/index.js +0 -18
  3. package/lib/deploy/DatabaseDeployer.js +238 -0
  4. package/lib/deploy/DeployOrchestrator.js +564 -0
  5. package/lib/deploy/FunctionDeployer.js +611 -0
  6. package/lib/deploy/GatewayDeployer.js +522 -0
  7. package/lib/deploy/StateStore.js +327 -0
  8. package/lib/deploy/StaticDeployer.js +289 -0
  9. package/lib/deploy/domain.js +39 -0
  10. package/lib/deploy/framework.js +105 -0
  11. package/lib/deploy/function/artifact.js +207 -0
  12. package/lib/deploy/function/builders/cloud.js +454 -0
  13. package/lib/deploy/function/cam-preflight.js +157 -0
  14. package/lib/deploy/function/cloud-build-service.js +170 -0
  15. package/lib/deploy/function/config-guard.js +588 -0
  16. package/lib/deploy/function/docker-preflight.js +87 -0
  17. package/lib/deploy/function/image-preflight.js +164 -0
  18. package/lib/deploy/function/local-builder.js +110 -0
  19. package/lib/deploy/function/planner.js +282 -0
  20. package/lib/deploy/function/preflight.js +264 -0
  21. package/lib/deploy/types.js +99 -0
  22. package/lib/environment.js +11 -0
  23. package/lib/hosting/index.js +4 -1
  24. package/lib/index.js +31 -0
  25. package/lib/projectValidator/index.js +375 -0
  26. package/lib/projectValidator/types.js +2 -0
  27. package/lib/storage/index.js +6 -7
  28. package/lib/utils/index.js +41 -1
  29. package/package.json +2 -1
  30. package/types/cloudApp/index.d.ts +4 -0
  31. package/types/cloudApp/types.d.ts +87 -6
  32. package/types/cloudrun/index.d.ts +1 -7
  33. package/types/cloudrun/type.d.ts +0 -22
  34. package/types/deploy/DatabaseDeployer.d.ts +70 -0
  35. package/types/deploy/DeployOrchestrator.d.ts +150 -0
  36. package/types/deploy/FunctionDeployer.d.ts +158 -0
  37. package/types/deploy/GatewayDeployer.d.ts +175 -0
  38. package/types/deploy/StateStore.d.ts +170 -0
  39. package/types/deploy/StaticDeployer.d.ts +104 -0
  40. package/types/deploy/domain.d.ts +17 -0
  41. package/types/deploy/framework.d.ts +23 -0
  42. package/types/deploy/function/artifact.d.ts +40 -0
  43. package/types/deploy/function/builders/cloud.d.ts +110 -0
  44. package/types/deploy/function/cam-preflight.d.ts +51 -0
  45. package/types/deploy/function/cloud-build-service.d.ts +10 -0
  46. package/types/deploy/function/config-guard.d.ts +41 -0
  47. package/types/deploy/function/docker-preflight.d.ts +17 -0
  48. package/types/deploy/function/image-preflight.d.ts +21 -0
  49. package/types/deploy/function/local-builder.d.ts +26 -0
  50. package/types/deploy/function/planner.d.ts +15 -0
  51. package/types/deploy/function/preflight.d.ts +9 -0
  52. package/types/deploy/types.d.ts +429 -0
  53. package/types/env/type.d.ts +4 -4
  54. package/types/environment.d.ts +7 -0
  55. package/types/function/types.d.ts +15 -0
  56. package/types/hosting/index.d.ts +6 -0
  57. package/types/index.d.ts +18 -0
  58. package/types/interfaces/function.interface.d.ts +1 -1
  59. package/types/projectValidator/index.d.ts +24 -0
  60. package/types/projectValidator/types.d.ts +26 -0
  61. package/types/storage/index.d.ts +6 -0
  62. package/types/utils/index.d.ts +13 -0
@@ -0,0 +1,564 @@
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.DeployOrchestrator = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const error_1 = require("../error");
9
+ const DatabaseDeployer_1 = require("./DatabaseDeployer");
10
+ const FunctionDeployer_1 = require("./FunctionDeployer");
11
+ const StaticDeployer_1 = require("./StaticDeployer");
12
+ const GatewayDeployer_1 = require("./GatewayDeployer");
13
+ const StateStore_1 = require("./StateStore");
14
+ const framework_1 = require("./framework");
15
+ /** 部署顺序(按依赖:database 最先,函数依赖新 Schema;gateway 最后,依赖函数/hosting) */
16
+ const DEPLOY_ORDER = ['database', 'functions', 'app', 'hosting', 'gateway'];
17
+ /**
18
+ * 声明式部署编排器
19
+ * 顺序:database → functions → app → hosting → gateway
20
+ * 支持 dry-run / only / skip / 幂等重试(database 失败/冲突会中断后续步骤)
21
+ */
22
+ class DeployOrchestrator {
23
+ constructor(environment) {
24
+ this.environment = environment;
25
+ this.databaseDeployer = new DatabaseDeployer_1.DatabaseDeployer(environment);
26
+ this.functionDeployer = new FunctionDeployer_1.FunctionDeployer(environment);
27
+ this.staticDeployer = new StaticDeployer_1.StaticDeployer(environment);
28
+ this.gatewayDeployer = new GatewayDeployer_1.GatewayDeployer(environment);
29
+ }
30
+ /**
31
+ * 计算部署计划(dry-run 输出)
32
+ */
33
+ async deployPlan(options) {
34
+ return this.buildPlan(options.config, options);
35
+ }
36
+ /**
37
+ * 执行部署
38
+ */
39
+ async deploy(options) {
40
+ var _a, _b, _c, _d, _e, _f, _g;
41
+ const { config, envId, dryRun, cwd = process.cwd(), log } = options;
42
+ // 1. 轻量 inline 守卫(validateProject 未接入时的兜底)
43
+ this.guardConfig(config);
44
+ const plan = await this.buildPlan(config, options);
45
+ if (dryRun) {
46
+ (_a = log === null || log === void 0 ? void 0 : log.info) === null || _a === void 0 ? void 0 : _a.call(log, 'dry-run 模式,仅输出部署计划');
47
+ return { plan, results: [] };
48
+ }
49
+ const results = [];
50
+ for (const item of plan) {
51
+ // skip:产物/配置未变更,静默跳过(不执行、不询问)
52
+ if (item.status === 'skip') {
53
+ (_b = log === null || log === void 0 ? void 0 : log.info) === null || _b === void 0 ? void 0 : _b.call(log, `[${item.type}] ${item.name}: ${item.action}`);
54
+ // reason: 'skip' 标记,供展示层区分「成功」与「跳过」
55
+ results.push({ type: item.type, name: item.name, ok: true, reason: 'skip' });
56
+ continue;
57
+ }
58
+ (_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}`);
59
+ // conflict:数据库迁移冲突 → 中断整个部署(后续资源可能依赖新 Schema)
60
+ if (item.status === 'conflict') {
61
+ const message = '检测到冲突,已中断部署';
62
+ (_d = log === null || log === void 0 ? void 0 : log.error) === null || _d === void 0 ? void 0 : _d.call(log, `[${item.type}] ${item.name} ${message}`);
63
+ results.push({ type: item.type, name: item.name, ok: false, error: message });
64
+ if (item.type === 'database') {
65
+ return { plan, results };
66
+ }
67
+ continue;
68
+ }
69
+ // 覆盖确认:仅函数已存在(update)时触发,对齐 tcb fn deploy 的 FunctionConflictChecker 语义
70
+ // yes=true 直接放行;注入 confirmUpdate 按回调决定;两者都无 → 保守跳过(no-confirm)
71
+ if (item.type === 'functions' && item.status === 'update') {
72
+ let confirmed = false;
73
+ if (options.yes) {
74
+ confirmed = true;
75
+ }
76
+ else if (options.confirmUpdate) {
77
+ confirmed = await options.confirmUpdate(item);
78
+ }
79
+ if (!confirmed) {
80
+ const reason = options.confirmUpdate
81
+ ? 'skipped-by-user'
82
+ : 'no-confirm';
83
+ (_e = log === null || log === void 0 ? void 0 : log.warn) === null || _e === void 0 ? void 0 : _e.call(log, `[${item.type}] ${item.name} 跳过覆盖更新(${reason})`);
84
+ results.push({ type: item.type, name: item.name, ok: false, reason });
85
+ continue;
86
+ }
87
+ }
88
+ try {
89
+ const stepResult = await this.executeStep({ item, config, envId, cwd });
90
+ results.push({ type: item.type, name: item.name, ok: true, url: stepResult });
91
+ }
92
+ catch (e) {
93
+ const message = (e === null || e === void 0 ? void 0 : e.message) || String(e);
94
+ (_f = log === null || log === void 0 ? void 0 : log.error) === null || _f === void 0 ? void 0 : _f.call(log, `[${item.type}] ${item.name} 部署失败: ${message}`);
95
+ results.push({ type: item.type, name: item.name, ok: false, error: message });
96
+ // database 失败中断整个部署:后续资源可能依赖新 Schema
97
+ if (item.type === 'database') {
98
+ (_g = log === null || log === void 0 ? void 0 : log.error) === null || _g === void 0 ? void 0 : _g.call(log, '数据库迁移失败,中断部署');
99
+ return { plan, results };
100
+ }
101
+ }
102
+ }
103
+ // 部署成功后写回本地 state(真增量基准;失败资源不写入,保留旧快照)
104
+ if (!dryRun) {
105
+ await this.persistState({ config, plan, results, cwd });
106
+ }
107
+ return { plan, results };
108
+ }
109
+ /**
110
+ * 部署成功后写回本地 state 快照
111
+ *
112
+ * - hosting:成功部署/未变更(skip)的项重算产物指纹;失败项保留旧快照
113
+ * - app:成功部署写 buildAppConfig 快照;skip 复用旧快照
114
+ * - gateway:保留旧快照(本轮不做路由级实时快照)
115
+ */
116
+ async persistState(options) {
117
+ var _a, _b, _c, _d, _e, _f;
118
+ const { config, plan, results, cwd } = options;
119
+ const stateStore = new StateStore_1.StateStore({ cwd });
120
+ const prev = stateStore.read();
121
+ // hosting
122
+ const hostingEntries = [];
123
+ for (const h of config.hosting || []) {
124
+ const name = h.name || 'default';
125
+ const planItem = plan.find(p => p.type === 'hosting' && p.name === name);
126
+ const result = results.find(r => r.type === 'hosting' && r.name === name);
127
+ if (result && !result.ok) {
128
+ // 部署失败:保留旧快照(下次 diff 仍会检测到差异)
129
+ 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);
130
+ if (prevEntry) {
131
+ hostingEntries.push(prevEntry);
132
+ }
133
+ continue;
134
+ }
135
+ if ((planItem === null || planItem === void 0 ? void 0 : planItem.status) === 'skip') {
136
+ 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);
137
+ if (prevEntry) {
138
+ hostingEntries.push(prevEntry);
139
+ continue;
140
+ }
141
+ }
142
+ // 成功部署:重算当前指纹(含 size,供下次 diff 做 size 预判)
143
+ // 注意:deployHosting 可能执行本地构建,产物已更新,必须独立重算而非复用 plan 指纹
144
+ const root = path_1.default.resolve(cwd, h.root || '.');
145
+ const buildCommand = (0, framework_1.resolveBuildCommand)(h, root);
146
+ const outputDir = h.outputDir
147
+ ? path_1.default.resolve(root, h.outputDir)
148
+ : buildCommand
149
+ ? path_1.default.resolve(root, 'dist')
150
+ : root;
151
+ try {
152
+ const files = await stateStore.computeHostingFingerprints(outputDir, h.ignore || []);
153
+ hostingEntries.push({ name, deployPath: h.deployPath, files });
154
+ }
155
+ catch (_g) {
156
+ // 目录缺失 / 文件数超限:不写入(下次 diff 走云端兜底或全量部署)
157
+ }
158
+ }
159
+ // app
160
+ let appEntry;
161
+ if (config.app) {
162
+ const appResult = results.find(r => r.type === 'app');
163
+ if (appResult === null || appResult === void 0 ? void 0 : appResult.ok) {
164
+ appEntry = stateStore.buildAppConfig(config.app);
165
+ }
166
+ else {
167
+ appEntry = (_e = prev === null || prev === void 0 ? void 0 : prev.resources) === null || _e === void 0 ? void 0 : _e.app;
168
+ }
169
+ }
170
+ const resources = {};
171
+ if (hostingEntries.length) {
172
+ resources.hosting = hostingEntries;
173
+ }
174
+ if (appEntry) {
175
+ resources.app = appEntry;
176
+ }
177
+ if ((_f = prev === null || prev === void 0 ? void 0 : prev.resources) === null || _f === void 0 ? void 0 : _f.gateway) {
178
+ resources.gateway = prev.resources.gateway;
179
+ }
180
+ if (Object.keys(resources).length) {
181
+ stateStore.write({ resources });
182
+ }
183
+ }
184
+ /**
185
+ * 构建完整部署计划(按依赖顺序 + only/skip 过滤)
186
+ *
187
+ * 通过查询云端状态判定每个资源的动作:
188
+ * - database:先按迁移文件做 preview,再聚合成单条资源级计划项(pending→create,存在 conflict→conflict,全部 applied→不出现在 plan)
189
+ * - functions:ListFunctions 判断是否已存在 → create / update(不做本地 hash / 字段 diff)
190
+ * - app:describeAppInfo 判断是否已存在 → create / update;本地 state 配置一致 → skip
191
+ * - hosting:本地 state 指纹 diff → 一致 skip(不重新上传)/ 有差异 deploy(带文件级明细)
192
+ * - gateway:planRoutes 对比云端路由 → create / update / skip(含变更字段明细)
193
+ * 云端查询失败时降级为保守判定(全 create),不影响 dry-run 展示。
194
+ */
195
+ async buildPlan(config, options) {
196
+ const { only, skip, refresh } = options;
197
+ const cwd = options.cwd || process.cwd();
198
+ const envId = options.envId;
199
+ const plan = [];
200
+ const isEnabled = (type) => {
201
+ if ((only === null || only === void 0 ? void 0 : only.length) && !only.includes(type))
202
+ return false;
203
+ if (skip === null || skip === void 0 ? void 0 : skip.includes(type))
204
+ return false;
205
+ return true;
206
+ };
207
+ // 本地 state 快照(diff 基准):--refresh 时忽略本地 skip 判定
208
+ const stateStore = new StateStore_1.StateStore({ cwd });
209
+ const state = refresh ? null : stateStore.read();
210
+ const planCtx = { config, envId, stateStore, state, refresh, cwd };
211
+ for (const type of DEPLOY_ORDER) {
212
+ if (!isEnabled(type))
213
+ continue;
214
+ const typePlans = await this.buildPlanForType(type, planCtx);
215
+ plan.push(...typePlans);
216
+ }
217
+ return plan;
218
+ }
219
+ /**
220
+ * 按资源类型构建计划(拆分自 buildPlan,控制圈复杂度)
221
+ */
222
+ async buildPlanForType(type, ctx) {
223
+ const { config, envId, stateStore, state, refresh, cwd } = ctx;
224
+ if (type === 'database') {
225
+ return this.buildDatabasePlan(config, cwd, envId);
226
+ }
227
+ if (type === 'functions') {
228
+ const existingFunctions = await this.listExistingFunctionNames();
229
+ return (config.functions || []).map((fn) => {
230
+ const exists = existingFunctions.has(fn.name);
231
+ const details = [];
232
+ const runtime = fn.runtime || 'Nodejs20.19';
233
+ const memorySize = fn.memorySize || 256;
234
+ details.push({ label: '运行时', value: `${runtime}, ${memorySize}MB` });
235
+ if (fn.type === 'HTTP') {
236
+ const strategy = fn.buildStrategy || 'zip';
237
+ details.push({ label: '构建', value: strategy });
238
+ if (fn.public) {
239
+ details.push({ label: '访问', value: 'public: true(自动配置匿名访问)' });
240
+ }
241
+ if (fn.gatewayPath) {
242
+ details.push({ label: '网关路径', value: fn.gatewayPath });
243
+ }
244
+ }
245
+ else {
246
+ const install = fn.installDependency === false ? '本地安装依赖打包' : '云端安装依赖';
247
+ details.push({ label: '依赖', value: install });
248
+ }
249
+ return {
250
+ type,
251
+ name: fn.name,
252
+ status: exists ? 'update' : 'create',
253
+ action: exists
254
+ ? `覆盖更新已存在函数 ${fn.name}`
255
+ : `新建函数 ${fn.name}`,
256
+ details
257
+ };
258
+ });
259
+ }
260
+ if (type === 'app') {
261
+ return this.buildAppPlan({ config, envId, stateStore, state, refresh });
262
+ }
263
+ if (type === 'hosting') {
264
+ return this.buildHostingPlans(config, ctx);
265
+ }
266
+ return this.buildGatewayPlans(config, envId);
267
+ }
268
+ /**
269
+ * database 计划:preview 聚合成单条资源级计划项
270
+ */
271
+ async buildDatabasePlan(config, cwd, envId) {
272
+ if (!config.database)
273
+ return [];
274
+ const dbConfig = config.database;
275
+ const migrationPlans = await this.databaseDeployer.plan(dbConfig, cwd, envId);
276
+ const pending = migrationPlans.filter(item => item.status === 'pending');
277
+ const conflicts = migrationPlans.filter(item => item.status === 'conflict');
278
+ // database 是资源级步骤:无论有多少 migration,plan 中最多只保留一条 database 记录
279
+ const details = migrationPlans.map(item => ({
280
+ label: `${item.version}_${item.name || 'unknown'}`,
281
+ value: item.status === 'pending'
282
+ ? '待应用'
283
+ : item.status === 'conflict'
284
+ ? `冲突:${item.reason || 'checksum mismatch'}`
285
+ : '已应用,跳过'
286
+ }));
287
+ if (conflicts.length > 0) {
288
+ return [
289
+ {
290
+ type: 'database',
291
+ name: 'postgresql',
292
+ status: 'conflict',
293
+ action: `数据库迁移存在 ${conflicts.length} 个冲突,执行时将中断部署`,
294
+ changes: conflicts.map(item => ({
295
+ field: `conflict.${item.version}_${item.name || 'unknown'}`,
296
+ to: item.reason || 'checksum mismatch'
297
+ })),
298
+ details
299
+ }
300
+ ];
301
+ }
302
+ if (pending.length > 0) {
303
+ return [
304
+ {
305
+ type: 'database',
306
+ name: 'postgresql',
307
+ status: 'create',
308
+ action: `将执行 ${pending.length} 条数据库迁移`,
309
+ changes: pending.map(item => ({
310
+ field: 'migration',
311
+ to: `${item.version}_${item.name}`
312
+ })),
313
+ details
314
+ }
315
+ ];
316
+ }
317
+ // 全部 applied 时不产生 database 计划项
318
+ return [];
319
+ }
320
+ /**
321
+ * app 计划:本地 state 配置一致 → skip;否则按云端存在性 → create / update
322
+ */
323
+ async buildAppPlan(options) {
324
+ var _a;
325
+ const { config, envId, stateStore, state, refresh } = options;
326
+ if (!config.app)
327
+ return [];
328
+ const name = config.app.serviceName || 'app';
329
+ // 本地 state 配置一致 → skip(真增量,不重复构建版本)
330
+ const appEntry = stateStore.buildAppConfig(config.app);
331
+ const stateApp = (_a = state === null || state === void 0 ? void 0 : state.resources) === null || _a === void 0 ? void 0 : _a.app;
332
+ const appUnchanged = !refresh &&
333
+ stateApp !== undefined &&
334
+ appEntry !== undefined &&
335
+ this.deepEqual(stateApp, appEntry);
336
+ if (appUnchanged) {
337
+ return [
338
+ {
339
+ type: 'app',
340
+ name,
341
+ status: 'skip',
342
+ action: `云应用 ${name} 配置未变更,跳过`
343
+ }
344
+ ];
345
+ }
346
+ const appExists = await this.appExists(name);
347
+ const changes = this.appConfigChanges(stateApp, appEntry);
348
+ return [
349
+ Object.assign({ type: 'app', name, status: appExists ? 'update' : 'create', action: appExists
350
+ ? `覆盖更新云应用 ${name}`
351
+ : `新建云应用 ${name}` }, (changes.length ? { changes } : {}))
352
+ ];
353
+ }
354
+ /**
355
+ * hosting 计划:本地 state 指纹 diff → 一致 skip / 有差异 deploy(带文件级明细)
356
+ */
357
+ async buildHostingPlans(config, ctx) {
358
+ var _a, _b;
359
+ const { stateStore, state, refresh, cwd } = ctx;
360
+ const plans = [];
361
+ for (const h of config.hosting || []) {
362
+ const name = h.name || 'default';
363
+ const root = path_1.default.resolve(cwd, h.root || '.');
364
+ // 与 StaticDeployer.deployHosting 同规则解析 buildCommand / outputDir
365
+ const buildCommand = (0, framework_1.resolveBuildCommand)(h, root);
366
+ const outputDir = h.outputDir
367
+ ? path_1.default.resolve(root, h.outputDir)
368
+ : buildCommand
369
+ ? path_1.default.resolve(root, 'dist')
370
+ : root;
371
+ 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);
372
+ // 懒加载 diff:size 预判 + 按需 hash(目录缺失 / 文件数超限 → 保守部署)
373
+ let fileDiff = null;
374
+ try {
375
+ fileDiff = await stateStore.diffHostingFiles(outputDir, stateHosting === null || stateHosting === void 0 ? void 0 : stateHosting.files, {
376
+ ignore: h.ignore || []
377
+ });
378
+ }
379
+ catch (_c) {
380
+ fileDiff = null;
381
+ }
382
+ const unchanged = !refresh && fileDiff !== null && fileDiff.unchanged;
383
+ if (unchanged) {
384
+ plans.push({
385
+ type: 'hosting',
386
+ name,
387
+ status: 'skip',
388
+ action: `静态托管 ${name} 产物未变更,跳过`
389
+ });
390
+ continue;
391
+ }
392
+ const hasBuild = !!h.buildCommand ||
393
+ (!!h.framework && h.framework !== 'static' && h.framework !== 'custom');
394
+ const details = [
395
+ { label: '框架', value: h.framework || 'static' },
396
+ {
397
+ label: '构建',
398
+ value: hasBuild
399
+ ? `本地构建(${buildCommand || 'npm run build'})→ 上传 ${outputDir}`
400
+ : '无构建,直接上传'
401
+ },
402
+ { label: '部署路径', value: h.deployPath || '/' }
403
+ ];
404
+ if (fileDiff === null || fileDiff === void 0 ? void 0 : fileDiff.overLimit) {
405
+ details.push({ label: '指纹', value: '文件数超上限,降级为全量部署' });
406
+ }
407
+ plans.push(Object.assign({ type: 'hosting', name, status: 'deploy', action: hasBuild
408
+ ? `本地构建后直传静态托管 ${name}`
409
+ : `直传覆盖静态托管 ${name}`, details }, (fileDiff && !fileDiff.overLimit
410
+ ? {
411
+ fileDiff: {
412
+ added: fileDiff.added,
413
+ modified: fileDiff.modified,
414
+ deleted: fileDiff.deleted,
415
+ totalChanged: fileDiff.totalChanged
416
+ }
417
+ }
418
+ : {})));
419
+ }
420
+ return plans;
421
+ }
422
+ /**
423
+ * gateway 计划:planRoutes 对比云端路由 → create / update / skip(含变更字段明细)
424
+ */
425
+ async buildGatewayPlans(config, envId) {
426
+ var _a;
427
+ const routes = ((_a = config.gateway) === null || _a === void 0 ? void 0 : _a.routes) || [];
428
+ if (routes.length === 0)
429
+ return [];
430
+ const routePlans = await this.gatewayDeployer.planRoutes({
431
+ routes,
432
+ envId,
433
+ hostings: config.hosting || []
434
+ });
435
+ const routeConfigMap = new Map(routes.map(r => [r.path, r]));
436
+ return routePlans.map(rp => {
437
+ const routeCfg = routeConfigMap.get(rp.path);
438
+ const details = [];
439
+ if (routeCfg === null || routeCfg === void 0 ? void 0 : routeCfg.target) {
440
+ details.push({ label: '目标', value: routeCfg.target });
441
+ }
442
+ if (routeCfg === null || routeCfg === void 0 ? void 0 : routeCfg.domain) {
443
+ details.push({ label: '域名', value: routeCfg.domain });
444
+ }
445
+ return Object.assign({ type: 'gateway', name: rp.path, status: rp.status, action: rp.status === 'create'
446
+ ? `新建路由 ${rp.path}`
447
+ : rp.status === 'update'
448
+ ? `更新路由 ${rp.path}`
449
+ : `路由 ${rp.path} 配置一致,跳过`, changes: rp.changes }, (details.length ? { details } : {}));
450
+ });
451
+ }
452
+ /**
453
+ * 查询环境中已存在的函数名集合(查询失败返回空集,降级为全新建判定)
454
+ */
455
+ async listExistingFunctionNames() {
456
+ try {
457
+ const list = await this.environment.getFunctionService().listFunctions(100);
458
+ // listFunctions 返回字段名为 FunctionName(SCF ListFunctions API),兜底兼容 Name/name
459
+ return new Set((list || []).map((f) => f.FunctionName || f.Name || f.name));
460
+ }
461
+ catch (_a) {
462
+ return new Set();
463
+ }
464
+ }
465
+ /**
466
+ * 判断云应用是否已存在(查询失败返回 false,降级为新建判定)
467
+ */
468
+ async appExists(name) {
469
+ try {
470
+ await this.environment.getCloudAppService().describeAppInfo({
471
+ deployType: 'static-hosting',
472
+ serviceName: name
473
+ });
474
+ return true;
475
+ }
476
+ catch (_a) {
477
+ return false;
478
+ }
479
+ }
480
+ /**
481
+ * 执行单个计划步骤
482
+ */
483
+ async executeStep(options) {
484
+ const { item, config, envId, cwd } = options;
485
+ switch (item.type) {
486
+ case 'database': {
487
+ const dbConfig = config.database;
488
+ const result = await this.databaseDeployer.deploy(dbConfig, cwd, envId);
489
+ return result.taskId ? `task:${result.taskId}` : undefined;
490
+ }
491
+ case 'functions': {
492
+ const fn = (config.functions || []).find((f) => f.name === item.name);
493
+ if (!fn)
494
+ return undefined;
495
+ // 函数代码目录解析(对齐 CLI FunctionPathResolver 的 dir 语义):
496
+ // - 显式配置 dir → path.resolve(cwd, dir)(独立于 functionRoot,不拼接)
497
+ // - 未配置 dir → path.join(functionRootPath, name) = {cwd}/{functionRoot}/{name}
498
+ const functionRoot = config.functionRoot || 'functions';
499
+ const functionRootPath = path_1.default.resolve(cwd, functionRoot);
500
+ const functionPath = fn.dir
501
+ ? path_1.default.resolve(cwd, fn.dir)
502
+ : path_1.default.join(functionRootPath, fn.name);
503
+ // 声明式部署幂等语义:函数已存在则覆盖更新(force=true),支持重跑续传
504
+ const result = await this.functionDeployer.deploy({
505
+ func: fn,
506
+ functionRootPath,
507
+ functionPath,
508
+ force: true
509
+ });
510
+ return result.url;
511
+ }
512
+ case 'app': {
513
+ const result = await this.staticDeployer.deployApp({ config: config.app, cwd });
514
+ return result.url;
515
+ }
516
+ case 'hosting': {
517
+ const h = (config.hosting || []).find((x) => (x.name || 'default') === item.name);
518
+ const result = await this.staticDeployer.deployHosting({ config: h, cwd });
519
+ return result.url;
520
+ }
521
+ case 'gateway': {
522
+ const result = await this.gatewayDeployer.deploy({
523
+ routes: config.gateway.routes,
524
+ envId,
525
+ // 传入 hosting 配置:hosting 路由自动生成 PathRewrite.Prefix = deployPath
526
+ hostings: config.hosting || []
527
+ });
528
+ return result.domain;
529
+ }
530
+ }
531
+ }
532
+ /** 深比较(JSON 快照语义):仅用于 app 配置快照等简单对象 */
533
+ deepEqual(a, b) {
534
+ return JSON.stringify(a) === JSON.stringify(b);
535
+ }
536
+ /** app 配置差异明细(from → to) */
537
+ appConfigChanges(prev, curr) {
538
+ const changes = [];
539
+ const keys = new Set([
540
+ ...Object.keys(prev || {}),
541
+ ...Object.keys(curr || {})
542
+ ]);
543
+ for (const key of keys) {
544
+ const from = prev === null || prev === void 0 ? void 0 : prev[key];
545
+ const to = curr === null || curr === void 0 ? void 0 : curr[key];
546
+ if (from !== to) {
547
+ changes.push({ field: String(key), from, to });
548
+ }
549
+ }
550
+ return changes;
551
+ }
552
+ /**
553
+ * 轻量 inline 守卫:配置合法 + 目录存在(validateProject 接线点)
554
+ */
555
+ guardConfig(config) {
556
+ if (!config || typeof config !== 'object') {
557
+ throw new error_1.CloudBaseError('无效的配置:config 必须为对象');
558
+ }
559
+ if (!config.envId) {
560
+ throw new error_1.CloudBaseError('无效的配置:缺少 envId');
561
+ }
562
+ }
563
+ }
564
+ exports.DeployOrchestrator = DeployOrchestrator;