@coralai/sps-cli 0.65.85 → 0.65.87
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/CHANGELOG.md +31 -0
- package/dist/commands/cardMarkComplete.js +2 -2
- package/dist/commands/cardMarkComplete.js.map +1 -1
- package/dist/console/index.d.ts.map +1 -1
- package/dist/console/index.js +10 -1
- package/dist/console/index.js.map +1 -1
- package/dist/console/routes/projects.d.ts.map +1 -1
- package/dist/console/routes/projects.js +47 -0
- package/dist/console/routes/projects.js.map +1 -1
- package/dist/console/routes/skills.d.ts.map +1 -1
- package/dist/console/routes/skills.js +30 -6
- package/dist/console/routes/skills.js.map +1 -1
- package/dist/console/routes/templates.d.ts +16 -0
- package/dist/console/routes/templates.d.ts.map +1 -0
- package/dist/console/routes/templates.js +164 -0
- package/dist/console/routes/templates.js.map +1 -0
- package/dist/console/routes/workers.d.ts.map +1 -1
- package/dist/console/routes/workers.js +9 -4
- package/dist/console/routes/workers.js.map +1 -1
- package/dist/core/agents/AgentBackend.d.ts +1 -1
- package/dist/core/agents/AgentBackend.js +1 -1
- package/dist/core/agents/resolveBackend.d.ts +1 -1
- package/dist/core/agents/resolveBackend.js +1 -1
- package/dist/core/agents/resolveBackend.js.map +1 -1
- package/dist/core/intel/agentsConfig.d.ts.map +1 -1
- package/dist/core/intel/agentsConfig.js +5 -3
- package/dist/core/intel/agentsConfig.js.map +1 -1
- package/dist/core/skills/policy.js +1 -1
- package/dist/core/templates/scaffold.d.ts +55 -0
- package/dist/core/templates/scaffold.d.ts.map +1 -0
- package/dist/core/templates/scaffold.js +162 -0
- package/dist/core/templates/scaffold.js.map +1 -0
- package/dist/core/templates/templateDigest.d.ts +16 -0
- package/dist/core/templates/templateDigest.d.ts.map +1 -0
- package/dist/core/templates/templateDigest.js +57 -0
- package/dist/core/templates/templateDigest.js.map +1 -0
- package/dist/core/templates/templatePackage.d.ts +155 -0
- package/dist/core/templates/templatePackage.d.ts.map +1 -0
- package/dist/core/templates/templatePackage.js +393 -0
- package/dist/core/templates/templatePackage.js.map +1 -0
- package/dist/engines/StageEngine.d.ts.map +1 -1
- package/dist/engines/StageEngine.js +4 -4
- package/dist/engines/StageEngine.js.map +1 -1
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -1
- package/dist/providers/art/artEvents.d.ts +5 -0
- package/dist/providers/art/artEvents.d.ts.map +1 -1
- package/dist/providers/art/artEvents.js +30 -1
- package/dist/providers/art/artEvents.js.map +1 -1
- package/dist/providers/art/atlas.d.ts +0 -2
- package/dist/providers/art/atlas.d.ts.map +1 -1
- package/dist/providers/art/atlas.js +4 -14
- package/dist/providers/art/atlas.js.map +1 -1
- package/dist/providers/art/withinDir.d.ts.map +1 -1
- package/dist/providers/art/withinDir.js +20 -0
- package/dist/providers/art/withinDir.js.map +1 -1
- package/dist/services/LogService.d.ts +0 -12
- package/dist/services/LogService.d.ts.map +1 -1
- package/dist/services/LogService.js +2 -61
- package/dist/services/LogService.js.map +1 -1
- package/dist/services/SkillService.d.ts +15 -1
- package/dist/services/SkillService.d.ts.map +1 -1
- package/dist/services/SkillService.js +19 -3
- package/dist/services/SkillService.js.map +1 -1
- package/package.json +7 -1
- package/skills/web-smoke/SKILL.md +145 -0
- package/dist/core/agents/workerOutputSink.d.ts +0 -4
- package/dist/core/agents/workerOutputSink.d.ts.map +0 -1
- package/dist/core/agents/workerOutputSink.js +0 -39
- package/dist/core/agents/workerOutputSink.js.map +0 -1
- package/dist/runtime/stub/NoopBackend.d.ts +0 -29
- package/dist/runtime/stub/NoopBackend.d.ts.map +0 -1
- package/dist/runtime/stub/NoopBackend.js +0 -56
- package/dist/runtime/stub/NoopBackend.js.map +0 -1
- package/dist/runtime/stub/NoopService.d.ts +0 -23
- package/dist/runtime/stub/NoopService.d.ts.map +0 -1
- package/dist/runtime/stub/NoopService.js +0 -136
- package/dist/runtime/stub/NoopService.js.map +0 -1
- package/dist/runtime/stub/index.d.ts +0 -8
- package/dist/runtime/stub/index.d.ts.map +0 -1
- package/dist/runtime/stub/index.js +0 -8
- package/dist/runtime/stub/index.js.map +0 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/templates/templateDigest
|
|
3
|
+
* @description 模板包的**规范化内容指纹** —— 内容寻址 templateId 的唯一依据。
|
|
4
|
+
* @layer core
|
|
5
|
+
* @boundedContext templates
|
|
6
|
+
*
|
|
7
|
+
* 🔴 **为什么不是 sha256(tar.gz 字节)**:gzip 头带时间戳、tar 条目顺序与 mtime/uid/gid 都会变
|
|
8
|
+
* ⇒ 同样的 63 个文件在两台机器上打包出来 digest 不同 ⇒ 内容寻址失效
|
|
9
|
+
* (`GET /api/templates/<id>` 永远 404、每次都重传)。所以指纹只认**解包后的文件树**,
|
|
10
|
+
* tar.gz 只当传输容器。
|
|
11
|
+
*
|
|
12
|
+
* 规范化规则(两侧必须逐字一致,服务端权威):
|
|
13
|
+
* ```
|
|
14
|
+
* 1. 只取普通文件(目录不进指纹 —— 空目录不影响语义;软链/硬链/设备在解包时就被拒了)
|
|
15
|
+
* 2. 相对路径用 '/' 分隔,按**字节序**排序(Buffer.compare,不是 localeCompare)
|
|
16
|
+
* 3. 每条: <相对路径> \0 <mode> \0 <sha256(文件内容) 十六进制> \n
|
|
17
|
+
* 4. mode 只有两档:可执行 '755',否则 '644'
|
|
18
|
+
* 🔴 **可执行与否取自模板包内 `template.json` 的 `exec` 声明,不取 tar 里的 mode 位**
|
|
19
|
+
* (2026-08-14 拍板)。取 tar 的话,谁在哪台机器 checkout/打包都可能改掉执行位 ⇒
|
|
20
|
+
* 退回"同样的文件两次打包 digest 不同",只是把洞从 gzip 头挪到 mode 位。
|
|
21
|
+
* 配套校验:**tar 里有执行位但不在 `exec` 声明里 → 422**(忘了声明不是静默降级,是当场失败);
|
|
22
|
+
* 声明了但 tar 没执行位 → 直接置 755(声明即意图)。
|
|
23
|
+
* 5. 全部拼接后再 sha256,取十六进制**前 12 位**
|
|
24
|
+
* ```
|
|
25
|
+
* 排除:mtime / uid / gid / tar 条目顺序 / gzip 头,一概不进。
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ studio 侧会各实现一份。**漂了就当场 422**(服务端解包后重算,与声明值比对)——
|
|
28
|
+
* 比"共用一份实现却没人验"更安全。
|
|
29
|
+
*/
|
|
30
|
+
import { createHash } from 'node:crypto';
|
|
31
|
+
/** 单个文件的规范化行。 */
|
|
32
|
+
function entryLine(e) {
|
|
33
|
+
const mode = e.executable ? '755' : '644';
|
|
34
|
+
const sha = createHash('sha256').update(e.content).digest('hex');
|
|
35
|
+
return `${e.path}\0${mode}\0${sha}\n`;
|
|
36
|
+
}
|
|
37
|
+
/** 规范化内容指纹(完整 64 位十六进制)。 */
|
|
38
|
+
export function templateTreeDigest(files) {
|
|
39
|
+
const sorted = [...files].sort((a, b) => Buffer.compare(Buffer.from(a.path), Buffer.from(b.path)));
|
|
40
|
+
const h = createHash('sha256');
|
|
41
|
+
for (const e of sorted)
|
|
42
|
+
h.update(entryLine(e));
|
|
43
|
+
return h.digest('hex');
|
|
44
|
+
}
|
|
45
|
+
/** `<engine>@<sha256 前 12>` —— 内容寻址的 templateId。 */
|
|
46
|
+
export function templateId(engine, files) {
|
|
47
|
+
return `${engine}@${templateTreeDigest(files).slice(0, 12)}`;
|
|
48
|
+
}
|
|
49
|
+
/** templateId 的形状:`<engine>@<12 位小写十六进制>`。engine 与 skill/项目名同字符集。 */
|
|
50
|
+
const TEMPLATE_ID_RE = /^([A-Za-z0-9_-]{1,64})@([0-9a-f]{12})$/;
|
|
51
|
+
export function parseTemplateId(id) {
|
|
52
|
+
const m = TEMPLATE_ID_RE.exec(id);
|
|
53
|
+
if (!m)
|
|
54
|
+
return null;
|
|
55
|
+
return { engine: m[1], digest12: m[2] };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=templateDigest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templateDigest.js","sourceRoot":"","sources":["../../../src/core/templates/templateDigest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAUzC,iBAAiB;AACjB,SAAS,SAAS,CAAC,CAAoB;IACrC,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1C,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAAC;AACxC,CAAC;AAED,4BAA4B;AAC5B,MAAM,UAAU,kBAAkB,CAAC,KAA0B;IAC3D,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnG,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,KAA0B;IACnE,OAAO,GAAG,MAAM,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED,qEAAqE;AACrE,MAAM,cAAc,GAAG,wCAAwC,CAAC;AAEhE,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { type TemplateFileEntry } from './templateDigest.js';
|
|
2
|
+
/** 解包上限。超限一律拒绝,并**说清超的是哪一条、当前值、上限值**(别给 "too large")。 */
|
|
3
|
+
export declare const TEMPLATE_LIMITS: {
|
|
4
|
+
/** 压缩包字节(路由层还有一道 bodyLimit)。 */
|
|
5
|
+
readonly maxArchiveBytes: number;
|
|
6
|
+
/** 解包后总字节。 */
|
|
7
|
+
readonly maxTotalBytes: number;
|
|
8
|
+
/** 文件条目数。 */
|
|
9
|
+
readonly maxFiles: 2000;
|
|
10
|
+
/** 单个文件字节。 */
|
|
11
|
+
readonly maxFileBytes: number;
|
|
12
|
+
/** 解压比上限(解包后 / 压缩后)—— 防解压炸弹。 */
|
|
13
|
+
readonly maxCompressionRatio: 100;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 发布声明 —— **产物长什么样、快照哪个目录,由模板说了算,sps 不猜**。
|
|
17
|
+
*
|
|
18
|
+
* 存在的理由(设计文档 §3.3):产物未必是静态站点。生产里同时有
|
|
19
|
+
* phaser 游戏(根目录即产物,当站点服务)和 video 项目(产物是 `final.mp4`,没有 index.html)。
|
|
20
|
+
* 把形态写死进 sps 就等于把产品塞进引擎 —— **新增产物类型应该是新增一个模板,不是改 sps**。
|
|
21
|
+
*
|
|
22
|
+
* 🔴 **sps 只存不解释**:它不读 outDir 去做任何事,只把声明原样回给调用方
|
|
23
|
+
* (`GET /api/templates/:id`)。快照由平台指挥 sidecar 做,sidecar 只收一个相对路径。
|
|
24
|
+
*/
|
|
25
|
+
export interface TemplatePublish {
|
|
26
|
+
/** 快照哪个目录(相对项目根);`"."` = 整个项目根。 */
|
|
27
|
+
outDir: string;
|
|
28
|
+
/** 反代怎么服务它:`site` = 当站点(补默认文档);`file` = 单文件制品(下载/播放)。 */
|
|
29
|
+
serve: 'site' | 'file';
|
|
30
|
+
/** `serve: 'file'` 时的制品路径(相对 `outDir`),此时**必填**。 */
|
|
31
|
+
artifact?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 烟测声明 —— **"这个产品算不算合格"由模板说了算,sps 只知道跑哪条命令、怎么读结论。**
|
|
35
|
+
*
|
|
36
|
+
* 由来(Eddy 2026-08-18 定):烟测脚本(骨架 + 产品判据)由 studio 提供、随模板走,
|
|
37
|
+
* **不进 sps-cli**(公共 npm 包不该带 playwright 依赖,更不该带任何产品判据)。
|
|
38
|
+
* 与 `publish` 同一个位置、同一条道理:**产物形态与合格判据都由模板定,sps 不猜。**
|
|
39
|
+
*
|
|
40
|
+
* 🔴 sps **不执行**这里的命令 —— 它只把声明原样回给调用方(`GET /api/templates/:id`),
|
|
41
|
+
* 由 agent 按 skill 的指引去跑。sps 一旦开始解释它,就变成了产品代码。
|
|
42
|
+
*/
|
|
43
|
+
export interface TemplateSmoke {
|
|
44
|
+
/** 入口脚本(相对项目根),如 `scripts/smoke.mjs`。 */
|
|
45
|
+
script: string;
|
|
46
|
+
/** 产品判据 id,原样作为 `--checks=<id>` 传给脚本;缺省表示脚本自带全部判据。 */
|
|
47
|
+
checks?: string;
|
|
48
|
+
/** 快速档参数(中途回归用);缺省 `--quick`。收尾必须跑全量,见 smoke skill。 */
|
|
49
|
+
quickFlag?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface TemplateManifest {
|
|
52
|
+
engine: string;
|
|
53
|
+
vars: string[];
|
|
54
|
+
/** 只有这些文件做 `{{VAR}}` 替换;其余原样拷(二进制安全)。 */
|
|
55
|
+
render: string[];
|
|
56
|
+
/** 允许 scaffold 删掉重建的路径。`replace` 必须是它的子集。 */
|
|
57
|
+
replaceable: string[];
|
|
58
|
+
/** 需要置 755 的文件 —— **执行位的唯一真源**。 */
|
|
59
|
+
exec: string[];
|
|
60
|
+
/**
|
|
61
|
+
* 🔴 **可选**,而且必须保持可选。
|
|
62
|
+
* `loadTemplate()` 每次读盘都重新解析 `template.json` ——
|
|
63
|
+
* 一旦它变成必填,**已注册的模板会当场变成读不出来**(生产上的
|
|
64
|
+
* `phaser@959ebe6fedcb` 就没有这个字段)。校验器收紧会让存量数据静默失效,
|
|
65
|
+
* 这是"陈旧副本"那一族里最难发现的一种:没人改数据,是读它的代码变了。
|
|
66
|
+
*/
|
|
67
|
+
publish?: TemplatePublish;
|
|
68
|
+
/** 🔴 同 `publish`:**可选**,且必须保持可选。理由见上面那条注释。 */
|
|
69
|
+
smoke?: TemplateSmoke;
|
|
70
|
+
}
|
|
71
|
+
export interface UnpackedTemplate {
|
|
72
|
+
manifest: TemplateManifest;
|
|
73
|
+
files: TemplateFileEntry[];
|
|
74
|
+
id: string;
|
|
75
|
+
digest: string;
|
|
76
|
+
totalBytes: number;
|
|
77
|
+
}
|
|
78
|
+
export type UnpackError = {
|
|
79
|
+
code: 'ARCHIVE_TOO_LARGE';
|
|
80
|
+
detail: string;
|
|
81
|
+
} | {
|
|
82
|
+
code: 'LIMIT_EXCEEDED';
|
|
83
|
+
detail: string;
|
|
84
|
+
} | {
|
|
85
|
+
code: 'UNSAFE_ENTRY';
|
|
86
|
+
detail: string;
|
|
87
|
+
} | {
|
|
88
|
+
code: 'BAD_ARCHIVE';
|
|
89
|
+
detail: string;
|
|
90
|
+
} | {
|
|
91
|
+
code: 'MANIFEST_INVALID';
|
|
92
|
+
detail: string;
|
|
93
|
+
} | {
|
|
94
|
+
code: 'EXEC_UNDECLARED';
|
|
95
|
+
detail: string;
|
|
96
|
+
};
|
|
97
|
+
export type UnpackResult = {
|
|
98
|
+
ok: true;
|
|
99
|
+
value: UnpackedTemplate;
|
|
100
|
+
} | {
|
|
101
|
+
ok: false;
|
|
102
|
+
error: UnpackError;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* 路径安全:必须是相对路径、不含 `..` 段、不以 `/` 开头、无空段、无盘符。
|
|
106
|
+
* 🔴 用**段级**判断,不用字符串 startsWith —— `..foo` 合法而 `a/../../b` 不合法,
|
|
107
|
+
* 只看前缀两者会判反。
|
|
108
|
+
*/
|
|
109
|
+
export declare function isSafeRelPath(p: string): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* 目录引用的安全判断 = `isSafeRelPath` 再加一个合法值 `"."`(表示项目根)。
|
|
112
|
+
*
|
|
113
|
+
* 单独一个函数而不是给 `isSafeRelPath` 加参数:`"."` 对**文件**路径永远非法
|
|
114
|
+
* (`render: ["."]` 没有意义),放宽它会同时放宽调用它的另外三处声明校验。
|
|
115
|
+
*/
|
|
116
|
+
export declare function isSafeRelDir(p: string): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* tar 条目路径归一:去掉前导 `./`、压掉重复 `/`、反斜杠转正斜杠。
|
|
119
|
+
*
|
|
120
|
+
* 🔴 **不归一就会把好包也拒掉**:`tar czf x.tgz -C dir .` 产出的条目全是 `./foo` ——
|
|
121
|
+
* 那是最常见的打包姿势。实测踩到过:三个恶意包全被拒,但 symlink / 炸弹两个是栽在 `./` 上,
|
|
122
|
+
* **它们各自那道守卫根本没被执行到**(假绿)。归一之后才轮得到真正该拒它们的那一条。
|
|
123
|
+
*/
|
|
124
|
+
export declare function normalizeEntryPath(raw: string): string;
|
|
125
|
+
/**
|
|
126
|
+
* 解包 + 校验 + 算指纹。**不落盘** —— 调用方拿到 UnpackedTemplate 后自行决定存不存。
|
|
127
|
+
*/
|
|
128
|
+
export declare function unpackTemplate(archive: Buffer): Promise<UnpackResult>;
|
|
129
|
+
/** `~/.coral/templates` —— 每次调用现读 HOME(与 runtimePaths 同惯例,方便测试)。 */
|
|
130
|
+
export declare function templatesRoot(): string;
|
|
131
|
+
export declare function templateDir(id: string): string;
|
|
132
|
+
export declare function isTemplateRegistered(id: string): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* 落盘。**先写临时目录再整体 rename** —— 半棵树被别人读到,比没有更糟
|
|
135
|
+
* (调用方会以为注册成功)。同 id 已存在 = 幂等,直接返回。
|
|
136
|
+
*/
|
|
137
|
+
export declare function storeTemplate(t: UnpackedTemplate): {
|
|
138
|
+
stored: boolean;
|
|
139
|
+
dir: string;
|
|
140
|
+
};
|
|
141
|
+
/** 读回已注册模板的文件树(scaffold 用)。未注册 → null。 */
|
|
142
|
+
export declare function loadTemplate(id: string): {
|
|
143
|
+
manifest: TemplateManifest;
|
|
144
|
+
files: TemplateFileEntry[];
|
|
145
|
+
} | null;
|
|
146
|
+
/** 已注册模板的摘要(GET /api/templates/:id)。 */
|
|
147
|
+
export declare function templateSummary(id: string): {
|
|
148
|
+
id: string;
|
|
149
|
+
engine: string;
|
|
150
|
+
files: number;
|
|
151
|
+
bytes: number;
|
|
152
|
+
publish?: TemplatePublish;
|
|
153
|
+
smoke?: TemplateSmoke;
|
|
154
|
+
} | null;
|
|
155
|
+
//# sourceMappingURL=templatePackage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templatePackage.d.ts","sourceRoot":"","sources":["../../../src/core/templates/templatePackage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/templates/templatePackage
|
|
3
|
+
* @description 模板包的解包 / 校验 / 落盘。**收别人的 tar = 收不可信输入**,安全全在这一处。
|
|
4
|
+
* @layer core
|
|
5
|
+
* @boundedContext templates
|
|
6
|
+
*
|
|
7
|
+
* 解包在**内存里**完成、校验全过之后才落盘 —— 边解边写会让"校验失败"留下半棵树。
|
|
8
|
+
* 内存用量由下面的上限兜住。
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { list as tarList } from 'tar';
|
|
14
|
+
import { templateId, templateTreeDigest } from './templateDigest.js';
|
|
15
|
+
/** 解包上限。超限一律拒绝,并**说清超的是哪一条、当前值、上限值**(别给 "too large")。 */
|
|
16
|
+
export const TEMPLATE_LIMITS = {
|
|
17
|
+
/** 压缩包字节(路由层还有一道 bodyLimit)。 */
|
|
18
|
+
maxArchiveBytes: 8 * 1024 * 1024,
|
|
19
|
+
/** 解包后总字节。 */
|
|
20
|
+
maxTotalBytes: 32 * 1024 * 1024,
|
|
21
|
+
/** 文件条目数。 */
|
|
22
|
+
maxFiles: 2000,
|
|
23
|
+
/** 单个文件字节。 */
|
|
24
|
+
maxFileBytes: 8 * 1024 * 1024,
|
|
25
|
+
/** 解压比上限(解包后 / 压缩后)—— 防解压炸弹。 */
|
|
26
|
+
maxCompressionRatio: 100,
|
|
27
|
+
};
|
|
28
|
+
const err = (code, detail) => ({ ok: false, error: { code, detail } });
|
|
29
|
+
/**
|
|
30
|
+
* 路径安全:必须是相对路径、不含 `..` 段、不以 `/` 开头、无空段、无盘符。
|
|
31
|
+
* 🔴 用**段级**判断,不用字符串 startsWith —— `..foo` 合法而 `a/../../b` 不合法,
|
|
32
|
+
* 只看前缀两者会判反。
|
|
33
|
+
*/
|
|
34
|
+
export function isSafeRelPath(p) {
|
|
35
|
+
if (!p || p.startsWith('/') || p.includes('\0') || /^[A-Za-z]:/.test(p))
|
|
36
|
+
return false;
|
|
37
|
+
if (p.endsWith('/'))
|
|
38
|
+
return false;
|
|
39
|
+
const segs = p.split('/');
|
|
40
|
+
return segs.every((s) => s.length > 0 && s !== '.' && s !== '..');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 目录引用的安全判断 = `isSafeRelPath` 再加一个合法值 `"."`(表示项目根)。
|
|
44
|
+
*
|
|
45
|
+
* 单独一个函数而不是给 `isSafeRelPath` 加参数:`"."` 对**文件**路径永远非法
|
|
46
|
+
* (`render: ["."]` 没有意义),放宽它会同时放宽调用它的另外三处声明校验。
|
|
47
|
+
*/
|
|
48
|
+
export function isSafeRelDir(p) {
|
|
49
|
+
return p === '.' || isSafeRelPath(p);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* tar 条目路径归一:去掉前导 `./`、压掉重复 `/`、反斜杠转正斜杠。
|
|
53
|
+
*
|
|
54
|
+
* 🔴 **不归一就会把好包也拒掉**:`tar czf x.tgz -C dir .` 产出的条目全是 `./foo` ——
|
|
55
|
+
* 那是最常见的打包姿势。实测踩到过:三个恶意包全被拒,但 symlink / 炸弹两个是栽在 `./` 上,
|
|
56
|
+
* **它们各自那道守卫根本没被执行到**(假绿)。归一之后才轮得到真正该拒它们的那一条。
|
|
57
|
+
*/
|
|
58
|
+
export function normalizeEntryPath(raw) {
|
|
59
|
+
let p = raw.replace(/\\/g, '/').replace(/\/{2,}/g, '/');
|
|
60
|
+
while (p.startsWith('./'))
|
|
61
|
+
p = p.slice(2);
|
|
62
|
+
return p;
|
|
63
|
+
}
|
|
64
|
+
function parseManifest(raw) {
|
|
65
|
+
let obj;
|
|
66
|
+
try {
|
|
67
|
+
obj = JSON.parse(raw.toString('utf-8'));
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
return { ok: false, detail: `template.json 不是合法 JSON:${e instanceof Error ? e.message : String(e)}` };
|
|
71
|
+
}
|
|
72
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
73
|
+
return { ok: false, detail: 'template.json 必须是对象' };
|
|
74
|
+
}
|
|
75
|
+
const o = obj;
|
|
76
|
+
const engine = o.engine;
|
|
77
|
+
if (typeof engine !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(engine)) {
|
|
78
|
+
return { ok: false, detail: 'engine 必须匹配 [A-Za-z0-9_-]{1,64}' };
|
|
79
|
+
}
|
|
80
|
+
const arr = (key) => {
|
|
81
|
+
const v = o[key];
|
|
82
|
+
if (v === undefined)
|
|
83
|
+
return [];
|
|
84
|
+
if (!Array.isArray(v) || v.some((x) => typeof x !== 'string'))
|
|
85
|
+
return null;
|
|
86
|
+
return v;
|
|
87
|
+
};
|
|
88
|
+
const vars = arr('vars');
|
|
89
|
+
const render = arr('render');
|
|
90
|
+
const replaceable = arr('replaceable');
|
|
91
|
+
const exec = arr('exec');
|
|
92
|
+
if (!vars || !render || !replaceable || !exec) {
|
|
93
|
+
return { ok: false, detail: 'vars / render / replaceable / exec 必须是 string[]' };
|
|
94
|
+
}
|
|
95
|
+
// 🔴 模板作者也是不可信输入:声明里的路径同样要过安全校验。
|
|
96
|
+
for (const [key, list] of [['render', render], ['replaceable', replaceable], ['exec', exec]]) {
|
|
97
|
+
for (const p of list) {
|
|
98
|
+
if (!isSafeRelPath(p))
|
|
99
|
+
return { ok: false, detail: `${key} 含非法路径:${JSON.stringify(p)}` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const v of vars) {
|
|
103
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(v))
|
|
104
|
+
return { ok: false, detail: `变量名必须是 [A-Z][A-Z0-9_]*:${JSON.stringify(v)}` };
|
|
105
|
+
}
|
|
106
|
+
const pub = parsePublish(o.publish);
|
|
107
|
+
if (!pub.ok)
|
|
108
|
+
return pub;
|
|
109
|
+
const smk = parseSmoke(o.smoke);
|
|
110
|
+
if (!smk.ok)
|
|
111
|
+
return smk;
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
value: {
|
|
115
|
+
engine, vars, render, replaceable, exec,
|
|
116
|
+
...(pub.value ? { publish: pub.value } : {}),
|
|
117
|
+
...(smk.value ? { smoke: smk.value } : {}),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 解析可选的 `publish` 声明。缺省 → `undefined`(**不是**一个默认值)。
|
|
123
|
+
*
|
|
124
|
+
* 🔴 不给缺省值:缺省成 `{outDir:'.', serve:'site'}` 会让"模板没声明"和"模板声明了根+站点"
|
|
125
|
+
* 变得无法区分,而这两者对调用方的意义相反 —— 前者是"这个模板还没定发布形态,别自作主张",
|
|
126
|
+
* 后者是"作者确认了根就是产物"。同族的坑刚在 GC 那边踩过:
|
|
127
|
+
* `pins` 用 `Set | undefined` 会被 `?? new Set()` 把"问不到"抹成"没人发布"。
|
|
128
|
+
*/
|
|
129
|
+
/**
|
|
130
|
+
* 解析可选的 `smoke` 声明。与 `parsePublish` 同规:缺省 → `undefined`(**不是默认值**)。
|
|
131
|
+
* "没声明烟测"和"声明了一个默认烟测"含义相反 —— 前者是"这个模板还没定",
|
|
132
|
+
* 后者是"作者确认了跑这条"。给默认值会让两者无法区分。
|
|
133
|
+
*/
|
|
134
|
+
function parseSmoke(v) {
|
|
135
|
+
if (v === undefined || v === null)
|
|
136
|
+
return { ok: true, value: undefined };
|
|
137
|
+
if (typeof v !== 'object' || Array.isArray(v))
|
|
138
|
+
return { ok: false, detail: 'smoke 必须是对象' };
|
|
139
|
+
const o = v;
|
|
140
|
+
const script = o.script;
|
|
141
|
+
if (typeof script !== 'string' || !isSafeRelPath(script)) {
|
|
142
|
+
return { ok: false, detail: `smoke.script 必须是安全的相对路径:${JSON.stringify(script)}` };
|
|
143
|
+
}
|
|
144
|
+
// 🔴 **不校验 script 在模板里存在** —— 与 publish.outDir 同理:脚本可能由 scaffold 后的
|
|
145
|
+
// `npm i` 生成、或由另一个模板层提供。加这条"顺手的"检查会把整类模板拒在门外。
|
|
146
|
+
const checks = o.checks;
|
|
147
|
+
if (checks !== undefined && (typeof checks !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(checks))) {
|
|
148
|
+
return { ok: false, detail: `smoke.checks 需匹配 [A-Za-z0-9_-]{1,64}:${JSON.stringify(checks)}` };
|
|
149
|
+
}
|
|
150
|
+
const quickFlag = o.quickFlag;
|
|
151
|
+
if (quickFlag !== undefined && (typeof quickFlag !== 'string' || !/^--[A-Za-z0-9-]{1,32}$/.test(quickFlag))) {
|
|
152
|
+
return { ok: false, detail: `smoke.quickFlag 需形如 --xxx:${JSON.stringify(quickFlag)}` };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
value: { script, ...(checks !== undefined ? { checks } : {}), ...(quickFlag !== undefined ? { quickFlag } : {}) },
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function parsePublish(v) {
|
|
160
|
+
if (v === undefined || v === null)
|
|
161
|
+
return { ok: true, value: undefined };
|
|
162
|
+
if (typeof v !== 'object' || Array.isArray(v))
|
|
163
|
+
return { ok: false, detail: 'publish 必须是对象' };
|
|
164
|
+
const p = v;
|
|
165
|
+
const outDir = p.outDir;
|
|
166
|
+
if (typeof outDir !== 'string' || !isSafeRelDir(outDir)) {
|
|
167
|
+
return { ok: false, detail: `publish.outDir 必须是安全的相对目录(可为 "."):${JSON.stringify(outDir)}` };
|
|
168
|
+
}
|
|
169
|
+
// 🔴 **不校验 outDir 在模板里存在** —— 需要构建的模板里 `dist` 本来就不存在,
|
|
170
|
+
// 它是构建时才产出的。加这条"顺手的"检查会把整类模板拒在门外。
|
|
171
|
+
const serve = p.serve;
|
|
172
|
+
if (serve !== 'site' && serve !== 'file') {
|
|
173
|
+
return { ok: false, detail: `publish.serve 只能是 "site" 或 "file":${JSON.stringify(serve)}` };
|
|
174
|
+
}
|
|
175
|
+
const artifact = p.artifact;
|
|
176
|
+
if (serve === 'file') {
|
|
177
|
+
// serve=file 却没说是哪个文件 ⇒ 反代无从服务。缺它是**声明不完整**,不是可以推断的默认。
|
|
178
|
+
if (typeof artifact !== 'string' || !isSafeRelPath(artifact)) {
|
|
179
|
+
return { ok: false, detail: `publish.serve="file" 时 artifact 必填且须是安全相对路径:${JSON.stringify(artifact)}` };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (artifact !== undefined) {
|
|
183
|
+
// 多写的字段要响亮地拒,否则作者会以为它生效了(和 vars 里未声明变量同一条理由)。
|
|
184
|
+
return { ok: false, detail: 'publish.artifact 只在 serve="file" 时有意义' };
|
|
185
|
+
}
|
|
186
|
+
return { ok: true, value: { outDir, serve, ...(artifact !== undefined ? { artifact } : {}) } };
|
|
187
|
+
}
|
|
188
|
+
/** 收集 tar 条目到内存(边收边判上限);拒绝一切非普通文件的条目。 */
|
|
189
|
+
async function readEntries(archive) {
|
|
190
|
+
const files = new Map();
|
|
191
|
+
let total = 0;
|
|
192
|
+
let fatal = null;
|
|
193
|
+
await new Promise((done) => {
|
|
194
|
+
const parser = tarList({
|
|
195
|
+
onentry(entry) {
|
|
196
|
+
if (fatal) {
|
|
197
|
+
entry.resume();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const type = String(entry.type);
|
|
201
|
+
// 🔴 只收普通文件。软链/硬链/设备/FIFO 一概拒 —— 软链条目是解包逃逸的经典入口
|
|
202
|
+
// (链指向 /etc,后续写入就落到链外)。目录条目不需要:落盘时按文件路径 mkdir -p。
|
|
203
|
+
if (type === 'Directory') {
|
|
204
|
+
entry.resume();
|
|
205
|
+
return;
|
|
206
|
+
} // 目录不进指纹,落盘时按需 mkdir -p
|
|
207
|
+
if (type !== 'File' && type !== 'OldFile' && type !== 'ContiguousFile') {
|
|
208
|
+
fatal = { code: 'UNSAFE_ENTRY', detail: `拒绝非普通文件条目:${entry.path}(type=${type})` };
|
|
209
|
+
entry.resume();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const p = normalizeEntryPath(String(entry.path));
|
|
213
|
+
if (!isSafeRelPath(p)) {
|
|
214
|
+
fatal = { code: 'UNSAFE_ENTRY', detail: `拒绝不安全路径:${JSON.stringify(p)}(不许 ..、绝对路径、空段)` };
|
|
215
|
+
entry.resume();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (files.size + 1 > TEMPLATE_LIMITS.maxFiles) {
|
|
219
|
+
fatal = { code: 'LIMIT_EXCEEDED', detail: `文件数超限:> ${TEMPLATE_LIMITS.maxFiles}(上限 ${TEMPLATE_LIMITS.maxFiles})` };
|
|
220
|
+
entry.resume();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const chunks = [];
|
|
224
|
+
let size = 0;
|
|
225
|
+
entry.on('data', (c) => {
|
|
226
|
+
if (fatal)
|
|
227
|
+
return;
|
|
228
|
+
size += c.length;
|
|
229
|
+
total += c.length;
|
|
230
|
+
if (size > TEMPLATE_LIMITS.maxFileBytes) {
|
|
231
|
+
fatal = { code: 'LIMIT_EXCEEDED', detail: `单文件超限:${p} 已达 ${size} 字节(上限 ${TEMPLATE_LIMITS.maxFileBytes})` };
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (total > TEMPLATE_LIMITS.maxTotalBytes) {
|
|
235
|
+
fatal = { code: 'LIMIT_EXCEEDED', detail: `解包后总字节超限:已达 ${total}(上限 ${TEMPLATE_LIMITS.maxTotalBytes})` };
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
chunks.push(c);
|
|
239
|
+
});
|
|
240
|
+
entry.on('end', () => {
|
|
241
|
+
if (fatal)
|
|
242
|
+
return;
|
|
243
|
+
files.set(p, { content: Buffer.concat(chunks), tarExec: (Number(entry.mode ?? 0) & 0o111) !== 0 });
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
parser.on('error', (e) => {
|
|
248
|
+
if (!fatal)
|
|
249
|
+
fatal = { code: 'BAD_ARCHIVE', detail: `解包失败:${e.message}` };
|
|
250
|
+
done();
|
|
251
|
+
});
|
|
252
|
+
parser.on('end', () => done());
|
|
253
|
+
parser.end(archive);
|
|
254
|
+
});
|
|
255
|
+
if (fatal)
|
|
256
|
+
return { ok: false, error: fatal };
|
|
257
|
+
return { ok: true, files, total };
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 解包 + 校验 + 算指纹。**不落盘** —— 调用方拿到 UnpackedTemplate 后自行决定存不存。
|
|
261
|
+
*/
|
|
262
|
+
export async function unpackTemplate(archive) {
|
|
263
|
+
if (archive.length > TEMPLATE_LIMITS.maxArchiveBytes) {
|
|
264
|
+
return err('ARCHIVE_TOO_LARGE', `压缩包 ${archive.length} 字节,超过上限 ${TEMPLATE_LIMITS.maxArchiveBytes}`);
|
|
265
|
+
}
|
|
266
|
+
const read = await readEntries(archive);
|
|
267
|
+
if (!read.ok)
|
|
268
|
+
return { ok: false, error: read.error };
|
|
269
|
+
const { files, total } = read;
|
|
270
|
+
// 解压比:防炸弹。放在收完之后判(收的过程已被总字节上限兜住,不会 OOM)。
|
|
271
|
+
const ratio = archive.length > 0 ? total / archive.length : 0;
|
|
272
|
+
if (ratio > TEMPLATE_LIMITS.maxCompressionRatio) {
|
|
273
|
+
return err('LIMIT_EXCEEDED', `解压比 ${ratio.toFixed(1)}:1 超限(压缩后 ${archive.length} → 解包后 ${total},上限 ${TEMPLATE_LIMITS.maxCompressionRatio}:1)`);
|
|
274
|
+
}
|
|
275
|
+
const manifestRaw = files.get('template.json');
|
|
276
|
+
if (!manifestRaw)
|
|
277
|
+
return err('MANIFEST_INVALID', '包根缺 template.json');
|
|
278
|
+
const parsed = parseManifest(manifestRaw.content);
|
|
279
|
+
if (!parsed.ok)
|
|
280
|
+
return err('MANIFEST_INVALID', parsed.detail);
|
|
281
|
+
const manifest = parsed.value;
|
|
282
|
+
const missing = [...manifest.render, ...manifest.exec].filter((p) => !files.has(p));
|
|
283
|
+
if (missing.length) {
|
|
284
|
+
return err('MANIFEST_INVALID', `render / exec 指向包里不存在的文件:${missing.join(', ')}`);
|
|
285
|
+
}
|
|
286
|
+
// 🔴 执行位的真源是 exec 声明。tar 里有执行位却没声明 → **当场 422**,
|
|
287
|
+
// 否则"忘了声明"会变成静默降级(文件拷过去没执行位 ⇒ hook 打不上 ACK 标签 ⇒ 每次构建必死,
|
|
288
|
+
// 而症状看起来像超时不像权限)。
|
|
289
|
+
const execSet = new Set(manifest.exec);
|
|
290
|
+
const undeclared = [...files.entries()].filter(([p, f]) => f.tarExec && !execSet.has(p)).map(([p]) => p);
|
|
291
|
+
if (undeclared.length) {
|
|
292
|
+
return err('EXEC_UNDECLARED', `这些文件在 tar 里带执行位但不在 template.json 的 exec 声明里:${undeclared.join(', ')}` +
|
|
293
|
+
'(执行位以声明为准;把它们加进 exec,或去掉执行位后重新打包)');
|
|
294
|
+
}
|
|
295
|
+
const entries = [...files.entries()].map(([path, f]) => ({
|
|
296
|
+
path,
|
|
297
|
+
content: f.content,
|
|
298
|
+
executable: execSet.has(path),
|
|
299
|
+
}));
|
|
300
|
+
return {
|
|
301
|
+
ok: true,
|
|
302
|
+
value: {
|
|
303
|
+
manifest,
|
|
304
|
+
files: entries,
|
|
305
|
+
digest: templateTreeDigest(entries),
|
|
306
|
+
id: templateId(manifest.engine, entries),
|
|
307
|
+
totalBytes: total,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// ─── 落盘 ────────────────────────────────────────────────────────────
|
|
312
|
+
/** `~/.coral/templates` —— 每次调用现读 HOME(与 runtimePaths 同惯例,方便测试)。 */
|
|
313
|
+
export function templatesRoot() {
|
|
314
|
+
return resolve(process.env.HOME ?? '/home/coral', '.coral', 'templates');
|
|
315
|
+
}
|
|
316
|
+
export function templateDir(id) {
|
|
317
|
+
return join(templatesRoot(), id);
|
|
318
|
+
}
|
|
319
|
+
export function isTemplateRegistered(id) {
|
|
320
|
+
return existsSync(join(templateDir(id), 'template.json'));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* 落盘。**先写临时目录再整体 rename** —— 半棵树被别人读到,比没有更糟
|
|
324
|
+
* (调用方会以为注册成功)。同 id 已存在 = 幂等,直接返回。
|
|
325
|
+
*/
|
|
326
|
+
export function storeTemplate(t) {
|
|
327
|
+
const dir = templateDir(t.id);
|
|
328
|
+
if (isTemplateRegistered(t.id))
|
|
329
|
+
return { stored: false, dir };
|
|
330
|
+
const tmp = `${dir}.tmp-${process.pid}-${createHash('sha256').update(t.id).digest('hex').slice(0, 8)}`;
|
|
331
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
332
|
+
mkdirSync(tmp, { recursive: true });
|
|
333
|
+
for (const f of t.files) {
|
|
334
|
+
const abs = join(tmp, f.path);
|
|
335
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
336
|
+
writeFileSync(abs, f.content, { mode: f.executable ? 0o755 : 0o644 });
|
|
337
|
+
}
|
|
338
|
+
mkdirSync(templatesRoot(), { recursive: true });
|
|
339
|
+
try {
|
|
340
|
+
renameSync(tmp, dir);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
// 并发下别人先 rename 到位了 → 幂等成功,清掉自己的临时树。
|
|
344
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
345
|
+
return { stored: false, dir };
|
|
346
|
+
}
|
|
347
|
+
return { stored: true, dir };
|
|
348
|
+
}
|
|
349
|
+
/** 读回已注册模板的文件树(scaffold 用)。未注册 → null。 */
|
|
350
|
+
export function loadTemplate(id) {
|
|
351
|
+
const dir = templateDir(id);
|
|
352
|
+
const manifestPath = join(dir, 'template.json');
|
|
353
|
+
if (!existsSync(manifestPath))
|
|
354
|
+
return null;
|
|
355
|
+
const parsed = parseManifest(readFileSync(manifestPath));
|
|
356
|
+
if (!parsed.ok)
|
|
357
|
+
return null;
|
|
358
|
+
const execSet = new Set(parsed.value.exec);
|
|
359
|
+
const files = [];
|
|
360
|
+
const walk = (abs, rel) => {
|
|
361
|
+
for (const e of readdirSync(abs, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
362
|
+
const childAbs = join(abs, e.name);
|
|
363
|
+
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
364
|
+
if (e.isDirectory()) {
|
|
365
|
+
walk(childAbs, childRel);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (!e.isFile())
|
|
369
|
+
continue; // 落盘的树里不该有软链;有也不读
|
|
370
|
+
files.push({ path: childRel, content: readFileSync(childAbs), executable: execSet.has(childRel) });
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
walk(dir, '');
|
|
374
|
+
return { manifest: parsed.value, files };
|
|
375
|
+
}
|
|
376
|
+
/** 已注册模板的摘要(GET /api/templates/:id)。 */
|
|
377
|
+
export function templateSummary(id) {
|
|
378
|
+
const t = loadTemplate(id);
|
|
379
|
+
if (!t)
|
|
380
|
+
return null;
|
|
381
|
+
const dir = templateDir(id);
|
|
382
|
+
let bytes = 0;
|
|
383
|
+
for (const f of t.files)
|
|
384
|
+
bytes += statSync(join(dir, f.path)).size;
|
|
385
|
+
// publish 原样回传 —— 平台据此告诉 sidecar 快照哪个相对路径。
|
|
386
|
+
// **缺省时这个键不出现**(不是 null),让调用方能区分"没声明"与"声明了空"。
|
|
387
|
+
return {
|
|
388
|
+
id, engine: t.manifest.engine, files: t.files.length, bytes,
|
|
389
|
+
...(t.manifest.publish ? { publish: t.manifest.publish } : {}),
|
|
390
|
+
...(t.manifest.smoke ? { smoke: t.manifest.smoke } : {}),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
//# sourceMappingURL=templatePackage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templatePackage.js","sourceRoot":"","sources":["../../../src/core/templates/templatePackage.ts"],"names":[],"mappings":"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"}
|