@lark-apaas/miaoda-cli 0.1.34 → 0.1.36

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.
@@ -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
- // 版本口径与下方本地分支保持一致:--version > BOE 的 dist-tag dev > latest
38
- const isBoe = process.env.FORCE_FRAMEWORK_ENVIRONMENT === 'boe';
39
- const effectiveVersion = params.version ?? (isBoe ? 'dev' : undefined);
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: effectiveVersion,
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. `FORCE_FRAMEWORK_ENVIRONMENT=boe` dist-tag `dev`(BOE 测试灰度,跟随
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
- * 3. 默认 `latest`
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 压根不执行,保持一致。
@@ -40,7 +40,27 @@ function inferMode() {
40
40
  */
41
41
  function syncCodingSteering(opts) {
42
42
  const logPrefix = opts.logPrefix ?? 'skills';
43
- const effectiveVersion = opts.version ?? 'latest';
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})...`);
@@ -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 = 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
- * TCC skillsUpdateTgzKey 反推 lane 名。
72
- * skills_versions/all_feat-xxx.tgz → feat-xxx
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
- function extractLaneFromTgzKey(tgzKey) {
86
- if (tgzKey === undefined || tgzKey === '') {
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,7 +98,9 @@ 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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -63,13 +63,47 @@ function isStylelintTarget(filePath) {
63
63
  return filePath.endsWith('.css');
64
64
  }
65
65
 
66
+ const STYLELINT_CONFIG_FILES = [
67
+ '.stylelintrc',
68
+ '.stylelintrc.js',
69
+ '.stylelintrc.cjs',
70
+ '.stylelintrc.json',
71
+ 'stylelint.config.js',
72
+ ];
73
+
74
+ /**
75
+ * 老模板(fullstack-nestjs-template 1.x 时代)的应用跑不了 stylelint,跑了就挂、把 pre-commit
76
+ * 卡死。两种形态都实测过:
77
+ *
78
+ * - 缺 scripts.stylelint → `npm error Missing script: "stylelint"`
79
+ * - 缺 .stylelintrc.* → `ConfigurationError: No configuration provided`
80
+ *
81
+ * 注意光判断 stylelint 装没装拦不住:@lark-apaas/fullstack-presets 的 dependencies 里有
82
+ * stylelint,只要 dep 了 presets 二进制就在。缺任一件就跳过这一步。
83
+ */
84
+ function canRunStylelint() {
85
+ let pkg;
86
+ try {
87
+ pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
88
+ } catch {
89
+ return false;
90
+ }
91
+
92
+ if (!pkg.scripts || !pkg.scripts.stylelint) return false;
93
+
94
+ return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
95
+ }
96
+
66
97
  async function runDefaultLint() {
67
- const code = await runCommand(getBinName('npx'), [
68
- 'concurrently',
69
- 'npm run eslint',
70
- 'npm run type:check',
71
- 'npm run stylelint',
72
- ]);
98
+ const commands = ['npm run eslint', 'npm run type:check'];
99
+
100
+ if (canRunStylelint()) {
101
+ commands.push('npm run stylelint');
102
+ } else {
103
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
104
+ }
105
+
106
+ const code = await runCommand(getBinName('npx'), ['concurrently', ...commands]);
73
107
  process.exit(code);
74
108
  }
75
109
 
@@ -107,7 +141,9 @@ async function runSelectiveLint(inputFiles) {
107
141
  tasks.push(runCommand(getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]));
108
142
  }
109
143
 
110
- if (stylelintFiles.length > 0) {
144
+ if (stylelintFiles.length > 0 && !canRunStylelint()) {
145
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
146
+ } else if (stylelintFiles.length > 0) {
111
147
  tasks.push(runCommand(getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]));
112
148
  }
113
149