@lark-apaas/miaoda-cli 0.1.45 → 0.1.47

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 (37) hide show
  1. package/README.md +20 -0
  2. package/dist/api/deploy/api.js +3 -1
  3. package/dist/api/deploy/approval.js +77 -0
  4. package/dist/api/deploy/index.js +7 -1
  5. package/dist/api/deploy/schemas.js +3 -0
  6. package/dist/api/deploy/types.js +19 -1
  7. package/dist/cli/commands/app/index.js +87 -15
  8. package/dist/cli/commands/deploy/index.js +30 -6
  9. package/dist/cli/handlers/app/init.js +74 -2
  10. package/dist/cli/handlers/app/migrate.js +131 -212
  11. package/dist/cli/handlers/app/sync.js +41 -4
  12. package/dist/cli/handlers/deploy/deploy.js +57 -4
  13. package/dist/cli/handlers/deploy/format.js +13 -0
  14. package/dist/cli/handlers/deploy/get.js +41 -1
  15. package/dist/cli/handlers/deploy/helpers.js +29 -2
  16. package/dist/cli/handlers/deploy/polling.js +13 -9
  17. package/dist/config/fullstack-managed-files.js +67 -0
  18. package/dist/config/migrate-configs/html-to-design-html.js +1 -1
  19. package/dist/config/migrate-configs/index.js +9 -0
  20. package/dist/config/migrate-configs/vite-react-to-nestjs-react-fullstack.js +227 -197
  21. package/dist/config/sync-configs/design-stack.js +3 -4
  22. package/dist/config/sync-configs/nestjs-react-fullstack.js +11 -23
  23. package/dist/services/app/init/async-install.js +21 -0
  24. package/dist/services/app/init/index.js +3 -1
  25. package/dist/services/app/init/workspace-check.js +105 -0
  26. package/dist/services/app/migrate/detect.js +34 -0
  27. package/dist/services/app/migrate/index.js +8 -0
  28. package/dist/services/app/migrate/restart.js +59 -0
  29. package/dist/services/app/migrate/run.js +565 -0
  30. package/dist/services/app/migrate/symlinks.js +146 -0
  31. package/dist/utils/engineering-state.js +52 -0
  32. package/dist/utils/migrate-adapters.js +634 -0
  33. package/dist/utils/migrate-rule.js +801 -36
  34. package/dist/utils/project-snapshot.js +99 -0
  35. package/dist/utils/spark-meta.js +31 -10
  36. package/dist/utils/sync-rule.js +80 -11
  37. package/package.json +1 -1
@@ -41,16 +41,28 @@ const error_1 = require("../../../utils/error");
41
41
  const index_1 = require("../../../api/deploy/index");
42
42
  const helpers_1 = require("./helpers");
43
43
  const format_1 = require("./format");
44
+ const polling_1 = require("./polling");
45
+ const deploy_1 = require("./deploy");
44
46
  /**
45
47
  * 单页拉取上限:BAM listPipelineInstances 限制 1~500,取 500 让 deploy get
46
48
  * 在最近 500 条记录里按 ID 命中。
47
49
  */
48
50
  const DEPLOY_GET_LOOKUP_LIMIT = 500;
51
+ /**
52
+ * 可能挂着审批单的状态:审批中、审批被拒,以及卡点异常(平台没能发起审批,顶层表现为 FAILED)。
53
+ * 只有这些状态才额外查详情取审批单,其余状态保持单次列表请求。
54
+ */
55
+ const APPROVAL_LOOKUP_STATUSES = [
56
+ index_1.NodeStatus.PENDING,
57
+ index_1.NodeStatus.REJECTED,
58
+ index_1.NodeStatus.FAILED,
59
+ ];
49
60
  /**
50
61
  * miaoda deploy get <deploy-id>
51
62
  *
52
63
  * BAM 没有 by-ID 的查询接口,复用 listPipelineInstances 在最近窗口里按 ID 筛一条。
53
64
  * 命中失败时抛 DEPLOY_NOT_FOUND,提示用户用 history 查窗口。
65
+ * status 为 pending / rejected / failed 时额外查详情,有审批单则输出 approval(审批链接、拒绝原因等)。
54
66
  */
