@lark-apaas/miaoda-cli 0.1.19-alpha.6dad9cd → 0.1.19-alpha.7856c6b
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/README.md +36 -2
- package/bin/miaoda.js +32 -0
- package/dist/api/comment/api.js +24 -0
- package/dist/api/comment/index.js +6 -0
- package/dist/api/comment/types.js +6 -0
- package/dist/api/index.js +3 -1
- package/dist/cli/commands/comment/index.js +55 -0
- package/dist/cli/commands/deploy/modern.js +7 -3
- package/dist/cli/commands/index.js +8 -2
- package/dist/cli/commands/registry/index.js +32 -15
- package/dist/cli/handlers/comment/index.js +7 -0
- package/dist/cli/handlers/comment/list.js +132 -0
- package/dist/cli/handlers/comment/resolve.js +44 -0
- package/dist/cli/handlers/deploy/patch.js +1 -0
- package/dist/cli/handlers/registry/add.js +55 -12
- package/dist/services/deploy/modern/atoms/design-build.js +8 -0
- package/dist/services/deploy/modern/atoms/index.js +2 -1
- package/dist/services/deploy/modern/patch/actions.js +4 -3
- package/dist/services/deploy/modern/patch/index.js +8 -3
- package/dist/services/deploy/modern/patch/routes.js +21 -12
- package/dist/services/deploy/modern/patch/source-scan.js +2 -0
- package/dist/services/deploy/modern/pipelines/design-local.js +30 -21
- package/dist/services/registry/index.js +88 -58
- package/dist/utils/http.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,12 +11,17 @@ pnpm add -g @lark-apaas/miaoda-cli
|
|
|
11
11
|
## 使用
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
#
|
|
14
|
+
# 设置默认应用(需要应用上下文的命令从 MIAODA_APP_ID 读取)
|
|
15
15
|
export MIAODA_APP_ID=app_demo_xxx
|
|
16
16
|
|
|
17
17
|
# 文件操作
|
|
18
18
|
miaoda file ls
|
|
19
19
|
miaoda file upload ./local/path
|
|
20
|
+
|
|
21
|
+
# 评论操作
|
|
22
|
+
miaoda comment list --json
|
|
23
|
+
miaoda comment list --only-unresolved --json
|
|
24
|
+
miaoda comment resolve 1703677660120110076 --json
|
|
20
25
|
```
|
|
21
26
|
|
|
22
27
|
JSON 结构化输出(Agent 推荐):
|
|
@@ -41,9 +46,38 @@ miaoda file ls --output json
|
|
|
41
46
|
| `miaoda file ...` | 文件操作:上传、下载、元数据、签名下载、批量删除 |
|
|
42
47
|
| `miaoda db ...` | 数据操作:SQL 执行、表结构查询、数据导入导出 |
|
|
43
48
|
| `miaoda observability ...` | 线上日志、链路、前端源码堆栈反查、监控指标、运营指标 |
|
|
49
|
+
| `miaoda comment ...` | 应用评论:查看评论列表、解决评论 |
|
|
50
|
+
| `miaoda registry ...` | 组件 registry:列出 / copy-in design-html SDK 文件(本地执行) |
|
|
44
51
|
|
|
45
52
|
完整命令通过 `miaoda --help` 或 `miaoda <domain> --help` 查看。
|
|
46
53
|
|
|
54
|
+
## 组件 registry(design-html)
|
|
55
|
+
|
|
56
|
+
`miaoda registry` 为 design-html 技术栈提供 shadcn 式的「组件 registry」:把 SDK 文件**拷进**当前项目(copy-in),而非作为依赖安装。registry 是独立 npm 包,按运行时分发(design-html → buildless runtime → `@lark-apaas/coding-registry-buildless`)。
|
|
57
|
+
|
|
58
|
+
前置:当前目录(或 `--dir`)已走过 `miaoda app init`,`.spark/meta.json` 含 `stack=design-html`。
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 列出可用 SDK 条目(输出各文件 USAGE 注释块原文,便于 Agent 直接读用法)
|
|
62
|
+
miaoda registry list
|
|
63
|
+
miaoda registry list --json
|
|
64
|
+
|
|
65
|
+
# 把条目 copy-in 到当前项目(自动按 USAGE 块的 Depends 解析依赖闭包一并拷入)
|
|
66
|
+
miaoda registry add deck-slide
|
|
67
|
+
miaoda registry add foo.css theme --dry-run # 只报告将写 / 将跳,不落盘
|
|
68
|
+
miaoda registry add deck-slide --overwrite # 目标已存在时覆盖(默认跳过以保护改动)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
要点:
|
|
72
|
+
|
|
73
|
+
- **条目**:registry 包根 `components/` · `scripts/` · `stylesheets/` 三个顶层目录(任意深度)下的代码文件即条目。USAGE 注释块**可选**(有则提供描述 + Depends,无则空描述、无依赖);包根直放文件、其它目录、非代码文件都不算条目。
|
|
74
|
+
- **寻址**:`add` / `Depends` 的 token 可写三种形式 —— `name`(去扩展名,如 `foo`)/ 完整文件名(如 `foo.css`)/ relPath(如 `stylesheets/foo.css`)。`name` **不要求唯一**,`list --json` 的 `file`(relPath)是永远无歧义的 canonical 地址。
|
|
75
|
+
- **多匹配全拉**:token 命中多个同名条目时全部 copy-in(如 `add foo` 把 `foo.jsx` + `foo.css` 一起拉),想只要其一就用完整文件名或 relPath。
|
|
76
|
+
- **拍平落地**:所有文件**拍平**到项目根(`scripts/deck-slide.js` → 项目 `deck-slide.js`,不保留子目录);闭包内同名 basename 撞车时后者覆盖前者。
|
|
77
|
+
- **skip-by-disk**:项目已有同名(拍平后的 basename)文件默认跳过,`--overwrite` 才覆盖;不写任何安装记账文件,项目文件树即状态。
|
|
78
|
+
- **add 回执带 usage**:`add` 结果的 `entries` 只列**你请求的条目**(不含被 `Depends` 传递拉进来的依赖——依赖照常落地在 `added`/`skipped`,但那是"管道"),逐条给出 `usage`(USAGE 块原文)与 `dependsOn`,Agent `add` 完即可直接读用法,无需再 `registry list`。请求的条目即使已存在被跳过也回带 usage(`status` 标 `added`/`skipped`,据此判断磁盘上是 registry 版还是你改过的旧版)。`--json` 结构为 `{registryVersion, added, skipped, dryRun, entries}`;pretty(默认)输出 `registry@<ver>` + `added:`/`skipped:` 逗号列表 + 各请求条目的 USAGE 块,稳定 label、无 JSON 数组、无截断,对 Agent 与人同样可读。
|
|
79
|
+
- `--version <ver>` 指定 registry 包版本或 dist-tag(缺省 `latest`),`--dir <path>` 指定项目目录(缺省当前目录)。
|
|
80
|
+
|
|
47
81
|
## 全局参数
|
|
48
82
|
|
|
49
83
|
- `--json [fields]`:输出结构化 JSON,可选字段级选择(如 `--json id,name`)。
|
|
@@ -55,7 +89,7 @@ miaoda file ls --output json
|
|
|
55
89
|
## 环境变量
|
|
56
90
|
|
|
57
91
|
- `MIAODA_APP_ID`:默认应用 ID,等价于 `--app-id`。
|
|
58
|
-
- `MIAODA_CANARY_HEADER`:HTTP 请求的 `x-tt-env` 小流量头取值。未设置时默认 `
|
|
92
|
+
- `MIAODA_CANARY_HEADER`:HTTP 请求的 `x-tt-env` 小流量头取值。未设置时默认 `boe_miaoda_ccm`;设为空字符串则不带该头(例:`export MIAODA_CANARY_HEADER=boe_xxx`)。值形如 `ppe_xxx` 时额外带上 `x-use-ppe: 1`。
|
|
59
93
|
|
|
60
94
|
## 输出契约
|
|
61
95
|
|
package/bin/miaoda.js
CHANGED
|
@@ -1,2 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// BOE 沙箱自动转发到 @dev 版本:让 BOE 沙箱内始终跑最新 dev 包,
|
|
4
|
+
// 本地版本仅作引导壳。避免无限递归靠 __MIAODA_BOE_FORWARDED sentinel。
|
|
5
|
+
// 逃生舱:MIAODA_DISABLE_BOE_FORWARD=1 时无论如何走本地。
|
|
6
|
+
const isBoe = process.env.FORCE_FRAMEWORK_ENVIRONMENT === "boe";
|
|
7
|
+
const alreadyForwarded = process.env.__MIAODA_BOE_FORWARDED === "1";
|
|
8
|
+
const disabled = process.env.MIAODA_DISABLE_BOE_FORWARD === "1";
|
|
9
|
+
|
|
10
|
+
if (isBoe && !alreadyForwarded && !disabled) {
|
|
11
|
+
const { spawnSync } = require("node:child_process");
|
|
12
|
+
|
|
13
|
+
if (process.env.MIAODA_DEBUG_BOE_FORWARD === "1" || process.argv.includes("--verbose")) {
|
|
14
|
+
process.stderr.write("[miaoda] BOE detected, forwarding to @lark-apaas/miaoda-cli@dev via npx\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const result = spawnSync(
|
|
19
|
+
"npx",
|
|
20
|
+
["-y", "@lark-apaas/miaoda-cli@dev", ...args],
|
|
21
|
+
{
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
env: { ...process.env, __MIAODA_BOE_FORWARDED: "1" },
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (result.error) {
|
|
28
|
+
process.stderr.write(`[miaoda] BOE forward failed: ${result.error.message}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
process.exit(result.status ?? 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
2
34
|
require("../dist/main.js");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getComments = getComments;
|
|
4
|
+
exports.resolveComment = resolveComment;
|
|
5
|
+
const http_1 = require("../../utils/http");
|
|
6
|
+
/**
|
|
7
|
+
* 评论接口走管理端 inner-api(getHttpClient + 小写 status_code 信封),与 app / deploy /
|
|
8
|
+
* observability 等域一致:统一的 verbose 请求/响应日志、错误映射、信封解析,不再域内自实现。
|
|
9
|
+
*/
|
|
10
|
+
/** GET /api/v1/studio/innerapi/apps/:appID/comments — 获取应用评论列表 */
|
|
11
|
+
async function getComments(appID, onlyUnresolved = false) {
|
|
12
|
+
let url = `/api/v1/studio/innerapi/apps/${encodeURIComponent(appID)}/comments`;
|
|
13
|
+
if (onlyUnresolved)
|
|
14
|
+
url += '?onlyUnresolved=true';
|
|
15
|
+
const body = await (0, http_1.getInnerApi)(url, {
|
|
16
|
+
errPrefix: 'Failed to list comments',
|
|
17
|
+
});
|
|
18
|
+
return body.items ?? [];
|
|
19
|
+
}
|
|
20
|
+
/** POST /api/v1/studio/innerapi/apps/:appID/comments/:commentID/resolve — 解决评论 */
|
|
21
|
+
async function resolveComment(appID, commentID) {
|
|
22
|
+
const url = `/api/v1/studio/innerapi/apps/${encodeURIComponent(appID)}/comments/${encodeURIComponent(commentID)}/resolve`;
|
|
23
|
+
await (0, http_1.postInnerApi)(url, undefined, { errPrefix: 'Failed to resolve comment' });
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveComment = exports.getComments = void 0;
|
|
4
|
+
var api_1 = require("./api");
|
|
5
|
+
Object.defineProperty(exports, "getComments", { enumerable: true, get: function () { return api_1.getComments; } });
|
|
6
|
+
Object.defineProperty(exports, "resolveComment", { enumerable: true, get: function () { return api_1.resolveComment; } });
|
package/dist/api/index.js
CHANGED
|
@@ -33,16 +33,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.deploy = exports.app = exports.observability = exports.db = exports.file = exports.plugin = void 0;
|
|
36
|
+
exports.comment = exports.deploy = exports.app = exports.observability = exports.db = exports.file = exports.plugin = void 0;
|
|
37
37
|
const _plugin = __importStar(require("../api/plugin/index"));
|
|
38
38
|
const _file = __importStar(require("../api/file/index"));
|
|
39
39
|
const _db = __importStar(require("../api/db/index"));
|
|
40
40
|
const _observability = __importStar(require("../api/observability/index"));
|
|
41
41
|
const _app = __importStar(require("../api/app/index"));
|
|
42
42
|
const _deploy = __importStar(require("../api/deploy/index"));
|
|
43
|
+
const _comment = __importStar(require("../api/comment/index"));
|
|
43
44
|
exports.plugin = { ..._plugin };
|
|
44
45
|
exports.file = { ..._file };
|
|
45
46
|
exports.db = { ..._db };
|
|
46
47
|
exports.observability = { ..._observability };
|
|
47
48
|
exports.app = { ..._app };
|
|
48
49
|
exports.deploy = { ..._deploy };
|
|
50
|
+
exports.comment = { ..._comment };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCommentCommands = registerCommentCommands;
|
|
4
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
5
|
+
const index_1 = require("../../../cli/handlers/comment/index");
|
|
6
|
+
function registerCommentCommands(program) {
|
|
7
|
+
const commentCmd = program
|
|
8
|
+
.command('comment')
|
|
9
|
+
.description('应用评论:查看评论列表、解决评论')
|
|
10
|
+
.usage('<command> [flags]');
|
|
11
|
+
commentCmd.action(() => {
|
|
12
|
+
commentCmd.outputHelp();
|
|
13
|
+
});
|
|
14
|
+
commentCmd.addHelpText('after', `
|
|
15
|
+
作用范围
|
|
16
|
+
操作当前应用的评论。应用上下文:--app-id <id> 或环境变量 MIAODA_APP_ID。
|
|
17
|
+
`);
|
|
18
|
+
const listCmd = commentCmd
|
|
19
|
+
.command('list')
|
|
20
|
+
.description('查看应用评论列表')
|
|
21
|
+
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
22
|
+
.option('--only-unresolved', '仅返回未解决的评论', false)
|
|
23
|
+
.addHelpText('after', `
|
|
24
|
+
JSON 输出
|
|
25
|
+
{"data": [{"comment_id": "...", "content": "...", "user_id": "...",
|
|
26
|
+
"finish": 0, "resolved": false, "create_time": 1700000000}]}
|
|
27
|
+
|
|
28
|
+
示例
|
|
29
|
+
$ miaoda comment list --json
|
|
30
|
+
$ miaoda comment list --only-unresolved --json
|
|
31
|
+
`);
|
|
32
|
+
listCmd.action((0, shared_1.withHelp)(listCmd, async (rawOpts) => {
|
|
33
|
+
(0, shared_1.rejectCliOverride)(listCmd, 'appId');
|
|
34
|
+
await (0, index_1.handleCommentList)({
|
|
35
|
+
appId: rawOpts.appId,
|
|
36
|
+
onlyUnresolved: rawOpts.onlyUnresolved,
|
|
37
|
+
});
|
|
38
|
+
}));
|
|
39
|
+
const resolveCmd = commentCmd
|
|
40
|
+
.command('resolve')
|
|
41
|
+
.description('把一条评论标记为已解决')
|
|
42
|
+
.argument('<commentID>', '待解决的评论 ID')
|
|
43
|
+
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
44
|
+
.addHelpText('after', `
|
|
45
|
+
JSON 输出
|
|
46
|
+
{"data": {"comment_id": "...", "resolved": true}}
|
|
47
|
+
|
|
48
|
+
示例
|
|
49
|
+
$ miaoda comment resolve 1703677660120110076 --json
|
|
50
|
+
`);
|
|
51
|
+
resolveCmd.action((0, shared_1.withHelp)(resolveCmd, async (commentId, rawOpts) => {
|
|
52
|
+
(0, shared_1.rejectCliOverride)(resolveCmd, 'appId');
|
|
53
|
+
await (0, index_1.handleCommentResolve)(commentId, { appId: rawOpts.appId });
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
@@ -59,15 +59,18 @@ JSON 输出(stdout)
|
|
|
59
59
|
}));
|
|
60
60
|
const patchCmd = deployCmd
|
|
61
61
|
.command('patch')
|
|
62
|
-
.description('design-html 增量发布:按文件 create/update/delete,.html
|
|
62
|
+
.description('design-html 增量发布:按文件 create/update/delete,.html 增删时同步 routes.json')
|
|
63
63
|
.option('--create <relpath>', '新增文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
64
64
|
.option('--update <relpath>', '覆盖已有文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
65
65
|
.option('--delete <relpath>', '删除文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
66
|
+
.option('--sessionId <id>', '会话 ID,发布时透传给下游(可选)')
|
|
66
67
|
.addHelpText('after', `
|
|
67
68
|
--dir 为项目目录(与 deploy 同名参数,默认当前目录)。
|
|
68
69
|
仅支持 design-html(design_local_deploy)。路径为工程根相对路径(= 服务端 latest 下 key),
|
|
69
|
-
禁绝对路径与 ".."。--create/--update
|
|
70
|
-
|
|
70
|
+
禁绝对路径与 ".."。--create/--update 的本地文件必须存在。.html 被新增/删除(路由集合变化)
|
|
71
|
+
时自动重算并随包更新 routes.json;纯 --update 现有 .html 只改内容,不动 routes.json。
|
|
72
|
+
routes.json 每项为 {path, file}:path 为相对路径(不含 base 前缀,由消费侧拼接),file 为
|
|
73
|
+
对应源文件(如 / → index.html)。
|
|
71
74
|
|
|
72
75
|
JSON 输出
|
|
73
76
|
{"data": {"upsertCount": <n>, "deleteCount": <n>, "actionsSent": <n>, "routesRegenerated": <bool>}}
|
|
@@ -84,6 +87,7 @@ JSON 输出
|
|
|
84
87
|
creates: rawOpts.create,
|
|
85
88
|
updates: rawOpts.update,
|
|
86
89
|
deletes: rawOpts.delete,
|
|
90
|
+
sessionId: rawOpts.sessionId,
|
|
87
91
|
});
|
|
88
92
|
}));
|
|
89
93
|
}
|
|
@@ -9,7 +9,8 @@ const index_4 = require("../../cli/commands/app/index");
|
|
|
9
9
|
const index_5 = require("../../cli/commands/deploy/index");
|
|
10
10
|
const modern_1 = require("../../cli/commands/deploy/modern");
|
|
11
11
|
const index_6 = require("../../cli/commands/skills/index");
|
|
12
|
-
const index_7 = require("../../cli/commands/
|
|
12
|
+
const index_7 = require("../../cli/commands/comment/index");
|
|
13
|
+
const index_8 = require("../../cli/commands/registry/index");
|
|
13
14
|
// scene 跟 dispatcher(MIAODA_APP_TYPE)同语义层级 —— app 业务类型维度,
|
|
14
15
|
// 对齐后端 devops app_common.AppType 枚举:
|
|
15
16
|
// 3 = AppType_APPLICATION(全栈应用,当前仅 nestjs-react-fullstack 一个 stack)
|
|
@@ -41,11 +42,13 @@ const SCENE_REGISTRARS = {
|
|
|
41
42
|
(0, index_1.registerFileCommands)(p);
|
|
42
43
|
(0, index_3.registerObservabilityCommands)(p);
|
|
43
44
|
(0, index_6.registerSkillsCommands)(p);
|
|
45
|
+
(0, index_7.registerCommentCommands)(p);
|
|
44
46
|
},
|
|
45
47
|
modern: (p) => {
|
|
46
48
|
(0, index_4.registerAppCommands)(p, { includeInit: true });
|
|
47
49
|
(0, modern_1.registerDeployCommandsModern)(p);
|
|
48
50
|
(0, index_6.registerSkillsCommands)(p);
|
|
51
|
+
(0, index_7.registerCommentCommands)(p);
|
|
49
52
|
},
|
|
50
53
|
// application scene(AppType_APPLICATION=3):
|
|
51
54
|
// 在 default 命令集基础上加 skills(按 meta.json.stack 拉对应 stack 技能)
|
|
@@ -57,6 +60,7 @@ const SCENE_REGISTRARS = {
|
|
|
57
60
|
(0, index_1.registerFileCommands)(p);
|
|
58
61
|
(0, index_3.registerObservabilityCommands)(p);
|
|
59
62
|
(0, index_6.registerSkillsCommands)(p);
|
|
63
|
+
(0, index_7.registerCommentCommands)(p);
|
|
60
64
|
},
|
|
61
65
|
// design scene(AppType_DESIGN=4):design-stack 仅 SSR 渲染、无后端业务逻辑、
|
|
62
66
|
// 无数据库、无 UGC 文件。命令集裁掉 db / file,保留 deploy / observability / app(init) / skills。
|
|
@@ -65,6 +69,7 @@ const SCENE_REGISTRARS = {
|
|
|
65
69
|
(0, index_5.registerDeployCommands)(p);
|
|
66
70
|
(0, index_3.registerObservabilityCommands)(p);
|
|
67
71
|
(0, index_6.registerSkillsCommands)(p);
|
|
72
|
+
(0, index_7.registerCommentCommands)(p);
|
|
68
73
|
},
|
|
69
74
|
// design-html scene(AppType_DESIGN_HTML=8):buildless 静态 HTML,
|
|
70
75
|
// 命令集与 modern 一致(app init/sync + modern 拆分版 deploy + skills),不挂 db/file/observability。
|
|
@@ -73,7 +78,8 @@ const SCENE_REGISTRARS = {
|
|
|
73
78
|
(0, index_4.registerAppCommands)(p, { includeInit: true });
|
|
74
79
|
(0, modern_1.registerDeployCommandsModern)(p);
|
|
75
80
|
(0, index_6.registerSkillsCommands)(p);
|
|
76
|
-
(0,
|
|
81
|
+
(0, index_8.registerRegistryCommands)(p);
|
|
82
|
+
(0, index_7.registerCommentCommands)(p);
|
|
77
83
|
},
|
|
78
84
|
};
|
|
79
85
|
function readEnv(name) {
|
|
@@ -7,9 +7,9 @@ const index_1 = require("../../../cli/handlers/registry/index");
|
|
|
7
7
|
* miaoda registry:design-html 技术栈的「组件 registry」(shadcn 式 copy-in)。
|
|
8
8
|
*
|
|
9
9
|
* registry 是独立 npm 包,按 runtime 命名分发(buildless runtime → @lark-apaas/coding-registry-buildless)。
|
|
10
|
-
* SDK
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* SDK 文件放在包根的 components/ · scripts/ · stylesheets/ 三个目录下(这三个顶层目录里的代码文件
|
|
11
|
+
* 才算条目);文件可带 USAGE 注释块(可选,声明描述 / 用法 / Depends)。CLI 抓 tarball、按
|
|
12
|
+
* .spark/meta.json.stack → runtime → 包名 选包,把 SDK 文件**拍平**copy-in 到用户项目根。
|
|
13
13
|
* 不写安装记账文件——项目文件树即状态(skip-by-disk)。
|
|
14
14
|
*/
|
|
15
15
|
function registerRegistryCommands(program) {
|
|
@@ -22,11 +22,14 @@ function registerRegistryCommands(program) {
|
|
|
22
22
|
});
|
|
23
23
|
registryCmd.addHelpText('after', `
|
|
24
24
|
概念
|
|
25
|
-
- registry 包:独立 npm 包(按 runtime
|
|
26
|
-
- 条目 (entry)
|
|
27
|
-
|
|
25
|
+
- registry 包:独立 npm 包(按 runtime 命名)
|
|
26
|
+
- 条目 (entry):包根 components/ · scripts/ · stylesheets/ 顶层目录(任意深度)下的代码文件;
|
|
27
|
+
name = 文件名去扩展名(不要求唯一)。包根直放文件、其它目录、非代码文件都不算条目
|
|
28
|
+
- 寻址:add / Depends 的 token 可写三种形式——name(foo)/ 完整文件名(foo.css)/ relPath(stylesheets/foo.css)
|
|
29
|
+
- 多匹配全拉:token 命中多个同名条目时全部 copy-in(不报歧义);想只要其一就写完整文件名 / relPath
|
|
30
|
+
- USAGE 块:可选元数据,声明描述 / Exports / Usage / Depends;无 USAGE 也是条目(空描述、无依赖)
|
|
28
31
|
- Depends:从 USAGE 块解析的跨条目依赖,add 时传递解析、一并 copy-in
|
|
29
|
-
- add
|
|
32
|
+
- add 落地:拍平到项目根(包根 scripts/x.js → 项目 x.js);同名 basename 撞车时后者覆盖前者
|
|
30
33
|
|
|
31
34
|
前置
|
|
32
35
|
当前目录(或 --dir)已走过 'miaoda app init'(.spark/meta.json 含 stack=design-html)
|
|
@@ -62,23 +65,37 @@ JSON 输出
|
|
|
62
65
|
function registerRegistryAdd(parent) {
|
|
63
66
|
const cmd = parent
|
|
64
67
|
.command('add')
|
|
65
|
-
.description('把一个或多个 SDK 文件 copy-in
|
|
66
|
-
.argument('<
|
|
68
|
+
.description('把一个或多个 SDK 文件 copy-in 到当前项目(拍平到项目根,含 Depends 闭包)')
|
|
69
|
+
.argument('<token...>', 'name / 完整文件名 / relPath,可传多个;同名多匹配全拉,Depends 自动传递解析')
|
|
67
70
|
.option('--dir <path>', '项目目录,默认当前目录', '.')
|
|
68
71
|
.option('--version <ver>', 'registry 包版本或 dist-tag,缺省 latest')
|
|
69
72
|
.option('--overwrite', '目标文件已存在时覆盖(默认跳过以保护用户改动)', false)
|
|
70
73
|
.option('--dry-run', '只报告将写 / 将跳哪些文件,不落盘', false)
|
|
71
74
|
.addHelpText('after', `
|
|
72
75
|
行为
|
|
73
|
-
-
|
|
74
|
-
|
|
76
|
+
- token 寻址:name(foo)/ 完整文件名(foo.css)/ relPath(stylesheets/foo.css)任一形式
|
|
77
|
+
- 多匹配全拉:token 命中多个同名条目时全部 copy-in;想只要其一就写完整文件名 / relPath
|
|
78
|
+
- 解析 USAGE 块 Depends 传递闭包,把 SDK 文件**拍平**拷到项目根
|
|
79
|
+
(包根 scripts/deck-stage.js → 项目 deck-stage.js;同名 basename 撞车时后者覆盖前者)
|
|
75
80
|
- skip-by-disk:目标文件已存在默认跳过;--overwrite 才覆盖
|
|
76
81
|
- 不写任何安装记账文件(项目文件树即状态)
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
输出(pretty,默认)
|
|
84
|
+
registry@<ver>
|
|
85
|
+
added: <落地 basename 逗号列表> # 含 Depends 闭包全部落地文件
|
|
86
|
+
skipped: <逗号列表> # 仅有跳过时出现
|
|
87
|
+
dry-run: true (no files written) # 仅 --dry-run 时出现
|
|
88
|
+
|
|
89
|
+
# <请求条目落地 basename>
|
|
90
|
+
<该条目 USAGE 块原文> # 只列你请求的条目,依赖不占 usage 位
|
|
91
|
+
|
|
92
|
+
JSON 输出(--json)
|
|
93
|
+
{"data": {"registryVersion": "...", "added": [...], "skipped": [...], "dryRun": false,
|
|
94
|
+
"entries": [{"name": "...", "file": "<来源 relPath>", "dest": "<落地 basename>",
|
|
95
|
+
"status": "added|skipped", "usage": "<USAGE 块原文>", "dependsOn": [...]}, ...]}}
|
|
96
|
+
(added / skipped 覆盖依赖闭包全部落地文件;entries 只列**你请求的条目**
|
|
97
|
+
(不含被 Depends 传递拉入的依赖),逐条给 usage / dependsOn,add 完直接读用法。
|
|
98
|
+
请求的条目即使已存在被 skip 也回带 usage,status 标 added / skipped)
|
|
82
99
|
|
|
83
100
|
示例
|
|
84
101
|
$ miaoda registry add deck-slide
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleCommentResolve = exports.handleCommentList = void 0;
|
|
4
|
+
var list_1 = require("./list");
|
|
5
|
+
Object.defineProperty(exports, "handleCommentList", { enumerable: true, get: function () { return list_1.handleCommentList; } });
|
|
6
|
+
var resolve_1 = require("./resolve");
|
|
7
|
+
Object.defineProperty(exports, "handleCommentResolve", { enumerable: true, get: function () { return resolve_1.handleCommentResolve; } });
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.handleCommentList = handleCommentList;
|
|
37
|
+
const api = __importStar(require("../../../api/index"));
|
|
38
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
39
|
+
const output_1 = require("../../../utils/output");
|
|
40
|
+
const fmtTime = output_1.fmt.sec();
|
|
41
|
+
/** 去掉飞书内联 `<at ...>@某人</at>` 标签,保留内部 @文本。 */
|
|
42
|
+
function stripAtTags(s) {
|
|
43
|
+
return s.replace(/<at\b[^>]*>/g, '').replace(/<\/at>/g, '');
|
|
44
|
+
}
|
|
45
|
+
/** 单个内联元素渲染成文本:text_run 出文本、docs_link 出链接、person 出 @user。 */
|
|
46
|
+
function renderElement(el) {
|
|
47
|
+
if (el.text_run?.text)
|
|
48
|
+
return stripAtTags(el.text_run.text);
|
|
49
|
+
if (el.docs_link?.url)
|
|
50
|
+
return el.docs_link.url;
|
|
51
|
+
if (el.person?.user_id)
|
|
52
|
+
return `@${el.person.user_id}`;
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
/** 一条 reply 的正文文本(各 element 拼接、去 at 标签)。 */
|
|
56
|
+
function replyText(r) {
|
|
57
|
+
return (r.content?.elements ?? []).map(renderElement).join('').trim();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 评论级 extra 里的 content_anchor_id(评论锚定的页面元素定位)。
|
|
61
|
+
* extra 后端返回 JSON 字符串(也兼容已是对象的情况),解析失败返回 undefined。
|
|
62
|
+
*/
|
|
63
|
+
function commentAnchor(c) {
|
|
64
|
+
const { extra } = c;
|
|
65
|
+
let obj = extra;
|
|
66
|
+
if (typeof extra === 'string') {
|
|
67
|
+
try {
|
|
68
|
+
obj = JSON.parse(extra);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
75
|
+
const anchor = obj.content_anchor_id;
|
|
76
|
+
if (typeof anchor === 'string' && anchor.length > 0)
|
|
77
|
+
return anchor;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/** 一条 reply 的附加标注:图片数量、reaction。 */
|
|
82
|
+
function replyAnnotations(r) {
|
|
83
|
+
const ann = [];
|
|
84
|
+
const images = r.extra?.image_list;
|
|
85
|
+
if (Array.isArray(images) && images.length > 0)
|
|
86
|
+
ann.push(`[图片×${String(images.length)}]`);
|
|
87
|
+
for (const rc of r.reactions ?? []) {
|
|
88
|
+
if (rc.reaction_key)
|
|
89
|
+
ann.push(`[${rc.reaction_key}×${String(rc.count ?? 0)}]`);
|
|
90
|
+
}
|
|
91
|
+
return ann;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 渲染一条评论为多行块(信息全保留,靠结构化标签 + 列对齐提升可读性):
|
|
95
|
+
* ● [未解决] <comment_id> · N 条回复
|
|
96
|
+
* 引用 <quote>
|
|
97
|
+
* 锚点 <content_anchor_id 全量>
|
|
98
|
+
* <user 对齐> <time> <正文 + 标注>
|
|
99
|
+
* userWidth 为所有回复 user_id 的最大宽度,用于跨评论列对齐。
|
|
100
|
+
*/
|
|
101
|
+
function renderComment(c, userWidth) {
|
|
102
|
+
const replies = c.reply_list?.replies ?? [];
|
|
103
|
+
const lines = [
|
|
104
|
+
`● ${c.is_solved ? '[已解决]' : '[未解决]'} ${c.comment_id} · ${String(replies.length)} 条回复`,
|
|
105
|
+
];
|
|
106
|
+
if (typeof c.quote === 'string' && c.quote.length > 0)
|
|
107
|
+
lines.push(` 引用 ${c.quote}`);
|
|
108
|
+
const anchor = commentAnchor(c);
|
|
109
|
+
if (anchor !== undefined)
|
|
110
|
+
lines.push(` 锚点 ${anchor}`);
|
|
111
|
+
for (const r of replies) {
|
|
112
|
+
const body = [replyText(r), ...replyAnnotations(r)].filter((p) => p.length > 0).join(' ');
|
|
113
|
+
lines.push(` ${(r.user_id ?? '-').padEnd(userWidth)} ${fmtTime(r.create_time)} ${body}`);
|
|
114
|
+
}
|
|
115
|
+
return lines.join('\n');
|
|
116
|
+
}
|
|
117
|
+
async function handleCommentList(opts) {
|
|
118
|
+
const appId = (0, shared_1.resolveAppId)({ appId: opts.appId });
|
|
119
|
+
const comments = await api.comment.getComments(appId, opts.onlyUnresolved ?? false);
|
|
120
|
+
// JSON 模式:原样透传完整评论结构(含 reply_list / replies / reactions …),与 openapi 一致。
|
|
121
|
+
if ((0, output_1.isJsonMode)()) {
|
|
122
|
+
(0, output_1.emit)({ data: comments });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// pretty 模式:信息密集的分块形态,一条评论一块、每条回复一行(含人 / 时间 / 正文 / 标注)。
|
|
126
|
+
// 跨评论对齐 user 列:取所有回复 user_id 的最大宽度。
|
|
127
|
+
const userWidth = Math.max(0, ...comments.flatMap((c) => c.reply_list?.replies ?? []).map((r) => (r.user_id ?? '-').length));
|
|
128
|
+
const body = comments.length === 0
|
|
129
|
+
? '(无评论)'
|
|
130
|
+
: comments.map((c) => renderComment(c, userWidth)).join('\n\n');
|
|
131
|
+
process.stdout.write(body + '\n');
|
|
132
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.handleCommentResolve = handleCommentResolve;
|
|
37
|
+
const api = __importStar(require("../../../api/index"));
|
|
38
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
39
|
+
const output_1 = require("../../../utils/output");
|
|
40
|
+
async function handleCommentResolve(commentId, opts) {
|
|
41
|
+
const appId = (0, shared_1.resolveAppId)({ appId: opts.appId });
|
|
42
|
+
await api.comment.resolveComment(appId, commentId);
|
|
43
|
+
(0, output_1.emitOk)({ comment_id: commentId, resolved: true });
|
|
44
|
+
}
|
|
@@ -9,7 +9,6 @@ const index_1 = require("../../../services/registry/index");
|
|
|
9
9
|
const shared_1 = require("./shared");
|
|
10
10
|
const error_1 = require("../../../utils/error");
|
|
11
11
|
const output_1 = require("../../../utils/output");
|
|
12
|
-
const logger_1 = require("../../../utils/logger");
|
|
13
12
|
/**
|
|
14
13
|
* miaoda registry add <name...> [--overwrite] [--dry-run] [--dir <path>] [--version <ver>]
|
|
15
14
|
*
|
|
@@ -35,18 +34,62 @@ async function handleRegistryAdd(opts) {
|
|
|
35
34
|
overwrite: opts.overwrite === true,
|
|
36
35
|
dryRun: opts.dryRun === true,
|
|
37
36
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
// entries:**只列用户请求的条目**(opts.names 命中的),给它们的 usage / dependsOn,
|
|
38
|
+
// 让 Agent add 完直接拿到用法、无需再 `registry list`。被 Depends 传递拉进来的依赖是
|
|
39
|
+
// "管道"(照常在 added/skipped 里落地),不占 usage 位。请求的条目即使已存在被 skip
|
|
40
|
+
// 也回带 usage(status=skipped,Agent 借此判断磁盘上是 registry 版还是自己改过的旧版)。
|
|
41
|
+
const requestedRelPaths = new Set();
|
|
42
|
+
for (const token of opts.names) {
|
|
43
|
+
for (const e of plan.entries) {
|
|
44
|
+
if ((0, index_1.tokenMatches)(e, token))
|
|
45
|
+
requestedRelPaths.add(e.relPath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// relPath → 落地的 PlannedFile(拿 dest / 落地状态)
|
|
49
|
+
const fileByRelPath = new Map(plan.files.map((f) => [f.srcRelPath, f]));
|
|
50
|
+
const addedSet = new Set(result.added);
|
|
51
|
+
const skippedSet = new Set(result.skipped);
|
|
52
|
+
const entries = plan.entries
|
|
53
|
+
.filter((e) => requestedRelPaths.has(e.relPath))
|
|
54
|
+
.map((e) => {
|
|
55
|
+
const f = fileByRelPath.get(e.relPath);
|
|
56
|
+
const dest = f?.dest ?? e.relPath.split('/').pop() ?? e.relPath;
|
|
57
|
+
const status = skippedSet.has(dest) && !addedSet.has(dest) ? 'skipped' : 'added';
|
|
58
|
+
return {
|
|
59
|
+
name: e.name,
|
|
60
|
+
file: e.relPath,
|
|
61
|
+
dest,
|
|
62
|
+
status,
|
|
63
|
+
usage: e.usage,
|
|
64
|
+
dependsOn: e.dependsOn,
|
|
65
|
+
};
|
|
49
66
|
});
|
|
67
|
+
// 只透出对 add 完的 Agent 可操作的字段:项目里实际多了什么(added)、什么没动(skipped)、
|
|
68
|
+
// 请求条目怎么用(entries)。requested(纯回显)、resolved(包内诊断路径)不透出。
|
|
69
|
+
const data = {
|
|
70
|
+
registryVersion: reg.version,
|
|
71
|
+
added: result.added,
|
|
72
|
+
skipped: result.skipped,
|
|
73
|
+
dryRun: opts.dryRun === true,
|
|
74
|
+
entries,
|
|
75
|
+
};
|
|
76
|
+
if ((0, output_1.isJsonMode)()) {
|
|
77
|
+
(0, output_1.emit)({ data });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// pretty 是 Agent 不加 --json 时的默认载荷,也要对 AI 友好:信息量与 JSON 对齐,
|
|
81
|
+
// 但换成稳定 label + 逗号列表(无 JSON 数组、无 TTY 截断、空/默认值不打印),
|
|
82
|
+
// summary 在前、各请求条目的 USAGE 块原文在后。整体走一次纯字符串 emit(不进 key-value 渲染)。
|
|
83
|
+
const summaryLines = [`registry@${reg.version}`];
|
|
84
|
+
if (opts.dryRun === true)
|
|
85
|
+
summaryLines.push('dry-run: true (no files written)');
|
|
86
|
+
if (result.added.length > 0)
|
|
87
|
+
summaryLines.push(`added: ${result.added.join(', ')}`);
|
|
88
|
+
if (result.skipped.length > 0)
|
|
89
|
+
summaryLines.push(`skipped: ${result.skipped.join(', ')}`);
|
|
90
|
+
const blocks = entries.filter((e) => e.usage !== '').map((e) => `# ${e.dest}\n${e.usage}`);
|
|
91
|
+
const parts = [summaryLines.join('\n'), ...blocks];
|
|
92
|
+
(0, output_1.emit)(parts.join('\n\n'));
|
|
50
93
|
}
|
|
51
94
|
finally {
|
|
52
95
|
reg.cleanup();
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.buildDesignOutput = buildDesignOutput;
|
|
7
|
+
exports.cleanDesignDist = cleanDesignDist;
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const error_1 = require("../../../../utils/error");
|
|
@@ -39,3 +40,10 @@ function buildDesignOutput(opts) {
|
|
|
39
40
|
throw new error_1.AppError('DEPLOY_BUILD_FAILED', `design build failed: ${err.message}`);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* 删除 <projectDir>/dist(本地构建产物)。幂等:不存在也不报错(force)。
|
|
45
|
+
* design 部署收尾调用,部署动作结束后不在用户项目里留产物。
|
|
46
|
+
*/
|
|
47
|
+
function cleanDesignDist(projectDir) {
|
|
48
|
+
node_fs_1.default.rmSync(node_path_1.default.join(projectDir, constants_1.DIST_DIR), { recursive: true, force: true });
|
|
49
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.savePluginInstances = exports.LocalReleaseStatus = exports.finalizeLocalRelease = exports.createLocalRelease = exports.uploadArtifacts = exports.uploadDesignArtifacts = exports.buildDesignOutput = exports.runBuild = exports.preRelease = exports.prepareDeployContext = void 0;
|
|
3
|
+
exports.savePluginInstances = exports.LocalReleaseStatus = exports.finalizeLocalRelease = exports.createLocalRelease = exports.uploadArtifacts = exports.uploadDesignArtifacts = exports.cleanDesignDist = exports.buildDesignOutput = exports.runBuild = exports.preRelease = exports.prepareDeployContext = void 0;
|
|
4
4
|
var context_1 = require("./context");
|
|
5
5
|
Object.defineProperty(exports, "prepareDeployContext", { enumerable: true, get: function () { return context_1.prepareDeployContext; } });
|
|
6
6
|
var pre_release_1 = require("./pre-release");
|
|
@@ -9,6 +9,7 @@ var build_1 = require("./build");
|
|
|
9
9
|
Object.defineProperty(exports, "runBuild", { enumerable: true, get: function () { return build_1.runBuild; } });
|
|
10
10
|
var design_build_1 = require("./design-build");
|
|
11
11
|
Object.defineProperty(exports, "buildDesignOutput", { enumerable: true, get: function () { return design_build_1.buildDesignOutput; } });
|
|
12
|
+
Object.defineProperty(exports, "cleanDesignDist", { enumerable: true, get: function () { return design_build_1.cleanDesignDist; } });
|
|
12
13
|
var design_upload_1 = require("./design-upload");
|
|
13
14
|
Object.defineProperty(exports, "uploadDesignArtifacts", { enumerable: true, get: function () { return design_upload_1.uploadDesignArtifacts; } });
|
|
14
15
|
var upload_1 = require("./upload");
|
|
@@ -36,7 +36,8 @@ function isHtml(rel) {
|
|
|
36
36
|
/**
|
|
37
37
|
* 把 changeset 组成 TosFileAction[]:
|
|
38
38
|
* - create/update 读本地文件、自动编码;delete 仅 FilePath。
|
|
39
|
-
* -
|
|
39
|
+
* - .html 被 create / delete(路由集合变化)时,追加重算 routes.json 的 UPDATE action;
|
|
40
|
+
* 纯 update 现有 .html 只改内容、不改路由集合,不重算 routes.json。
|
|
40
41
|
*/
|
|
41
42
|
function buildTosActions(projectDir, cs) {
|
|
42
43
|
const actions = [];
|
|
@@ -47,8 +48,8 @@ function buildTosActions(projectDir, cs) {
|
|
|
47
48
|
for (const rel of cs.deletes) {
|
|
48
49
|
actions.push({ actionType: index_1.TosActionType.DELETE, filePath: normalizeRel(rel) });
|
|
49
50
|
}
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
51
|
+
const routeSetChanged = [...cs.creates, ...cs.deletes].some(isHtml);
|
|
52
|
+
if (routeSetChanged) {
|
|
52
53
|
actions.push({
|
|
53
54
|
actionType: index_1.TosActionType.UPDATE,
|
|
54
55
|
filePath: 'routes.json',
|
|
@@ -11,8 +11,8 @@ const actions_1 = require("./actions");
|
|
|
11
11
|
var actions_2 = require("./actions");
|
|
12
12
|
Object.defineProperty(exports, "buildTosActions", { enumerable: true, get: function () { return actions_2.buildTosActions; } });
|
|
13
13
|
/**
|
|
14
|
-
* design-html 增量发布:读本地文件组 TosFileAction
|
|
15
|
-
*
|
|
14
|
+
* design-html 增量发布:读本地文件组 TosFileAction(create/delete .html 时带重算的
|
|
15
|
+
* routes.json),调后端 applyTosDiff 应用到 latest。scope 限 design_local_deploy。
|
|
16
16
|
*/
|
|
17
17
|
async function patchDesignDeploy(opts) {
|
|
18
18
|
if (opts.creates.length + opts.updates.length + opts.deletes.length === 0) {
|
|
@@ -30,7 +30,12 @@ async function patchDesignDeploy(opts) {
|
|
|
30
30
|
});
|
|
31
31
|
const routesRegenerated = actions.some((a) => a.filePath === 'routes.json');
|
|
32
32
|
(0, logger_1.log)('deploy', `Applying ${String(actions.length)} file action(s)...`);
|
|
33
|
-
const res = await (0, index_1.applyTosDiff)({
|
|
33
|
+
const res = await (0, index_1.applyTosDiff)({
|
|
34
|
+
appID: opts.appId,
|
|
35
|
+
templateKey,
|
|
36
|
+
actions,
|
|
37
|
+
sessionId: opts.sessionId,
|
|
38
|
+
});
|
|
34
39
|
(0, logger_1.log)('deploy', `Patched: upsert=${String(res.upsertCount)} delete=${String(res.deleteCount)}`);
|
|
35
40
|
return {
|
|
36
41
|
upsertCount: res.upsertCount,
|
|
@@ -2,27 +2,36 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateRoutes = generateRoutes;
|
|
4
4
|
const source_scan_1 = require("./source-scan");
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* .html → route path 推导(相对路径,不带 base 前缀)。rel 为 posix 相对路径。
|
|
7
|
+
* base 前缀交由消费侧(运行时/网关)自行拼接,design-html 链路不在 routes.json 内带前缀。
|
|
8
|
+
*/
|
|
9
|
+
function toRoute(rel) {
|
|
7
10
|
let r = '/' + rel;
|
|
8
11
|
r = r.replace(/\.html$/i, '');
|
|
9
12
|
r = r.replace(/(^|\/)index$/, '$1');
|
|
10
13
|
if (r.length > 1)
|
|
11
14
|
r = r.replace(/\/$/, '');
|
|
12
|
-
|
|
13
|
-
return full === '' ? '/' : full;
|
|
15
|
+
return r === '' ? '/' : r;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
|
-
* 扫描 projectDir 下所有 .html(套 source-scan 的 EXCLUDES
|
|
17
|
-
*
|
|
18
|
+
* 扫描 projectDir 下所有 .html(套 source-scan 的 EXCLUDES),生成 routes.json 文本(含尾换行)。
|
|
19
|
+
* 每项为 { path, file }:path 为相对路径(不带 CLIENT_BASE_PATH 前缀,由消费侧拼接),
|
|
20
|
+
* file 为该路由对应的原始 .html 源文件。按 path 排序、同 path 取字典序靠前的 file 去重。
|
|
21
|
+
* 零 html 时输出空数组——不伪造解析不到文件的根路由。
|
|
18
22
|
*/
|
|
19
23
|
function generateRoutes(projectDir) {
|
|
20
|
-
const base = (process.env.CLIENT_BASE_PATH ?? '').replace(/\/+$/, '');
|
|
21
24
|
const htmls = (0, source_scan_1.listSourceFiles)(projectDir).filter((f) => f.toLowerCase().endsWith('.html'));
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const byPath = new Map();
|
|
26
|
+
for (const entry of htmls.map((file) => ({ path: toRoute(file), file })).sort(byPathThenFile)) {
|
|
27
|
+
if (!byPath.has(entry.path))
|
|
28
|
+
byPath.set(entry.path, entry);
|
|
29
|
+
}
|
|
30
|
+
const routes = [...byPath.values()];
|
|
27
31
|
return JSON.stringify(routes, null, 2) + '\n';
|
|
28
32
|
}
|
|
33
|
+
function byPathThenFile(a, b) {
|
|
34
|
+
return a.path === b.path
|
|
35
|
+
? (a.file ?? '').localeCompare(b.file ?? '')
|
|
36
|
+
: a.path.localeCompare(b.path);
|
|
37
|
+
}
|
|
@@ -20,28 +20,37 @@ async function designLocalPublishPipeline(opts) {
|
|
|
20
20
|
throw new error_1.AppError('DEPLOY_PRE_RELEASE_DATA_MISSING', 'preRelease did not return a `data` map — backend tcc may not be configured for current template_key');
|
|
21
21
|
}
|
|
22
22
|
const data = pre.data;
|
|
23
|
-
|
|
24
|
-
(0, index_1.buildDesignOutput)({ projectDir: ctx.projectDir });
|
|
25
|
-
}
|
|
26
|
-
(0, logger_1.log)('deploy', 'Creating local release...');
|
|
27
|
-
const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version);
|
|
23
|
+
// dist 是本地构建产物,部署动作结束(无论成败)都清掉,不在用户项目里留产物。
|
|
28
24
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
await (0, index_1.
|
|
34
|
-
|
|
25
|
+
if (!opts.skipBuild) {
|
|
26
|
+
(0, index_1.buildDesignOutput)({ projectDir: ctx.projectDir });
|
|
27
|
+
}
|
|
28
|
+
(0, logger_1.log)('deploy', 'Creating local release...');
|
|
29
|
+
const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version, {
|
|
30
|
+
checkPointVersion: opts.checkPointVersion,
|
|
31
|
+
commitID: opts.commitID,
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
await (0, index_1.uploadDesignArtifacts)({ projectDir: ctx.projectDir, data });
|
|
35
|
+
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Finished);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
await (0, index_1.finalizeLocalRelease)(ctx.appId, release.releaseID, index_1.LocalReleaseStatus.Failed);
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
if (release.onlineUrl !== undefined && release.onlineUrl !== '') {
|
|
42
|
+
(0, spark_meta_1.writeSparkMeta)(ctx.projectDir, { appUrl: release.onlineUrl });
|
|
43
|
+
}
|
|
44
|
+
(0, logger_1.log)('deploy', 'Deployed successfully');
|
|
45
|
+
return {
|
|
46
|
+
appId: ctx.appId,
|
|
47
|
+
version: pre.version,
|
|
48
|
+
url: release.onlineUrl ?? '',
|
|
49
|
+
releaseID: release.releaseID,
|
|
50
|
+
preReleaseID: pre.preReleaseID,
|
|
51
|
+
};
|
|
35
52
|
}
|
|
36
|
-
|
|
37
|
-
(0,
|
|
53
|
+
finally {
|
|
54
|
+
(0, index_1.cleanDesignDist)(ctx.projectDir);
|
|
38
55
|
}
|
|
39
|
-
(0, logger_1.log)('deploy', 'Deployed successfully');
|
|
40
|
-
return {
|
|
41
|
-
appId: ctx.appId,
|
|
42
|
-
version: pre.version,
|
|
43
|
-
url: release.onlineUrl ?? '',
|
|
44
|
-
releaseID: release.releaseID,
|
|
45
|
-
preReleaseID: pre.preReleaseID,
|
|
46
|
-
};
|
|
47
56
|
}
|
|
@@ -8,7 +8,7 @@ exports.resolveRegistryPackage = resolveRegistryPackage;
|
|
|
8
8
|
exports.loadRegistry = loadRegistry;
|
|
9
9
|
exports.extractUsageBlock = extractUsageBlock;
|
|
10
10
|
exports.parseDependsOn = parseDependsOn;
|
|
11
|
-
exports.
|
|
11
|
+
exports.tokenMatches = tokenMatches;
|
|
12
12
|
exports.resolveDependencyClosure = resolveDependencyClosure;
|
|
13
13
|
exports.planAdd = planAdd;
|
|
14
14
|
exports.applyAdd = applyAdd;
|
|
@@ -95,12 +95,21 @@ function loadRegistry(opts) {
|
|
|
95
95
|
*/
|
|
96
96
|
const SDK_FILE_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx', '.css']);
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
98
|
+
* 条目所在的顶层目录白名单。只有包根下这几个目录(任意深度)里的代码文件才算条目,
|
|
99
|
+
* 包根直放的文件(eslint.config.js 等)、其它顶层目录(lib/ 等)一律排除——
|
|
100
|
+
* 避免 USAGE 可选后把工程配置 / 非 SDK 代码误判为条目。
|
|
101
|
+
*/
|
|
102
|
+
const SDK_TOP_DIRS = new Set(['components', 'scripts', 'stylesheets']);
|
|
103
|
+
/**
|
|
104
|
+
* 从包根递归扫整棵树,提取 SDK 条目。条目判据:
|
|
105
|
+
* 1. 在白名单顶层目录下(SDK_TOP_DIRS,任意深度);
|
|
106
|
+
* 2. 是代码文件(SDK_FILE_EXTENSIONS)。
|
|
107
|
+
* USAGE 块**可选**:有则解析描述 + Depends,无则 usage 为空、dependsOn 为空——仍是条目。
|
|
108
|
+
* name(去扩展名 basename)**不要求唯一**:同名的不同文件全部收录为独立条目,由 relPath
|
|
109
|
+
* (树内唯一)区分;寻址 / 拉取由 tokenMatches + resolveDependencyClosure 处理(同名 → 全拉)。
|
|
100
110
|
*/
|
|
101
111
|
function scanEntries(registryRoot) {
|
|
102
112
|
const entries = [];
|
|
103
|
-
const byName = new Map(); // name → relPath(撞名诊断)
|
|
104
113
|
const walk = (dir) => {
|
|
105
114
|
for (const dirent of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
106
115
|
const absPath = node_path_1.default.join(dir, dirent.name);
|
|
@@ -110,23 +119,17 @@ function scanEntries(registryRoot) {
|
|
|
110
119
|
}
|
|
111
120
|
if (!dirent.isFile())
|
|
112
121
|
continue;
|
|
113
|
-
// 只扫代码文件,排除 README/LICENSE/package.json
|
|
122
|
+
// 只扫代码文件,排除 README/LICENSE/package.json 等非 SDK 文件
|
|
114
123
|
if (!SDK_FILE_EXTENSIONS.has(node_path_1.default.extname(dirent.name).toLowerCase()))
|
|
115
124
|
continue;
|
|
116
|
-
const content = node_fs_1.default.readFileSync(absPath, 'utf-8');
|
|
117
|
-
const usage = extractUsageBlock(content);
|
|
118
|
-
if (usage === null)
|
|
119
|
-
continue; // 非 SDK 文件(无 USAGE 块)
|
|
120
125
|
// 相对包根的路径,统一用 '/' 分隔(落地时再按平台 join)
|
|
121
126
|
const relPath = node_path_1.default.relative(registryRoot, absPath).split(node_path_1.default.sep).join('/');
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
byName.set(name, relPath);
|
|
127
|
+
// 只认白名单顶层目录下的文件(顶层段 ∈ SDK_TOP_DIRS)
|
|
128
|
+
if (!SDK_TOP_DIRS.has(relPath.split('/')[0]))
|
|
129
|
+
continue;
|
|
130
|
+
const usage = extractUsageBlock(node_fs_1.default.readFileSync(absPath, 'utf-8')) ?? '';
|
|
128
131
|
entries.push({
|
|
129
|
-
name,
|
|
132
|
+
name: stripExt(dirent.name),
|
|
130
133
|
relPath,
|
|
131
134
|
usage,
|
|
132
135
|
dependsOn: parseDependsOn(usage),
|
|
@@ -135,8 +138,8 @@ function scanEntries(registryRoot) {
|
|
|
135
138
|
}
|
|
136
139
|
};
|
|
137
140
|
walk(registryRoot);
|
|
138
|
-
//
|
|
139
|
-
entries.sort((a, b) => a.
|
|
141
|
+
// 按 relPath(唯一)稳定排序,便于 list 输出可预期
|
|
142
|
+
entries.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
140
143
|
return entries;
|
|
141
144
|
}
|
|
142
145
|
/** 提取 BEGIN/END USAGE 标记之间的原文(含标记行)。无 USAGE 块返回 null。 */
|
|
@@ -172,62 +175,81 @@ function stripExt(file) {
|
|
|
172
175
|
const ext = node_path_1.default.extname(file);
|
|
173
176
|
return ext === '' ? file : file.slice(0, -ext.length);
|
|
174
177
|
}
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
/**
|
|
179
|
+
* 一个 token 是否命中某条目。token 可写三种寻址形式(按特异度,任一相等即命中):
|
|
180
|
+
* - relPath:相对包根路径,如 components/foo.jsx(含 '/')
|
|
181
|
+
* - 完整文件名:含扩展名的 basename,如 foo.jsx
|
|
182
|
+
* - name:去扩展名的 basename,如 foo
|
|
183
|
+
* 三种形式无歧义重叠(relPath 含 '/',basename 不含;完整文件名含原扩展名,name 不含),
|
|
184
|
+
* 故用一个等值谓词即可。多个条目可同时命中同一 token(同名 → 全拉),由调用方处理。
|
|
185
|
+
*/
|
|
186
|
+
function tokenMatches(entry, token) {
|
|
187
|
+
if (token === entry.relPath)
|
|
188
|
+
return true;
|
|
189
|
+
const fileName = entry.relPath.split('/').pop() ?? '';
|
|
190
|
+
if (token === fileName)
|
|
191
|
+
return true;
|
|
192
|
+
return token === entry.name;
|
|
181
193
|
}
|
|
182
194
|
/**
|
|
183
|
-
* 解析 Depends 的传递闭包:从 requested 出发,递归把所有依赖一并纳入。
|
|
195
|
+
* 解析 Depends 的传递闭包:从 requested token 出发,递归把所有依赖一并纳入。
|
|
184
196
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
197
|
+
* token 经 tokenMatches 解析成**一组**条目(同名 → 全拉),逐个展开。返回拓扑序
|
|
198
|
+
* (依赖在前、被依赖在后),便于按序拷贝。token 零命中抛 REGISTRY_ENTRY_NOT_FOUND;
|
|
199
|
+
* 检测循环依赖并抛 REGISTRY_CYCLE。visited / onStack / 环检测均以 relPath(唯一)为键,
|
|
200
|
+
* 故同名(去扩展名)的不同文件互不干扰、不会误判成环。
|
|
187
201
|
*/
|
|
188
202
|
function resolveDependencyClosure(entries, requested) {
|
|
189
|
-
const index = indexEntries(entries);
|
|
190
203
|
const ordered = [];
|
|
191
|
-
const visited = new Set();
|
|
192
|
-
const onStack = new Set();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
throw new error_1.AppError('REGISTRY_CYCLE', `registry 条目依赖成环:${[...trail, name].join(' -> ')}`);
|
|
198
|
-
}
|
|
199
|
-
const entry = index.get(name);
|
|
200
|
-
if (entry === undefined) {
|
|
201
|
-
// requested 顶层缺失 vs 依赖链里缺失,都报清楚是谁引的
|
|
204
|
+
const visited = new Set(); // by relPath
|
|
205
|
+
const onStack = new Set(); // by relPath
|
|
206
|
+
// token → 命中条目集合;零命中 fail-fast(避免拷半套)
|
|
207
|
+
const resolveToken = (token, trail) => {
|
|
208
|
+
const matches = entries.filter((e) => tokenMatches(e, token));
|
|
209
|
+
if (matches.length === 0) {
|
|
202
210
|
const via = trail.length > 0 ? `(被 "${trail[trail.length - 1]}" 依赖)` : '';
|
|
203
|
-
throw new error_1.AppError('REGISTRY_ENTRY_NOT_FOUND', `registry 没有条目 "${
|
|
211
|
+
throw new error_1.AppError('REGISTRY_ENTRY_NOT_FOUND', `registry 没有条目 "${token}"${via}`, {
|
|
204
212
|
next_actions: ['用 `miaoda registry list` 查看可用条目'],
|
|
205
213
|
});
|
|
206
214
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
return matches;
|
|
216
|
+
};
|
|
217
|
+
const visit = (entry, trail) => {
|
|
218
|
+
if (visited.has(entry.relPath))
|
|
219
|
+
return;
|
|
220
|
+
if (onStack.has(entry.relPath)) {
|
|
221
|
+
throw new error_1.AppError('REGISTRY_CYCLE', `registry 条目依赖成环:${[...trail, entry.relPath].join(' -> ')}`);
|
|
222
|
+
}
|
|
223
|
+
onStack.add(entry.relPath);
|
|
224
|
+
for (const depToken of entry.dependsOn) {
|
|
225
|
+
for (const depEntry of resolveToken(depToken, [...trail, entry.relPath])) {
|
|
226
|
+
visit(depEntry, [...trail, entry.relPath]);
|
|
227
|
+
}
|
|
210
228
|
}
|
|
211
|
-
onStack.delete(
|
|
212
|
-
visited.add(
|
|
229
|
+
onStack.delete(entry.relPath);
|
|
230
|
+
visited.add(entry.relPath);
|
|
213
231
|
ordered.push(entry);
|
|
214
232
|
};
|
|
215
|
-
for (const
|
|
216
|
-
|
|
233
|
+
for (const token of requested) {
|
|
234
|
+
for (const entry of resolveToken(token, []))
|
|
235
|
+
visit(entry, []);
|
|
236
|
+
}
|
|
217
237
|
return ordered;
|
|
218
238
|
}
|
|
219
239
|
/**
|
|
220
|
-
* 规划 add:解析依赖闭包 →
|
|
221
|
-
* 纯计算,不落盘(dry-run 与真实 add 共用)。
|
|
240
|
+
* 规划 add:解析依赖闭包 → 每个条目**拍平为 basename** 落到项目根 → 标记是否已存在。
|
|
241
|
+
* 纯计算,不落盘(dry-run 与真实 add 共用)。exists 在此快照(落盘前),故闭包内同名
|
|
242
|
+
* basename 撞车时双方 exists 都为 false、applyAdd 里后者覆盖前者。
|
|
222
243
|
*/
|
|
223
244
|
function planAdd(opts) {
|
|
224
245
|
const entries = resolveDependencyClosure(opts.entries, opts.names);
|
|
225
246
|
const files = entries.map((entry) => {
|
|
226
|
-
|
|
227
|
-
const destAbs = node_path_1.default.join(opts.targetDir,
|
|
247
|
+
const dest = entry.relPath.split('/').pop() ?? entry.relPath; // 拍平为 basename
|
|
248
|
+
const destAbs = node_path_1.default.join(opts.targetDir, dest);
|
|
228
249
|
return {
|
|
229
250
|
entry: entry.name,
|
|
230
|
-
|
|
251
|
+
srcRelPath: entry.relPath,
|
|
252
|
+
dest,
|
|
231
253
|
destAbs,
|
|
232
254
|
srcAbs: entry.absPath,
|
|
233
255
|
exists: node_fs_1.default.existsSync(destAbs),
|
|
@@ -236,28 +258,36 @@ function planAdd(opts) {
|
|
|
236
258
|
return { entries, files };
|
|
237
259
|
}
|
|
238
260
|
/**
|
|
239
|
-
* 执行 add
|
|
240
|
-
* - 目标已存在且未 overwrite → skip
|
|
261
|
+
* 执行 add 计划:把 SDK 文件**拍平**拷到项目根。
|
|
262
|
+
* - 目标已存在且未 overwrite → skip(skip-by-disk 保护用户改动)
|
|
241
263
|
* - 目标已存在且 overwrite → 覆盖写
|
|
242
264
|
* - 目标不存在 → 写
|
|
243
|
-
*
|
|
265
|
+
* 闭包内同名 basename 撞车(双方 exists 均为 plan 前快照 false)→ 顺序拷贝、后者覆盖前者,
|
|
266
|
+
* added 去重只报一次。dryRun=true 时只分类不落盘。不写任何安装记账文件(项目文件树即状态)。
|
|
244
267
|
*/
|
|
245
268
|
function applyAdd(files, opts) {
|
|
246
269
|
const added = [];
|
|
247
270
|
const skipped = [];
|
|
271
|
+
const seen = new Set();
|
|
272
|
+
const pushUnique = (list, dest) => {
|
|
273
|
+
if (seen.has(dest))
|
|
274
|
+
return;
|
|
275
|
+
seen.add(dest);
|
|
276
|
+
list.push(dest);
|
|
277
|
+
};
|
|
248
278
|
for (const f of files) {
|
|
249
279
|
if (f.exists && !opts.overwrite) {
|
|
250
|
-
skipped
|
|
280
|
+
pushUnique(skipped, f.dest);
|
|
251
281
|
continue;
|
|
252
282
|
}
|
|
253
283
|
if (!opts.dryRun) {
|
|
254
284
|
if (!node_fs_1.default.existsSync(f.srcAbs)) {
|
|
255
|
-
throw new error_1.AppError('REGISTRY_FILE_MISSING', `registry 包内缺少文件 ${f.
|
|
285
|
+
throw new error_1.AppError('REGISTRY_FILE_MISSING', `registry 包内缺少文件 ${f.srcRelPath}(条目 "${f.entry}")`);
|
|
256
286
|
}
|
|
257
287
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(f.destAbs), { recursive: true });
|
|
258
288
|
node_fs_1.default.copyFileSync(f.srcAbs, f.destAbs);
|
|
259
289
|
}
|
|
260
|
-
added
|
|
290
|
+
pushUnique(added, f.dest);
|
|
261
291
|
}
|
|
262
292
|
return { added, skipped };
|
|
263
293
|
}
|
package/dist/utils/http.js
CHANGED
|
@@ -64,7 +64,7 @@ function createClient(opts) {
|
|
|
64
64
|
* 默认灰度泳道。当前 miaoda CLI 接入的新接口(modern deploy 等)只在该泳道生效,
|
|
65
65
|
* 缺省走这个。MIAODA_CANARY_HEADER 可显式覆盖;设为空字符串则去掉头。
|
|
66
66
|
*/
|
|
67
|
-
const DEFAULT_CANARY_HEADER = '
|
|
67
|
+
const DEFAULT_CANARY_HEADER = 'boe_miaoda_ccm';
|
|
68
68
|
/**
|
|
69
69
|
* 注入 x-tt-env 小流量头:env 显式设值 > DEFAULT_CANARY_HEADER。
|
|
70
70
|
*
|