@lark-apaas/miaoda-cli 0.1.32 → 0.1.33-alpha.414062
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/dist/cli/commands/deploy/modern.js +6 -5
- package/dist/cli/handlers/skills/sync.js +20 -7
- package/dist/services/deploy/modern/patch/actions.js +6 -4
- package/dist/services/deploy/modern/patch/html-title.js +75 -0
- package/dist/services/deploy/modern/patch/index.js +1 -1
- package/dist/services/deploy/modern/patch/routes.js +13 -3
- package/dist/utils/coding-steering.js +21 -1
- package/dist/utils/npm-pack.js +30 -0
- package/dist/utils/sandbox-skills.js +22 -31
- package/package.json +1 -1
|
@@ -72,7 +72,7 @@ JSON 输出(stdout)
|
|
|
72
72
|
}));
|
|
73
73
|
const patchCmd = deployCmd
|
|
74
74
|
.command('patch')
|
|
75
|
-
.description('design-html 增量发布:按文件 create/update/delete,.html
|
|
75
|
+
.description('design-html 增量发布:按文件 create/update/delete,.html 变动时同步 routes.json')
|
|
76
76
|
.option('--create <relpath>', '新增文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
77
77
|
.option('--update <relpath>', '覆盖已有文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
78
78
|
.option('--delete <relpath>', '删除文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
@@ -80,10 +80,11 @@ JSON 输出(stdout)
|
|
|
80
80
|
.addHelpText('after', `
|
|
81
81
|
--dir 为项目目录(与 deploy 同名参数,默认当前目录)。
|
|
82
82
|
仅支持 design-html(design_local_deploy)。路径为工程根相对路径(= 服务端 latest 下 key),
|
|
83
|
-
禁绝对路径与 ".."。--create/--update
|
|
84
|
-
时自动重算并随包更新 routes.json
|
|
85
|
-
routes.json 每项为 {path, file}:path 为相对路径(不含 base 前缀,由消费侧拼接),
|
|
86
|
-
|
|
83
|
+
禁绝对路径与 ".."。--create/--update 的本地文件必须存在。改动里含任意 .html(增 / 改 / 删)
|
|
84
|
+
时自动重算并随包更新 routes.json;纯非 .html 改动不动 routes.json。
|
|
85
|
+
routes.json 每项为 {path, file, name?}:path 为相对路径(不含 base 前缀,由消费侧拼接),
|
|
86
|
+
file 为对应源文件(如 / → index.html),name 为该 HTML 的 <title> 文本(方案名,可缺省,
|
|
87
|
+
缺省时由消费侧 fallback 到 path)。
|
|
87
88
|
|
|
88
89
|
JSON 输出
|
|
89
90
|
{"data": {"upsertCount": <n>, "deleteCount": <n>, "actionsSent": <n>, "routesRegenerated": <bool>}}
|
|
@@ -34,14 +34,20 @@ function syncSteeringForSandbox(params) {
|
|
|
34
34
|
catch (err) {
|
|
35
35
|
return { status: 'skipped', reason: `read .spark/meta.json failed: ${err.message}` };
|
|
36
36
|
}
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// 版本口径:--version > lane(TCC tgzKey)> BOE 的 dist-tag dev > latest。
|
|
38
|
+
//
|
|
39
|
+
// **这里不预先把 BOE 解成 'dev'**(下方本地分支为了回报 versionSource 才那么做):
|
|
40
|
+
// 一旦提前填进 version,就会盖住 lane —— version 在优先级里高于 lane,BOE 沙箱上
|
|
41
|
+
// 配了 tgzKey 也永远命中不了。把 version 原样透传(未指定就是 undefined),让
|
|
42
|
+
// syncCodingSteering 一处算全链:version > lane > dev > latest。
|
|
40
43
|
try {
|
|
41
44
|
const r = (0, coding_steering_1.syncCodingSteering)({
|
|
42
45
|
stack,
|
|
43
46
|
targetDir: params.targetDir,
|
|
44
|
-
version:
|
|
47
|
+
version: params.version,
|
|
48
|
+
// TCC 的 X 与平铺 skill 共用:pickLaneVersion 只匹配 `-alpha.<X>` 后缀,
|
|
49
|
+
// 不看 base 版本,所以一个 key 同时定向 coding-steering 与 sandbox-skills。
|
|
50
|
+
tgzKey: params.tgzKey,
|
|
45
51
|
// 必须显式传 mode,不能落到 inferMode():它靠 env MIAODA_DEP_CACHE_DIR 嗅探,
|
|
46
52
|
// 而 mode 决定读包内哪个 layer(sandbox→`skills/`,local→`skills_local/`)。
|
|
47
53
|
// `--type` 分支本身就是沙箱语义,嗅探失手会去读不存在的 skills_local/、
|
|
@@ -70,9 +76,16 @@ function syncSteeringForSandbox(params) {
|
|
|
70
76
|
*
|
|
71
77
|
* 版本来源优先级(从高到低):
|
|
72
78
|
* 1. CLI `--version` flag(显式指定)
|
|
73
|
-
* 2. `
|
|
79
|
+
* 2. lane:`--type` 分支下由 TCC `skillsUpdateTgzKey` 透传 `--tgz-key`,反推 X 后
|
|
80
|
+
* 解析出 `-alpha.<X>` 版本。**与平铺 skill 共用同一个 X** —— 匹配只看后缀、
|
|
81
|
+
* 不看 base 版本,所以 TCC 配一个 key 就同时定向 coding-steering 与 sandbox-skills。
|
|
82
|
+
* 本地分支(不带 `--type`)没有 tgzKey,这一档天然不参与。
|
|
83
|
+
* 3. `FORCE_FRAMEWORK_ENVIRONMENT=boe` → dist-tag `dev`(BOE 测试灰度,跟随
|
|
74
84
|
* publish-alpha.sh 在 develop 分支打的 dev tag,便于在 BOE 沙箱里验证预发 steering)
|
|
75
|
-
*
|
|
85
|
+
* 4. 默认 `latest`
|
|
86
|
+
*
|
|
87
|
+
* 2/3/4 档统一在 `syncCodingSteering` 里算(见那边注释),handler 只负责透传,避免
|
|
88
|
+
* 提前把 `dev` 填进 version 把 lane 挤掉。
|
|
76
89
|
*
|
|
77
90
|
* **不读 meta.json**——steering 版本不在 meta 里钉。
|
|
78
91
|
*/
|
|
@@ -98,7 +111,7 @@ async function handleSkillsSync(opts) {
|
|
|
98
111
|
const result = (0, sandbox_skills_1.syncSandboxSkills)({ type, tgzKey, targetDir });
|
|
99
112
|
const steering = result.skippedByKeepFile
|
|
100
113
|
? { status: 'skipped', reason: '.keep file found, sync frozen' }
|
|
101
|
-
: syncSteeringForSandbox({ targetDir, version: opts.version });
|
|
114
|
+
: syncSteeringForSandbox({ targetDir, version: opts.version, tgzKey });
|
|
102
115
|
// ③ 收尾锁只读(update-skills.sh:241 的 chmod -R a=rX)。必须在 ①② 全部写完之后,
|
|
103
116
|
// 锁上就写不进去了。`.keep` 冻结时不锁 —— 老链路那条路径是 exit 0 直接走、
|
|
104
117
|
// 末尾的 chmod 压根不执行,保持一致。
|
|
@@ -36,8 +36,10 @@ function isHtml(rel) {
|
|
|
36
36
|
/**
|
|
37
37
|
* 把 changeset 组成 TosFileAction[]:
|
|
38
38
|
* - create/update 读本地文件、自动编码;delete 仅 FilePath。
|
|
39
|
-
* - .html 被 create / delete
|
|
40
|
-
*
|
|
39
|
+
* - 任一 .html 被 create / update / delete 时,追加重算 routes.json 的 UPDATE action。
|
|
40
|
+
* update 也算:routes.json 的 name 取自 HTML 的 `<title>`,改方案名就是一次纯 update,
|
|
41
|
+
* 只按 create/delete 判定会让改名永远进不了 routes.json。
|
|
42
|
+
* - 纯非 .html 改动(js / css / 图片)不动 routes.json。
|
|
41
43
|
*/
|
|
42
44
|
function buildTosActions(projectDir, cs) {
|
|
43
45
|
const actions = [];
|
|
@@ -48,8 +50,8 @@ function buildTosActions(projectDir, cs) {
|
|
|
48
50
|
for (const rel of cs.deletes) {
|
|
49
51
|
actions.push({ actionType: index_1.TosActionType.DELETE, filePath: normalizeRel(rel) });
|
|
50
52
|
}
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
+
const routesMayChange = [...cs.creates, ...cs.updates, ...cs.deletes].some(isHtml);
|
|
54
|
+
if (routesMayChange) {
|
|
53
55
|
actions.push({
|
|
54
56
|
actionType: index_1.TosActionType.UPDATE,
|
|
55
57
|
filePath: 'routes.json',
|
|
@@ -0,0 +1,75 @@
|
|
|
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.extractTitle = extractTitle;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
/**
|
|
9
|
+
* 读 `<title>` 时的扫描窗口上限(字节)。title 规范上在 `<head>` 里、正常紧贴文档开头,
|
|
10
|
+
* 64KB 足以覆盖 head 里塞了内联 style / script 的情况,同时给大 HTML 兜住 IO 上限
|
|
11
|
+
* (generateRoutes 会遍历工程内所有 .html,不能整文件读)。
|
|
12
|
+
*/
|
|
13
|
+
const HEAD_SCAN_BYTES = 64 * 1024;
|
|
14
|
+
const TITLE_RE = /<title[^>]*>([^<]*)<\/title>/i;
|
|
15
|
+
const HEAD_CLOSE_RE = /<\/head\s*>/i;
|
|
16
|
+
const COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
17
|
+
const ENTITY_RE = /&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi;
|
|
18
|
+
/** HTML 里可能出现在 title 文本中的命名实体。未列出的原样保留,不做全量实体表。 */
|
|
19
|
+
const NAMED_ENTITIES = {
|
|
20
|
+
amp: '&',
|
|
21
|
+
lt: '<',
|
|
22
|
+
gt: '>',
|
|
23
|
+
quot: '"',
|
|
24
|
+
apos: "'",
|
|
25
|
+
nbsp: ' ',
|
|
26
|
+
};
|
|
27
|
+
/** 把 title 文本里的实体还原成明文——name 是给前端直接当文本渲染的,不该带 `&`。 */
|
|
28
|
+
function decodeEntities(s) {
|
|
29
|
+
return s.replace(ENTITY_RE, (matched, body) => {
|
|
30
|
+
if (!body.startsWith('#')) {
|
|
31
|
+
return NAMED_ENTITIES[body.toLowerCase()] ?? matched;
|
|
32
|
+
}
|
|
33
|
+
const code = /^#x/i.test(body) ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);
|
|
34
|
+
if (!Number.isInteger(code) || code <= 0 || code > 0x10ffff)
|
|
35
|
+
return matched;
|
|
36
|
+
return String.fromCodePoint(code);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** 只读文件头部 HEAD_SCAN_BYTES 字节。尾部可能截断多字节字符,对 title 提取无影响。 */
|
|
40
|
+
function readHead(absPath) {
|
|
41
|
+
const fd = node_fs_1.default.openSync(absPath, 'r');
|
|
42
|
+
try {
|
|
43
|
+
const buf = Buffer.alloc(HEAD_SCAN_BYTES);
|
|
44
|
+
const bytes = node_fs_1.default.readSync(fd, buf, 0, HEAD_SCAN_BYTES, 0);
|
|
45
|
+
return buf.subarray(0, bytes).toString('utf-8');
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
node_fs_1.default.closeSync(fd);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 取 HTML 的 `<title>` 文本作为该页面的方案名。
|
|
53
|
+
*
|
|
54
|
+
* - 扫描范围截到第一个 `</head>`:body 里的 `<title>` 不算——内联 SVG 图标的 a11y
|
|
55
|
+
* `<title>` 很常见,抓到就会把图标描述当成方案名。找不到 `</head>` 时退回整个窗口。
|
|
56
|
+
* - 先剔 HTML 注释,避免抓到被注释掉的 title。
|
|
57
|
+
* - 实体还原 + 空白折叠 + trim;空 title / 无 title / 读失败一律返回 undefined,
|
|
58
|
+
* 由消费侧 fallback 到 path(旧应用没有 title 时保持显示页面地址)。
|
|
59
|
+
*/
|
|
60
|
+
function extractTitle(absPath) {
|
|
61
|
+
let head;
|
|
62
|
+
try {
|
|
63
|
+
head = readHead(absPath);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const headClose = HEAD_CLOSE_RE.exec(head)?.index ?? -1;
|
|
69
|
+
const scope = (headClose >= 0 ? head.slice(0, headClose) : head).replace(COMMENT_RE, '');
|
|
70
|
+
const raw = TITLE_RE.exec(scope)?.[1];
|
|
71
|
+
if (raw === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const text = decodeEntities(raw).replace(/\s+/g, ' ').trim();
|
|
74
|
+
return text === '' ? undefined : text;
|
|
75
|
+
}
|
|
@@ -11,7 +11,7 @@ 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
|
|
14
|
+
* design-html 增量发布:读本地文件组 TosFileAction(改动含 .html 时带重算的
|
|
15
15
|
* routes.json),调后端 applyTosDiff 应用到 latest。scope 限 design_local_deploy。
|
|
16
16
|
*/
|
|
17
17
|
async function patchDesignDeploy(opts) {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.generateRoutes = generateRoutes;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const html_title_1 = require("./html-title");
|
|
4
9
|
const source_scan_1 = require("./source-scan");
|
|
5
10
|
/**
|
|
6
11
|
* .html → route path 推导(相对路径,不带 base 前缀)。rel 为 posix 相对路径。
|
|
@@ -16,8 +21,9 @@ function toRoute(rel) {
|
|
|
16
21
|
}
|
|
17
22
|
/**
|
|
18
23
|
* 扫描 projectDir 下所有 .html(套 source-scan 的 EXCLUDES),生成 routes.json 文本(含尾换行)。
|
|
19
|
-
* 每项为 { path, file }:path 为相对路径(不带 CLIENT_BASE_PATH 前缀,由消费侧拼接),
|
|
20
|
-
* file 为该路由对应的原始 .html
|
|
24
|
+
* 每项为 { path, file, name? }:path 为相对路径(不带 CLIENT_BASE_PATH 前缀,由消费侧拼接),
|
|
25
|
+
* file 为该路由对应的原始 .html 源文件,name 为该文件 `<title>` 解析出的方案名(可缺省)。
|
|
26
|
+
* 按 path 排序、同 path 取字典序靠前的 file 去重。
|
|
21
27
|
* 零 html 时输出空数组——不伪造解析不到文件的根路由。
|
|
22
28
|
*/
|
|
23
29
|
function generateRoutes(projectDir) {
|
|
@@ -27,7 +33,11 @@ function generateRoutes(projectDir) {
|
|
|
27
33
|
if (!byPath.has(entry.path))
|
|
28
34
|
byPath.set(entry.path, entry);
|
|
29
35
|
}
|
|
30
|
-
|
|
36
|
+
// title 只读去重后胜出的 file:同 path 多源(foo.html / foo/index.html)时不做无用 IO。
|
|
37
|
+
const routes = [...byPath.values()].map((entry) => {
|
|
38
|
+
const name = (0, html_title_1.extractTitle)(node_path_1.default.join(projectDir, entry.file));
|
|
39
|
+
return name === undefined ? entry : { ...entry, name };
|
|
40
|
+
});
|
|
31
41
|
return JSON.stringify(routes, null, 2) + '\n';
|
|
32
42
|
}
|
|
33
43
|
function byPathThenFile(a, b) {
|
|
@@ -40,7 +40,27 @@ function inferMode() {
|
|
|
40
40
|
*/
|
|
41
41
|
function syncCodingSteering(opts) {
|
|
42
42
|
const logPrefix = opts.logPrefix ?? 'skills';
|
|
43
|
-
|
|
43
|
+
// 版本优先级:--version(显式 pin / 止血)> lane(TCC 定向灰度)> BOE 默认 @dev > @latest
|
|
44
|
+
//
|
|
45
|
+
// 与 sandbox-skills 的 syncSandboxSkills 完全同一套口径,只是包名不同。BOE 的 @dev
|
|
46
|
+
// 由 miaoda-coding develop 分支的自动发布维护;lane 必须压过 dev —— TCC 配了 tgzKey
|
|
47
|
+
// 就是定向到某个 feat 分支的快照,不该被"BOE 跟 develop"这条默认规则盖掉。
|
|
48
|
+
//
|
|
49
|
+
// BOE→dev 这一档在本函数里算,不由调用方预填:`skills sync --type` 分支会带 tgzKey
|
|
50
|
+
// 进来,调用方若先把 'dev' 塞进 version,version 档位更高,lane 就永远命不中。
|
|
51
|
+
// 本地分支 / app init 为了回报 versionSource 仍会自己算 dev 再传 version —— 那两条路
|
|
52
|
+
// 没有 tgzKey,lane 档天然为空,两处口径一致不会打架。
|
|
53
|
+
//
|
|
54
|
+
// lane 解析与 sandbox-skills 同一套:`extractLaneFromTgzKey` 反推 X,`pickLaneVersion`
|
|
55
|
+
// 过滤后缀 `-alpha.<X>`。**匹配只看后缀、不看 base 版本**,所以 coding-steering
|
|
56
|
+
// (`0.1.3x-alpha.<X>`)和 sandbox-skills(`0.1.x-alpha.<X>`)能共用 TCC 里的同一个
|
|
57
|
+
// key —— 前提是 lane 发布脚本用同一个 X 把两边一起发出去。这个包没发 `-alpha.<X>`
|
|
58
|
+
// 时 pickLaneVersion 返回 null(它对报错 / 无匹配都返回 null,绝不 throw),静默
|
|
59
|
+
// 回落到 dev / latest,不影响现有行为。
|
|
60
|
+
const lane = (0, npm_pack_1.extractLaneFromTgzKey)(opts.tgzKey);
|
|
61
|
+
const laneVersion = lane === null ? null : (0, npm_pack_1.pickLaneVersion)(STEERING_PACKAGE, lane);
|
|
62
|
+
const isBoe = process.env.FORCE_FRAMEWORK_ENVIRONMENT === 'boe';
|
|
63
|
+
const effectiveVersion = opts.version ?? laneVersion ?? (isBoe ? 'dev' : 'latest');
|
|
44
64
|
const mode = opts.mode ?? inferMode();
|
|
45
65
|
const layout = opts.outputLayout ?? 'nested';
|
|
46
66
|
(0, logger_1.log)(logPrefix, `Fetching ${STEERING_PACKAGE}@${effectiveVersion} (mode=${mode}, layout=${layout})...`);
|
package/dist/utils/npm-pack.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.pickLaneVersion = pickLaneVersion;
|
|
7
|
+
exports.extractLaneFromTgzKey = extractLaneFromTgzKey;
|
|
7
8
|
exports.fetchNpmPackage = fetchNpmPackage;
|
|
8
9
|
const node_child_process_1 = require("node:child_process");
|
|
9
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -44,6 +45,35 @@ function pickLaneVersion(packageName, X, registry) {
|
|
|
44
45
|
return null;
|
|
45
46
|
}
|
|
46
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* 从 TCC skillsUpdateTgzKey 反推 lane 名。
|
|
50
|
+
* skills_versions/all_feat-xxx.tgz → feat-xxx
|
|
51
|
+
* skills_versions/all_feat_xxx.tgz → feat-xxx (_ sanitize → -)
|
|
52
|
+
* skills_versions/all_latest.tgz → null(走各自的默认版本口径)
|
|
53
|
+
* 任何 all_<X>.tgz → <X> 里 _ 全部转 -
|
|
54
|
+
*
|
|
55
|
+
* **宽进严出**:正则允许 `_` 匹配(兼容老 miaoda-skills 时代 TOS key 含 `_` 的存量 TCC
|
|
56
|
+
* 命名),但返回时 sanitize 成 `-` —— 对齐 publish-sandbox-skills.sh 侧只发 `-` 版
|
|
57
|
+
* lane 的现实(npm SemVer pre-release 只允许 `[A-Za-z0-9-]`,不允许 `_`)。
|
|
58
|
+
* 存量 TCC 里如果有 `all_feat_xxx.tgz` 这种命名,反推出 `feat-xxx` 后能正确命中
|
|
59
|
+
* npm 上实际发出的 `-alpha.feat-xxx-<ts>` 包,而不是静默 fallback。
|
|
60
|
+
*
|
|
61
|
+
* 用 nullable 而不是空字符串 —— 让调用方能判断"没 lane 就走默认版本"。
|
|
62
|
+
*/
|
|
63
|
+
function extractLaneFromTgzKey(tgzKey) {
|
|
64
|
+
if (tgzKey === undefined || tgzKey === '') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const match = /(?:^|\/)all_([A-Za-z0-9][A-Za-z0-9_-]*)\.tgz$/.exec(tgzKey);
|
|
68
|
+
if (match === null) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const lane = match[1].replace(/_/g, '-');
|
|
72
|
+
if (lane === 'latest') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return lane;
|
|
76
|
+
}
|
|
47
77
|
/**
|
|
48
78
|
* `npm pack <pkg>@<ver> --registry <r>` + `tar -xzf`,解到临时目录。
|
|
49
79
|
* extractDir 是包内容根(含 package.json)。使用完必须 cleanup()。
|
|
@@ -3,7 +3,7 @@ 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.extractLaneFromTgzKey =
|
|
6
|
+
exports.extractLaneFromTgzKey = void 0;
|
|
7
7
|
exports.syncSandboxSkills = syncSandboxSkills;
|
|
8
8
|
exports.validateSandboxSyncOpts = validateSandboxSyncOpts;
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -68,34 +68,11 @@ const BUSINESS_LINE_BY_TYPE = {
|
|
|
68
68
|
openclaw: null, // openclaw 独立包,直接展平
|
|
69
69
|
};
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* skills_versions/all_feat_xxx.tgz → feat-xxx (_ sanitize → -)
|
|
74
|
-
* skills_versions/all_latest.tgz → null(走 @latest)
|
|
75
|
-
* 任何 all_<X>.tgz → <X> 里 _ 全部转 -
|
|
76
|
-
*
|
|
77
|
-
* **宽进严出**:正则允许 `_` 匹配(兼容老 miaoda-skills 时代 TOS key 含 `_` 的存量 TCC
|
|
78
|
-
* 命名),但返回时 sanitize 成 `-` —— 对齐 publish-sandbox-skills.sh 侧只发 `-` 版
|
|
79
|
-
* lane 的现实(npm SemVer pre-release 只允许 `[A-Za-z0-9-]`,不允许 `_`)。
|
|
80
|
-
* 存量 TCC 里如果有 `all_feat_xxx.tgz` 这种命名,反推出 `feat-xxx` 后能正确命中
|
|
81
|
-
* npm 上实际发出的 `@lane-feat-xxx` 包,而不是静默 fallback 到 @latest。
|
|
82
|
-
*
|
|
83
|
-
* 用 nullable 而不是空字符串 —— 让调用方能判断"没 lane 就走 latest"。
|
|
71
|
+
* lane 解析已下沉到 `utils/npm-pack`(与 `pickLaneVersion` 同处一地,供
|
|
72
|
+
* sandbox-skills 与 coding-steering 共用)。这里保留再导出,维持既有引用面。
|
|
84
73
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
const match = /(?:^|\/)all_([A-Za-z0-9][A-Za-z0-9_-]*)\.tgz$/.exec(tgzKey);
|
|
90
|
-
if (match === null) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
const lane = match[1].replace(/_/g, '-');
|
|
94
|
-
if (lane === 'latest') {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
return lane;
|
|
98
|
-
}
|
|
74
|
+
var npm_pack_2 = require("./npm-pack");
|
|
75
|
+
Object.defineProperty(exports, "extractLaneFromTgzKey", { enumerable: true, get: function () { return npm_pack_2.extractLaneFromTgzKey; } });
|
|
99
76
|
/**
|
|
100
77
|
* 主流程:按 type 拉包 + sync 到目标目录。
|
|
101
78
|
*
|
|
@@ -113,7 +90,7 @@ function syncSandboxSkills(opts) {
|
|
|
113
90
|
const targetDir = node_path_1.default.resolve(opts.targetDir ?? process.cwd());
|
|
114
91
|
const packageName = PACKAGE_BY_TYPE[opts.type];
|
|
115
92
|
const businessLine = BUSINESS_LINE_BY_TYPE[opts.type];
|
|
116
|
-
const lane = extractLaneFromTgzKey(opts.tgzKey);
|
|
93
|
+
const lane = (0, npm_pack_1.extractLaneFromTgzKey)(opts.tgzKey);
|
|
117
94
|
// ---- lane 拉包 ----
|
|
118
95
|
// 新姿势(2026-08 迁离 lane-<X> dist-tag 之后):
|
|
119
96
|
// TCC 里的 X(比如 `feat-migrate-skills-from-miaoda-skills-20260805143512`)
|
|
@@ -121,14 +98,25 @@ function syncSandboxSkills(opts) {
|
|
|
121
98
|
// 过滤 `endsWith('-alpha.' + X)`(因 X 含 timestamp 全局唯一,唯一命中或 0 命中)。
|
|
122
99
|
// 0 命中 → fallback @latest(对齐旧 TCC 值找不到、灰度切换期间的兜底语义)。
|
|
123
100
|
// `pickLaneVersion` 内部对 `npm view` 报错 / 无匹配都返回 null,绝不 throw ——
|
|
124
|
-
// 目标就是"不挂掉"
|
|
101
|
+
// 目标就是"不挂掉"。**同一个 X 也被 coding-steering 侧用同样方式解析**(见
|
|
102
|
+
// `syncCodingSteering`):匹配只看 `-alpha.<X>` 后缀、不看 base 版本,所以两个包
|
|
103
|
+
// base 版本不同也能共用一个 TCC key。
|
|
125
104
|
let fetched;
|
|
126
105
|
const laneVersion = lane === null ? null : (0, npm_pack_1.pickLaneVersion)(packageName, lane, opts.registry);
|
|
127
106
|
if (lane !== null && laneVersion === null) {
|
|
128
107
|
(0, logger_1.debug)(`sandbox-skills: no npm version matched -alpha.${lane}, fallback to @latest ` +
|
|
129
108
|
`(TCC 里的 X 可能是老 lane dist-tag 命名 / 已 unpublish / npm view 挂 / 网络断)`);
|
|
130
109
|
}
|
|
131
|
-
|
|
110
|
+
// 版本优先级:lane(TCC 显式定向)> BOE 默认 @dev > @latest
|
|
111
|
+
//
|
|
112
|
+
// `dev` 通道由 miaoda-coding 的 publish-sandbox-skills-dev.sh 在 develop 分支自动发布
|
|
113
|
+
// (`<base>-dev.<sha>` + dist-tag `dev`),口径与 coding-steering 完全一致 ——
|
|
114
|
+
// 后者在本仓 coding-steering 侧也是 `FORCE_FRAMEWORK_ENVIRONMENT=boe` 时默认取 `dev`。
|
|
115
|
+
//
|
|
116
|
+
// lane 必须压过 dev:TCC 里配了 tgz-key 就说明是定向灰度(钉某个 feat 分支的快照),
|
|
117
|
+
// 不能被"BOE 就跟 develop"这条默认规则覆盖掉。
|
|
118
|
+
const isBoe = process.env.FORCE_FRAMEWORK_ENVIRONMENT === 'boe';
|
|
119
|
+
const primaryVersion = laneVersion ?? (isBoe ? 'dev' : 'latest');
|
|
132
120
|
try {
|
|
133
121
|
(0, logger_1.debug)(`sandbox-skills: fetching ${packageName}@${primaryVersion}`);
|
|
134
122
|
fetched = (0, npm_pack_1.fetchNpmPackage)({
|
|
@@ -138,6 +126,9 @@ function syncSandboxSkills(opts) {
|
|
|
138
126
|
});
|
|
139
127
|
}
|
|
140
128
|
catch (err) {
|
|
129
|
+
// @latest 是最后一档,它再挂就没得降了,直接抛。
|
|
130
|
+
// lane / dev 拉失败都降到 @latest:dev 通道可能因为 develop 还没发过包而不存在
|
|
131
|
+
// (dist-tag 缺失 → npm pack 404),这时候不该让整条 sync 挂掉。
|
|
141
132
|
if (primaryVersion === 'latest') {
|
|
142
133
|
throw err;
|
|
143
134
|
}
|