55
67
  async function handleDeployGet(opts) {
56
68
  if (!opts.deployId)
@@ -67,5 +79,33 @@ async function handleDeployGet(opts) {
67
79
  next_actions: ['运行 `miaoda deploy history` 确认 deploy-id 是否在最近窗口内'],
68
80
  });
69
81
  }
70
- (0, output_1.emit)({ data: (0, format_1.formatSimpleInstance)(instance), next_cursor: null, has_more: false }, index_1.deployGetSchema);
82
+ const approval = APPROVAL_LOOKUP_STATUSES.includes(instance.status)
83
+ ? await lookupApproval(appID, opts.deployId, instance.status)
84
+ : undefined;
85
+ (0, output_1.emit)({
86
+ data: { ...(0, format_1.formatSimpleInstance)(instance), ...(approval && { approval }) },
87
+ next_cursor: null,
88
+ has_more: false,
89
+ }, index_1.deployGetSchema);
90
+ if (!(0, output_1.isJsonMode)())
91
+ (0, deploy_1.writeApprovalNotice)(instance.status, approval, opts.deployId);
92
+ }
93
+ /**
94
+ * 列表项不含审批信息:按同一个 ID 查详情接口(审批展示视图)取审批单。
95
+ *
96
+ * pending / rejected 时详情失败直接抛错:丢掉审批链接 / 拒绝原因却无人察觉,比报错更糟。
97
+ * failed 时降级为只输出列表数据并在 stderr 提示:普通构建失败远多于卡点异常,不能因为详情接口
98
+ * 出错就让调用方连 status 都拿不到,卡住失败排查。
99
+ */
100
+ async function lookupApproval(appID, deployId, status) {
101
+ try {
102
+ return (0, index_1.findApproval)((0, polling_1.normalizePipelineInstance)(await api.deploy.queryPipelineInstance({ appID, instanceID: deployId })));
103
+ }
104
+ catch (err) {
105
+ if (status !== index_1.NodeStatus.FAILED)
106
+ throw err;
107
+ const reason = err instanceof Error ? err.message : String(err);
108
+ process.stderr.write(`warning: 查询审批单失败,输出不含 approval:${reason}\n`);
109
+ return undefined;
110
+ }
71
111
  }
@@ -3,18 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseStatusFlag = parseStatusFlag;
4
4
  exports.validateLimit = validateLimit;
5
5
  exports.parseDeployId = parseDeployId;
6
+ exports.validateApplyReason = validateApplyReason;
6
7
  const args_1 = require("../../../utils/args");
7
8
  const index_1 = require("../../../api/deploy/index");
8
9
  /**
9
10
  * 把 CLI --status 字符串映射成 BAM NodeStatus 数字;非法值抛 ARGS_INVALID。
10
11
  *
11
12
  * 历史接口(CliListPipelineInstances)返回 SimpleInstance,状态语义是 pipeline 节点状态,
12
- * 取值 todo / running / success / failed / canceled / hold_on。
13
+ * 取值 todo / running / success / failed / canceled / rejected / pending / skipped / hold_on(已废弃)。
13
14
  */
14
15
  function parseStatusFlag(text) {
15
16
  const value = (0, index_1.nodeStatusFromText)(text);
16
17
  if (value === undefined) {
17
- (0, args_1.failArgs)(`--status 无效:'${text}'。可选值:todo | running | success | failed | canceled | hold_on`);
18
+ (0, args_1.failArgs)(`--status 无效:'${text}'。可选值:todo | running | success | failed | canceled | rejected | pending | skipped | hold_on`);
18
19
  }
19
20
  return value;
20
21
  }
