@cloudbase/manager-node 5.9.4 → 5.10.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.
package/lib/utils/fs.js CHANGED
@@ -3,14 +3,100 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.toPosixPath = toPosixPath;
7
+ exports.isIgnoredPath = isIgnoredPath;
8
+ exports.matchesIgnorePattern = matchesIgnorePattern;
6
9
  exports.checkFullAccess = checkFullAccess;
7
10
  exports.checkReadable = checkReadable;
8
11
  exports.isDirectory = isDirectory;
9
12
  exports.formateFileSize = formateFileSize;
10
13
  exports.delSync = delSync;
11
14
  const fs_1 = __importDefault(require("fs"));
15
+ const micromatch_1 = __importDefault(require("micromatch"));
12
16
  const error_1 = require("../error");
13
17
  const del_1 = __importDefault(require("del"));
18
+ /**
19
+ * 把路径统一成 glob 模式使用的 `/` 分隔形式。
20
+ *
21
+ * micromatch 的 pattern 一律用 `/`(`**\/.env`、`**\/node_modules/**`),而
22
+ * `path.join` 在 Windows 上产出 `sub\dist\.env` ⇒ `isMatch` 全部返回 false。
23
+ * 也就是说:**在 Windows 上 ignore 清单会整份失效**。
24
+ *
25
+ * 这不是理论问题:静态托管的敏感文件排除(`.git` / `.env` / 私钥)就完全依赖
26
+ * `micromatch.isMatch` 的结果,`outputDir: '.'` 时整份提交历史与密钥会被发布到
27
+ * 公开可访问的托管桶。
28
+ *
29
+ * 用 `replace(/\\/g, '/')` 而不是 `split(path.sep).join('/')`:后者在 POSIX 上
30
+ * 不会转换反斜杠,跨平台测试无法覆盖 Windows 分支(回归会重新漏掉)。
31
+ */
32
+ function toPosixPath(filePath) {
33
+ return filePath.replace(/\\/g, '/');
34
+ }
35
+ /**
36
+ * 把「相对于 ignore 根目录」的路径归一化成可参与 glob 匹配的形式。
37
+ *
38
+ * 上传入口传进来的 `cloudPath` 可能是 `./assets/a.js`(用户手写的相对写法)
39
+ * 或 `/assets/a.js`(以 `/` 起头的 COS Key 习惯写法),两者都带一个多余的前导段,
40
+ * 直接交给 micromatch 会让根锚定 pattern(`assets/*`)匹配不上。
41
+ */
42
+ function toGlobRelativePath(relativePath) {
43
+ return toPosixPath(relativePath)
44
+ .replace(/^\.\//, '')
45
+ .replace(/^\/+/, '');
46
+ }
47
+ /**
48
+ * 判断本地文件路径是否被 ignore 清单命中。
49
+ *
50
+ * ignore 是「相对于业务根目录」的 glob 语义(见 `utils/ignore.ts` 的
51
+ * DEFAULT_ARTIFACT_IGNORE),而上传入口拿到的 `filePath` 常常是 `path.resolve()`
52
+ * 后的绝对路径(hosting 单文件分支塞进 `files[].localPath` 的就是这种)。
53
+ * micromatch 不会把 `/proj/dist/node_modules/x.js` 与 `node_modules/**` 匹配上,
54
+ * 只做 `toPosixPath` 只统一了分隔符、没统一「基准」⇒ 根锚定 pattern 静默失效。
55
+ *
56
+ * 对齐办法由调用方显式提供 `relativePath`(文件相对于 ignore 根目录的路径,
57
+ * 上传场景就是 `cloudPath`):
58
+ * - 绝对路径本身参与匹配 ⇒ `**\/` 前缀的 pattern 正常命中;
59
+ * - `relativePath` 参与匹配 ⇒ 根锚定 pattern 按「业务根目录」语义命中。
60
+ *
61
+ * ⚠️ 绝不通过「逐段剥离绝对路径」去猜根目录:那会把用户想表达的根锚定 pattern
62
+ * 静默放大成任意层级匹配 —— `static/**` 会连 `public/static/logo.png` 一起排除,
63
+ * 产物缺文件、线上 404 且无任何告警(stbug-51EX9nJY4f)。
64
+ * 宁可让根锚定 pattern 在拿不到 `relativePath` 时不命中(保守放行),
65
+ * 也不能让它命中不该命中的文件(静默破坏产物)。
66
+ *
67
+ * ⚠️ 与 walkLocalDir 的口径一致:两者最终都是拿「相对路径」去匹配,
68
+ * 差别只在 walkLocalDir 自己算相对路径,这里由调用方传进来。
69
+ */
70
+ function isIgnoredPath(filePath, ignore, relativePath) {
71
+ if (!ignore || (Array.isArray(ignore) && ignore.length === 0)) {
72
+ return false;
73
+ }
74
+ const posixPath = toPosixPath(filePath);
75
+ const candidates = [posixPath];
76
+ const relative = relativePath ? toGlobRelativePath(relativePath) : '';
77
+ if (relative && relative !== posixPath) {
78
+ candidates.push(relative);
79
+ }
80
+ return candidates.some(candidate => matchesIgnorePattern(candidate, ignore));
81
+ }
82
+ /**
83
+ * 统一的 ignore 匹配入口(`isIgnoredPath` 与 `walkLocalDir` 共用)。
84
+ *
85
+ * `dot: true` 是必需的,不是为了放宽用户规则,而是为了堵住一个静默失效:
86
+ * micromatch 默认 `dot: false`,`**` / `*` **不会穿越以点开头的目录**。
87
+ * 于是 `**\/id_rsa*` 打不到 `.ssh/id_rsa`、`**\/*.pem` 打不到 `.ssh/key.pem`、
88
+ * `**\/*.key` 打不到 `.gnupg/*.key` —— 点目录里的私钥整体逃过排除,
89
+ * 而项目根存在 `.ssh/` 在本地开发机上极其常见(stbug-KlBy1JWhvD)。
90
+ *
91
+ * 副作用仅限「排除范围变大」:用户写 `*.map` 时 `.foo.map` 也会被排除。
92
+ * 排除语义放大是可以接受的,漏排一个私钥不可接受。
93
+ *
94
+ * ⚠️ `dot: true` 只让通配符能进点目录,**不能**凭空识别 `.npmrc` 这类
95
+ * 仅靠文件名区分的凭证 —— 这类条目必须显式写进清单。
96
+ */
97
+ function matchesIgnorePattern(candidate, ignore) {
98
+ return micromatch_1.default.isMatch(candidate, ignore, { dot: true });
99
+ }
14
100
  // 检查路径是否可以访问(读、写)
15
101
  function checkFullAccess(dest, throwError = false) {
16
102
  try {
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * 上传类操作的默认排除清单。
4
+ *
5
+ * ⚠️ 这里必须保持「无 import」:`src/utils/fs.ts`、`src/storage/index.ts`、
6
+ * `src/deploy/StaticDeployer.ts` 三方都要引用它,带依赖会立刻形成循环引用
7
+ * (StaticDeployer → environment → storage → utils/fs → StaticDeployer)。
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.DEFAULT_ARTIFACT_IGNORE = exports.SENSITIVE_FILE_IGNORE = void 0;
11
+ exports.mergeArtifactIgnore = mergeArtifactIgnore;
12
+ /**
13
+ * 上传产物 / 源码包时默认排除的敏感文件。
14
+ *
15
+ * 两类场景都可能把密钥发布出去:
16
+ * 1. `outputDir: '.'` 时产物目录就是项目根,`.env` 会进**公开可访问**的托管桶
17
+ * 2. `skipBuild` 场景的源码包虽不参与构建,但仍会落到 COS
18
+ *
19
+ * `node_modules` 由 `uploadCode` 的 `excludeNodeModules`(默认 true)与
20
+ * `DEFAULT_ARTIFACT_IGNORE` 分别处理,不在此列。
21
+ *
22
+ * 两条硬约束(各自都出过真实缺陷,改动本清单前先看):
23
+ *
24
+ * 1. **每一项都必须能命中绝对路径**:文件级上传(`uploadFilesCustom`)拿到的
25
+ * `files[].localPath` 是 `path.resolve()` 后的绝对路径,根锚定写法(`.env`)
26
+ * 匹配不到它,只有 `**\/` 前缀形式能命中。
27
+ * 2. **点目录必须显式成条目**:micromatch 默认 `dot: false`,`**` / `*` 不会
28
+ * 穿越以点开头的目录 —— `**\/id_rsa*` 打不到 `.ssh/id_rsa`,`**\/*.pem` 同样
29
+ * 打不到。`isIgnoredPath` / `walkLocalDir` 已统一改用 `{ dot: true }` 兜住
30
+ * 通配符盲区(stbug-KlBy1JWhvD),但**新文件名仍需显式列出**:`dot: true`
31
+ * 只让 `**` 能进点目录,不能让通配符猜出 `.npmrc` 这类只靠文件名识别的凭证。
32
+ *
33
+ * 为什么仍是黑名单而不是「默认拒绝 + 白名单放行」:本清单同时决定静态产物能否
34
+ * 上传,白名单化会静默漏掉未知产物(站点缺文件、线上 404)。因此采用
35
+ * 「黑名单 + dot:true + 显式点目录条目」的组合,覆盖已知凭证载体。
36
+ */
37
+ exports.SENSITIVE_FILE_IGNORE = [
38
+ // 版本库元数据:含完整提交历史(已删除的 .env / 私钥仍在 objects 里)。
39
+ // outputDir 为 '.' 时 uploadFiles 会递归上传项目根,而 uploadFiles 内部没有任何默认排除,
40
+ // 不挡住就会经 https://{CdnDomain}/.git/... 被公开读取。
41
+ // 与 uploadCode 分支共用本清单,避免两处口径再次分叉
42
+ '.git/**',
43
+ '**/.git/**',
44
+ // 项目配置:envVariables 等可能含不应公开的值
45
+ '**/cloudbaserc.json',
46
+ '**/cloudbaserc.js',
47
+ '**/cloudbaserc.yaml',
48
+ '**/cloudbaserc.yml',
49
+ '**/.env',
50
+ // 保留 .env.example / .env.sample / .env.template:它们是可提交的模板、不含真实密钥。
51
+ // 口径与 src/deploy/function/cloud-build-service.ts 保持一致,避免一处保留一处挡掉
52
+ '**/.env.!(example|sample|template)',
53
+ // SSH:整个 .ssh 目录一起挡 —— 除私钥外 known_hosts / config 也没有上传价值,
54
+ // 逐个枚举私钥文件名只会漏掉用户自定义命名的 key
55
+ '.ssh/**',
56
+ '**/.ssh/**',
57
+ // 私钥文件:文件名形态(不能只靠扩展名,OpenSSH 默认生成的私钥没有扩展名)
58
+ '**/*.pem',
59
+ '**/*.key',
60
+ '**/id_rsa*',
61
+ '**/id_ed25519*',
62
+ '**/id_ecdsa*',
63
+ '**/id_dsa*',
64
+ // 云厂商 / 包管理 / SCM 凭证:装过 aws cli、配过 npm token、用过 credential.helper
65
+ // 的开发者目录里必然存在,outputDir 为 '.' 时会整体公开
66
+ '.aws/**',
67
+ '**/.aws/**',
68
+ '.gnupg/**',
69
+ '**/.gnupg/**',
70
+ '.kube/**',
71
+ '**/.kube/**',
72
+ '.docker/**',
73
+ '**/.docker/**',
74
+ '**/.npmrc',
75
+ // Yarn 1 用 `.yarnrc`,Yarn 2+(Berry)改用 `.yarnrc.yml` 且把 registry token
76
+ // 存在里面(npmScopes.<s>.npmAuthToken / npmRegistries.<reg>.npmAuthToken)。
77
+ // 两者是不同代际的独立文件,项目里可能同时存在,两条都必须留。
78
+ '**/.yarnrc',
79
+ '**/.yarnrc.yml',
80
+ '**/.netrc',
81
+ '**/.git-credentials',
82
+ '**/.pgpass',
83
+ '**/.my.cnf',
84
+ '**/.pypirc'
85
+ ];
86
+ /**
87
+ * 产物上传(目录级 `uploadDirectoryCustom` / 文件级 `uploadFilesCustom`)的默认排除清单,
88
+ * app / hosting 共用。
89
+ *
90
+ * 用户配置的 `ignore` 只能**追加**在其后,不能替换:hosting 曾用
91
+ * `config.ignore || ['.DS_Store']`,纯静态回退 root 时 `.env` / `.git` / 私钥会被公开发布。
92
+ *
93
+ * ⚠️ 作用域:只在**静态托管 / 静态部署**链路生效(`StaticDeployer.uploadArtifact`、
94
+ * `HostingService.uploadFiles`)。通用云存储上传(`storage.uploadFiles` /
95
+ * `uploadDirectory`)是私有桶语义,用户可能确实要备份 `.env` / 配置文件,
96
+ * 本次**不**强制套用本清单 —— 强制并入会让这类上传静默丢文件且无任何提示。
97
+ *
98
+ * `node_modules`:静态托管不构建、不运行 Node,上传它没有任何作用,
99
+ * 还会拖慢上传并公开依赖清单。必须同时写两条:`node_modules/**` 只匹配根目录那一层,
100
+ * `web/node_modules` 这类嵌套目录要靠 `**\/node_modules/**`。
101
+ * 例外:页面里直接引用 `./node_modules/xxx` 的写法会 404,应改为拷贝到 `vendor/` 或走 CDN。
102
+ */
103
+ exports.DEFAULT_ARTIFACT_IGNORE = [
104
+ '.DS_Store',
105
+ '**/.DS_Store',
106
+ 'node_modules/**',
107
+ '**/node_modules/**',
108
+ ...exports.SENSITIVE_FILE_IGNORE
109
+ ];
110
+ /**
111
+ * 把调用方传入的 `ignore` 追加到 `DEFAULT_ARTIFACT_IGNORE` 之后并去重。
112
+ *
113
+ * **唯一**的清单构造入口,三条路径共用同一份结果:
114
+ * 1. 静态托管直传(`HostingService.uploadFiles` → `uploadDirectoryCustom` / `uploadFilesCustom`)
115
+ * 2. 静态部署直传(`StaticDeployer.uploadArtifact`)
116
+ * 3. 版本快照打包(`StaticDeployer.deployAppStatic` → `cloudAppService.uploadCode`)
117
+ *
118
+ * 1 与 2/3 曾经各写一份:托管桶排 node_modules / .DS_Store,快照 ZIP 只排敏感文件,
119
+ * 于是产物目录含 node_modules 时「版本快照 = 实际发布内容」的契约不成立
120
+ * (回滚拿到快照里多了线上没有的文件)。现在统一,不再提供 scope 参数 ——
121
+ * 两侧范围本来就该一致,留一个可选 scope 只会让下一次分叉更容易发生
122
+ * (stbug-OSPixWD3KS)。
123
+ *
124
+ * 用户 ignore 只能追加:`undefined` / `[]` / 单字符串都表示「不加额外规则」,
125
+ * 绝不表示「不排除敏感文件」。去重是必要的:调用链上会被合并两次
126
+ * (StaticDeployer → hosting.uploadFiles),必须保证重复合并 = 幂等。
127
+ *
128
+ * ⚠️ 空串必须在这里丢掉(与 `StaticDeployer.normalizeIgnore` 同一口径):
129
+ * 空串不是合法 glob,留着会一路透传到 `utils/fs.ts:matchesIgnorePattern` 的
130
+ * `micromatch.isMatch`,picomatch 直接抛 `TypeError: Expected pattern to be a
131
+ * non-empty string` —— 上传在第一次匹配时崩溃,且抛的是底层库错误、无任何指向性。
132
+ * 非字符串项同样丢弃:本文件必须保持「无 import」,不能引入 INVALID_PARAM 抛错。
133
+ */
134
+ function mergeArtifactIgnore(ignore) {
135
+ // 三种形态分开写而非嵌套三元:未配置 / 已是数组 / 单个字符串各自一行,便于后续扩展
136
+ let userIgnore;
137
+ if (ignore === undefined || ignore === null) {
138
+ userIgnore = [];
139
+ }
140
+ else if (Array.isArray(ignore)) {
141
+ userIgnore = ignore;
142
+ }
143
+ else {
144
+ userIgnore = [ignore];
145
+ }
146
+ const list = [...exports.DEFAULT_ARTIFACT_IGNORE, ...userIgnore].filter(item => typeof item === 'string' && item !== '');
147
+ return Array.from(new Set(list));
148
+ }
@@ -30,6 +30,8 @@ exports.sleep = sleep;
30
30
  exports.upperCaseStringFisrt = upperCaseStringFisrt;
31
31
  exports.upperCaseObjKey = upperCaseObjKey;
32
32
  exports.fetchTemplates = fetchTemplates;
33
+ exports.progressLog = progressLog;
34
+ exports.debugLog = debugLog;
33
35
  exports.successLog = successLog;
34
36
  const archiver_1 = __importDefault(require("archiver"));
35
37
  const crypto_1 = __importDefault(require("crypto"));
@@ -462,7 +464,27 @@ const getCompleteTimeRange = (timeRange) => {
462
464
  };
463
465
  };
464
466
  exports.getCompleteTimeRange = getCompleteTimeRange;
467
+ /**
468
+ * SDK 侧日志收敛:stdout 只承载结构化数据(`--json` 契约),日志一律走 stderr。
469
+ *
470
+ * 背景:此前 SDK 直接用 console.log 打印上传进度/诊断信息,CLI 以 `--json` 调用时
471
+ * 这些行混在 stdout 里,导致调用方 `JSON.parse(stdout)` 必然失败。
472
+ */
473
+ /** 是否开启诊断日志:环境变量优先,其次看命令行是否带 --verbose */
474
+ function isVerboseLog() {
475
+ return process.env.TCB_MANAGER_VERBOSE === '1' || process.argv.includes('--verbose');
476
+ }
477
+ /** 用户可见的进度日志(准备上传 / 批次 / 完成),写 stderr,不污染 stdout */
478
+ function progressLog(msg) {
479
+ process.stderr.write(`${msg}\n`);
480
+ }
481
+ /** 诊断日志,默认静默,仅 verbose 时输出 */
482
+ function debugLog(msg) {
483
+ if (!isVerboseLog())
484
+ return;
485
+ process.stderr.write(`[debug] ${msg}\n`);
486
+ }
465
487
  function successLog(msg) {
466
488
  // 空格,兼容中文字符编码长度问题
467
- console.log(`${msg}`);
489
+ process.stderr.write(`${msg}\n`);
468
490
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.9.4",
3
+ "version": "5.10.0",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  import { IServiceVersion, IExistsRes, CreateIndex, DropIndex, IndexInfo, TableInfo, Pager, IResponseInfo, CollectionDispension } from '../interfaces/';
2
2
  import { Environment } from '../environment';
3
- import { IDescribePGUserMigrationOptions, IDescribePGUserMigrationResult, IDescribeTaskResultOptions, IDescribeTaskResultResult, IListAllPGUserMigrationsOptions, IListPGUserMigrationsOptions, IListPGUserMigrationsResult, IPreviewPGUserMigrationsOptions, IPreviewPGUserMigrationsResult, IPushPGUserMigrationsOptions, IPushPGUserMigrationsResult, IRepairPGUserMigrationHistoryOptions } from './type';
3
+ import { IEnablePGRealtimeOptions, IEnablePGRealtimeResult, IDescribePGRealtimeSettingsOptions, IDescribePGRealtimeSettingsResult, IModifyPGRealtimeSettingsOptions, IModifyPGRealtimeSettingsResult, IDescribePGUserMigrationOptions, IDescribePGUserMigrationResult, IDescribeTaskResultOptions, IDescribeTaskResultResult, IListAllPGUserMigrationsOptions, IListPGUserMigrationsOptions, IListPGUserMigrationsResult, IPreviewPGUserMigrationsOptions, IPreviewPGUserMigrationsResult, IPushPGUserMigrationsOptions, IPushPGUserMigrationsResult, IRepairPGUserMigrationHistoryOptions } from './type';
4
4
  interface IDatabaseConfig {
5
5
  Tag: string;
6
6
  }
@@ -227,6 +227,60 @@ export declare class DatabaseService {
227
227
  * console.log(res.Rows) // ['["1","Alice"]']
228
228
  */
229
229
  executePGSql(options: IExecutePGSqlOptions): Promise<IExecutePGSqlResult>;
230
+ /**
231
+ * 开通 PG 数据库 Realtime 能力
232
+ * 用于服务首次开通;已开通时重复调用仍返回 Enabled: true(幂等)
233
+ *
234
+ * @param options 开通参数
235
+ * @param options.EnvId 环境 ID(可选,不传则自动使用当前环境 ID)
236
+ * @returns 开通状态
237
+ *
238
+ * @example
239
+ * // 使用当前环境 ID 开通
240
+ * const res = await database.enablePGRealtime()
241
+ * console.log(res.Enabled) // true
242
+ *
243
+ * @example
244
+ * // 指定环境 ID 开通
245
+ * const res = await database.enablePGRealtime({ EnvId: 'pg-shared-d4gq5c0ne1a30f155' })
246
+ */
247
+ enablePGRealtime(options?: IEnablePGRealtimeOptions): Promise<IEnablePGRealtimeResult>;
248
+ /**
249
+ * 查询 PG Realtime 设置
250
+ * 返回当前挂起状态、频道策略、限流阈值、连接池大小及创建/更新时间
251
+ *
252
+ * @param options 查询参数
253
+ * @param options.EnvId 环境 ID(可选,不传则自动使用当前环境 ID)
254
+ * @returns Realtime 当前配置
255
+ *
256
+ * @example
257
+ * const settings = await database.describePGRealtimeSettings()
258
+ * console.log(settings.Suspend, settings.MaxConcurrentUsers)
259
+ */
260
+ describePGRealtimeSettings(options?: IDescribePGRealtimeSettingsOptions): Promise<IDescribePGRealtimeSettingsResult>;
261
+ /**
262
+ * 修改 PG Realtime 设置
263
+ *
264
+ * 增量修改:只需传要变更的字段,未传字段保持服务端当前值;
265
+ * 建议先用 describePGRealtimeSettings 获取当前配置再按需调整。
266
+ * 关闭/挂起 Realtime 会断开所有已有连接,客户端需重连。
267
+ *
268
+ * @param options 需要变更的设置项
269
+ * @param options.EnvId 环境 ID(可选,不传则自动使用当前环境 ID)
270
+ * @returns RequestId
271
+ *
272
+ * @example
273
+ * // 启用 Realtime(取消挂起)
274
+ * await database.modifyPGRealtimeSettings({ Suspend: false })
275
+ *
276
+ * @example
277
+ * // 更新最大并发用户数与限流阈值
278
+ * await database.modifyPGRealtimeSettings({
279
+ * MaxConcurrentUsers: 50002,
280
+ * MaxEventsPerSecond: 200
281
+ * })
282
+ */
283
+ modifyPGRealtimeSettings(options?: IModifyPGRealtimeSettingsOptions): Promise<IModifyPGRealtimeSettingsResult>;
230
284
  /**
231
285
  * 预览 PG 用户 migrations 的远端执行计划(不实际执行 SQL)
232
286
  * @param options 预览参数
@@ -193,6 +193,118 @@ export interface IDescribeTaskResultResult {
193
193
  /** 请求唯一 ID */
194
194
  RequestId: string;
195
195
  }
196
+ /**
197
+ * 开通 PG Realtime 请求参数
198
+ */
199
+ export interface IEnablePGRealtimeOptions {
200
+ /** 云开发环境 ID(可选,不传则自动使用当前环境 ID) */
201
+ EnvId?: string;
202
+ }
203
+ /**
204
+ * 开通 PG Realtime 返回参数
205
+ */
206
+ export interface IEnablePGRealtimeResult {
207
+ /** 开通状态 */
208
+ Enabled: boolean;
209
+ /** 请求唯一 ID */
210
+ RequestId: string;
211
+ }
212
+ /**
213
+ * 查询 PG Realtime 设置请求参数
214
+ */
215
+ export interface IDescribePGRealtimeSettingsOptions {
216
+ /** 云开发环境 ID(可选,不传则自动使用当前环境 ID) */
217
+ EnvId?: string;
218
+ }
219
+ /**
220
+ * 查询 PG Realtime 设置返回参数
221
+ */
222
+ export interface IDescribePGRealtimeSettingsResult {
223
+ /**
224
+ * 是否挂起,全局生效(所有节点)
225
+ * true 时立即断开全部在线 WebSocket 连接,拒绝新连接(HTTP 403),并禁用服务端广播 API
226
+ */
227
+ Suspend: boolean;
228
+ /** 是否仅允许 private 频道 */
229
+ PrivateOnly: boolean;
230
+ /** presence 总开关 */
231
+ PresenceEnabled: boolean;
232
+ /** 最大并发用户数(按用户统计,同一用户多连接合并计数) */
233
+ MaxConcurrentUsers: number;
234
+ /** 每秒最大事件数(所有客户端 broadcast 事件计数总和) */
235
+ MaxEventsPerSecond: number;
236
+ /** 每秒最大 join 数 */
237
+ MaxJoinsPerSecond: number;
238
+ /** 每秒最大消息字节数(所有广播负载流量) */
239
+ MaxBytesPerSecond: number;
240
+ /** 单客户端最大频道数 */
241
+ MaxChannelsPerClient: number;
242
+ /** 每秒最大 presence 事件数(所有客户端 track/untrack 计数总和) */
243
+ MaxPresenceEventsPerSecond: number;
244
+ /** 单条消息最大负载(KB) */
245
+ MaxPayloadSizeInKB: number;
246
+ /** 数据库连接池大小(供 RLS 鉴权查询使用) */
247
+ ConnectionPool: number;
248
+ /** CDC(postgres_changes)专用数据库连接池大小 */
249
+ PostgresChangesPool: number;
250
+ /** 编辑更新时间(ISO8601,格式 YYYY-MM-DDTHH:mm:ssZ) */
251
+ UpdatedTime: string;
252
+ /** 创建时间(ISO8601,格式 YYYY-MM-DDTHH:mm:ssZ) */
253
+ CreatedTime: string;
254
+ /** 平台冻结时间(ISO8601;空字符串表示未冻结,仅计划内调用返回) */
255
+ AdminSuspendedTime?: string;
256
+ /** 请求唯一 ID */
257
+ RequestId: string;
258
+ }
259
+ /**
260
+ * 修改 PG Realtime 设置请求参数
261
+ *
262
+ * 均为增量修改:只传需要变更的字段,未传字段保持服务端当前值。
263
+ * 建议先通过 DescribePGRealtimeSettings 获取当前配置后再按需调整。
264
+ */
265
+ export interface IModifyPGRealtimeSettingsOptions {
266
+ /** 云开发环境 ID(可选,不传则自动使用当前环境 ID) */
267
+ EnvId?: string;
268
+ /**
269
+ * 挂起租户,全局生效(所有节点)
270
+ * true:立即断开该租户全部在线 WebSocket 连接,拒绝新连接(HTTP 403),并禁用服务端广播 API
271
+ * false:恢复
272
+ * 注意:suspend 值的变化都会触发一次断连,客户端需重连
273
+ */
274
+ Suspend?: boolean;
275
+ /** 仅允许 private 频道。true 后客户端无法 join 带 public: 前缀的频道 */
276
+ PrivateOnly?: boolean;
277
+ /** presence 总开关。false 时客户端对频道的 presence join 请求被拒绝 */
278
+ PresenceEnabled?: boolean;
279
+ /**
280
+ * 租户最大并发用户数(按用户统计,同一用户多连接合并计数),超限时新连接被拒
281
+ * 服务端校验范围:[0, 15000](接口文档示例值 50002 会被服务端拒绝)
282
+ */
283
+ MaxConcurrentUsers?: number;
284
+ /** 每秒最大事件数(所有客户端 broadcast 事件计数总和),超限返回限流错误 */
285
+ MaxEventsPerSecond?: number;
286
+ /** 每秒最大 join 数,超限新 join 被限流 */
287
+ MaxJoinsPerSecond?: number;
288
+ /** 每秒最大消息字节数(所有广播负载流量),超限消息被拒 */
289
+ MaxBytesPerSecond?: number;
290
+ /** 单客户端最大频道数,超限时该客户端无法 join 更多频道 */
291
+ MaxChannelsPerClient?: number;
292
+ /** 每秒最大 presence 事件数(所有客户端 track/untrack 计数总和),超限 presence 变更被拒 */
293
+ MaxPresenceEventsPerSecond?: number;
294
+ /** 单条消息最大负载(KB),超限消息被拒 */
295
+ MaxPayloadSizeInKB?: number;
296
+ /** 租户数据库连接池大小(供 RLS 鉴权查询使用) */
297
+ ConnectionPool?: number;
298
+ /** CDC(postgres_changes)专用数据库连接池大小,与广播/鉴权池隔离 */
299
+ PostgresChangesPool?: number;
300
+ }
301
+ /**
302
+ * 修改 PG Realtime 设置返回参数(仅返回 RequestId)
303
+ */
304
+ export interface IModifyPGRealtimeSettingsResult {
305
+ /** 请求唯一 ID */
306
+ RequestId: string;
307
+ }
196
308
  /**
197
309
  * 修复 PG 用户 migration history 请求参数
198
310
  */
@@ -35,6 +35,35 @@ export interface IDeployOptions {
35
35
  error?: (msg: string) => void;
36
36
  };
37
37
  }
38
+ /**
39
+ * app / hosting 的构建上下文(机器可读)
40
+ *
41
+ * 声明式部署只部署不构建,所以调用方(CLI / MCP agent)需要知道「该跑什么、产物在哪、
42
+ * 现在能不能 apply」。这些信息此前只存在于 `details` 的中文字符串里(如
43
+ * 「不构建,上传已有产物(产物目录按框架/构建配置推断)」),调用方只能靠解析文案去猜
44
+ * —— 文案一改就断。这里把它结构化暴露,推断逻辑仍只有本仓库这一个真相源。
45
+ *
46
+ * 典型用法(agent 自助构建闭环):
47
+ * deployPlan → 发现 artifactReady=false → 自行执行 buildCommand → 再 plan → deployApply
48
+ *
49
+ * ⚠️ 本字段**可能缺省**。缺省 ≠「产物就绪」,而是「无法给出可靠的构建上下文」,常见原因:
50
+ * - `root` / `outputDir` 类型非法、含 NUL、解析后越出项目目录,或经软链指向项目外
51
+ * (apply 的参数校验会给出准确错误;plan 既不该报 `artifactReady`,也不该去遍历项目外目录)
52
+ * 调用方不要把 `buildHint === undefined` 当作可以直接 apply。
53
+ */
54
+ export interface IBuildHint {
55
+ /** 推断出的构建命令;null = 纯静态,本就无需构建 */
56
+ buildCommand: string | null;
57
+ /**
58
+ * 产物目录绝对路径(apply 时实际上传的目录)
59
+ *
60
+ * 与 `StaticDeployer` 共用 `resolveArtifactDir`(app / hosting 同一套规则):
61
+ * 配了 outputDir 按配置;没配按 buildCommand / framework / package.json 推断,纯静态回退 root。
62
+ */
63
+ outputDir: string;
64
+ /** 产物是否已就绪(目录存在且非空)。false ⇒ apply 必定抛 BUILD_OUTPUT_NOT_FOUND */
65
+ artifactReady: boolean;
66
+ }
38
67
  /** 部署计划项(资源级) */
39
68
  export interface IDeployPlanItem {
40
69
  type: ResourceType;
@@ -54,6 +83,8 @@ export interface IDeployPlanItem {
54
83
  label: string;
55
84
  value: string;
56
85
  }>;
86
+ /** 构建上下文(仅 app / hosting):声明式部署不构建,调用方据此自行构建 */
87
+ buildHint?: IBuildHint;
57
88
  }
58
89
  /** 部署结果 */
59
90
  export interface IDeployResult {
@@ -100,6 +131,25 @@ export declare class DeployOrchestrator {
100
131
  * - database 失败始终强制中断(无论是否 continueOnError)
101
132
  */
102
133
  deploy(options: IDeployOptions): Promise<IDeployResult>;
134
+ /**
135
+ * 部署入口归一化:cloudbaserc 原文 → 编排器内部契约(唯一的输入形状收口处)
136
+ *
137
+ * cloudbaserc 是用户手改的 JSON,schema 只在 `tcb validate` / MCP 注入侧生效
138
+ * (见 `StaticDeployer.resolveInsideBase` 注释里 `"root": 123` 的例子),因此
139
+ * `"functions": ""` / `0` / `false` / `{}`、`"hosting": 123`、`"gateway": []`
140
+ * 都是真实可达输入。
141
+ *
142
+ * 为什么必须收口在**入口**而不是各消费点自己防:
143
+ * `(config.hosting || []).map(...)` 对 `''` 恰好安全,对 `{}` / `123` 会抛
144
+ * `x.map is not a function` —— 不含字段名,用户无从定位。这类守卫此前分散在
145
+ * `buildHostingPlans` / `executeStep` / 网关归一化三处,每新增一个消费点就漏一处。
146
+ * 归一化后,消费点可以假设「顶层资源声明一定是对象数组」。
147
+ *
148
+ * 降级口径与历史 `config.hosting || []` 一致 —— **非数组一律降级为空数组且不报错**,
149
+ * 存量把手写错的配置仍能跑通其余资源的部署;数组元素非对象则没有静默降级的合理语义
150
+ * (会漏部署一个站点/一条路由),显式报 `INVALID_PARAM` 并带字段名与下标。
151
+ */
152
+ private normalizeConfigInput;
103
153
  /**
104
154
  * 归一化声明式函数配置:直接消费 cloudbaserc 的嵌套形状(imageConfig.build),
105
155
  * 不做字段平铺映射;非 HTTP 函数原样返回。
@@ -135,12 +185,59 @@ export declare class DeployOrchestrator {
135
185
  private buildDatabasePlan;
136
186
  /**
137
187
  * app 计划:按云端存在性 → create / update
188
+ *
189
+ * 附带 buildHint:声明式部署不构建,调用方需要据此自行构建(见 IBuildHint)。
138
190
  */
139
191
  private buildAppPlan;
192
+ /**
193
+ * 解析 app / hosting 的构建上下文(见 IBuildHint)
194
+ *
195
+ * ⚠️ root / outputDir 必须复用 `StaticDeployer` 导出的 `resolveDeployRoot` /
196
+ * `resolveArtifactDir`,**不能自己再写一份解析**:
197
+ * 1. plan 与 apply 口径一旦分叉,`artifactReady` 的契约就会反转 —— 例如 apply 拒绝的配置
198
+ * plan 报「可以 apply」,或 apply 能部署的配置 plan 报「配置非法」
199
+ * 2. 不做越界校验时 `hasFilesInDir` 会**同步递归遍历项目外的目录树**;
200
+ * `outputDir: '/'` 或用户主目录会让 plan 直接卡死
201
+ *
202
+ * 非法值(非字符串 / 含 NUL / 越界 / 软链逃逸)一律返回 `undefined` 而不抛错:
203
+ * plan 是只读预演,报错留给 apply 的参数校验,让 plan 始终能完整渲染。
204
+ * `null` / `''` 与「未配置」同义,走推断,不属非法值。
205
+ */
206
+ private resolveBuildHint;
140
207
  /**
141
208
  * hosting 计划:每次直传覆盖(无本地指纹 skip)
209
+ *
210
+ * 声明式部署只部署不构建,因此 `buildCommand` / `framework` 在计划里只用来说明
211
+ * **产物目录是怎么推断出来的**,不代表会执行构建(历史文案「本地构建后直传」已废除)。
212
+ * 机器可读的构建上下文见每项的 `buildHint`。
142
213
  */
143
214
  private buildHostingPlans;
215
+ /**
216
+ * 传给网关的 hosting 配置:deployPath 必须是**归一化后**的值
217
+ *
218
+ * 同一个字段被两处消费,必须同源:GatewayDeployer 用它生成 `PathRewrite.Prefix`
219
+ * (parseRouteWithType),StaticDeployer 用它算上传的 `cloudPath`
220
+ * (`deployHosting` 里的 normalizeDeployPath)。只归一化预演显示的那一份时,
221
+ * `"deployPath": "web"` 会让产物传到 `/web`、网关前缀却写成 `web`,请求重写后
222
+ * 落到存储根路径 → 线上 404,而 plan 显示 `/web`、apply 也不报错。
223
+ *
224
+ * 判据是「归一化是否成功」,不是「原始值是否为真」—— 下游 `if (hosting.deployPath)`
225
+ * 只判真值,`123` 与 `' '` 都算真值,原样下发会得到 `Prefix: 123` / `Prefix: ' '`。
226
+ *
227
+ * 三类输入分别处理:
228
+ * 1. 未配置(null/undefined/''/0/false):保持原样,与 `deployHosting` 的
229
+ * `deployPath || '/'` 同口径,网关侧 falsy → 不生成 PathRewrite(不能补成
230
+ * `Prefix: '/'`,否则给存量配置制造出路由 diff)
231
+ * 2. 非字符串(手改 JSON 的 `"deployPath": 123`):先 `String()` 收口再归一化,
232
+ * 与 `deployHosting` 内部 `String(value)` 同源,两侧都得到 `/123`
233
+ * 3. 纯空白 / 归一化失败(`' '`、`'../web'`):**不回落原值**,置为 undefined
234
+ * 让网关不生成 Prefix,失败出口收敛到 apply 的 INVALID_PARAM,避免「部署成功
235
+ * 但前缀与真实上传路径不一致」的静默 404
236
+ *
237
+ * 入参形状由 `normalizeConfigInput` 保证(一定是对象数组),因此这里不再需要
238
+ * `Array.isArray` / `|| []` 守卫 —— 守卫分散在各消费点正是「修一处漏一处」的来源。
239
+ */
240
+ private normalizeHostingsForGateway;
144
241
  /**
145
242
  * gateway 计划:planRoutes 对比云端路由 → create / update / skip(含变更字段明细)
146
243
  */
@@ -150,7 +247,7 @@ export declare class DeployOrchestrator {
150
247
  */
151
248
  private listExistingFunctionNames;
152
249
  /**
153
- * 判断云应用是否已存在(查询失败返回 false,降级为新建判定)
250
+ * 判断静态托管应用是否已存在(查询失败返回 false,降级为新建判定)
154
251
  */
155
252
  private appExists;
156
253
  /**
@@ -161,4 +258,13 @@ export declare class DeployOrchestrator {
161
258
  * 轻量 inline 守卫:配置合法 + 目录存在(validateProject 接线点)
162
259
  */
163
260
  private guardConfig;
261
+ /**
262
+ * config 必须是普通对象(不含数组)。
263
+ *
264
+ * 拆出来给 plan / apply 共用:此前只有 `deploy` 走 `guardConfig`,`deployPlan`
265
+ * 完全没校验,`deployPlan({ config: null })` 会在 `buildPlanForType` 里抛原生
266
+ * TypeError。`envId` 的校验仍留在 `guardConfig` —— plan 侧的 envId 真相源是
267
+ * `options.envId`(`config.envId` 允许缺省)。
268
+ */
269
+ private guardConfigShape;
164
270
  }