@coralai/sps-cli 0.65.84 → 0.65.86

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.
Files changed (35) hide show
  1. package/dist/console/index.d.ts.map +1 -1
  2. package/dist/console/index.js +10 -1
  3. package/dist/console/index.js.map +1 -1
  4. package/dist/console/routes/projects.d.ts.map +1 -1
  5. package/dist/console/routes/projects.js +47 -0
  6. package/dist/console/routes/projects.js.map +1 -1
  7. package/dist/console/routes/templates.d.ts +16 -0
  8. package/dist/console/routes/templates.d.ts.map +1 -0
  9. package/dist/console/routes/templates.js +78 -0
  10. package/dist/console/routes/templates.js.map +1 -0
  11. package/dist/core/skills/configure.d.ts +2 -0
  12. package/dist/core/skills/configure.d.ts.map +1 -1
  13. package/dist/core/skills/configure.js +8 -5
  14. package/dist/core/skills/configure.js.map +1 -1
  15. package/dist/core/skills/distribution.d.ts +4 -1
  16. package/dist/core/skills/distribution.d.ts.map +1 -1
  17. package/dist/core/skills/distribution.js +48 -3
  18. package/dist/core/skills/distribution.js.map +1 -1
  19. package/dist/core/skills/policy.d.ts +3 -1
  20. package/dist/core/skills/policy.d.ts.map +1 -1
  21. package/dist/core/skills/policy.js +4 -2
  22. package/dist/core/skills/policy.js.map +1 -1
  23. package/dist/core/templates/scaffold.d.ts +55 -0
  24. package/dist/core/templates/scaffold.d.ts.map +1 -0
  25. package/dist/core/templates/scaffold.js +162 -0
  26. package/dist/core/templates/scaffold.js.map +1 -0
  27. package/dist/core/templates/templateDigest.d.ts +16 -0
  28. package/dist/core/templates/templateDigest.d.ts.map +1 -0
  29. package/dist/core/templates/templateDigest.js +57 -0
  30. package/dist/core/templates/templateDigest.js.map +1 -0
  31. package/dist/core/templates/templatePackage.d.ts +100 -0
  32. package/dist/core/templates/templatePackage.d.ts.map +1 -0
  33. package/dist/core/templates/templatePackage.js +298 -0
  34. package/dist/core/templates/templatePackage.js.map +1 -0
  35. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/core/skills/policy.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D,0BAA0B;AAC1B,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,CAmBnE;AAED,4BAA4B;AAC5B,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,CAElD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAQhF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAK5F"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/core/skills/policy.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D,0BAA0B;AAC1B,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,CAmBnE;AAED,4BAA4B;AAC5B,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,CAElD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAQhF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,MAAM;AACb,sEAAsE;AACtE,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,EAAE,GAAG,IAAI,CAKjB"}