@@ -39,3 +40,29 @@ function parseDeployId(text) {
39
40
  }
40
41
  return n;
41
42
  }
43
+ /** 发布理由上限,按 Unicode code point 计,对齐 lark-cli `apps +release-create` */
44
+ const MAX_APPLY_REASON_CODE_POINTS = 1000;
45
+ /** C0 / C1 控制字符(含换行、制表符) */
46
+ const CONTROL_CHARS = /\p{Cc}/u;
47
+ /**
48
+ * 不可见格式字符(零宽、BOM、双向控制、词连接符等)与行/段分隔符:肉眼不可见,却会改变审批单里的展示。
49
+ * 与 deploy 输出服务端文本时的过滤口径一致。
50
+ */
51
+ const INVISIBLE_CHARS = /[\p{Cf}\u2028\u2029]/u;
52
+ /**
53
+ * 校验 --apply-reason 的格式;通过后调用方原样透传,不 trim。
54
+ * 是否必填由服务端按应用类型判定,这里不管。
55
+ */
56
+ function validateApplyReason(value) {
57
+ if (value.trim() === '')
58
+ (0, args_1.failArgs)('--apply-reason 不能为空');
59
+ if (CONTROL_CHARS.test(value)) {
60
+ (0, args_1.failArgs)('--apply-reason 不能包含换行、制表符等控制字符,需为单行文本');
61
+ }
62
+ if (INVISIBLE_CHARS.test(value)) {
63
+ (0, args_1.failArgs)('--apply-reason 不能包含零宽字符、双向控制字符或行/段分隔符');
64
+ }
65
+ if (Array.from(value).length > MAX_APPLY_REASON_CODE_POINTS) {
66
+ (0, args_1.failArgs)(`--apply-reason 最多 ${String(MAX_APPLY_REASON_CODE_POINTS)} 个字符`);
67
+ }
68
+ }
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.DEFAULT_POLL_INTERVAL_MS = void 0;
40
40
  exports.waitForPipeline = waitForPipeline;
41
+ exports.normalizePipelineInstance = normalizePipelineInstance;
41
42
  const ora_1 = __importDefault(require("ora"));
42
43
  const api = __importStar(require("../../../api/index"));
43
44
  const error_1 = require("../../../utils/error");
@@ -45,16 +46,11 @@ const output_1 = require("../../../utils/output");
45
46
  const index_1 = require("../../../api/deploy/index");
46
47
  /** 默认轮询间隔(毫秒) */
47
48
  exports.DEFAULT_POLL_INTERVAL_MS = 2_000;
48
- const TERMINAL_STATUSES = [
49
- index_1.NodeStatus.SUCCESS,
50
- index_1.NodeStatus.FAILED,
51
- index_1.NodeStatus.CANCELED,
52
- ];
53
49
  function defaultSleep(ms) {
54
50
  return new Promise((res) => setTimeout(res, ms));
55
51
  }
