@coralai/sps-cli 0.65.87 → 0.65.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/console/index.d.ts.map +1 -1
- package/dist/console/index.js +2 -0
- package/dist/console/index.js.map +1 -1
- package/dist/console/lib/artPaths.d.ts +5 -5
- package/dist/console/lib/artPaths.d.ts.map +1 -1
- package/dist/console/lib/artPaths.js +8 -26
- package/dist/console/lib/artPaths.js.map +1 -1
- package/dist/console/routes/storage.d.ts +21 -0
- package/dist/console/routes/storage.d.ts.map +1 -0
- package/dist/console/routes/storage.js +115 -0
- package/dist/console/routes/storage.js.map +1 -0
- package/dist/core/pathFence.d.ts +20 -0
- package/dist/core/pathFence.d.ts.map +1 -0
- package/dist/core/pathFence.js +60 -0
- package/dist/core/pathFence.js.map +1 -0
- package/dist/daemon/sessionDaemon.d.ts +1 -0
- package/dist/daemon/sessionDaemon.d.ts.map +1 -1
- package/dist/daemon/sessionDaemon.js +6 -0
- package/dist/daemon/sessionDaemon.js.map +1 -1
- package/dist/daemon/storageSyncLoop.d.ts +9 -0
- package/dist/daemon/storageSyncLoop.d.ts.map +1 -0
- package/dist/daemon/storageSyncLoop.js +70 -0
- package/dist/daemon/storageSyncLoop.js.map +1 -0
- package/dist/providers/storage/FakeStorage.d.ts +14 -0
- package/dist/providers/storage/FakeStorage.d.ts.map +1 -0
- package/dist/providers/storage/FakeStorage.js +33 -0
- package/dist/providers/storage/FakeStorage.js.map +1 -0
- package/dist/providers/storage/MinioBackend.d.ts +31 -0
- package/dist/providers/storage/MinioBackend.d.ts.map +1 -0
- package/dist/providers/storage/MinioBackend.js +60 -0
- package/dist/providers/storage/MinioBackend.js.map +1 -0
- package/dist/providers/storage/StorageBackend.d.ts +43 -0
- package/dist/providers/storage/StorageBackend.d.ts.map +1 -0
- package/dist/providers/storage/StorageBackend.js +29 -0
- package/dist/providers/storage/StorageBackend.js.map +1 -0
- package/dist/providers/storage/keys.d.ts +33 -0
- package/dist/providers/storage/keys.d.ts.map +1 -0
- package/dist/providers/storage/keys.js +52 -0
- package/dist/providers/storage/keys.js.map +1 -0
- package/dist/providers/storage/scanWorkspace.d.ts +56 -0
- package/dist/providers/storage/scanWorkspace.d.ts.map +1 -0
- package/dist/providers/storage/scanWorkspace.js +205 -0
- package/dist/providers/storage/scanWorkspace.js.map +1 -0
- package/dist/providers/storage/storageConfig.d.ts +46 -0
- package/dist/providers/storage/storageConfig.d.ts.map +1 -0
- package/dist/providers/storage/storageConfig.js +94 -0
- package/dist/providers/storage/storageConfig.js.map +1 -0
- package/dist/providers/storage/syncProject.d.ts +67 -0
- package/dist/providers/storage/syncProject.d.ts.map +1 -0
- package/dist/providers/storage/syncProject.js +136 -0
- package/dist/providers/storage/syncProject.js.map +1 -0
- package/dist/providers/storage/syncRunner.d.ts +59 -0
- package/dist/providers/storage/syncRunner.d.ts.map +1 -0
- package/dist/providers/storage/syncRunner.js +119 -0
- package/dist/providers/storage/syncRunner.js.map +1 -0
- package/dist/providers/storage/syncScope.d.ts +25 -0
- package/dist/providers/storage/syncScope.d.ts.map +1 -0
- package/dist/providers/storage/syncScope.js +68 -0
- package/dist/providers/storage/syncScope.js.map +1 -0
- package/dist/providers/storage/syncable.d.ts +47 -0
- package/dist/providers/storage/syncable.d.ts.map +1 -0
- package/dist/providers/storage/syncable.js +84 -0
- package/dist/providers/storage/syncable.js.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncRunner.d.ts","sourceRoot":"","sources":["../../../src/providers/storage/syncRunner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAiB,KAAK,SAAS,EAAe,MAAM,kBAAkB,CAAC;AAG9E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,gDAAgD;IAChD,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,0BAA0B;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAQD,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAyEpF;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAgB9F"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module providers/storage/syncRunner
|
|
3
|
+
* @description 对账循环:发现项目 → 新鲜度过滤 → 逐项目同步 → 汇总。
|
|
4
|
+
*
|
|
5
|
+
* @layer providers
|
|
6
|
+
*
|
|
7
|
+
* 🔴 **它是对账循环,不是事件驱动**(照抄 coral 的判断,它是对的):
|
|
8
|
+
* 文件事件会漏(worker 在别的用户下写 / 容器里写 / 进程崩在一半),
|
|
9
|
+
* 而漏掉的表现是"桶里少了一个文件",**没有任何报错**。对账每轮都会自愈。
|
|
10
|
+
*
|
|
11
|
+
* 🔴 **跑在 daemon,不在 console 的请求路径。**
|
|
12
|
+
* sidecar 是独立进程,而 console 是**共享事件循环** —— 同一段代码换了宿主就会开始
|
|
13
|
+
* 阻塞 `/api/health` 探针与 SSE 心跳。
|
|
14
|
+
* > 判据:**同步在移交件里是对的 —— 它本来跑在 CLI 里;换了宿主之后才变错。**
|
|
15
|
+
*
|
|
16
|
+
* 🔴 **每一类"没做"都要有自己的计数**,别合并成一个"跳过":
|
|
17
|
+
* 超期跳过 / 范围内一个路径都不存在 / 权限读不到 / 超大 / 被下限拒 —— 五种原因,五个数。
|
|
18
|
+
* 合并的代价今天实测过:coral 那个 `0` 同时表示"从没改过"和"目录不存在",吃掉了 36 个项目。
|
|
19
|
+
*/
|
|
20
|
+
import { MinioBackend } from './MinioBackend.js';
|
|
21
|
+
import { discoverProjects, scopeLastModified } from './scanWorkspace.js';
|
|
22
|
+
import { activeStorageConfig } from './storageConfig.js';
|
|
23
|
+
import { syncProject } from './syncProject.js';
|
|
24
|
+
import { readSyncScope, scopeForProduct } from './syncScope.js';
|
|
25
|
+
/** 默认缩略图实现:惰性加载 sharp(它带原生二进制,不该为启动而拉进内存)。 */
|
|
26
|
+
async function defaultMakeThumb(input) {
|
|
27
|
+
const sharp = (await import('sharp')).default;
|
|
28
|
+
return sharp(input).resize(320, 320, { fit: 'inside', withoutEnlargement: true }).webp({ quality: 78 }).toBuffer();
|
|
29
|
+
}
|
|
30
|
+
export async function runSyncPass(opts) {
|
|
31
|
+
const log = opts.log ?? ((l) => console.log(l));
|
|
32
|
+
const cfg = activeStorageConfig();
|
|
33
|
+
// 🔴 未配置/关着 ⇒ **整条路径不存在**。不是错误,也不该打日志刷屏。
|
|
34
|
+
if (!cfg && !opts.storage)
|
|
35
|
+
return null;
|
|
36
|
+
const deps = {
|
|
37
|
+
storage: opts.storage ?? new MinioBackend(cfg),
|
|
38
|
+
makeThumb: opts.makeThumb ?? defaultMakeThumb,
|
|
39
|
+
maxBytes: opts.maxBytes,
|
|
40
|
+
};
|
|
41
|
+
const started = opts.now ?? Date.now();
|
|
42
|
+
const scope = readSyncScope();
|
|
43
|
+
let projects = await discoverProjects(opts.root);
|
|
44
|
+
if (opts.onlyProject)
|
|
45
|
+
projects = projects.filter((p) => p.rel === opts.onlyProject);
|
|
46
|
+
const sum = {
|
|
47
|
+
projects: 0, scanned: 0, uploaded: 0, thumbs: 0, skipped: 0, failed: 0,
|
|
48
|
+
unreadable: 0, missing: 0, skippedDot: 0, oversized: 0, rejected: 0, skippedOld: 0, skippedEmptyScope: 0,
|
|
49
|
+
defaultScope: 0, perProject: [],
|
|
50
|
+
};
|
|
51
|
+
for (const p of projects) {
|
|
52
|
+
const resolved = scopeForProduct(p.product, scope);
|
|
53
|
+
if (opts.sinceDays > 0 && !opts.onlyProject) {
|
|
54
|
+
const last = await scopeLastModified(p.abs, resolved.include);
|
|
55
|
+
if (last === null) {
|
|
56
|
+
sum.skippedEmptyScope++; // ← 不是"很旧",是范围内什么都没有
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (last < started - opts.sinceDays * 86_400_000) {
|
|
60
|
+
sum.skippedOld++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (resolved.usedDefault)
|
|
65
|
+
sum.defaultScope++;
|
|
66
|
+
try {
|
|
67
|
+
const s = await syncProject(deps, p, resolved.include, resolved.usedDefault);
|
|
68
|
+
sum.perProject.push(s);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
// 单个项目炸掉不拖垮整轮 —— 但要出现在汇总里
|
|
72
|
+
sum.perProject.push({
|
|
73
|
+
project: p.rel, scanned: 0, uploaded: 0, thumbs: 0, skipped: 0, failed: 1,
|
|
74
|
+
unreadable: 0, missing: 0, skippedDot: 0, oversized: 0, rejected: [], usedDefaultScope: resolved.usedDefault,
|
|
75
|
+
errors: [e instanceof Error ? e.message : String(e)],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const s of sum.perProject) {
|
|
80
|
+
sum.projects++;
|
|
81
|
+
sum.scanned += s.scanned;
|
|
82
|
+
sum.uploaded += s.uploaded;
|
|
83
|
+
sum.thumbs += s.thumbs;
|
|
84
|
+
sum.skipped += s.skipped;
|
|
85
|
+
sum.failed += s.failed;
|
|
86
|
+
sum.unreadable += s.unreadable;
|
|
87
|
+
sum.missing += s.missing;
|
|
88
|
+
sum.skippedDot += s.skippedDot;
|
|
89
|
+
sum.oversized += s.oversized;
|
|
90
|
+
sum.rejected += s.rejected.length;
|
|
91
|
+
}
|
|
92
|
+
log(formatSummary(sum, started, opts));
|
|
93
|
+
// 只印有动静的项目;**失败必须每轮都印**(它不会自己消失)
|
|
94
|
+
for (const s of sum.perProject) {
|
|
95
|
+
if (s.uploaded || s.thumbs || s.failed || s.unreadable || s.oversized || s.rejected.length) {
|
|
96
|
+
log(` ${s.project}: +${s.uploaded} 缩略图${s.thumbs} 失败${s.failed}${s.rejected.length ? ` 拒${s.rejected.length}` : ''}`);
|
|
97
|
+
for (const e of s.errors)
|
|
98
|
+
log(` ! ${e}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return sum;
|
|
102
|
+
}
|
|
103
|
+
/** 汇总行。每一类"没做"都占一格 —— 一个悄悄缩小了范围的同步器,和一个坏掉的同步器,日志上一模一样。 */
|
|
104
|
+
export function formatSummary(sum, started, opts) {
|
|
105
|
+
const ms = (opts.now ?? Date.now()) - started;
|
|
106
|
+
return (`[sps-sync] 项目 ${sum.projects} · 扫 ${sum.scanned} · 上传 ${sum.uploaded} · 缩略图 ${sum.thumbs} · ` +
|
|
107
|
+
`已在桶 ${sum.skipped} · 失败 ${sum.failed} · ${ms}ms` +
|
|
108
|
+
// 基线是 0 ⇒ 非 0 即变化,所以是告警不是趋势
|
|
109
|
+
(sum.unreadable ? ` · 🔴 因权限读不到而跳过 ${sum.unreadable} 个文件(基线应为 0 —— 立刻查是防线生效还是误伤)` : '') +
|
|
110
|
+
(sum.rejected ? ` · 🔴 被安全下限拒 ${sum.rejected} 个文件(**只跳过它们**,其余照传)` : '') +
|
|
111
|
+
(sum.oversized ? ` · 🔴 超单文件上限 ${sum.oversized} 个(未同步)` : '') +
|
|
112
|
+
(sum.skippedOld ? ` · 因超过 ${opts.sinceDays} 天没动而跳过 ${sum.skippedOld} 个项目` : '') +
|
|
113
|
+
// 🔴 只报**事实**,不猜原因:实测 195 个项目里有 48 个范围内确实什么都没有
|
|
114
|
+
// (coralplatform 的 21 个里 14 个只有 AGENTS.md)—— 那是正常状态,不是"声明写错"。
|
|
115
|
+
// 一个每轮都在猜错原因的告警,会把人训练成忽略它。
|
|
116
|
+
(sum.skippedEmptyScope ? ` · 范围内无文件而跳过 ${sum.skippedEmptyScope} 个项目` : '') +
|
|
117
|
+
(sum.defaultScope ? ` · ⚠️ ${sum.defaultScope} 个项目没有产品声明,用了窄兜底(只 assets/)` : ''));
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=syncRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncRunner.js","sourceRoot":"","sources":["../../../src/providers/storage/syncRunner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAiC,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAsChE,8CAA8C;AAC9C,KAAK,UAAU,gBAAgB,CAAC,KAAa;IAC3C,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAC9C,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;AACrH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAqB;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,mBAAmB,EAAE,CAAC;IAClC,yCAAyC;IACzC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAEvC,MAAM,IAAI,GAAa;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,YAAY,CAAC,GAA8B,CAAC;QACzE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,gBAAgB;QAC7C,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,IAAI,QAAQ,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjD,IAAI,IAAI,CAAC,WAAW;QAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,WAAW,CAAC,CAAC;IAEpF,MAAM,GAAG,GAAgB;QACvB,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;QACtE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC;QACxG,YAAY,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE;KAChC,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC9D,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,qBAAqB;gBAC9C,SAAS;YACX,CAAC;YACD,IAAI,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC,SAAS,GAAG,UAAU,EAAE,CAAC;gBACjD,GAAG,CAAC,UAAU,EAAE,CAAC;gBACjB,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,QAAQ,CAAC,WAAW;YAAE,GAAG,CAAC,YAAY,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC7E,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,0BAA0B;YAC1B,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;gBAClB,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;gBACzE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,gBAAgB,EAAE,QAAQ,CAAC,WAAW;gBAC5G,MAAM,EAAE,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;aACrD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC/B,GAAG,CAAC,QAAQ,EAAE,CAAC;QACf,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC;QACzB,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC;QAC3B,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;QACvB,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC;QACzB,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;QACvB,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC;QAC/B,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC;QACzB,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC;QAC/B,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC;QAC7B,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;IACpC,CAAC;IAED,GAAG,CAAC,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IACvC,iCAAiC;IACjC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC3F,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,QAAQ,OAAO,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvH,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM;gBAAE,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,aAAa,CAAC,GAAgB,EAAE,OAAe,EAAE,IAAqB;IACpF,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IAC9C,OAAO,CACL,iBAAiB,GAAG,CAAC,QAAQ,QAAQ,GAAG,CAAC,OAAO,SAAS,GAAG,CAAC,QAAQ,UAAU,GAAG,CAAC,MAAM,KAAK;QAC9F,OAAO,GAAG,CAAC,OAAO,SAAS,GAAG,CAAC,MAAM,MAAM,EAAE,IAAI;QACjD,4BAA4B;QAC5B,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,mBAAmB,GAAG,CAAC,UAAU,8BAA8B,CAAC,CAAC,CAAC,EAAE,CAAC;QACvF,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,QAAQ,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,SAAS,WAAW,GAAG,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/E,+CAA+C;QAC/C,iEAAiE;QACjE,8BAA8B;QAC9B,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,iBAAiB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,YAAY,6BAA6B,CAAC,CAAC,CAAC,EAAE,CAAC,CACjF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ProductScope {
|
|
2
|
+
/**
|
|
3
|
+
* 相对项目根的路径。以 `/` 结尾 = 整个目录(递归);否则 = 单个文件。
|
|
4
|
+
* **不支持通配** —— 通配会把 `package.json` / `tsconfig.json` 这类顺带带上,
|
|
5
|
+
* 而"带上了什么"没人会去 diff(coral 选具名白名单的理由,实测有效)。
|
|
6
|
+
*/
|
|
7
|
+
include: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface SyncScope {
|
|
10
|
+
/** 产品名 → 声明。键就是工作区布局里的产品段。 */
|
|
11
|
+
products: Record<string, ProductScope>;
|
|
12
|
+
}
|
|
13
|
+
/** 没有声明时的兜底。**窄**,而且用它这件事要被报出来。 */
|
|
14
|
+
export declare const DEFAULT_INCLUDE: readonly ["assets/"];
|
|
15
|
+
export declare function syncScopeFile(): string;
|
|
16
|
+
export declare function readSyncScope(): SyncScope | null;
|
|
17
|
+
export declare function writeSyncScope(scope: SyncScope): void;
|
|
18
|
+
export interface ResolvedScope {
|
|
19
|
+
include: string[];
|
|
20
|
+
/** 🔴 是否用了兜底。调用方**必须**把它报进日志 —— 否则"范围悄悄变窄"看不出来。 */
|
|
21
|
+
usedDefault: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** 取某产品的同步范围。找不到声明 → 兜底 + `usedDefault: true`。 */
|
|
24
|
+
export declare function scopeForProduct(product: string, scope: SyncScope | null): ResolvedScope;
|
|
25
|
+
//# sourceMappingURL=syncScope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncScope.d.ts","sourceRoot":"","sources":["../../../src/providers/storage/syncScope.ts"],"names":[],"mappings":"AAgCA,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACxC;AAED,mCAAmC;AACnC,eAAO,MAAM,eAAe,sBAAuB,CAAC;AAEpD,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,aAAa,IAAI,SAAS,GAAG,IAAI,CAgBhD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAErD;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mDAAmD;IACnD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,kDAAkD;AAClD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,aAAa,CAIvF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module providers/storage/syncScope
|
|
3
|
+
* @description "同步**什么**"的声明(`~/.coral/sync-scope.json`)。**按产品**,由平台写入,sps 只执行。
|
|
4
|
+
*
|
|
5
|
+
* @layer providers
|
|
6
|
+
*
|
|
7
|
+
* 🔴 **为什么不是 `template.json`(计划里原本这么写,落地时改了):**
|
|
8
|
+
* `scaffold.ts:178` —— **`template.json` 不进项目**。所以对一个已存在的工作区,
|
|
9
|
+
* sps **无从查到它是哪个模板铺的**,更取不到模板里的声明。
|
|
10
|
+
* ⇒ 按模板声明这条路只对"将来新建的项目"成立,而同步要覆盖**存量 141 个**。
|
|
11
|
+
* (这一格是写代码时才发现的,不是设计时想到的 —— 已回写进实施计划文档。)
|
|
12
|
+
*
|
|
13
|
+
* 🔴 **为什么按「产品」而不是按「项目」:** 差异本来就在产品级 ——
|
|
14
|
+
* `assets/` 是通用的,而 `docs/PRD.md` / `docs/build-manifest.json` / `<产品>.json`
|
|
15
|
+
* 是产品词表。而 sps **已经知道产品**:它是工作区布局的第二段
|
|
16
|
+
* (`<WORKSPACES_ROOT>/t<租户>/<产品>/<项目>`,见 `assetLocator.ts:52`)。
|
|
17
|
+
*
|
|
18
|
+
* 🔴 **这不违反 §2.1 第①条(配置里放"去哪儿",不放"同步什么")。**
|
|
19
|
+
* 那一条禁的是**sps 把产品知识写死在自己代码里**(coral sidecar 的
|
|
20
|
+
* `scan.ts:38` 用「目录下含 assets/」猜产品语义,静默漏掉 36 个设计师阶段项目)。
|
|
21
|
+
* 这里的内容**由平台通过 API 写入**,sps 一个产品名都不硬编码 —— 是**数据**不是**代码**。
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ **没有声明时只同步 `assets/`,而且必须每轮报出来。**
|
|
24
|
+
* 同步怕少发,所以窄缺省是有代价的;代价的解法不是放宽缺省(那会把 node_modules 推上去),
|
|
25
|
+
* 而是**让"我用的是缺省"这件事出现在日志里**。
|
|
26
|
+
* > 判据:一个悄悄缩小了范围的同步器,和一个坏掉的同步器,日志上一模一样。
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { hardenSecretFile, writeSecretFile } from '../../infra/secretFile.js';
|
|
31
|
+
import { coralRoot } from '../../shared/runtimePaths.js';
|
|
32
|
+
/** 没有声明时的兜底。**窄**,而且用它这件事要被报出来。 */
|
|
33
|
+
export const DEFAULT_INCLUDE = ['assets/'];
|
|
34
|
+
export function syncScopeFile() {
|
|
35
|
+
return join(coralRoot(), 'sync-scope.json');
|
|
36
|
+
}
|
|
37
|
+
export function readSyncScope() {
|
|
38
|
+
const p = syncScopeFile();
|
|
39
|
+
if (!existsSync(p))
|
|
40
|
+
return null;
|
|
41
|
+
hardenSecretFile(p);
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(readFileSync(p, 'utf-8'));
|
|
44
|
+
if (!raw.products || typeof raw.products !== 'object')
|
|
45
|
+
return null;
|
|
46
|
+
const products = {};
|
|
47
|
+
for (const [k, v] of Object.entries(raw.products)) {
|
|
48
|
+
const inc = v?.include;
|
|
49
|
+
if (Array.isArray(inc) && inc.every((x) => typeof x === 'string'))
|
|
50
|
+
products[k] = { include: inc };
|
|
51
|
+
}
|
|
52
|
+
return { products };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function writeSyncScope(scope) {
|
|
59
|
+
writeSecretFile(syncScopeFile(), `${JSON.stringify(scope, null, 2)}\n`);
|
|
60
|
+
}
|
|
61
|
+
/** 取某产品的同步范围。找不到声明 → 兜底 + `usedDefault: true`。 */
|
|
62
|
+
export function scopeForProduct(product, scope) {
|
|
63
|
+
const declared = scope?.products[product]?.include;
|
|
64
|
+
if (declared && declared.length > 0)
|
|
65
|
+
return { include: [...declared], usedDefault: false };
|
|
66
|
+
return { include: [...DEFAULT_INCLUDE], usedDefault: true };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=syncScope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncScope.js","sourceRoot":"","sources":["../../../src/providers/storage/syncScope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAgBzD,mCAAmC;AACnC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,SAAS,CAAU,CAAC;AAEpD,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,GAAG,aAAa,EAAE,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAuB,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACnE,MAAM,QAAQ,GAAiC,EAAE,CAAC;QAClD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClD,MAAM,GAAG,GAAI,CAA8B,EAAE,OAAO,CAAC;YACrD,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;gBAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;QACpG,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAgB;IAC7C,eAAe,CAAC,aAAa,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC1E,CAAC;AAQD,kDAAkD;AAClD,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,KAAuB;IACtE,MAAM,QAAQ,GAAG,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IACnD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,QAAQ,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC3F,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,eAAe,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type SyncRejection = {
|
|
2
|
+
code: 'ABSOLUTE';
|
|
3
|
+
rel: string;
|
|
4
|
+
} | {
|
|
5
|
+
code: 'DOT_SEGMENT';
|
|
6
|
+
rel: string;
|
|
7
|
+
} | {
|
|
8
|
+
code: 'PARENT_SEGMENT';
|
|
9
|
+
rel: string;
|
|
10
|
+
} | {
|
|
11
|
+
code: 'EMPTY_SEGMENT';
|
|
12
|
+
rel: string;
|
|
13
|
+
} | {
|
|
14
|
+
code: 'ESCAPES_PROJECT';
|
|
15
|
+
rel: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 纯字符串这一半(**不含**软链检查 —— 那半必须走 `isRealUnderRoot`)。
|
|
19
|
+
*
|
|
20
|
+
* 单独抽出来不是为了复用,是为了让**两半都必须被调用**这件事在类型上看得见:
|
|
21
|
+
* 只调这一半的实现会"看起来对",而它恰恰漏掉了唯一挡得住 agent 自造软链的那道。
|
|
22
|
+
*/
|
|
23
|
+
export declare function syncableShape(rel: string): SyncRejection | null;
|
|
24
|
+
/**
|
|
25
|
+
* 完整判据 = 形状 + **realpath 围栏**。
|
|
26
|
+
*
|
|
27
|
+
* @param rel 相对项目根的路径(声明枚举出来的)
|
|
28
|
+
* @param absPath 它在盘上的绝对路径
|
|
29
|
+
* @param projectDir 项目根
|
|
30
|
+
*/
|
|
31
|
+
export declare function syncable(rel: string, absPath: string, projectDir: string): Promise<SyncRejection | null>;
|
|
32
|
+
/**
|
|
33
|
+
* 对一整批做下限判定。
|
|
34
|
+
*
|
|
35
|
+
* 🔴 **返回全部拒绝项,不是第一个** —— coral 的取舍 3:清单会过期,配套的是
|
|
36
|
+
* **拒绝时把名字报出去**,而不是"尽量列全"。列不全没关系,漏的会被看见。
|
|
37
|
+
*
|
|
38
|
+
* 🔴 **调用方必须"枚举完再推第一个字节"**:围栏若写在上传循环里,
|
|
39
|
+
* 状态码确实会回错,但**排在拒绝项前面的文件已经进桶了** ——
|
|
40
|
+
* 「整体失败」是关于**副作用**的断言,不是关于返回码的(coral 实测,用
|
|
41
|
+
* `assert(uploaded.length === 0)` 才照出来)。
|
|
42
|
+
*/
|
|
43
|
+
export declare function screenAll(files: {
|
|
44
|
+
rel: string;
|
|
45
|
+
abs: string;
|
|
46
|
+
}[], projectDir: string): Promise<SyncRejection[]>;
|
|
47
|
+
//# sourceMappingURL=syncable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncable.d.ts","sourceRoot":"","sources":["../../../src/providers/storage/syncable.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAU/D;AAED;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAM/B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,EACrC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,EAAE,CAAC,CAO1B"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module providers/storage/syncable
|
|
3
|
+
* @description **同步**这条路的安全下限。设计见 `object-storage-and-workspace-sync.md` §2.1 第五条。
|
|
4
|
+
*
|
|
5
|
+
* @layer providers
|
|
6
|
+
*
|
|
7
|
+
* 🔴🔴 **它与发布的下限方向相反,别互相抄。**
|
|
8
|
+
* ```
|
|
9
|
+
* 发布 publishable() 怕**多发** —— 树里会长出什么由 agent 决定,黑名单追不上 ⇒ **后缀白名单**
|
|
10
|
+
* 同步 syncable() 怕**少发** —— 用户会放 .blend / .psd / .aseprite ⇒ **不能用后缀白名单**
|
|
11
|
+
* ```
|
|
12
|
+
* 抄错方向的表现是:**用户的源文件不见了,而且不报错**。
|
|
13
|
+
* (「照搬一份现成的安全规则」恰恰是最容易顺手抄整份的时候。)
|
|
14
|
+
*
|
|
15
|
+
* 🔴 **声明决定"要什么",本模块决定"绝不给什么"** —— 后者不是产品知识,是主体安全,
|
|
16
|
+
* 且**不可被声明或配置放宽**。它防的是一件具体的事:项目树里有带活 token 的
|
|
17
|
+
* `.claude/settings.local.json`(实测:coral 那侧 `publishable('src/main.js')` 放行,
|
|
18
|
+
* 而 `readFile` 实际拿到 `ANTHROPIC_AUTH_TOKEN=sk-live-…`,落点还是公开可取的前缀)。
|
|
19
|
+
*
|
|
20
|
+
* 🔴 **真正拦住凭据的是"任一路径段以 `.` 开头"这一条**,不是别的:
|
|
21
|
+
* `settings.local.json` 本身后缀无害,是 `.claude/` 这个点开头段拦住的。
|
|
22
|
+
* ⇒ 将来谁"为了支持 `.well-known/` 放宽点开头",会在不知情的情况下把底线整个打开。
|
|
23
|
+
* > 判据:**守卫要标注它靠哪一条在生效** —— 否则放宽的人以为自己在动一条无关的规则。
|
|
24
|
+
*/
|
|
25
|
+
import { isRealUnderRoot } from '../../core/pathFence.js';
|
|
26
|
+
/**
|
|
27
|
+
* 纯字符串这一半(**不含**软链检查 —— 那半必须走 `isRealUnderRoot`)。
|
|
28
|
+
*
|
|
29
|
+
* 单独抽出来不是为了复用,是为了让**两半都必须被调用**这件事在类型上看得见:
|
|
30
|
+
* 只调这一半的实现会"看起来对",而它恰恰漏掉了唯一挡得住 agent 自造软链的那道。
|
|
31
|
+
*/
|
|
32
|
+
export function syncableShape(rel) {
|
|
33
|
+
if (!rel)
|
|
34
|
+
return { code: 'EMPTY_SEGMENT', rel };
|
|
35
|
+
if (rel.startsWith('/'))
|
|
36
|
+
return { code: 'ABSOLUTE', rel };
|
|
37
|
+
for (const seg of rel.split('/')) {
|
|
38
|
+
if (!seg)
|
|
39
|
+
return { code: 'EMPTY_SEGMENT', rel };
|
|
40
|
+
if (seg === '..')
|
|
41
|
+
return { code: 'PARENT_SEGMENT', rel };
|
|
42
|
+
// ← 承重的是这条:`.claude/`、`.git/`、`.sps/` 全靠它
|
|
43
|
+
if (seg.startsWith('.'))
|
|
44
|
+
return { code: 'DOT_SEGMENT', rel };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 完整判据 = 形状 + **realpath 围栏**。
|
|
50
|
+
*
|
|
51
|
+
* @param rel 相对项目根的路径(声明枚举出来的)
|
|
52
|
+
* @param absPath 它在盘上的绝对路径
|
|
53
|
+
* @param projectDir 项目根
|
|
54
|
+
*/
|
|
55
|
+
export async function syncable(rel, absPath, projectDir) {
|
|
56
|
+
const shape = syncableShape(rel);
|
|
57
|
+
if (shape)
|
|
58
|
+
return shape;
|
|
59
|
+
// 软链:声明可以指到一个含软链的目录,软链再指向 ~/.coral。字符串判据一条都挡不住。
|
|
60
|
+
if (!(await isRealUnderRoot(absPath, projectDir)))
|
|
61
|
+
return { code: 'ESCAPES_PROJECT', rel };
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 对一整批做下限判定。
|
|
66
|
+
*
|
|
67
|
+
* 🔴 **返回全部拒绝项,不是第一个** —— coral 的取舍 3:清单会过期,配套的是
|
|
68
|
+
* **拒绝时把名字报出去**,而不是"尽量列全"。列不全没关系,漏的会被看见。
|
|
69
|
+
*
|
|
70
|
+
* 🔴 **调用方必须"枚举完再推第一个字节"**:围栏若写在上传循环里,
|
|
71
|
+
* 状态码确实会回错,但**排在拒绝项前面的文件已经进桶了** ——
|
|
72
|
+
* 「整体失败」是关于**副作用**的断言,不是关于返回码的(coral 实测,用
|
|
73
|
+
* `assert(uploaded.length === 0)` 才照出来)。
|
|
74
|
+
*/
|
|
75
|
+
export async function screenAll(files, projectDir) {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const f of files) {
|
|
78
|
+
const r = await syncable(f.rel, f.abs, projectDir);
|
|
79
|
+
if (r)
|
|
80
|
+
out.push(r);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=syncable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncable.js","sourceRoot":"","sources":["../../../src/providers/storage/syncable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAS1D;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC;IAChD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;IAC1D,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC;QAChD,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC;QACzD,0CAA0C;QAC1C,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,GAAW,EACX,OAAe,EACf,UAAkB;IAElB,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,gDAAgD;IAChD,IAAI,CAAC,CAAC,MAAM,eAAe,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC;IAC3F,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAqC,EACrC,UAAkB;IAElB,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coralai/sps-cli",
|
|
3
|
-
"version": "0.65.
|
|
3
|
+
"version": "0.65.91",
|
|
4
4
|
"description": "SPS CLI — AI-driven development pipeline orchestrator",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"test:watch": "vitest",
|
|
20
20
|
"test:coverage": "vitest run --coverage",
|
|
21
21
|
"test:e2e": "playwright test",
|
|
22
|
-
"prepublishOnly": "npm run build"
|
|
22
|
+
"prepublishOnly": "npm run check && npm run build",
|
|
23
|
+
"check": "npm run typecheck && npm run typecheck:test && npm run check:api-docs"
|
|
23
24
|
},
|
|
24
25
|
"engines": {
|
|
25
26
|
"node": ">=18.0.0"
|
|
@@ -98,5 +99,8 @@
|
|
|
98
99
|
"types": "./dist/chat-protocol/index.d.ts",
|
|
99
100
|
"import": "./dist/chat-protocol/index.js"
|
|
100
101
|
}
|
|
102
|
+
},
|
|
103
|
+
"optionalDependencies": {
|
|
104
|
+
"minio": "^8.0.7"
|
|
101
105
|
}
|
|
102
106
|
}
|