@lark-apaas/miaoda-cli 0.1.33-alpha.b340b4a → 0.1.33-alpha.bcb2fec
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 +0 -22
- package/dist/cli/commands/app/index.js +0 -64
- package/dist/cli/handlers/app/index.js +1 -4
- package/dist/cli/handlers/app/init.js +5 -0
- package/dist/cli/handlers/app/migrate.js +2 -4
- package/dist/cli/handlers/skills/sync.js +31 -7
- package/dist/config/sync-configs/design-stack.js +1 -1
- package/dist/config/sync-configs/nestjs-react-fullstack.js +4 -4
- package/dist/services/deploy/modern/patch/source-scan.js +40 -3
- package/dist/utils/coding-steering.js +139 -6
- package/dist/utils/env.js +3 -3
- package/dist/utils/npm-pack.js +30 -0
- package/dist/utils/sandbox-skills.js +33 -30
- package/dist/utils/skill-mirror.js +121 -0
- package/package.json +1 -1
- package/dist/cli/handlers/app/export-standalone.js +0 -165
- package/dist/services/app/export-standalone/archive.js +0 -33
- package/dist/services/app/export-standalone/copy-tree.js +0 -51
- package/dist/services/app/export-standalone/download.js +0 -48
- package/dist/services/app/export-standalone/index.js +0 -21
- package/dist/services/app/export-standalone/naming.js +0 -65
- package/dist/services/app/export-standalone/rewrite.js +0 -72
- package/dist/services/app/export-standalone/scan.js +0 -72
- package/dist/services/app/export-standalone/strategies.js +0 -127
- package/dist/utils/exclude-patterns.js +0 -49
package/README.md
CHANGED
|
@@ -48,31 +48,9 @@ miaoda file ls --output json
|
|
|
48
48
|
| `miaoda observability ...` | 线上日志、链路、前端源码堆栈反查、监控指标、运营指标 |
|
|
49
49
|
| `miaoda comment ...` | 应用评论:查看评论列表、解决评论(仅 design-html 场景开放) |
|
|
50
50
|
| `miaoda registry ...` | 组件 registry:列出 / copy-in SDK 文件(本地执行) |
|
|
51
|
-
| `miaoda app ...` | 应用生命周期:元数据、初始化、平台同步、源码导出、静态产物导出 |
|
|
52
51
|
|
|
53
52
|
完整命令通过 `miaoda --help` 或 `miaoda <domain> --help` 查看。
|
|
54
53
|
|
|
55
|
-
## 导出可本地访问的静态产物
|
|
56
|
-
|
|
57
|
-
`miaoda app export-standalone` 把当前应用构建成一份自包含静态产物,可脱离妙搭平台本地打开。
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
miaoda app export-standalone # → dist/standalone/ + dist/standalone.zip
|
|
61
|
-
miaoda app export-standalone --out dist/share
|
|
62
|
-
miaoda app export-standalone --json
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
支持 `design-html` / `html` / `vite-react` / `nestjs-react-fullstack`。页面与代码里字面以
|
|
66
|
-
`/spark/app` 开头的存储图片会经 dev server 代理下载到 `images/` 并改写成相对路径。
|
|
67
|
-
|
|
68
|
-
默认输出落在 `dist/` 下是刻意的:`dist` 已在部署扫描的 EXCLUDES 与各模板 `.gitignore` 中,
|
|
69
|
-
产物不会污染部署包、不会被误提交;代价是下一次构建会清掉它。
|
|
70
|
-
|
|
71
|
-
**只保证纯静态可看** —— 接口调用会失败,不做数据快照。多路由 SPA 在 `file://` 下不可用
|
|
72
|
-
(`BrowserRouter` 匹配不上路径),需用本地 HTTP server 打开,命令会在 `warnings` 里提示。
|
|
73
|
-
|
|
74
|
-
> 与 `miaoda app export` **语义无关**:后者导出的是源码归档(创意应用升级链路的只读参考物)。
|
|
75
|
-
|
|
76
54
|
## 组件 registry
|
|
77
55
|
|
|
78
56
|
`miaoda registry` 提供 shadcn 式的「组件 registry」:把 SDK 文件**拷进**当前项目(copy-in),而非作为依赖安装。registry 是独立 npm 包,按运行时分发(design-html / html → buildless runtime → `@lark-apaas/coding-registry-buildless`)。
|
|
@@ -22,7 +22,6 @@ function registerAppCommands(program, opts = {}) {
|
|
|
22
22
|
registerAppGet(appCmd);
|
|
23
23
|
registerAppUpdate(appCmd);
|
|
24
24
|
registerAppExport(appCmd);
|
|
25
|
-
registerAppExportStandalone(appCmd);
|
|
26
25
|
if (opts.includeInit) {
|
|
27
26
|
registerAppInit(appCmd);
|
|
28
27
|
registerAppSync(appCmd);
|
|
@@ -128,10 +127,6 @@ function registerAppExport(parent) {
|
|
|
128
127
|
.option('--out <dir>', '输出目录(相对当前目录)', 'source_package/creative')
|
|
129
128
|
.option('--checkpoint <n>', '按检查点版本导出;缺省为最新提交(HEAD)')
|
|
130
129
|
.addHelpText('after', `
|
|
131
|
-
与 'miaoda app export-standalone' 的区别(**两个命令语义无关,别选错**)
|
|
132
|
-
app export 导出源码归档(本命令)
|
|
133
|
-
app export-standalone 导出可本地访问的构建产物
|
|
134
|
-
|
|
135
130
|
说明
|
|
136
131
|
从服务端导出源应用(可以不是当前应用)的源码归档并解压到 --out 目录,导出完成后写
|
|
137
132
|
--out/.upgrade-manifest.json(含 sourceAppId / 导出时间 / 文件数),供升级链路幂等
|
|
@@ -178,65 +173,6 @@ JSON 输出
|
|
|
178
173
|
});
|
|
179
174
|
}));
|
|
180
175
|
}
|
|
181
|
-
function registerAppExportStandalone(parent) {
|
|
182
|
-
const cmd = parent
|
|
183
|
-
.command('export-standalone')
|
|
184
|
-
.description('把当前应用构建成自包含静态产物(目录 + zip),可脱离妙搭平台本地访问')
|
|
185
|
-
.option('--dir <path>', '项目目录,默认 cwd(需含 .spark/meta.json)')
|
|
186
|
-
.option('--out <dir>', '输出目录(相对项目根)', index_1.DEFAULT_STANDALONE_OUT)
|
|
187
|
-
.option('--dev-server <url>', 'dev server 基址,默认 http://localhost:${CLIENT_DEV_PORT:-8080}')
|
|
188
|
-
.addHelpText('after', `
|
|
189
|
-
与 'miaoda app export' 的区别(**两个命令语义无关,别选错**)
|
|
190
|
-
app export 导出源码归档(创意应用升级链路的只读参考物)
|
|
191
|
-
app export-standalone 导出可本地访问的构建产物(本命令)
|
|
192
|
-
|
|
193
|
-
支持的 stack
|
|
194
|
-
design-html / html / vite-react / nestjs-react-fullstack
|
|
195
|
-
|
|
196
|
-
产物
|
|
197
|
-
<out>/ index.html + assets/ + images/(+ static/,vite 系有 shared/static 时)
|
|
198
|
-
<out>.zip 同内容的压缩包
|
|
199
|
-
默认 <out> 是 dist/standalone —— 落在 dist/ 下是刻意的:dist 已在部署扫描的
|
|
200
|
-
EXCLUDES 与各模板 .gitignore 中,产物不会污染部署包、不会被误提交。
|
|
201
|
-
代价:下一次构建会 rm -rf dist,导出产物是易失的。
|
|
202
|
-
|
|
203
|
-
图片本地化
|
|
204
|
-
只处理页面/代码里字面以 /spark/app 开头的 URL,经 dev server 代理下载到
|
|
205
|
-
<out>/images/ 并改写成相对路径。其它挂载前缀(/spark/p/、/app/<id>、反代)不处理。
|
|
206
|
-
接口运行时返回的 download_url 属动态 URL,静态扫描覆盖不到。
|
|
207
|
-
|
|
208
|
-
已知限制
|
|
209
|
-
只保证纯静态可看——接口调用会失败,不做数据快照。
|
|
210
|
-
多路由 SPA 在 file:// 下不可用(BrowserRouter 匹配不上路径、pushState 抛 SecurityError),
|
|
211
|
-
请用本地 HTTP server 打开;检测到时会在 warnings 里给 SPA_ROUTER_FILE_PROTOCOL。
|
|
212
|
-
design-html 的 cd-elements SDK 是 CDN 外链,断网打开自定义元素不渲染。
|
|
213
|
-
|
|
214
|
-
JSON 输出
|
|
215
|
-
{"data": {"appId": "...", "stack": "...", "out": "dist/standalone", "zip": "dist/standalone.zip",
|
|
216
|
-
"fileCount": N, "bytes": N,
|
|
217
|
-
"images": {"matched": N, "downloaded": N, "failed": [{"url": "...", "reason": "..."}]},
|
|
218
|
-
"rewrittenFiles": [...],
|
|
219
|
-
"warnings": [{"code": "...", "message": "..."}]}}
|
|
220
|
-
|
|
221
|
-
失败
|
|
222
|
-
单张图片下载失败 → 跳过并计入 images.failed + warnings,退出码 0。
|
|
223
|
-
命中 > 0 但全部失败 → EXPORT_STANDALONE_IMAGE_ALL_FAILED,退出码 1(系统性问题)。
|
|
224
|
-
stack 不支持 → EXPORT_STANDALONE_STACK_UNSUPPORTED。
|
|
225
|
-
构建失败 → EXPORT_STANDALONE_BUILD_FAILED。
|
|
226
|
-
|
|
227
|
-
示例
|
|
228
|
-
$ miaoda app export-standalone
|
|
229
|
-
$ miaoda app export-standalone --out dist/share
|
|
230
|
-
$ miaoda app export-standalone --json
|
|
231
|
-
`);
|
|
232
|
-
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
233
|
-
await (0, index_1.handleAppExportStandalone)({
|
|
234
|
-
dir: rawOpts.dir,
|
|
235
|
-
out: rawOpts.out,
|
|
236
|
-
devServer: rawOpts.devServer,
|
|
237
|
-
});
|
|
238
|
-
}));
|
|
239
|
-
}
|
|
240
176
|
function registerAppGet(parent) {
|
|
241
177
|
const cmd = parent
|
|
242
178
|
.command('get')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.SUPPORTED_STACKS = exports.handleAppMigrate = exports.UPGRADE_MANIFEST_FILENAME = exports.DEFAULT_EXPORT_OUT = exports.handleAppExport = exports.handleAppUpgrade = exports.handleAppSync = exports.handleAppInit = exports.handleAppUpdate = exports.handleAppGet = void 0;
|
|
4
4
|
var get_1 = require("./get");
|
|
5
5
|
Object.defineProperty(exports, "handleAppGet", { enumerable: true, get: function () { return get_1.handleAppGet; } });
|
|
6
6
|
var update_1 = require("./update");
|
|
@@ -19,6 +19,3 @@ Object.defineProperty(exports, "handleAppMigrate", { enumerable: true, get: func
|
|
|
19
19
|
// commands 层渲染 help 时需要这份枚举;从 handler barrel 转发,避免 commands → services 越界
|
|
20
20
|
var index_1 = require("../../../services/app/init/index");
|
|
21
21
|
Object.defineProperty(exports, "SUPPORTED_STACKS", { enumerable: true, get: function () { return index_1.SUPPORTED_STACKS; } });
|
|
22
|
-
var export_standalone_1 = require("./export-standalone");
|
|
23
|
-
Object.defineProperty(exports, "handleAppExportStandalone", { enumerable: true, get: function () { return export_standalone_1.handleAppExportStandalone; } });
|
|
24
|
-
Object.defineProperty(exports, "DEFAULT_STANDALONE_OUT", { enumerable: true, get: function () { return export_standalone_1.DEFAULT_STANDALONE_OUT; } });
|
|
@@ -86,6 +86,8 @@ async function handleAppInit(opts) {
|
|
|
86
86
|
version: 'unknown',
|
|
87
87
|
syncedSkills: [],
|
|
88
88
|
techSynced: false,
|
|
89
|
+
prunedSkills: [],
|
|
90
|
+
prunedStacks: [],
|
|
89
91
|
};
|
|
90
92
|
let steeringError;
|
|
91
93
|
if (scaffoldSteering) {
|
|
@@ -95,6 +97,9 @@ async function handleAppInit(opts) {
|
|
|
95
97
|
targetDir,
|
|
96
98
|
logPrefix: 'init',
|
|
97
99
|
outputLayout,
|
|
100
|
+
// match-template-version 门禁的比对基准:用刚渲染的 template 实际版本,
|
|
101
|
+
// 与随后写入 .spark/meta.json 的 version 同源(见下方 writeSparkMeta)
|
|
102
|
+
templateVersion: tplResult.version,
|
|
98
103
|
});
|
|
99
104
|
}
|
|
100
105
|
catch (err) {
|
|
@@ -144,12 +144,10 @@ async function handleAppMigrate(opts) {
|
|
|
144
144
|
// ignoreOutdatedRequests fix 注释)。直接 rm -rf 让新 vite 从干净状态 cold
|
|
145
145
|
// optimize, 一次写到位 lockfile 一致的 metadata, 不再触发中途 re-optimize。
|
|
146
146
|
//
|
|
147
|
-
// -
|
|
147
|
+
// - 只在沙箱内做(isSandboxEnv;本地一般不会撞这条链路)
|
|
148
148
|
// - 只在 install 成功时做(install 挂了 node_modules 状态不对, 清 cache 也救不回来)
|
|
149
149
|
// - 软失败:.vite 不存在 / 删失败都不阻断
|
|
150
|
-
if (installError === undefined &&
|
|
151
|
-
process.env.SANDBOX_ID !== undefined &&
|
|
152
|
-
process.env.SANDBOX_ID !== '') {
|
|
150
|
+
if (installError === undefined && (0, env_1.isSandboxEnv)()) {
|
|
153
151
|
const viteCacheDir = node_path_1.default.join(targetDir, 'node_modules', '.vite');
|
|
154
152
|
if (node_fs_1.default.existsSync(viteCacheDir)) {
|
|
155
153
|
(0, logger_1.log)('migrate', '清理 node_modules/.vite (vite cache 跟新 lockfile 错位会 504)...');
|
|
@@ -24,24 +24,35 @@ const output_1 = require("../../../utils/output");
|
|
|
24
24
|
*/
|
|
25
25
|
function syncSteeringForSandbox(params) {
|
|
26
26
|
let stack;
|
|
27
|
+
// 提到 try 外:下面 syncCodingSteering 要用它做 match-template-version 门禁
|
|
28
|
+
let templateVersion;
|
|
27
29
|
try {
|
|
28
30
|
const meta = (0, spark_meta_1.readSparkMeta)(params.targetDir);
|
|
29
31
|
if (meta.stack === undefined || meta.stack === '' || meta.stack === 'null') {
|
|
30
32
|
return { status: 'skipped', reason: '.spark/meta.json missing stack' };
|
|
31
33
|
}
|
|
32
34
|
stack = meta.stack;
|
|
35
|
+
templateVersion = meta.version;
|
|
33
36
|
}
|
|
34
37
|
catch (err) {
|
|
35
38
|
return { status: 'skipped', reason: `read .spark/meta.json failed: ${err.message}` };
|
|
36
39
|
}
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
// 版本口径:--version > lane(TCC tgzKey)> BOE 的 dist-tag dev > latest。
|
|
41
|
+
//
|
|
42
|
+
// **这里不预先把 BOE 解成 'dev'**(下方本地分支为了回报 versionSource 才那么做):
|
|
43
|
+
// 一旦提前填进 version,就会盖住 lane —— version 在优先级里高于 lane,BOE 沙箱上
|
|
44
|
+
// 配了 tgzKey 也永远命中不了。把 version 原样透传(未指定就是 undefined),让
|
|
45
|
+
// syncCodingSteering 一处算全链:version > lane > dev > latest。
|
|
40
46
|
try {
|
|
41
47
|
const r = (0, coding_steering_1.syncCodingSteering)({
|
|
42
48
|
stack,
|
|
43
49
|
targetDir: params.targetDir,
|
|
44
|
-
version:
|
|
50
|
+
version: params.version,
|
|
51
|
+
// TCC 的 X 与平铺 skill 共用:pickLaneVersion 只匹配 `-alpha.<X>` 后缀,
|
|
52
|
+
// 不看 base 版本,所以一个 key 同时定向 coding-steering 与 sandbox-skills。
|
|
53
|
+
tgzKey: params.tgzKey,
|
|
54
|
+
// match-template-version 门禁的比对基准(老 update-skills.sh 读的同一个字段)
|
|
55
|
+
templateVersion,
|
|
45
56
|
// 必须显式传 mode,不能落到 inferMode():它靠 env MIAODA_DEP_CACHE_DIR 嗅探,
|
|
46
57
|
// 而 mode 决定读包内哪个 layer(sandbox→`skills/`,local→`skills_local/`)。
|
|
47
58
|
// `--type` 分支本身就是沙箱语义,嗅探失手会去读不存在的 skills_local/、
|
|
@@ -56,6 +67,8 @@ function syncSteeringForSandbox(params) {
|
|
|
56
67
|
version: r.version,
|
|
57
68
|
syncedSkills: r.syncedSkills,
|
|
58
69
|
techSynced: r.techSynced,
|
|
70
|
+
prunedSkills: r.prunedSkills,
|
|
71
|
+
prunedStacks: r.prunedStacks,
|
|
59
72
|
};
|
|
60
73
|
}
|
|
61
74
|
catch (err) {
|
|
@@ -70,9 +83,16 @@ function syncSteeringForSandbox(params) {
|
|
|
70
83
|
*
|
|
71
84
|
* 版本来源优先级(从高到低):
|
|
72
85
|
* 1. CLI `--version` flag(显式指定)
|
|
73
|
-
* 2. `
|
|
86
|
+
* 2. lane:`--type` 分支下由 TCC `skillsUpdateTgzKey` 透传 `--tgz-key`,反推 X 后
|
|
87
|
+
* 解析出 `-alpha.<X>` 版本。**与平铺 skill 共用同一个 X** —— 匹配只看后缀、
|
|
88
|
+
* 不看 base 版本,所以 TCC 配一个 key 就同时定向 coding-steering 与 sandbox-skills。
|
|
89
|
+
* 本地分支(不带 `--type`)没有 tgzKey,这一档天然不参与。
|
|
90
|
+
* 3. `FORCE_FRAMEWORK_ENVIRONMENT=boe` → dist-tag `dev`(BOE 测试灰度,跟随
|
|
74
91
|
* publish-alpha.sh 在 develop 分支打的 dev tag,便于在 BOE 沙箱里验证预发 steering)
|
|
75
|
-
*
|
|
92
|
+
* 4. 默认 `latest`
|
|
93
|
+
*
|
|
94
|
+
* 2/3/4 档统一在 `syncCodingSteering` 里算(见那边注释),handler 只负责透传,避免
|
|
95
|
+
* 提前把 `dev` 填进 version 把 lane 挤掉。
|
|
76
96
|
*
|
|
77
97
|
* **不读 meta.json**——steering 版本不在 meta 里钉。
|
|
78
98
|
*/
|
|
@@ -98,7 +118,7 @@ async function handleSkillsSync(opts) {
|
|
|
98
118
|
const result = (0, sandbox_skills_1.syncSandboxSkills)({ type, tgzKey, targetDir });
|
|
99
119
|
const steering = result.skippedByKeepFile
|
|
100
120
|
? { status: 'skipped', reason: '.keep file found, sync frozen' }
|
|
101
|
-
: syncSteeringForSandbox({ targetDir, version: opts.version });
|
|
121
|
+
: syncSteeringForSandbox({ targetDir, version: opts.version, tgzKey });
|
|
102
122
|
// ③ 收尾锁只读(update-skills.sh:241 的 chmod -R a=rX)。必须在 ①② 全部写完之后,
|
|
103
123
|
// 锁上就写不进去了。`.keep` 冻结时不锁 —— 老链路那条路径是 exit 0 直接走、
|
|
104
124
|
// 末尾的 chmod 压根不执行,保持一致。
|
|
@@ -121,6 +141,7 @@ async function handleSkillsSync(opts) {
|
|
|
121
141
|
lane: result.lane,
|
|
122
142
|
syncedTop: result.syncedTop,
|
|
123
143
|
deleted: result.deleted,
|
|
144
|
+
deletedFiles: result.deletedFiles,
|
|
124
145
|
skippedByKeepFile: result.skippedByKeepFile,
|
|
125
146
|
lockedReadonly,
|
|
126
147
|
targetDir: result.targetDir,
|
|
@@ -141,6 +162,7 @@ async function handleSkillsSync(opts) {
|
|
|
141
162
|
stack: meta.stack,
|
|
142
163
|
targetDir,
|
|
143
164
|
version: effectiveVersion,
|
|
165
|
+
templateVersion: meta.version,
|
|
144
166
|
outputLayout: opts.local === true ? 'flat' : 'nested',
|
|
145
167
|
});
|
|
146
168
|
(0, output_1.emit)({
|
|
@@ -150,6 +172,8 @@ async function handleSkillsSync(opts) {
|
|
|
150
172
|
versionSource,
|
|
151
173
|
syncedSkills: result.syncedSkills,
|
|
152
174
|
techSynced: result.techSynced,
|
|
175
|
+
prunedSkills: result.prunedSkills,
|
|
176
|
+
prunedStacks: result.prunedStacks,
|
|
153
177
|
...(result.claudeSkillsLink !== undefined
|
|
154
178
|
? { claudeSkillsLink: result.claudeSkillsLink }
|
|
155
179
|
: {}),
|
|
@@ -91,7 +91,7 @@ exports.SYNC_CONFIG = {
|
|
|
91
91
|
ifStartsWith: 'npm run upgrade && ',
|
|
92
92
|
},
|
|
93
93
|
// ===== miaoda-cli 本地开发新增规则 =====
|
|
94
|
-
// M1. scripts.dev:local ——
|
|
94
|
+
// M1. scripts.dev:local —— 本地用户绕过沙箱判定直接跑本地链路(npm run dev:local)。
|
|
95
95
|
{
|
|
96
96
|
type: 'add-script',
|
|
97
97
|
name: 'dev:local',
|
|
@@ -135,8 +135,8 @@ exports.SYNC_CONFIG = {
|
|
|
135
135
|
ifStartsWith: 'concurrently ',
|
|
136
136
|
},
|
|
137
137
|
// 10. 把老 `npm run upgrade && ./scripts/dev.sh` 形式迁移到 `./scripts/dev.sh`,
|
|
138
|
-
// 让本地 `npm run dev` 不再前置跑 fullstack-cli sync。新版 dev.sh 按
|
|
139
|
-
//
|
|
138
|
+
// 让本地 `npm run dev` 不再前置跑 fullstack-cli sync。新版 dev.sh 按 MIAODA_DEP_CACHE_DIR
|
|
139
|
+
// 是否非空判分支:沙箱直接 exec dev.js(脚本同步由平台 pod 启动时做过,dev 入口不再 upgrade);
|
|
140
140
|
// 本地走 miaoda app sync 兜底 + dev-local.js。
|
|
141
141
|
{
|
|
142
142
|
type: 'patch-script',
|
|
@@ -160,8 +160,8 @@ exports.SYNC_CONFIG = {
|
|
|
160
160
|
ifStartsWith: 'npx -y @lark-apaas/fullstack-cli sync',
|
|
161
161
|
},
|
|
162
162
|
// ===== miaoda-cli 本地开发新增规则(fullstack-cli sync 不会执行) =====
|
|
163
|
-
// M1. scripts.dev:local ——
|
|
164
|
-
// dev.sh 在
|
|
163
|
+
// M1. scripts.dev:local —— 本地用户绕过沙箱判定直接跑本地链路(npm run dev:local)。
|
|
164
|
+
// dev.sh 在 MIAODA_DEP_CACHE_DIR 非空时跑 dev.js(沙箱保活)、否则 exec dev-local.js;显式 dev:local
|
|
165
165
|
// 用于 agent 在本地稳定调试,不受任何 env 影响。
|
|
166
166
|
{
|
|
167
167
|
type: 'add-script',
|
|
@@ -7,8 +7,45 @@ exports.EXCLUDES = void 0;
|
|
|
7
7
|
exports.listSourceFiles = listSourceFiles;
|
|
8
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// 与模板 scripts/build.sh 的 rsync EXCLUDES 对齐:这些名字(任意层级 basename)不进产物/不计入路由。
|
|
11
|
+
exports.EXCLUDES = new Set([
|
|
12
|
+
'.git',
|
|
13
|
+
'node_modules',
|
|
14
|
+
'dist',
|
|
15
|
+
'scripts',
|
|
16
|
+
'package.json',
|
|
17
|
+
'package-lock.json',
|
|
18
|
+
'pnpm-lock.yaml',
|
|
19
|
+
'yarn.lock',
|
|
20
|
+
'.gitignore',
|
|
21
|
+
'.npmrc',
|
|
22
|
+
// agent / 编辑器配置目录与指令文件:只服务本地开发,不属于应用资产。
|
|
23
|
+
// 注意 .claude/skills 在 flat layout 下是指向 ../.agents/skills 的软链,而 listSourceFiles
|
|
24
|
+
// 会 follow 软链,所以必须按 basename 把 .claude 整个排掉,否则 skills 会从软链侧被带进产物。
|
|
25
|
+
'.agent',
|
|
26
|
+
'.agents',
|
|
27
|
+
'.claude',
|
|
28
|
+
'.codex',
|
|
29
|
+
'.cursor',
|
|
30
|
+
'.gemini',
|
|
31
|
+
'.trae',
|
|
32
|
+
'.windsurf',
|
|
33
|
+
'.vscode',
|
|
34
|
+
'.idea',
|
|
35
|
+
'AGENTS.md',
|
|
36
|
+
'CLAUDE.md',
|
|
37
|
+
'GEMINI.md',
|
|
38
|
+
'skills',
|
|
39
|
+
'.env',
|
|
40
|
+
'.env.local',
|
|
41
|
+
'README.md',
|
|
42
|
+
'.DS_Store',
|
|
43
|
+
'.spark',
|
|
44
|
+
// 本地开发的临时/运行时目录:tmp 里的 .html 是草稿或中间产物,logs 是本地日志,
|
|
45
|
+
// 两者既不进产物也不该出现在 routes.json。
|
|
46
|
+
'tmp',
|
|
47
|
+
'logs',
|
|
48
|
+
]);
|
|
12
49
|
/**
|
|
13
50
|
* 递归列出 root 下所有文件的相对 posix 路径,跳过 EXCLUDES(任意层级 basename),
|
|
14
51
|
* 用 statSync 判定目录/文件以 follow 符号链接(含指向目录的软链),断链等无法 stat 的条目跳过。
|
|
@@ -17,7 +54,7 @@ function listSourceFiles(root) {
|
|
|
17
54
|
const out = [];
|
|
18
55
|
const walk = (dir, rel) => {
|
|
19
56
|
for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
20
|
-
if (
|
|
57
|
+
if (exports.EXCLUDES.has(entry.name))
|
|
21
58
|
continue;
|
|
22
59
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
23
60
|
const abs = node_path_1.default.join(dir, entry.name);
|
|
@@ -7,7 +7,9 @@ exports.syncCodingSteering = syncCodingSteering;
|
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const npm_pack_1 = require("../utils/npm-pack");
|
|
10
|
+
const file_ops_1 = require("../utils/file-ops");
|
|
10
11
|
const logger_1 = require("../utils/logger");
|
|
12
|
+
const logger_2 = require("../utils/logger");
|
|
11
13
|
const env_1 = require("../utils/env");
|
|
12
14
|
const STEERING_PACKAGE = '@lark-apaas/coding-steering';
|
|
13
15
|
function inferMode() {
|
|
@@ -40,10 +42,30 @@ function inferMode() {
|
|
|
40
42
|
*/
|
|
41
43
|
function syncCodingSteering(opts) {
|
|
42
44
|
const logPrefix = opts.logPrefix ?? 'skills';
|
|
43
|
-
|
|
45
|
+
// 版本优先级:--version(显式 pin / 止血)> lane(TCC 定向灰度)> BOE 默认 @dev > @latest
|
|
46
|
+
//
|
|
47
|
+
// 与 sandbox-skills 的 syncSandboxSkills 完全同一套口径,只是包名不同。BOE 的 @dev
|
|
48
|
+
// 由 miaoda-coding develop 分支的自动发布维护;lane 必须压过 dev —— TCC 配了 tgzKey
|
|
49
|
+
// 就是定向到某个 feat 分支的快照,不该被"BOE 跟 develop"这条默认规则盖掉。
|
|
50
|
+
//
|
|
51
|
+
// BOE→dev 这一档在本函数里算,不由调用方预填:`skills sync --type` 分支会带 tgzKey
|
|
52
|
+
// 进来,调用方若先把 'dev' 塞进 version,version 档位更高,lane 就永远命不中。
|
|
53
|
+
// 本地分支 / app init 为了回报 versionSource 仍会自己算 dev 再传 version —— 那两条路
|
|
54
|
+
// 没有 tgzKey,lane 档天然为空,两处口径一致不会打架。
|
|
55
|
+
//
|
|
56
|
+
// lane 解析与 sandbox-skills 同一套:`extractLaneFromTgzKey` 反推 X,`pickLaneVersion`
|
|
57
|
+
// 过滤后缀 `-alpha.<X>`。**匹配只看后缀、不看 base 版本**,所以 coding-steering
|
|
58
|
+
// (`0.1.3x-alpha.<X>`)和 sandbox-skills(`0.1.x-alpha.<X>`)能共用 TCC 里的同一个
|
|
59
|
+
// key —— 前提是 lane 发布脚本用同一个 X 把两边一起发出去。这个包没发 `-alpha.<X>`
|
|
60
|
+
// 时 pickLaneVersion 返回 null(它对报错 / 无匹配都返回 null,绝不 throw),静默
|
|
61
|
+
// 回落到 dev / latest,不影响现有行为。
|
|
62
|
+
const lane = (0, npm_pack_1.extractLaneFromTgzKey)(opts.tgzKey);
|
|
63
|
+
const laneVersion = lane === null ? null : (0, npm_pack_1.pickLaneVersion)(STEERING_PACKAGE, lane);
|
|
64
|
+
const isBoe = process.env.FORCE_FRAMEWORK_ENVIRONMENT === 'boe';
|
|
65
|
+
const effectiveVersion = opts.version ?? laneVersion ?? (isBoe ? 'dev' : 'latest');
|
|
44
66
|
const mode = opts.mode ?? inferMode();
|
|
45
67
|
const layout = opts.outputLayout ?? 'nested';
|
|
46
|
-
(0,
|
|
68
|
+
(0, logger_2.log)(logPrefix, `Fetching ${STEERING_PACKAGE}@${effectiveVersion} (mode=${mode}, layout=${layout})...`);
|
|
47
69
|
const fetched = (0, npm_pack_1.fetchNpmPackage)({
|
|
48
70
|
packageName: STEERING_PACKAGE,
|
|
49
71
|
version: effectiveVersion,
|
|
@@ -81,7 +103,21 @@ function syncCodingSteering(opts) {
|
|
|
81
103
|
node_fs_1.default.copyFileSync(techSrc, dstTechPath);
|
|
82
104
|
techSynced = true;
|
|
83
105
|
}
|
|
106
|
+
else if (node_fs_1.default.existsSync(dstTechPath)) {
|
|
107
|
+
// 源包这版不再提供 tech.md → 删掉上一轮留下的,否则永久残留(老脚本逐文件删除会带走它)
|
|
108
|
+
try {
|
|
109
|
+
(0, file_ops_1.forceRemove)(dstTechPath);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
(0, logger_1.debug)(`coding-steering: remove stale tech.md failed: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
84
115
|
const synced = [];
|
|
116
|
+
// 目标目录里「本轮应当存在」的 skill 目录名 —— 下面 prune 的保留集。
|
|
117
|
+
// 被 match-template-version 判否的**不进**这个集合,于是会被 prune 删掉 ——
|
|
118
|
+
// 与老脚本 `should_sync` 判否即 `rm`(update-skills.sh:196-215)行为一致。
|
|
119
|
+
const keep = new Set();
|
|
120
|
+
const filteredOut = [];
|
|
85
121
|
// 顺序拷贝,后者覆盖前者:skills_common 铺底,专有层(skills 或 skills_local)覆盖同名
|
|
86
122
|
const exclusiveLayer = mode === 'sandbox' ? 'skills' : 'skills_local';
|
|
87
123
|
const layers = [
|
|
@@ -95,22 +131,72 @@ function syncCodingSteering(opts) {
|
|
|
95
131
|
const src = node_path_1.default.join(layer.dir, name);
|
|
96
132
|
if (!node_fs_1.default.statSync(src).isDirectory())
|
|
97
133
|
continue;
|
|
134
|
+
if (!matchesTemplateVersion(src, opts.templateVersion)) {
|
|
135
|
+
filteredOut.push(`${layer.label}/${name}`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
98
138
|
const dst = node_path_1.default.join(dstSkillsDir, name);
|
|
99
139
|
if (node_fs_1.default.existsSync(dst))
|
|
100
140
|
node_fs_1.default.rmSync(dst, { recursive: true, force: true });
|
|
101
141
|
copyDir(src, dst);
|
|
142
|
+
keep.add(name);
|
|
102
143
|
synced.push(`${layer.label}/${name}`);
|
|
103
144
|
}
|
|
104
145
|
}
|
|
146
|
+
// 收尾 ①:删掉目标里源包已不提供、或被版本门禁判否的 skill 目录。
|
|
147
|
+
// 新链路此前只在 sandbox-skills 侧做「顶层 prune」且显式跳过 steering/,导致 steering
|
|
148
|
+
// 下的过期 skill 永久残留。
|
|
149
|
+
const prunedSkills = [];
|
|
150
|
+
for (const name of node_fs_1.default.readdirSync(dstSkillsDir)) {
|
|
151
|
+
if (keep.has(name))
|
|
152
|
+
continue;
|
|
153
|
+
try {
|
|
154
|
+
(0, file_ops_1.forceRemove)(node_path_1.default.join(dstSkillsDir, name));
|
|
155
|
+
prunedSkills.push(name);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
(0, logger_1.debug)(`coding-steering: prune stale skill ${name} failed: ${err.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// 收尾 ②(仅 nested):删掉同级其它 stack 的整棵 steering 目录。
|
|
162
|
+
// meta.json 的 stack 唯一,切栈后旧 stack 的 steering 不该再留 —— 修的正是
|
|
163
|
+
// 「vite-react 升级到 nestjs-react-fullstack 后 steering/vite-react/ 永久残留」。
|
|
164
|
+
// 老脚本靠 should_sync 里 `path_stack != STACK → return 1` + 逐文件删除达到同样效果。
|
|
165
|
+
// flat layout 没有 stack 命名空间,天然不涉及。
|
|
166
|
+
const prunedStacks = [];
|
|
167
|
+
if (layout === 'nested') {
|
|
168
|
+
const steeringDst = node_path_1.default.join(opts.targetDir, '.agent', 'skills', 'steering');
|
|
169
|
+
if (node_fs_1.default.existsSync(steeringDst)) {
|
|
170
|
+
for (const entry of node_fs_1.default.readdirSync(steeringDst)) {
|
|
171
|
+
if (entry === opts.stack)
|
|
172
|
+
continue;
|
|
173
|
+
try {
|
|
174
|
+
(0, file_ops_1.forceRemove)(node_path_1.default.join(steeringDst, entry));
|
|
175
|
+
prunedStacks.push(entry);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
(0, logger_1.debug)(`coding-steering: prune stale stack ${entry} failed: ${err.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
105
183
|
let claudeSkillsLink;
|
|
106
184
|
if (layout === 'flat') {
|
|
107
185
|
claudeSkillsLink = ensureClaudeSkillsSymlink(opts.targetDir, logPrefix);
|
|
108
186
|
}
|
|
109
|
-
|
|
187
|
+
const extras = [
|
|
188
|
+
prunedSkills.length > 0 ? `pruned ${String(prunedSkills.length)} stale skill(s)` : '',
|
|
189
|
+
prunedStacks.length > 0 ? `pruned stack(s) ${prunedStacks.join(',')}` : '',
|
|
190
|
+
filteredOut.length > 0 ? `filtered ${String(filteredOut.length)} by version` : '',
|
|
191
|
+
].filter((x) => x !== '');
|
|
192
|
+
(0, logger_2.log)(logPrefix, `Synced ${String(synced.length)} skill(s), tech.md ${techSynced ? 'yes' : 'no'} ` +
|
|
193
|
+
`(mode=${mode}, layout=${layout})${extras.length > 0 ? ` [${extras.join('; ')}]` : ''}`);
|
|
110
194
|
return {
|
|
111
195
|
version: fetched.version,
|
|
112
196
|
syncedSkills: synced,
|
|
113
197
|
techSynced,
|
|
198
|
+
prunedSkills,
|
|
199
|
+
prunedStacks,
|
|
114
200
|
...(claudeSkillsLink !== undefined ? { claudeSkillsLink } : {}),
|
|
115
201
|
};
|
|
116
202
|
}
|
|
@@ -144,7 +230,7 @@ function ensureClaudeSkillsSymlink(targetDir, logPrefix) {
|
|
|
144
230
|
}
|
|
145
231
|
if (existing === null) {
|
|
146
232
|
node_fs_1.default.symlinkSync(linkTarget, linkPath, 'dir');
|
|
147
|
-
(0,
|
|
233
|
+
(0, logger_2.log)(logPrefix, ` ✓ .claude/skills → ${linkTarget} (symlink created)`);
|
|
148
234
|
return 'created';
|
|
149
235
|
}
|
|
150
236
|
if (existing.isSymbolicLink()) {
|
|
@@ -154,12 +240,59 @@ function ensureClaudeSkillsSymlink(targetDir, logPrefix) {
|
|
|
154
240
|
}
|
|
155
241
|
node_fs_1.default.unlinkSync(linkPath);
|
|
156
242
|
node_fs_1.default.symlinkSync(linkTarget, linkPath, 'dir');
|
|
157
|
-
(0,
|
|
243
|
+
(0, logger_2.log)(logPrefix, ` ✓ .claude/skills → ${linkTarget} (symlink updated, was → ${current})`);
|
|
158
244
|
return 'updated';
|
|
159
245
|
}
|
|
160
|
-
(0,
|
|
246
|
+
(0, logger_2.log)(logPrefix, ` ⚠ .claude/skills 已是普通目录/文件,跳过软链创建`);
|
|
161
247
|
return 'conflict';
|
|
162
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* 读 SKILL.md frontmatter 里的 `match-template-version`。逐字对齐老脚本的取值方式
|
|
251
|
+
* (`update-skills.sh:80-90`):只看第一段 `---` 围起来的 frontmatter,去掉引号与全部空白。
|
|
252
|
+
* 没有该字段(或没有 SKILL.md)返回 undefined = 不设版本门禁。
|
|
253
|
+
*/
|
|
254
|
+
function readMatchTemplateVersion(skillDir) {
|
|
255
|
+
const skillMd = node_path_1.default.join(skillDir, 'SKILL.md');
|
|
256
|
+
if (!node_fs_1.default.existsSync(skillMd))
|
|
257
|
+
return undefined;
|
|
258
|
+
let content;
|
|
259
|
+
try {
|
|
260
|
+
content = node_fs_1.default.readFileSync(skillMd, 'utf-8');
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
266
|
+
if (fm === null)
|
|
267
|
+
return undefined;
|
|
268
|
+
const line = /^match-template-version:(.*)$/m.exec(fm[1]);
|
|
269
|
+
if (line === null)
|
|
270
|
+
return undefined;
|
|
271
|
+
const value = line[1].replace(/["']/g, '').replace(/\s/g, '');
|
|
272
|
+
return value === '' ? undefined : value;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* `match-template-version` 门禁,语义逐字对齐老 `check_version_match`
|
|
276
|
+
* (`update-skills.sh:76-108`):
|
|
277
|
+
* - skill 没声明该字段 → 放行
|
|
278
|
+
* - 声明值是纯整数 → 与用户版本的 **major** 比(`3` 匹配 `3.2.1`)
|
|
279
|
+
* - 其它形态(如 `2.2.6`) → 与用户版本**全等**
|
|
280
|
+
* - 用户版本未知(meta 里没 version)→ 放行(见 opts.templateVersion 注释)
|
|
281
|
+
*/
|
|
282
|
+
function matchesTemplateVersion(skillDir, templateVersion) {
|
|
283
|
+
const required = readMatchTemplateVersion(skillDir);
|
|
284
|
+
if (required === undefined)
|
|
285
|
+
return true;
|
|
286
|
+
if (templateVersion === undefined || templateVersion === '') {
|
|
287
|
+
(0, logger_1.debug)(`coding-steering: ${node_path_1.default.basename(skillDir)} 声明 match-template-version=${required},` +
|
|
288
|
+
`但 .spark/meta.json 无 version,放行不过滤`);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
if (/^\d+$/.test(required)) {
|
|
292
|
+
return templateVersion.split('.')[0] === required;
|
|
293
|
+
}
|
|
294
|
+
return templateVersion === required;
|
|
295
|
+
}
|
|
163
296
|
function copyDir(src, dest) {
|
|
164
297
|
node_fs_1.default.mkdirSync(dest, { recursive: true });
|
|
165
298
|
for (const entry of node_fs_1.default.readdirSync(src, { withFileTypes: true })) {
|
package/dist/utils/env.js
CHANGED
|
@@ -7,9 +7,9 @@ exports.isSandboxEnv = isSandboxEnv;
|
|
|
7
7
|
* 口径:`process.env.MIAODA_DEP_CACHE_DIR` 非空(既非 undefined 也非空串)即为沙箱。
|
|
8
8
|
* 沙箱平台运行时会给应用进程注入依赖缓存目录;本地 dev / agent 环境拿不到该变量。
|
|
9
9
|
*
|
|
10
|
-
* 历史上用 `SANDBOX_ID`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* 历史上用 `SANDBOX_ID` 判断,但它由 sandbox_console 在绑定沙箱时才写入
|
|
11
|
+
* `.force/environment/env`,预热池实例在绑定前拿不到,故换成 `MIAODA_DEP_CACHE_DIR`。
|
|
12
|
+
* 下发给 user app 的 `scripts/dev.sh` 是独立 bash 链路(不复用本函数),现已同口径。
|
|
13
13
|
*
|
|
14
14
|
* 这是 CLI 运行时沙箱判定的 SSOT —— init outputLayout 分流、migrate 重启 dev process、
|
|
15
15
|
* coding-steering 同步 mode 推断、async-install 写 marker 都走这里,别再各处内联读环境变量。
|
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()。
|