56
52
  /**
57
- * 轮询 pipeline 状态直到终态或超时。
53
+ * 轮询 pipeline 状态直到终态、进入人工审批或超时。
58
54
  *
59
55
  * - JSON 模式:轮询期间静默;终态后由 caller 输出最终信封。
60
56
  * - Pretty 非 TTY:同 JSON。
@@ -85,7 +81,11 @@ async function waitForPipeline(opts) {
85
81
  const detail = normalizePipelineInstance(resp);
86
82
  if (spinner)
87
83
  advanceProgress(detail, printedStages, spinner);
88
- if (TERMINAL_STATUSES.includes(detail.status)) {
84
+ if (index_1.TERMINAL_NODE_STATUSES.includes(detail.status)) {
85
+ return detail;
86
+ }
87
+ // 等待人工审批(顶层 PENDING):可能持续数小时,立即交还给调用方,不再轮询到超时
88
+ if (detail.status === index_1.NodeStatus.PENDING) {
89
89
  return detail;
90
90
  }
91
91
  if (now() - start >= timeoutMs) {
@@ -113,7 +113,7 @@ function advanceProgress(detail, printed, spinner) {
113
113
  for (const stage of stages) {
114
114
  if (printed.has(stage.id))
115
115
  continue;
116
- if (!TERMINAL_STATUSES.includes(stage.status))
116
+ if (!index_1.TERMINAL_NODE_STATUSES.includes(stage.status))
117
117
  continue;
118
118
  spinner.stopAndPersist({
119
119
  symbol: stageSymbol(stage),
@@ -153,7 +153,11 @@ function isPipelineInstance(value) {
153
153
  return typeof item.status === 'number';
154
154
  }
155
155
  function stageSymbol(stage) {
156
- return stage.status === index_1.NodeStatus.SUCCESS ? '✓' : stage.status === index_1.NodeStatus.FAILED ? '✗' : '•';
156
+ if (stage.status === index_1.NodeStatus.SUCCESS)
157
+ return '✓';
158
+ if (stage.status === index_1.NodeStatus.FAILED || stage.status === index_1.NodeStatus.REJECTED)
159
+ return '✗';
160
+ return '•';
157
161
  }
158
162
  function formatStageBody(stage) {
159
163
  const cost = stage.costTime >= 0 ? `(${(stage.costTime / 1000).toFixed(1)}s)` : '';
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ /**
3
+ * nestjs-react-fullstack 工程里**明确由平台托管**的文件清单 —— sync 与 migrate 共用的 SSOT。
4
+ *
5
+ * 所有权用"本次会触及的明确清单"表达(不做通用三方合并、不补全量历史模板基线):
6
+ * - 清单内文件:按兼容模板版本整文件更新(同名覆盖)。
7
+ * - 清单外文件:一律视为用户所有。scripts/ 下用户自建的 report.js 等额外文件不删除、不覆盖。
8
+ *
9
+ * 清单取自本仓 upgrade/templates/nestjs-react-fullstack/templates 下的 scripts/ 与 .githooks/。
10
+ * test/config/fullstack-managed-files 会校验 upgrade 模板目录与本清单一一对应 —— 模板新增托管脚本
11
+ * 必须同步加到这里,否则 CI 失败。
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.FULLSTACK_MANAGED_BUILD_DEV_DEPENDENCIES = exports.FULLSTACK_MANAGED_FILES = exports.FULLSTACK_MANAGED_HOOK_FILES = exports.FULLSTACK_MANAGED_SCRIPT_FILES = void 0;
15
+ exports.managedFileRules = managedFileRules;
16
+ exports.managedBuildDependencyRules = managedBuildDependencyRules;
17
+ /** 平台托管的脚本(相对项目根) */
18
+ exports.FULLSTACK_MANAGED_SCRIPT_FILES = [
19
+ 'scripts/dev.sh',
20
+ 'scripts/dev.js',
21
+ 'scripts/dev-local.js',
22
+ 'scripts/build.sh',
23
+ 'scripts/run.sh',
24
+ 'scripts/lint.js',
25
+ 'scripts/prune-smart.js',
26
+ 'scripts/hooks/run-precommit.js',
27
+ ];
28
+ /** 平台托管的 git hook 入口(相对项目根) */
29
+ exports.FULLSTACK_MANAGED_HOOK_FILES = ['.githooks/pre-commit'];
30
+ /** 全部托管文件 */
31
+ exports.FULLSTACK_MANAGED_FILES = [
32
+ ...exports.FULLSTACK_MANAGED_SCRIPT_FILES,
33
+ ...exports.FULLSTACK_MANAGED_HOOK_FILES,
34
+ ];
35
+ /**
36
+ * 把托管清单展开成逐文件的 `file` 规则(overwrite=true)。
37
+ *
38
+ * 不用 `directory` 规则:directory 会把模板目录里**任何**文件都当托管文件同步过去,清单边界
39
+ * 由模板目录内容隐式决定;逐文件展开让"哪些文件归平台"显式可审。源文件缺失时规则返回
40
+ * skipped(模板与清单漂移由测试兜底)。
41
+ */
42
+ function managedFileRules() {
43
+ return exports.FULLSTACK_MANAGED_FILES.map((file) => ({
44
+ type: 'file',
45
+ from: file,
46
+ to: file,
47
+ overwrite: true,
48
+ }));
49
+ }
50
+ /**
51
+ * 托管构建脚本的配套依赖:build.sh 的智能裁剪(prune-smart.js)require('@vercel/nft'),模板与
52
+ * 沙箱镜像都不提供它。迁移后的工程第一次完整构建(发布或本地 npm run build)会用到,所以
53
+ * migrate / repair 对未声明的工程补 devDependencies(版本为实测的 0.30.4);已声明的用户版本
54
+ * 不降级、不覆盖。
55
+ */
56
+ exports.FULLSTACK_MANAGED_BUILD_DEV_DEPENDENCIES = {
57
+ '@vercel/nft': '0.30.4',
58
+ };
59
+ /** 把托管构建依赖展开成 `ensure-dependency` 规则(缺失才补,已声明保持用户版本)。 */
60
+ function managedBuildDependencyRules() {
61
+ return Object.entries(exports.FULLSTACK_MANAGED_BUILD_DEV_DEPENDENCIES).map(([name, version]) => ({
62
+ type: 'ensure-dependency',
63
+ name,
64
+ version,
65
+ section: 'devDependencies',
66
+ }));
67
+ }
@@ -19,7 +19,7 @@
19
19
  * - 不从 design-html template 拷任何文件(模板本身只有 _gitignore + package.json;
20
20
  * 用户已有的 .gitignore 是 html 版超集,保留无害)
