@coralai/sps-cli 0.90.0 → 0.90.2
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/assembly/base.yml +4 -4
- package/dist/console/index.d.ts.map +1 -1
- package/dist/console/index.js +0 -2
- package/dist/console/index.js.map +1 -1
- package/dist/core/agents/ClaudeSdkBackend.d.ts +9 -0
- package/dist/core/agents/ClaudeSdkBackend.d.ts.map +1 -1
- package/dist/core/agents/ClaudeSdkBackend.js +5 -0
- package/dist/core/agents/ClaudeSdkBackend.js.map +1 -1
- package/dist/core/pathFence.d.ts +14 -14
- package/dist/core/pathFence.d.ts.map +1 -1
- package/dist/core/pathFence.js +11 -51
- package/dist/core/pathFence.js.map +1 -1
- package/dist/daemon/sessionDaemon.d.ts +16 -3
- package/dist/daemon/sessionDaemon.d.ts.map +1 -1
- package/dist/daemon/sessionDaemon.js +16 -7
- package/dist/daemon/sessionDaemon.js.map +1 -1
- package/dist/seams/agents-builtin/index.d.ts.map +1 -1
- package/dist/seams/agents-builtin/index.js +66 -51
- package/dist/seams/agents-builtin/index.js.map +1 -1
- package/dist/seams/memory/index.d.ts +8 -0
- package/dist/seams/memory/index.d.ts.map +1 -1
- package/dist/seams/memory/index.js +10 -0
- package/dist/seams/memory/index.js.map +1 -1
- package/dist/seams/memory/optional.d.ts +5 -0
- package/dist/seams/memory/optional.d.ts.map +1 -1
- package/dist/seams/memory/optional.js +7 -1
- package/dist/seams/memory/optional.js.map +1 -1
- package/dist/seams/workspace/index.d.ts +40 -0
- package/dist/seams/workspace/index.d.ts.map +1 -0
- package/dist/seams/workspace/index.js +46 -0
- package/dist/seams/workspace/index.js.map +1 -0
- package/docs/help/02-commands.md +2 -2
- package/docs/help/05-api.md +2 -12
- package/package.json +2 -2
- package/dist/console/routes/storage.d.ts +0 -21
- package/dist/console/routes/storage.d.ts.map +0 -1
- package/dist/console/routes/storage.js +0 -115
- package/dist/console/routes/storage.js.map +0 -1
- package/dist/daemon/storageSyncLoop.d.ts +0 -9
- package/dist/daemon/storageSyncLoop.d.ts.map +0 -1
- package/dist/daemon/storageSyncLoop.js +0 -70
- package/dist/daemon/storageSyncLoop.js.map +0 -1
- package/dist/providers/storage/FakeStorage.d.ts +0 -16
- package/dist/providers/storage/FakeStorage.d.ts.map +0 -1
- package/dist/providers/storage/FakeStorage.js +0 -37
- package/dist/providers/storage/FakeStorage.js.map +0 -1
- package/dist/providers/storage/MinioBackend.d.ts +0 -41
- package/dist/providers/storage/MinioBackend.d.ts.map +0 -1
- package/dist/providers/storage/MinioBackend.js +0 -85
- package/dist/providers/storage/MinioBackend.js.map +0 -1
- package/dist/providers/storage/StorageBackend.d.ts +0 -52
- package/dist/providers/storage/StorageBackend.d.ts.map +0 -1
- package/dist/providers/storage/StorageBackend.js +0 -29
- package/dist/providers/storage/StorageBackend.js.map +0 -1
- package/dist/providers/storage/keys.d.ts +0 -33
- package/dist/providers/storage/keys.d.ts.map +0 -1
- package/dist/providers/storage/keys.js +0 -52
- package/dist/providers/storage/keys.js.map +0 -1
- package/dist/providers/storage/storageConfig.d.ts +0 -46
- package/dist/providers/storage/storageConfig.d.ts.map +0 -1
- package/dist/providers/storage/storageConfig.js +0 -94
- package/dist/providers/storage/storageConfig.js.map +0 -1
- package/dist/providers/storage/syncProject.d.ts +0 -67
- package/dist/providers/storage/syncProject.d.ts.map +0 -1
- package/dist/providers/storage/syncProject.js +0 -136
- package/dist/providers/storage/syncProject.js.map +0 -1
- package/dist/providers/storage/syncRunner.d.ts +0 -59
- package/dist/providers/storage/syncRunner.d.ts.map +0 -1
- package/dist/providers/storage/syncRunner.js +0 -119
- package/dist/providers/storage/syncRunner.js.map +0 -1
- package/dist/providers/storage/syncable.d.ts +0 -47
- package/dist/providers/storage/syncable.d.ts.map +0 -1
- package/dist/providers/storage/syncable.js +0 -84
- package/dist/providers/storage/syncable.js.map +0 -1
- package/dist/seams/storage/index.d.ts +0 -53
- package/dist/seams/storage/index.d.ts.map +0 -1
- package/dist/seams/storage/index.js +0 -69
- package/dist/seams/storage/index.js.map +0 -1
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module providers/storage/syncProject
|
|
3
|
-
* @description 一个项目的一轮对账同步:枚举 → 过下限 → 与桶对账 → 上传(缩略图先、原图后)。
|
|
4
|
-
*
|
|
5
|
-
* @layer providers
|
|
6
|
-
*
|
|
7
|
-
* 九条不变量见 `docs/design/storage-sync-implementation.md` §〇。落在代码里的要害是这几条:
|
|
8
|
-
*
|
|
9
|
-
* 🔴 **对账基准是桶本身,不是本地状态文件。** 本地清单一旦与桶脱节(桶被清过 / 上轮崩在一半 /
|
|
10
|
-
* 换了机器)会**永久跳过**某些文件且不报错;以桶的实际内容为准,一轮巡检就自愈。
|
|
11
|
-
*
|
|
12
|
-
* 🔴 **不删。** 本地文件没了 ≠ 用户要删桶里那份(工作区可能被重建、节点可能换了)。
|
|
13
|
-
* `StorageBackend` 接口上**根本没有 delete** —— 比"记得别调"可靠。
|
|
14
|
-
*
|
|
15
|
-
* 🔴 **幂等判据的覆盖面必须等于产物面。** 这一轮产出两样(原图 + 缩略图),判据就得同时问两样:
|
|
16
|
-
* 只问原图 ⇒ 一张生成失败过的缩略图**永远不会被补上**,表现是"同步很成功、预览是空的"。
|
|
17
|
-
*
|
|
18
|
-
* 🔴 **缩略图先写、原图后写**,且缩略图**上传**失败时本轮不写原图 ——
|
|
19
|
-
* 原图 etag 与本地相等是这条流水线的**提交点**,顺序反了会永久停留在旧缩略图且不报错。
|
|
20
|
-
* ⚠️ 区分**生成**失败(确定性:图坏了,照常传原图,否则坏文件把自己永远挡在桶外)
|
|
21
|
-
* 与**上传**失败(会留下陈旧缩略图 ⇒ 必须回退)。
|
|
22
|
-
*
|
|
23
|
-
* 🔴 **枚举完再判下限,再推第一个字节。** 下限若写在上传循环里,
|
|
24
|
-
* 排在拒绝项前面的文件**已经进桶了** —— 「整体失败」是关于**副作用**的断言,不是返回码。
|
|
25
|
-
*
|
|
26
|
-
* 🔴🔴 **但被拒的文件是"跳过并报出来",不是"整趟不推"**(coral 2026-08-19 指出,已改):
|
|
27
|
-
* 同一条下限用在**发布**路上,"整趟失败"是对的 —— 半个包比拒绝更难查;
|
|
28
|
-
* 用在**周期性**的同步路上就变成**那个项目永久停止同步**,而且理由看起来像安全事件。
|
|
29
|
-
* > 判据:**同一条规则,在"一次性动作"和"周期性动作"上后果完全不同。**
|
|
30
|
-
* > 周期性的路上,任何"整趟失败"都要能自己恢复,否则它等于一个**不报警的开关**。
|
|
31
|
-
* 而下限要防的事(凭据进桶)本来就是**逐文件**的 —— 跳过那一个就够了,
|
|
32
|
-
* 赔上整个项目既不更安全,又制造了一个静默停摆。
|
|
33
|
-
*/
|
|
34
|
-
import type { StorageBackend } from './StorageBackend.js';
|
|
35
|
-
import { type ProjectDir } from './scanWorkspace.js';
|
|
36
|
-
import { type SyncRejection } from './syncable.js';
|
|
37
|
-
export declare const contentTypeOf: (rel: string) => string | undefined;
|
|
38
|
-
export declare const thumbnailable: (rel: string) => boolean;
|
|
39
|
-
export interface SyncStats {
|
|
40
|
-
project: string;
|
|
41
|
-
scanned: number;
|
|
42
|
-
uploaded: number;
|
|
43
|
-
/** 与 uploaded **分开计** —— 合在一起就看不出预览到底有没有产出。 */
|
|
44
|
-
thumbs: number;
|
|
45
|
-
skipped: number;
|
|
46
|
-
failed: number;
|
|
47
|
-
unreadable: number;
|
|
48
|
-
/** 声明里有、盘上没有 —— **不是错误**(设计师还没写 PRD 也走这里)。与 unreadable 分开。 */
|
|
49
|
-
missing: number;
|
|
50
|
-
/** 枚举阶段跳过的点开头条目(中间产物)。不触发整趟拒绝。 */
|
|
51
|
-
skippedDot: number;
|
|
52
|
-
oversized: number;
|
|
53
|
-
/** 被安全下限拒的项。**整趟不推**(见模块头)。 */
|
|
54
|
-
rejected: SyncRejection[];
|
|
55
|
-
/** 用了窄兜底(该产品没有声明)。**必须被报出来**。 */
|
|
56
|
-
usedDefaultScope: boolean;
|
|
57
|
-
errors: string[];
|
|
58
|
-
}
|
|
59
|
-
export interface SyncDeps {
|
|
60
|
-
storage: StorageBackend;
|
|
61
|
-
/** 缩略图生成。注入是为了让用例不必依赖 sharp 的原生二进制。 */
|
|
62
|
-
makeThumb: (input: Buffer) => Promise<Buffer>;
|
|
63
|
-
/** 单文件上限(字节)。显式给,不靠运气。 */
|
|
64
|
-
maxBytes: number;
|
|
65
|
-
}
|
|
66
|
-
export declare function syncProject(deps: SyncDeps, project: ProjectDir, include: string[], usedDefaultScope: boolean): Promise<SyncStats>;
|
|
67
|
-
//# sourceMappingURL=syncProject.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"syncProject.d.ts","sourceRoot":"","sources":["../../../src/providers/storage/syncProject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,KAAK,aAAa,EAAa,MAAM,eAAe,CAAC;AAS9D,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAA6C,CAAC;AAInG,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,OAAiD,CAAC;AAE9F,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iCAAiC;IACjC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,cAAc,CAAC;IACxB,uCAAuC;IACvC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,gBAAgB,EAAE,OAAO,GACxB,OAAO,CAAC,SAAS,CAAC,CAmFpB"}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module providers/storage/syncProject
|
|
3
|
-
* @description 一个项目的一轮对账同步:枚举 → 过下限 → 与桶对账 → 上传(缩略图先、原图后)。
|
|
4
|
-
*
|
|
5
|
-
* @layer providers
|
|
6
|
-
*
|
|
7
|
-
* 九条不变量见 `docs/design/storage-sync-implementation.md` §〇。落在代码里的要害是这几条:
|
|
8
|
-
*
|
|
9
|
-
* 🔴 **对账基准是桶本身,不是本地状态文件。** 本地清单一旦与桶脱节(桶被清过 / 上轮崩在一半 /
|
|
10
|
-
* 换了机器)会**永久跳过**某些文件且不报错;以桶的实际内容为准,一轮巡检就自愈。
|
|
11
|
-
*
|
|
12
|
-
* 🔴 **不删。** 本地文件没了 ≠ 用户要删桶里那份(工作区可能被重建、节点可能换了)。
|
|
13
|
-
* `StorageBackend` 接口上**根本没有 delete** —— 比"记得别调"可靠。
|
|
14
|
-
*
|
|
15
|
-
* 🔴 **幂等判据的覆盖面必须等于产物面。** 这一轮产出两样(原图 + 缩略图),判据就得同时问两样:
|
|
16
|
-
* 只问原图 ⇒ 一张生成失败过的缩略图**永远不会被补上**,表现是"同步很成功、预览是空的"。
|
|
17
|
-
*
|
|
18
|
-
* 🔴 **缩略图先写、原图后写**,且缩略图**上传**失败时本轮不写原图 ——
|
|
19
|
-
* 原图 etag 与本地相等是这条流水线的**提交点**,顺序反了会永久停留在旧缩略图且不报错。
|
|
20
|
-
* ⚠️ 区分**生成**失败(确定性:图坏了,照常传原图,否则坏文件把自己永远挡在桶外)
|
|
21
|
-
* 与**上传**失败(会留下陈旧缩略图 ⇒ 必须回退)。
|
|
22
|
-
*
|
|
23
|
-
* 🔴 **枚举完再判下限,再推第一个字节。** 下限若写在上传循环里,
|
|
24
|
-
* 排在拒绝项前面的文件**已经进桶了** —— 「整体失败」是关于**副作用**的断言,不是返回码。
|
|
25
|
-
*
|
|
26
|
-
* 🔴🔴 **但被拒的文件是"跳过并报出来",不是"整趟不推"**(coral 2026-08-19 指出,已改):
|
|
27
|
-
* 同一条下限用在**发布**路上,"整趟失败"是对的 —— 半个包比拒绝更难查;
|
|
28
|
-
* 用在**周期性**的同步路上就变成**那个项目永久停止同步**,而且理由看起来像安全事件。
|
|
29
|
-
* > 判据:**同一条规则,在"一次性动作"和"周期性动作"上后果完全不同。**
|
|
30
|
-
* > 周期性的路上,任何"整趟失败"都要能自己恢复,否则它等于一个**不报警的开关**。
|
|
31
|
-
* 而下限要防的事(凭据进桶)本来就是**逐文件**的 —— 跳过那一个就够了,
|
|
32
|
-
* 赔上整个项目既不更安全,又制造了一个静默停摆。
|
|
33
|
-
*/
|
|
34
|
-
import { readFile } from 'node:fs/promises';
|
|
35
|
-
import { extname } from 'node:path';
|
|
36
|
-
import { assetKey, assetPrefix, thumbKey } from './keys.js';
|
|
37
|
-
import { scanByScope } from './scanWorkspace.js';
|
|
38
|
-
import { screenAll } from './syncable.js';
|
|
39
|
-
/** 桶是直连的 —— **没有一层服务器能在返回时补救**,缺省的 octet-stream 会让预览图变成一次下载。 */
|
|
40
|
-
const MIME = {
|
|
41
|
-
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp',
|
|
42
|
-
'.gif': 'image/gif', '.svg': 'image/svg+xml', '.json': 'application/json', '.txt': 'text/plain',
|
|
43
|
-
'.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4',
|
|
44
|
-
'.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.md': 'text/markdown',
|
|
45
|
-
};
|
|
46
|
-
export const contentTypeOf = (rel) => MIME[extname(rel).toLowerCase()];
|
|
47
|
-
/** 能缩略的栅格图。**不认识的类型一律否**,别去猜;SVG 不缩(本来就小,且避开服务端光栅化任意 SVG)。 */
|
|
48
|
-
const RASTER = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.tiff', '.avif']);
|
|
49
|
-
export const thumbnailable = (rel) => RASTER.has(extname(rel).toLowerCase());
|
|
50
|
-
export async function syncProject(deps, project, include, usedDefaultScope) {
|
|
51
|
-
const stats = {
|
|
52
|
-
project: project.rel, scanned: 0, uploaded: 0, thumbs: 0, skipped: 0, failed: 0,
|
|
53
|
-
unreadable: 0, missing: 0, skippedDot: 0, oversized: 0, rejected: [], usedDefaultScope, errors: [],
|
|
54
|
-
};
|
|
55
|
-
const scan = await scanByScope(project.abs, include, deps.maxBytes);
|
|
56
|
-
stats.scanned = scan.files.length;
|
|
57
|
-
stats.unreadable = scan.unreadable.length;
|
|
58
|
-
stats.missing = scan.missing.length;
|
|
59
|
-
stats.skippedDot = scan.skippedDot.length;
|
|
60
|
-
stats.oversized = scan.oversized.length;
|
|
61
|
-
for (const rel of scan.unreadable.slice(0, 3))
|
|
62
|
-
stats.errors.push(`权限读不到(已跳过) ${rel}`);
|
|
63
|
-
for (const rel of scan.oversized.slice(0, 3))
|
|
64
|
-
stats.errors.push(`超过单文件上限(未同步) ${rel}`);
|
|
65
|
-
// 🔴 下限在**推第一个字节之前**整趟判完(不是边推边判)。
|
|
66
|
-
// 但结果是**把被拒的那些从名单里摘掉**,不是整趟不推 —— 理由见模块头。
|
|
67
|
-
stats.rejected = await screenAll(scan.files.map((f) => ({ rel: f.rel, abs: f.abs })), project.abs);
|
|
68
|
-
const blocked = new Set(stats.rejected.map((r) => r.rel));
|
|
69
|
-
// 拒绝项要把名字报出去:清单会过期,而**漏掉的必须能被看见**。
|
|
70
|
-
for (const r of stats.rejected.slice(0, 5))
|
|
71
|
-
stats.errors.push(`安全下限拒绝 ${r.code}: ${r.rel}`);
|
|
72
|
-
const allowed = scan.files.filter((f) => !blocked.has(f.rel));
|
|
73
|
-
let remote;
|
|
74
|
-
try {
|
|
75
|
-
remote = new Map((await deps.storage.list(assetPrefix(project.rel))).map((o) => [o.key, o.etag]));
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
stats.failed++;
|
|
79
|
-
stats.errors.push(`列举桶失败:${e instanceof Error ? e.message : String(e)}`);
|
|
80
|
-
return stats;
|
|
81
|
-
}
|
|
82
|
-
for (const f of allowed) {
|
|
83
|
-
const key = assetKey(project.rel, f.rel);
|
|
84
|
-
const changed = remote.get(key) !== f.md5; // ETag == md5,**不看 mtime**
|
|
85
|
-
const tKey = thumbnailable(f.rel) ? thumbKey(project.rel, f.rel) : null;
|
|
86
|
-
const thumbMissing = tKey !== null && !remote.has(tKey);
|
|
87
|
-
if (!changed && !thumbMissing) {
|
|
88
|
-
stats.skipped++;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const fail = (e, what) => {
|
|
92
|
-
stats.failed++;
|
|
93
|
-
if (stats.errors.length < 8)
|
|
94
|
-
stats.errors.push(`${what} ${f.rel}: ${e instanceof Error ? e.message : String(e)}`);
|
|
95
|
-
};
|
|
96
|
-
let bytes;
|
|
97
|
-
try {
|
|
98
|
-
bytes = await readFile(f.abs);
|
|
99
|
-
}
|
|
100
|
-
catch (e) {
|
|
101
|
-
fail(e, '读文件');
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
let thumbUploadFailed = false;
|
|
105
|
-
if (tKey !== null && (changed || thumbMissing)) {
|
|
106
|
-
let thumb = null;
|
|
107
|
-
try {
|
|
108
|
-
thumb = await deps.makeThumb(bytes);
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
fail(e, '生成缩略图'); // 生成失败是确定性的 ⇒ 照常传原图
|
|
112
|
-
}
|
|
113
|
-
if (thumb) {
|
|
114
|
-
try {
|
|
115
|
-
await deps.storage.put(tKey, thumb, 'image/webp');
|
|
116
|
-
stats.thumbs++;
|
|
117
|
-
}
|
|
118
|
-
catch (e) {
|
|
119
|
-
fail(e, '上传缩略图');
|
|
120
|
-
thumbUploadFailed = true; // ⇒ 本轮不写原图,免得下一轮判"没变"而永远停在旧缩略图
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (!changed || thumbUploadFailed)
|
|
125
|
-
continue;
|
|
126
|
-
try {
|
|
127
|
-
await deps.storage.put(key, bytes, contentTypeOf(f.rel));
|
|
128
|
-
stats.uploaded++;
|
|
129
|
-
}
|
|
130
|
-
catch (e) {
|
|
131
|
-
fail(e, '上传'); // 单文件失败不中断整轮 —— 下轮对账会再试,但原因要留下
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return stats;
|
|
135
|
-
}
|
|
136
|
-
//# sourceMappingURL=syncProject.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"syncProject.js","sourceRoot":"","sources":["../../../src/providers/storage/syncProject.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,59 +0,0 @@
|
|
|
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 type { StorageBackend } from './StorageBackend.js';
|
|
21
|
-
import { type SyncStats } from './syncProject.js';
|
|
22
|
-
export interface SyncPassOptions {
|
|
23
|
-
root: string;
|
|
24
|
-
/** 只同步近 N 天动过的项目;0 = 全部。 */
|
|
25
|
-
sinceDays: number;
|
|
26
|
-
/** 单文件上限。 */
|
|
27
|
-
maxBytes: number;
|
|
28
|
-
/** 只跑一个项目(相对共享根),排障用。 */
|
|
29
|
-
onlyProject?: string;
|
|
30
|
-
log?: (line: string) => void;
|
|
31
|
-
/** 注入点:用例用假后端 / 假缩略图,不碰 minio 与 sharp 的原生依赖。 */
|
|
32
|
-
storage?: StorageBackend;
|
|
33
|
-
makeThumb?: (input: Buffer) => Promise<Buffer>;
|
|
34
|
-
now?: number;
|
|
35
|
-
}
|
|
36
|
-
export interface PassSummary {
|
|
37
|
-
projects: number;
|
|
38
|
-
scanned: number;
|
|
39
|
-
uploaded: number;
|
|
40
|
-
thumbs: number;
|
|
41
|
-
skipped: number;
|
|
42
|
-
failed: number;
|
|
43
|
-
unreadable: number;
|
|
44
|
-
missing: number;
|
|
45
|
-
skippedDot: number;
|
|
46
|
-
oversized: number;
|
|
47
|
-
rejected: number;
|
|
48
|
-
/** 因超过 sinceDays 没动而跳过。 */
|
|
49
|
-
skippedOld: number;
|
|
50
|
-
/** 🔴 范围内**一个路径都不存在** —— 与"很旧"分开数(它多半意味着声明写错了)。 */
|
|
51
|
-
skippedEmptyScope: number;
|
|
52
|
-
/** 用了窄兜底(该产品没有声明)的项目数。 */
|
|
53
|
-
defaultScope: number;
|
|
54
|
-
perProject: SyncStats[];
|
|
55
|
-
}
|
|
56
|
-
export declare function runSyncPass(opts: SyncPassOptions): Promise<PassSummary | null>;
|
|
57
|
-
/** 汇总行。每一类"没做"都占一格 —— 一个悄悄缩小了范围的同步器,和一个坏掉的同步器,日志上一模一样。 */
|
|
58
|
-
export declare function formatSummary(sum: PassSummary, started: number, opts: SyncPassOptions): string;
|
|
59
|
-
//# sourceMappingURL=syncRunner.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,119 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"syncRunner.js","sourceRoot":"","sources":["../../../src/providers/storage/syncRunner.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,47 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,84 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module seams/storage
|
|
3
|
-
* @description **对象存储 seam** —— 插件读写桶,不必自带 S3 客户端和一套凭据。
|
|
4
|
-
*
|
|
5
|
-
* @layer seam
|
|
6
|
-
*
|
|
7
|
-
* 类型:**甲 · 工厂型**(进程里一个实现)。
|
|
8
|
-
*
|
|
9
|
-
* 🔴 **"sps 不认识桶"这条前提在 2026-08-19 已被推翻**
|
|
10
|
-
* (`object-storage-and-workspace-sync.md` §2.1:对象存储改为 sps 的一项能力,sidecar 退役)。
|
|
11
|
-
* 理由是多节点下 studio / coral 都不在工作区那台机器上,唯一共享面就是桶;
|
|
12
|
-
* 而"studio 点发布 → 请 sps 产出发布包并落桶"本身就已经要求 sps 往桶里写字节。
|
|
13
|
-
* ⚠️ 我的记忆一度停在推翻之前 —— 读到那条旧结论时要先核文档。
|
|
14
|
-
*
|
|
15
|
-
* 🔴 **它只做搬字节,不做账。** id / 索引 / 缩略图 / 分类 / 配额 / 计费 / undo 都在 studio ——
|
|
16
|
-
* 一进 sps,这个公共 npm 包就认识"用户"和"配额"了。
|
|
17
|
-
*
|
|
18
|
-
* ⚠️ **权限是按前缀分的,而且很紧**(单写方规则):
|
|
19
|
-
* sps 今天的 key 只够 `assets/`;`library/**` 实测 Access Denied。
|
|
20
|
-
* 所以这一层**不承诺你能读任何前缀** —— 读不了会抛,而不是回空。
|
|
21
|
-
* "库是空的"和"我没权限"混成一个,人会去补素材而不是去补策略。
|
|
22
|
-
*/
|
|
23
|
-
import { type Context, Service } from '@deepseek-ai/cordis';
|
|
24
|
-
import type { StorageObject } from '../../providers/storage/StorageBackend.js';
|
|
25
|
-
export declare const STORAGE = "sps.storage";
|
|
26
|
-
export declare const category = "\u5B58\u50A8";
|
|
27
|
-
export declare const description = "\u5BF9\u8C61\u5B58\u50A8(\u8BFB/\u5217/\u5199):\u63D2\u4EF6\u8BFB\u5199\u6876,\u4E0D\u5FC5\u81EA\u5E26 S3 \u5BA2\u6237\u7AEF\u3002";
|
|
28
|
-
export default class StorageSeam extends Service {
|
|
29
|
-
constructor(ctx: Context);
|
|
30
|
-
/**
|
|
31
|
-
* 桶配没配。**没配时 `get`/`list`/`put` 会抛** —— 插件应当先问这一句,
|
|
32
|
-
* 而不是拿异常当分支(那样"没配"和"读不了"在日志里长得一样)。
|
|
33
|
-
*/
|
|
34
|
-
configured(): boolean;
|
|
35
|
-
/** 读一个对象。不存在 ⇒ `null`;连不上/没权限 ⇒ **抛**。 */
|
|
36
|
-
get(key: string): Promise<Buffer | null>;
|
|
37
|
-
/** 列一个前缀。 */
|
|
38
|
-
list(prefix: string): Promise<StorageObject[]>;
|
|
39
|
-
/**
|
|
40
|
-
* 写一个对象。
|
|
41
|
-
* ⚠️ `contentType` 拿不准时**自己决定**,别指望后端猜 ——
|
|
42
|
-
* 桶是直连的,缺省的 octet-stream 会让预览变成一次下载。
|
|
43
|
-
*/
|
|
44
|
-
put(key: string, bytes: Buffer, contentType?: string): Promise<void>;
|
|
45
|
-
private backend;
|
|
46
|
-
private must;
|
|
47
|
-
}
|
|
48
|
-
declare module '@deepseek-ai/cordis' {
|
|
49
|
-
interface Context {
|
|
50
|
-
'sps.storage': StorageSeam;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/seams/storage/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAG/F,eAAO,MAAM,OAAO,gBAAgB,CAAC;AAErC,eAAO,MAAM,QAAQ,iBAAO,CAAC;AAC7B,eAAO,MAAM,WAAW,uIAAmC,CAAC;AAE5D,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;gBAClC,GAAG,EAAE,OAAO;IAIxB;;;OAGG;IACH,UAAU,IAAI,OAAO;IAIrB,0CAA0C;IACpC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAI9C,aAAa;IACP,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAIpD;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1E,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,IAAI;CAOb;AAGD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,aAAa,EAAE,WAAW,CAAC;KAC5B;CACF"}
|