@@ -73,8 +73,10 @@ export function writeProjectSkillPolicy(projectDir, gate) {
73
73
  * **空策略 → `null`**(不门控信号:调用方据此保持"全量、不 prune"的向后兼容行为)。
74
74
  * 组合 `loadProjectSkillPolicy` + `SkillsManager.discover/eligible/names`。
75
75
  */
76
- export function resolveEligibleSkillNames(projectDir, home) {
77
- const policy = loadProjectSkillPolicy(projectDir);
76
+ export function resolveEligibleSkillNames(projectDir, home,
77
+ /** 用它替代磁盘上那份 —— 让调用方能在**落盘之前**先算差集(见 configureProjectSkills 的原子性)。 */
78
+ override) {
79
+ const policy = override ?? loadProjectSkillPolicy(projectDir);
78
80
  if (isEmptyPolicy(policy))
79
81
  return null;
80
82
  const mgr = new SkillsManager({ cwd: projectDir, home });
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/skills/policy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/skills/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ export interface ScaffoldRequest {
2
+ templateId: string;
3
+ vars: Record<string, string>;
4
+ /** 只认 dependencies / devDependencies 两个键 —— 别的键会把这里变成通用写口。 */
5
+ deps?: {
6
+ dependencies?: Record<string, string>;
7
+ devDependencies?: Record<string, string>;
8
+ };
9
+ /** 要先删掉重建的路径。**必须是 manifest.replaceable 的子集**。 */
10
+ replace?: string[];
11
+ }
12
+ export interface DepsReport {
13
+ added: string[];
14
+ unchanged: string[];
15
+ conflicted: Array<{
16
+ name: string;
17
+ existing: string;
18
+ incoming: string;
19
+ }>;
20
+ }
21
+ export interface ScaffoldReport {
22
+ templateId: string;
23
+ wrote: string[];
24
+ replaced: string[];
25
+ deps: DepsReport;
26
+ }
27
+ export type ScaffoldError = {
28
+ code: 'TEMPLATE_NOT_FOUND';
29
+ detail: string;
30
+ } | {
31
+ code: 'VARS_MISSING';
32
+ detail: string;
33
+ } | {
34
+ code: 'VARS_UNDECLARED';
35
+ detail: string;
36
+ } | {
37
+ code: 'REPLACE_NOT_ALLOWED';
38
+ detail: string;
39
+ } | {
40
+ code: 'DEPS_CONFLICT';
41
+ detail: string;
42
+ report: DepsReport;
43
+ } | {
44
+ code: 'DEPS_INVALID';
45
+ detail: string;
46
+ };
47
+ export type ScaffoldResult = {
48
+ ok: true;
49
+ value: ScaffoldReport;
50
+ } | {
51
+ ok: false;
52
+ error: ScaffoldError;
53
+ };
54
+ export declare function scaffoldProject(projectDir: string, req: ScaffoldRequest): ScaffoldResult;
55
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/core/templates/scaffold.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,8DAA8D;IAC9D,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IAC3F,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AA8DvG,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,cAAc,CAuFxF"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * @module core/templates/scaffold
3
+ * @description 把已注册模板落进项目目录:变量渲染 / 定域 replace / package.json deps 定域 merge。
4
+ * @layer core
5
+ * @boundedContext templates
6
+ *
7
+ * 边界:**sps 只做机械替换,不认识任何产品**(gameforge / 引擎语义留在模板包里)。
8
+ * 这条是"scaffold 端点"能存在的前提 —— 一旦 sps 开始理解模板内容,它就变成了产品代码。
9
+ */
10
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { dirname, join } from 'node:path';
12
+ import { isSafeRelPath, loadTemplate } from './templatePackage.js';
13
+ const VAR_RE = /\{\{([A-Z][A-Z0-9_]*)\}\}/g;
14
+ /** 渲染 `{{VAR}}`。未声明的变量在调用前已被拒,这里只做替换。 */
15
+ function render(text, vars) {
16
+ return text.replace(VAR_RE, (_m, name) => vars[name] ?? '');
17
+ }
18
+ /** 模板正文里出现的全部 `{{VAR}}`(只扫 render 名单内的文件)。 */
19
+ function usedVars(files, renderList) {
20
+ const set = new Set();
21
+ const inRender = new Set(renderList);
22
+ for (const f of files) {
23
+ if (!inRender.has(f.path))
24
+ continue;
25
+ for (const m of f.content.toString('utf-8').matchAll(VAR_RE))
26
+ set.add(m[1]);
27
+ }
28
+ return set;
29
+ }
30
+ /** deps 定域 merge。同名同版本 = no-op(幂等);同名异版本 = 冲突,由调用方决定是否 409。 */
31
+ function mergeDeps(pkg, incoming) {
32
+ const report = { added: [], unchanged: [], conflicted: [] };
33
+ for (const key of ['dependencies', 'devDependencies']) {
34
+ const want = incoming[key];
35
+ if (!want)
36
+ continue;
37
+ const cur = pkg[key] ?? {};
38
+ for (const [name, version] of Object.entries(want)) {
39
+ const existing = cur[name];
40
+ if (existing === undefined)
41
+ report.added.push(`${key}/${name}@${version}`);
42
+ else if (existing === version)
43
+ report.unchanged.push(`${key}/${name}@${version}`);
44
+ else
45
+ report.conflicted.push({ name: `${key}/${name}`, existing, incoming: version });
46
+ }
47
+ }
48
+ return report;
49
+ }
50
+ function applyDeps(pkg, incoming) {
51
+ for (const key of ['dependencies', 'devDependencies']) {
52
+ const want = incoming[key];
53
+ if (!want)
54
+ continue;
55
+ const cur = { ...(pkg[key] ?? {}) };
56
+ for (const [name, version] of Object.entries(want))
57
+ cur[name] = version;
58
+ pkg[key] = Object.fromEntries(Object.entries(cur).sort(([a], [b]) => a.localeCompare(b)));
59
+ }
60
+ }
61
+ function validateReplace(req, manifest) {
62
+ const asked = req.replace ?? [];
63
+ if (!asked.length)
64
+ return null;
65
+ const allowed = new Set(manifest.replaceable);
66
+ const bad = asked.filter((p) => !allowed.has(p));
67
+ if (bad.length) {
68
+ return {
69
+ code: 'REPLACE_NOT_ALLOWED',
70
+ // 🔴 列出允许值 —— 这样"远程 rm -rf"在**契约层**就不可能,而不是靠 sps 自觉。
71
+ detail: `replace 只能取 template.json 的 replaceable 子集。越界:${bad.join(', ')};允许:${manifest.replaceable.join(', ') || '(空)'}`,
72
+ };
73
+ }
74
+ return null;
75
+ }
76
+ export function scaffoldProject(projectDir, req) {
77
+ const tpl = loadTemplate(req.templateId);
78
+ if (!tpl)
79
+ return { ok: false, error: { code: 'TEMPLATE_NOT_FOUND', detail: `模板未注册:${req.templateId}` } };
80
+ const { manifest, files } = tpl;
81
+ const replaceErr = validateReplace(req, manifest);
82
+ if (replaceErr)
83
+ return { ok: false, error: replaceErr };
84
+ // 变量:双向都要响亮 —— 缺了会产出"看起来建好了"的坏项目;
85
+ // 多了(模板里写了未声明的 {{VAR}})多半是作者把变量名打错,原样写进文件后看起来像内容不像 bug。
86
+ const declared = new Set(manifest.vars);
87
+ const given = new Set(Object.keys(req.vars ?? {}));
88
+ const missing = manifest.vars.filter((v) => !given.has(v));
89
+ if (missing.length) {
90
+ return { ok: false, error: { code: 'VARS_MISSING', detail: `缺变量:${missing.join(', ')}(模板声明:${manifest.vars.join(', ')})` } };
91
+ }
92
+ const used = usedVars(files, manifest.render);
93
+ const undeclared = [...used].filter((v) => !declared.has(v));
94
+ if (undeclared.length) {
95
+ return {
96
+ ok: false,
97
+ error: {
98
+ code: 'VARS_UNDECLARED',
99
+ detail: `模板正文里出现未声明的变量:${undeclared.map((v) => `{{${v}}}`).join(', ')}(多半是变量名打错;要用就加进 template.json 的 vars)`,
100
+ },
101
+ };
102
+ }
103
+ // deps 先算后写:冲突要在**动盘之前**就返回,别留半截。
104
+ const pkgPath = join(projectDir, 'package.json');
105
+ let pkg = null;
106
+ let depsReport = { added: [], unchanged: [], conflicted: [] };
107
+ if (req.deps) {
108
+ for (const key of Object.keys(req.deps)) {
109
+ if (key !== 'dependencies' && key !== 'devDependencies') {
110
+ return { ok: false, error: { code: 'DEPS_INVALID', detail: `deps 只接受 dependencies / devDependencies,收到:${key}` } };
111
+ }
112
+ }
113
+ if (!existsSync(pkgPath)) {
114
+ return { ok: false, error: { code: 'DEPS_INVALID', detail: `项目里没有 package.json,无法合并依赖:${pkgPath}` } };
115
+ }
116
+ try {
117
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
118
+ }
119
+ catch (e) {
120
+ return { ok: false, error: { code: 'DEPS_INVALID', detail: `package.json 不是合法 JSON:${e instanceof Error ? e.message : String(e)}` } };
121
+ }
122
+ depsReport = mergeDeps(pkg, req.deps);
123
+ if (depsReport.conflicted.length) {
124
+ return {
125
+ ok: false,
126
+ error: {
127
+ code: 'DEPS_CONFLICT',
128
+ detail: depsReport.conflicted.map((c) => `${c.name}: 已有 ${c.existing} ≠ 传入 ${c.incoming}`).join('; '),
129
+ report: depsReport,
130
+ },
131
+ };
132
+ }
133
+ }
134
+ // ── 到这里全部校验通过,才开始动盘 ──
135
+ const replaced = [];
136
+ for (const rel of req.replace ?? []) {
137
+ const abs = join(projectDir, rel);
138
+ if (existsSync(abs)) {
139
+ rmSync(abs, { recursive: true, force: true });
140
+ replaced.push(rel);
141
+ }
142
+ }
143
+ const renderSet = new Set(manifest.render);
144
+ const wrote = [];
145
+ for (const f of files) {
146
+ if (f.path === 'template.json')
147
+ continue; // 清单不进项目
148
+ if (!isSafeRelPath(f.path))
149
+ continue; // 落盘时的第二道(注册时已校过一次)
150
+ const abs = join(projectDir, f.path);
151
+ mkdirSync(dirname(abs), { recursive: true });
152
+ const body = renderSet.has(f.path) ? Buffer.from(render(f.content.toString('utf-8'), req.vars)) : f.content;
153
+ writeFileSync(abs, body, { mode: f.executable ? 0o755 : 0o644 });
154
+ wrote.push(f.path);
155
+ }
156
+ if (pkg && req.deps) {
157
+ applyDeps(pkg, req.deps);
158
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
159
+ }
160
+ return { ok: true, value: { templateId: req.templateId, wrote: wrote.sort(), replaced, deps: depsReport } };
161
+ }
162
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/core/templates/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface TemplateFileEntry {
2
+ /** 相对模板根的路径,'/' 分隔,不以 '/' 开头。 */
3
+ path: string;
4
+ content: Buffer;
5
+ /** 是否可执行 —— **来自 `template.json` 的 `exec` 声明**,不是 tar 的 mode 位。 */
6
+ executable: boolean;
7
+ }
8
+ /** 规范化内容指纹(完整 64 位十六进制)。 */
9
+ export declare function templateTreeDigest(files: TemplateFileEntry[]): string;
10
+ /** `<engine>@<sha256 前 12>` —— 内容寻址的 templateId。 */
11
+ export declare function templateId(engine: string, files: TemplateFileEntry[]): string;
12
+ export declare function parseTemplateId(id: string): {
13
+ engine: string;
14
+ digest12: string;
15
+ } | null;
16
+ //# sourceMappingURL=templateDigest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templateDigest.d.ts","sourceRoot":"","sources":["../../../src/core/templates/templateDigest.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,EAAE,OAAO,CAAC;CACrB;AASD,4BAA4B;AAC5B,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAKrE;AAED,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAE7E;AAKD,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAIvF"}
@@ -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,100 @@
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
+ export interface TemplateManifest {
16
+ engine: string;
17
+ vars: string[];
18
+ /** 只有这些文件做 `{{VAR}}` 替换;其余原样拷(二进制安全)。 */
19
+ render: string[];
20
+ /** 允许 scaffold 删掉重建的路径。`replace` 必须是它的子集。 */
21
+ replaceable: string[];
22
+ /** 需要置 755 的文件 —— **执行位的唯一真源**。 */
23
+ exec: string[];
24
+ }
25
+ export interface UnpackedTemplate {
26
+ manifest: TemplateManifest;
27
+ files: TemplateFileEntry[];
28
+ id: string;
29
+ digest: string;
30
+ totalBytes: number;
31
+ }
32
+ export type UnpackError = {
33
+ code: 'ARCHIVE_TOO_LARGE';
34
+ detail: string;
35
+ } | {
36
+ code: 'LIMIT_EXCEEDED';
37
+ detail: string;
38
+ } | {
39
+ code: 'UNSAFE_ENTRY';
40
+ detail: string;
41
+ } | {
42
+ code: 'BAD_ARCHIVE';
43
+ detail: string;
44
+ } | {
45
+ code: 'MANIFEST_INVALID';
46
+ detail: string;
47
+ } | {
48
+ code: 'EXEC_UNDECLARED';
49
+ detail: string;
50
+ };
51
+ export type UnpackResult = {
52
+ ok: true;
53
+ value: UnpackedTemplate;
54
+ } | {
55
+ ok: false;
56
+ error: UnpackError;
57
+ };
58
+ /**
59
+ * 路径安全:必须是相对路径、不含 `..` 段、不以 `/` 开头、无空段、无盘符。
60
+ * 🔴 用**段级**判断,不用字符串 startsWith —— `..foo` 合法而 `a/../../b` 不合法,
61
+ * 只看前缀两者会判反。
62
+ */
63
+ export declare function isSafeRelPath(p: string): boolean;
64
+ /**
65
+ * tar 条目路径归一:去掉前导 `./`、压掉重复 `/`、反斜杠转正斜杠。
66
+ *
67
+ * 🔴 **不归一就会把好包也拒掉**:`tar czf x.tgz -C dir .` 产出的条目全是 `./foo` ——
68
+ * 那是最常见的打包姿势。实测踩到过:三个恶意包全被拒,但 symlink / 炸弹两个是栽在 `./` 上,
69
+ * **它们各自那道守卫根本没被执行到**(假绿)。归一之后才轮得到真正该拒它们的那一条。
70
+ */
71
+ export declare function normalizeEntryPath(raw: string): string;
72
+ /**
73
+ * 解包 + 校验 + 算指纹。**不落盘** —— 调用方拿到 UnpackedTemplate 后自行决定存不存。
74
+ */
75
+ export declare function unpackTemplate(archive: Buffer): Promise<UnpackResult>;
76
+ /** `~/.coral/templates` —— 每次调用现读 HOME(与 runtimePaths 同惯例,方便测试)。 */
77
+ export declare function templatesRoot(): string;
78
+ export declare function templateDir(id: string): string;
79
+ export declare function isTemplateRegistered(id: string): boolean;
80
+ /**
81
+ * 落盘。**先写临时目录再整体 rename** —— 半棵树被别人读到,比没有更糟
82
+ * (调用方会以为注册成功)。同 id 已存在 = 幂等,直接返回。
83
+ */
84
+ export declare function storeTemplate(t: UnpackedTemplate): {
85
+ stored: boolean;
86
+ dir: string;
87
+ };
88
+ /** 读回已注册模板的文件树(scaffold 用)。未注册 → null。 */
89
+ export declare function loadTemplate(id: string): {
90
+ manifest: TemplateManifest;
91
+ files: TemplateFileEntry[];
92
+ } | null;
93
+ /** 已注册模板的摘要(GET /api/templates/:id)。 */
94
+ export declare function templateSummary(id: string): {
95
+ id: string;
96
+ engine: string;
97
+ files: number;
98
+ bytes: number;
99
+ } | null;
100
+ //# sourceMappingURL=templatePackage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templatePackage.d.ts","sourceRoot":"","sources":["../../../src/core/templates/templatePackage.ts"],"names":[],"mappings":"AAaA,OAAO,EAAkC,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7F,yDAAyD;AACzD,eAAO,MAAM,eAAe;IAC1B,gCAAgC;;IAEhC,cAAc;;IAEd,aAAa;;IAEb,cAAc;;IAEd,gCAAgC;;CAExB,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,yCAAyC;IACzC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mCAAmC;IACnC,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AAIrG;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAKhD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAItD;AA4GD;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAyD3E;AAID,oEAAoE;AACpE,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,gBAAgB,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAqBnF;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,iBAAiB,EAAE,CAAA;CAAE,GAAG,IAAI,CAmB1G;AAED,wCAAwC;AACxC,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO/G"}