21
21
  * - 不动 README.md / .npmrc / .env —— 部署时均被 EXCLUDES 过滤
22
- * - 不管 dev 进程 / MIAODA_APP_TYPE 切换(平台职责;沙箱 pkill 匹配的
22
+ * - 不管 dev 进程 / MIAODA_APP_TYPE 切换(平台职责;沙箱重启信号匹配的
23
23
  * scripts/dev.js 在 html 项目里不存在,自然跳过)
24
24
  *
25
25
  * handler 收尾的 npm install 照常跑:devserver 依赖删掉后重新生成精简 lockfile,
@@ -16,6 +16,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
16
16
  };
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.getMigrateConfig = getMigrateConfig;
19
+ exports.getRepairConfig = getRepairConfig;
19
20
  exports.listSupportedMigrations = listSupportedMigrations;
20
21
  const vite_react_to_nestjs_react_fullstack_1 = __importDefault(require("./vite-react-to-nestjs-react-fullstack"));
21
22
  const html_to_design_html_1 = __importDefault(require("./html-to-design-html"));
@@ -28,6 +29,14 @@ const MIGRATE_REGISTRY = {
28
29
  function getMigrateConfig(from, to) {
29
30
  return MIGRATE_REGISTRY[`${from}:${to}`] ?? null;
30
31
  }
32
+ /**
33
+ * 磁盘已是目标 stack 时(同目标重试、历史恢复后平台已新、旧版 CLI 半完成样本)用于 repair 的
34
+ * 配置:取第一条以 stack 为目标的迁移配置。规则本身对已转换工程幂等(源目录不存在即跳过、
35
+ * 定向适配已对齐即 noop),配合 repair 模式跳过 migrateOnly 规则。
36
+ */
37
+ function getRepairConfig(stack) {
38
+ return Object.values(MIGRATE_REGISTRY).find((c) => c.to === stack) ?? null;
39
+ }
31
40
  /** 列出已支持的迁移路径,给 handler / 命令 help 用 */
32
41
  function listSupportedMigrations() {
33
42
  return Object.keys(MIGRATE_REGISTRY).map((key) => {