@lobu/connector-sdk 7.1.0 → 8.0.0
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/connector-runtime.d.ts +10 -2
- package/dist/connector-runtime.d.ts.map +1 -1
- package/dist/connector-runtime.js +21 -1
- package/dist/connector-runtime.js.map +1 -1
- package/dist/connector-types.d.ts +0 -6
- package/dist/connector-types.d.ts.map +1 -1
- package/dist/connector-types.js +0 -7
- package/dist/connector-types.js.map +1 -1
- package/dist/file-source.d.ts +112 -0
- package/dist/file-source.d.ts.map +1 -0
- package/dist/file-source.js +40 -0
- package/dist/file-source.js.map +1 -0
- package/dist/index.d.ts +12 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/sources/cache.d.ts +82 -0
- package/dist/sources/cache.d.ts.map +1 -0
- package/dist/sources/cache.js +169 -0
- package/dist/sources/cache.js.map +1 -0
- package/dist/sources/git-file-source.d.ts +33 -0
- package/dist/sources/git-file-source.d.ts.map +1 -0
- package/dist/sources/git-file-source.js +207 -0
- package/dist/sources/git-file-source.js.map +1 -0
- package/dist/sources/git-http.d.ts +48 -0
- package/dist/sources/git-http.d.ts.map +1 -0
- package/dist/sources/git-http.js +179 -0
- package/dist/sources/git-http.js.map +1 -0
- package/dist/sources/git-snapshot.d.ts +14 -0
- package/dist/sources/git-snapshot.d.ts.map +1 -0
- package/dist/sources/git-snapshot.js +96 -0
- package/dist/sources/git-snapshot.js.map +1 -0
- package/dist/sources/glob.d.ts +31 -0
- package/dist/sources/glob.d.ts.map +1 -0
- package/dist/sources/glob.js +129 -0
- package/dist/sources/glob.js.map +1 -0
- package/dist/sources/local-file-source.d.ts +29 -0
- package/dist/sources/local-file-source.d.ts.map +1 -0
- package/dist/sources/local-file-source.js +343 -0
- package/dist/sources/local-file-source.js.map +1 -0
- package/dist/sources/resolver.d.ts +6 -0
- package/dist/sources/resolver.d.ts.map +1 -0
- package/dist/sources/resolver.js +47 -0
- package/dist/sources/resolver.js.map +1 -0
- package/dist/sources/snapshot.d.ts +19 -0
- package/dist/sources/snapshot.d.ts.map +1 -0
- package/dist/sources/snapshot.js +91 -0
- package/dist/sources/snapshot.js.map +1 -0
- package/dist/sources/tarball-file-source.d.ts +28 -0
- package/dist/sources/tarball-file-source.d.ts.map +1 -0
- package/dist/sources/tarball-file-source.js +273 -0
- package/dist/sources/tarball-file-source.js.map +1 -0
- package/dist/types.d.ts +3 -65
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -3
- package/dist/event-taxonomy.d.ts +0 -3
- package/dist/event-taxonomy.d.ts.map +0 -1
- package/dist/event-taxonomy.js +0 -30
- package/dist/event-taxonomy.js.map +0 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalFileSource — adapter over a local directory.
|
|
3
|
+
*
|
|
4
|
+
* URI shape: `file:///absolute/path/`
|
|
5
|
+
*
|
|
6
|
+
* - `fetch()` stream-copies the source tree into a per-ref cache dir
|
|
7
|
+
* (`${WORKSPACE_DIR}/.lobu-cache/sources/<uri-hash>/refs/<ref>/`),
|
|
8
|
+
* sealing each file's sha256 during the copy so the manifest's hashes
|
|
9
|
+
* record exactly the bytes that landed in the cache. The Snapshot
|
|
10
|
+
* reads from that per-ref dir — never the live source — so
|
|
11
|
+
* `snapshot.ref` matches `sha256(snapshot.readFile(path))` for the
|
|
12
|
+
* bytes captured at fetch time, even under concurrent writes against
|
|
13
|
+
* the source. See `Snapshot` in `file-source.ts` for the honest
|
|
14
|
+
* same-UID mutability contract.
|
|
15
|
+
* - `ref` is the canonical manifest hash (sha256 of sorted `(path, sha256)`
|
|
16
|
+
* pairs).
|
|
17
|
+
* - `diffSinceRef(prevRef)`: re-walks the live directory, builds a fresh
|
|
18
|
+
* manifest, and diffs it against the stored per-ref manifest that
|
|
19
|
+
* matches `prevRef`. If no stored manifest matches (cache was cleared
|
|
20
|
+
* between runs), the diff treats every current file as `added`.
|
|
21
|
+
*/
|
|
22
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
23
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
24
|
+
import { mkdir, readFile, readdir, realpath, rename, rm, stat, } from 'node:fs/promises';
|
|
25
|
+
import { pipeline } from 'node:stream/promises';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
28
|
+
import { canonicalManifestRef, cachePathsFor, defaultCacheRoot, diffManifests, readAndVerifyMeta, readManifest, requireMeta, withSourceLock, writeManifest, writeMeta, } from './cache.js';
|
|
29
|
+
import { walkDirectoryRelative } from './glob.js';
|
|
30
|
+
import { DirectorySnapshot } from './snapshot.js';
|
|
31
|
+
export class LocalFileSource {
|
|
32
|
+
#uri;
|
|
33
|
+
#rootDir;
|
|
34
|
+
#paths;
|
|
35
|
+
/**
|
|
36
|
+
* When the local source root happens to *contain* the SDK cache dir
|
|
37
|
+
* (`${WORKSPACE_DIR}/.lobu-cache`) — typical when the source is the
|
|
38
|
+
* workspace root, OR when WORKSPACE_DIR is nested inside the source —
|
|
39
|
+
* we must NOT ingest our own cache files, or every fetch() would mutate
|
|
40
|
+
* the ref and self-pollute.
|
|
41
|
+
*
|
|
42
|
+
* Resolved at fetch() time using realpath() on both source root and the
|
|
43
|
+
* cache directory, so we exclude exactly the right subtree no matter
|
|
44
|
+
* where the cache lives relative to the source. If the cache is outside
|
|
45
|
+
* the source entirely, the predicate excludes nothing.
|
|
46
|
+
*/
|
|
47
|
+
#exclude = () => false;
|
|
48
|
+
constructor(uri) {
|
|
49
|
+
if (!uri.startsWith('file://')) {
|
|
50
|
+
throw new Error(`LocalFileSource: expected file:// URI, got ${uri}`);
|
|
51
|
+
}
|
|
52
|
+
this.#uri = uri;
|
|
53
|
+
this.#rootDir = fileURLToPath(uri);
|
|
54
|
+
this.#paths = cachePathsFor(uri);
|
|
55
|
+
}
|
|
56
|
+
fetch() {
|
|
57
|
+
return withSourceLock(this.#uri, async () => {
|
|
58
|
+
const s = await stat(this.#rootDir).catch(() => null);
|
|
59
|
+
if (!s || !s.isDirectory()) {
|
|
60
|
+
throw new Error(`LocalFileSource: ${this.#rootDir} is not a directory`);
|
|
61
|
+
}
|
|
62
|
+
await mkdir(this.#paths.root, { recursive: true });
|
|
63
|
+
await readAndVerifyMeta(this.#paths.metaPath, this.#uri);
|
|
64
|
+
// Recompute exclude predicate each fetch(): the cache dir's location
|
|
65
|
+
// relative to the source can shift if WORKSPACE_DIR changes between
|
|
66
|
+
// calls (different process, different cwd).
|
|
67
|
+
this.#exclude = await resolveCacheExclude(this.#rootDir);
|
|
68
|
+
// Race-free pipeline:
|
|
69
|
+
//
|
|
70
|
+
// 1. List the source's relative file paths (no byte reads yet).
|
|
71
|
+
// 2. For each file: stream-pipe the source bytes through a sha256 hash
|
|
72
|
+
// AND into a staging copy in a single pass. The per-file hash is
|
|
73
|
+
// SEALED at copy time — the bytes that landed in staging are
|
|
74
|
+
// identical to the bytes the hash was computed over, and there
|
|
75
|
+
// is no second pass over staging that could observe a post-copy
|
|
76
|
+
// mutation. THIS is the actual mechanism that pins
|
|
77
|
+
// `snapshot.ref` to the bytes the Snapshot will read; an earlier
|
|
78
|
+
// pass also chmod-locked the per-ref dir to 0500/0400, but
|
|
79
|
+
// chmod doesn't bind same-UID writers (the owner can re-mode
|
|
80
|
+
// their own files), so it was theater against the threat model
|
|
81
|
+
// that matters and has been removed.
|
|
82
|
+
// 3. Staging lives INSIDE the cache root at
|
|
83
|
+
// `${root}/refs/<crypto-random-32hex>` — same filesystem as the
|
|
84
|
+
// destination per-ref dir, so `rename()` is atomic.
|
|
85
|
+
// 4. Compute the canonical ref from the sealed per-file hashes.
|
|
86
|
+
// 5. If `refs/<ref>` already exists, drop staging; else rename
|
|
87
|
+
// staging into the per-ref dir.
|
|
88
|
+
//
|
|
89
|
+
// The Snapshot reads from the per-ref dir — never from the live
|
|
90
|
+
// source — so `snapshot.ref` matches the bytes Snapshot.readFile
|
|
91
|
+
// returns at the moment fetch() resolved. Same-UID post-fetch
|
|
92
|
+
// mutation is documented as out-of-scope in the Snapshot contract.
|
|
93
|
+
const relPaths = await listRelativeFiles(this.#rootDir, this.#exclude);
|
|
94
|
+
await mkdir(join(this.#paths.root, 'refs'), { recursive: true });
|
|
95
|
+
const stagingDir = join(this.#paths.root, 'refs', randomBytes(16).toString('hex'));
|
|
96
|
+
await mkdir(stagingDir, { recursive: true, mode: 0o700 });
|
|
97
|
+
let stagingMoved = false;
|
|
98
|
+
let refDir;
|
|
99
|
+
let files;
|
|
100
|
+
let ref;
|
|
101
|
+
try {
|
|
102
|
+
files = await streamCopyAndHash(this.#rootDir, stagingDir, relPaths);
|
|
103
|
+
ref = canonicalManifestRef(files);
|
|
104
|
+
refDir = perRefDir(this.#paths.root, ref);
|
|
105
|
+
if (await dirExists(refDir)) {
|
|
106
|
+
// Same content already installed; throw away the staging copy.
|
|
107
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
108
|
+
stagingMoved = true;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
await rename(stagingDir, refDir);
|
|
112
|
+
stagingMoved = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
if (!stagingMoved) {
|
|
117
|
+
await rm(stagingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const manifest = {
|
|
121
|
+
ref,
|
|
122
|
+
files,
|
|
123
|
+
fetched_at: new Date().toISOString(),
|
|
124
|
+
};
|
|
125
|
+
await writeManifest(this.#paths.manifestPath, manifest);
|
|
126
|
+
await writeMeta(this.#paths.metaPath, { uri: this.#uri, kind: 'local' });
|
|
127
|
+
// Also persist a per-ref copy so diffSinceRef can look up prior refs.
|
|
128
|
+
await writePerRefManifest(this.#paths.root, manifest);
|
|
129
|
+
// Keep the per-ref cache bounded — `refs/` accumulates a new dir per
|
|
130
|
+
// distinct content state if the source is rewritten. Always preserve
|
|
131
|
+
// the just-returned dir (`refDir`), including the cache-hit branch
|
|
132
|
+
// where its mtime is older than newer entries — pruning it would
|
|
133
|
+
// ENOENT the Snapshot we just handed back. Mirrors the
|
|
134
|
+
// `protectedRefDir` argument tarball-file-source.ts already uses.
|
|
135
|
+
await pruneOldRefDirs(this.#paths.root, MAX_REF_DIRS, refDir);
|
|
136
|
+
return new DirectorySnapshot(refDir, ref);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
diffSinceRef(prevRef) {
|
|
140
|
+
return withSourceLock(this.#uri, async () => {
|
|
141
|
+
await requireMeta(this.#paths.metaPath, this.#uri);
|
|
142
|
+
this.#exclude = await resolveCacheExclude(this.#rootDir);
|
|
143
|
+
const files = await collectFilesFromLive(this.#rootDir, this.#exclude);
|
|
144
|
+
const curRef = canonicalManifestRef(files);
|
|
145
|
+
if (curRef === prevRef)
|
|
146
|
+
return { added: [], modified: [], removed: [] };
|
|
147
|
+
const prev = await readPerRefManifest(this.#paths.root, prevRef);
|
|
148
|
+
const next = { ref: curRef, files, fetched_at: new Date().toISOString() };
|
|
149
|
+
if (!prev) {
|
|
150
|
+
// No record of the previous ref — caller's checkpoint references a ref
|
|
151
|
+
// we no longer have on disk. Treat as "everything is new" so the
|
|
152
|
+
// connector re-ingests rather than silently dropping data.
|
|
153
|
+
return { added: files.map((f) => f.path), modified: [], removed: [] };
|
|
154
|
+
}
|
|
155
|
+
return diffManifests(prev, next);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Compute the exclude predicate for `.lobu-cache`. Realpaths both source root
|
|
161
|
+
* and `${cacheRoot}/.lobu-cache`, then:
|
|
162
|
+
* - If the cache dir is contained under the source root, exclude that exact
|
|
163
|
+
* relative subtree (POSIX-separated).
|
|
164
|
+
* - Otherwise return a no-op predicate — no exclusion needed.
|
|
165
|
+
*
|
|
166
|
+
* This handles three layouts correctly:
|
|
167
|
+
* (a) source root === workspace root → exclude `.lobu-cache/`
|
|
168
|
+
* (b) WORKSPACE_DIR is nested inside source (e.g. `source/workspace/`) →
|
|
169
|
+
* exclude `workspace/.lobu-cache/`
|
|
170
|
+
* (c) workspace lives outside source → no exclusion
|
|
171
|
+
*
|
|
172
|
+
* Realpath defends against symlinked workspace dirs.
|
|
173
|
+
*/
|
|
174
|
+
async function resolveCacheExclude(sourceRoot) {
|
|
175
|
+
const cacheBase = join(defaultCacheRoot(), '.lobu-cache');
|
|
176
|
+
const realSource = await realpath(sourceRoot).catch(() => sourceRoot);
|
|
177
|
+
const realCache = await realpath(cacheBase).catch(() => cacheBase);
|
|
178
|
+
const rel = relative(realSource, realCache);
|
|
179
|
+
if (rel === '' || rel.startsWith('..') || rel.split(sep).includes('..')) {
|
|
180
|
+
// cache is outside source (or coincides with the source root, which is
|
|
181
|
+
// degenerate — nothing meaningful to exclude). No-op predicate.
|
|
182
|
+
return () => false;
|
|
183
|
+
}
|
|
184
|
+
const posixRel = sep === '/' ? rel : rel.split(sep).join('/');
|
|
185
|
+
const prefix = `${posixRel}/`;
|
|
186
|
+
return (relPath) => relPath === posixRel || relPath.startsWith(prefix);
|
|
187
|
+
}
|
|
188
|
+
function perRefDir(root, ref) {
|
|
189
|
+
return join(root, 'refs', ref);
|
|
190
|
+
}
|
|
191
|
+
async function dirExists(p) {
|
|
192
|
+
const s = await stat(p).catch(() => null);
|
|
193
|
+
return !!s && s.isDirectory();
|
|
194
|
+
}
|
|
195
|
+
/** Max number of `refs/<hash>` per-ref directories kept on disk. */
|
|
196
|
+
const MAX_REF_DIRS = 3;
|
|
197
|
+
/** List relative paths from the source root, applying the exclude predicate. */
|
|
198
|
+
async function listRelativeFiles(rootDir, exclude) {
|
|
199
|
+
const out = [];
|
|
200
|
+
for await (const rel of walkDirectoryRelative(rootDir)) {
|
|
201
|
+
if (exclude(rel))
|
|
202
|
+
continue;
|
|
203
|
+
out.push(rel);
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Stream-copy each tracked file from `sourceRoot` into `stagingDir`, feeding
|
|
209
|
+
* the bytes through a sha256 hasher in the same pass. Per-file hash is
|
|
210
|
+
* SEALED at copy time — the manifest entry records exactly the bytes that
|
|
211
|
+
* were written into staging. There is no second pass over staging that
|
|
212
|
+
* could observe a post-copy mutation.
|
|
213
|
+
*
|
|
214
|
+
* Why streaming instead of copy-on-write + post-hash: COW (`COPYFILE_FICLONE`)
|
|
215
|
+
* shares pages with the source until either side writes; a concurrent
|
|
216
|
+
* truncate-and-rewrite on the source breaks the share and may race the
|
|
217
|
+
* hash pass. A read-then-hash-then-write streaming pipe seals the bytes
|
|
218
|
+
* the instant they leave the source.
|
|
219
|
+
*/
|
|
220
|
+
async function streamCopyAndHash(sourceRoot, stagingDir, relPaths) {
|
|
221
|
+
const out = [];
|
|
222
|
+
for (const rel of relPaths) {
|
|
223
|
+
const src = join(sourceRoot, rel);
|
|
224
|
+
const dst = join(stagingDir, rel);
|
|
225
|
+
await mkdir(dirname(dst), { recursive: true });
|
|
226
|
+
const hash = createHash('sha256');
|
|
227
|
+
const reader = createReadStream(src);
|
|
228
|
+
reader.on('data', (chunk) => {
|
|
229
|
+
hash.update(chunk);
|
|
230
|
+
});
|
|
231
|
+
await pipeline(reader, createWriteStream(dst));
|
|
232
|
+
out.push({ path: rel, sha256: hash.digest('hex') });
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Hash files directly from the live source root — used by `diffSinceRef()`,
|
|
238
|
+
* which doesn't return a Snapshot and only needs a manifest to compare
|
|
239
|
+
* against. Race-tolerant: a mid-walk write just shows up as part of the
|
|
240
|
+
* next diff.
|
|
241
|
+
*/
|
|
242
|
+
async function collectFilesFromLive(rootDir, exclude) {
|
|
243
|
+
const out = [];
|
|
244
|
+
for await (const rel of walkDirectoryRelative(rootDir)) {
|
|
245
|
+
if (exclude(rel))
|
|
246
|
+
continue;
|
|
247
|
+
const abs = join(rootDir, rel);
|
|
248
|
+
const buf = await readFile(abs);
|
|
249
|
+
const sha = createHash('sha256').update(buf).digest('hex');
|
|
250
|
+
out.push({ path: rel, sha256: sha });
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Keep at most `keep` per-ref directories under `${root}/refs/`. Sorted by
|
|
256
|
+
* mtime descending; the oldest are rm-rf'd. `protectedRefDir` is always
|
|
257
|
+
* preserved regardless of mtime — the cache-hit branch in fetch() returns
|
|
258
|
+
* an existing dir whose mtime may be older than newer entries, and the
|
|
259
|
+
* Snapshot we just handed back is reading from it. Pre-this-guard, the
|
|
260
|
+
* mtime sort happily pruned exactly the dir the caller was about to read,
|
|
261
|
+
* leaving the Snapshot pointing at an ENOENT. Matches the
|
|
262
|
+
* `protectedRefDir` parameter tarball-file-source.ts already uses
|
|
263
|
+
* (lines 137 + 228-273).
|
|
264
|
+
*
|
|
265
|
+
* Snapshots already handed out keep working as long as their backing
|
|
266
|
+
* dir wasn't pruned — `keep=3` accommodates a fresh fetch plus two
|
|
267
|
+
* in-flight overlapping syncs.
|
|
268
|
+
*
|
|
269
|
+
* Filters by name shape (64-hex sha256). Skips in-flight staging dirs
|
|
270
|
+
* (32-hex random names co-located in `refs/`) AND any legacy `.staging.*`
|
|
271
|
+
* directories from older builds. Per-ref manifest JSON files
|
|
272
|
+
* (`<ref>.json`) are files, not directories, and are kept indefinitely
|
|
273
|
+
* so historical diffs work even after the data dir is gone.
|
|
274
|
+
*
|
|
275
|
+
* v1 limitation: this prune is process-local (`withSourceLock` is an
|
|
276
|
+
* in-memory mutex). Two processes sharing the same
|
|
277
|
+
* `${WORKSPACE_DIR}/.lobu-cache` can race-prune each other's per-ref
|
|
278
|
+
* dirs — including the one a peer's Snapshot is reading. v1 supports
|
|
279
|
+
* one cache owner per workspace; multi-process sharing would need a
|
|
280
|
+
* filesystem advisory lock around fetch+prune.
|
|
281
|
+
*/
|
|
282
|
+
async function pruneOldRefDirs(root, keep, protectedRefDir) {
|
|
283
|
+
const refsRoot = join(root, 'refs');
|
|
284
|
+
let entries;
|
|
285
|
+
try {
|
|
286
|
+
entries = await readdir(refsRoot, { withFileTypes: true });
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
if (err.code === 'ENOENT')
|
|
290
|
+
return;
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
const candidates = [];
|
|
294
|
+
for (const ent of entries) {
|
|
295
|
+
if (!ent.isDirectory())
|
|
296
|
+
continue;
|
|
297
|
+
// Only touch completed per-ref dirs (64-hex sha256). In-flight staging
|
|
298
|
+
// dirs use a 32-hex randomBytes name and must not be pruned.
|
|
299
|
+
if (!/^[a-f0-9]{64}$/.test(ent.name))
|
|
300
|
+
continue;
|
|
301
|
+
const abs = join(refsRoot, ent.name);
|
|
302
|
+
try {
|
|
303
|
+
const s = await stat(abs);
|
|
304
|
+
candidates.push({
|
|
305
|
+
name: ent.name,
|
|
306
|
+
abs,
|
|
307
|
+
mtimeMs: s.mtimeMs,
|
|
308
|
+
protected: abs === protectedRefDir,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// Skip — concurrent prune from another process is fine.
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (candidates.length <= keep)
|
|
316
|
+
return;
|
|
317
|
+
const protectedDirs = candidates.filter((c) => c.protected);
|
|
318
|
+
const others = candidates
|
|
319
|
+
.filter((c) => !c.protected)
|
|
320
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
321
|
+
const keepers = new Set([
|
|
322
|
+
...protectedDirs.map((c) => c.name),
|
|
323
|
+
...others.slice(0, Math.max(0, keep - protectedDirs.length)).map((c) => c.name),
|
|
324
|
+
]);
|
|
325
|
+
for (const c of candidates) {
|
|
326
|
+
if (keepers.has(c.name))
|
|
327
|
+
continue;
|
|
328
|
+
await rm(c.abs, { recursive: true, force: true }).catch(() => undefined);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Per-ref manifest storage under `<root>/refs/<ref>.json`. Lets a connector
|
|
333
|
+
* keep checkpointing arbitrary `prevRef`s without us guessing.
|
|
334
|
+
*/
|
|
335
|
+
async function writePerRefManifest(root, manifest) {
|
|
336
|
+
const { mkdir } = await import('node:fs/promises');
|
|
337
|
+
await mkdir(join(root, 'refs'), { recursive: true });
|
|
338
|
+
await writeManifest(join(root, 'refs', `${manifest.ref}.json`), manifest);
|
|
339
|
+
}
|
|
340
|
+
async function readPerRefManifest(root, ref) {
|
|
341
|
+
return readManifest(join(root, 'refs', `${ref}.json`));
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=local-file-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-file-source.js","sourceRoot":"","sources":["../../src/sources/local-file-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/sources/resolver.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAK1D,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CA8DxD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { GitFileSource } from './git-file-source.js';
|
|
2
|
+
import { LocalFileSource } from './local-file-source.js';
|
|
3
|
+
import { TarballFileSource } from './tarball-file-source.js';
|
|
4
|
+
export function resolveUri(uri) {
|
|
5
|
+
if (typeof uri !== 'string' || uri.length === 0) {
|
|
6
|
+
throw new Error('fileSystemSourceFromUri: uri must be a non-empty string');
|
|
7
|
+
}
|
|
8
|
+
// git+ssh:// — explicit reject with helpful suggestion.
|
|
9
|
+
if (uri.startsWith('git+ssh://') || uri.startsWith('ssh://')) {
|
|
10
|
+
throw new Error(`Unsupported scheme: ${uri.split('://')[0]}://. SSH auth requires operator keys ` +
|
|
11
|
+
`and is out of scope for v1. Use git+https://github.com/owner/repo.git instead.`);
|
|
12
|
+
}
|
|
13
|
+
// git+http:// — explicit reject (no plaintext clones).
|
|
14
|
+
if (uri.startsWith('git+http://')) {
|
|
15
|
+
throw new Error(`Unsupported scheme: git+http://. Plaintext git fetch is rejected; use git+https://.`);
|
|
16
|
+
}
|
|
17
|
+
if (uri.startsWith('git+https://')) {
|
|
18
|
+
return new GitFileSource(uri);
|
|
19
|
+
}
|
|
20
|
+
if (uri.startsWith('file://')) {
|
|
21
|
+
return new LocalFileSource(uri);
|
|
22
|
+
}
|
|
23
|
+
if (uri.startsWith('https://')) {
|
|
24
|
+
// Tarball schemes: must end in .tar.gz or .tgz (query-stripped).
|
|
25
|
+
const pathPart = uri.split('?')[0]?.split('#')[0] ?? uri;
|
|
26
|
+
if (pathPart.endsWith('.tar.gz') || pathPart.endsWith('.tgz')) {
|
|
27
|
+
return new TarballFileSource(uri);
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`Unsupported HTTPS URL: ${uri}. Only .tar.gz / .tgz are supported in v1 ` +
|
|
30
|
+
`(no .zip, .7z, or raw directories).`);
|
|
31
|
+
}
|
|
32
|
+
if (uri.startsWith('http://')) {
|
|
33
|
+
throw new Error(`Unsupported scheme: http://. Plaintext tarball fetch is rejected; use https://.`);
|
|
34
|
+
}
|
|
35
|
+
// Future schemes — surface them by name so the error tells operators what
|
|
36
|
+
// we know about, not just "unknown".
|
|
37
|
+
const knownFutureSchemes = ['s3://', 'gs://', 'azure://', 'gcs://', 'r2://'];
|
|
38
|
+
for (const scheme of knownFutureSchemes) {
|
|
39
|
+
if (uri.startsWith(scheme)) {
|
|
40
|
+
throw new Error(`Scheme ${scheme} is reserved for a future implementation and not supported ` +
|
|
41
|
+
`in v1 of FileSystemSource.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Unsupported FileSystemSource URI: ${uri}. ` +
|
|
45
|
+
`Expected git+https://…, https://…tar.gz, or file:///…`);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/sources/resolver.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,wDAAwD;IACxD,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,uBAAuB,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,uCAAuC;YAC/E,gFAAgF,CACnF,CAAC;IACJ,CAAC;IAED,uDAAuD;IACvD,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,qFAAqF,CACtF,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,iEAAiE;QACjE,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;QACzD,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9D,OAAO,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,IAAI,KAAK,CACb,0BAA0B,GAAG,4CAA4C;YACvE,qCAAqC,CACxC,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,iFAAiF,CAClF,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,qCAAqC;IACrC,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7E,KAAK,MAAM,MAAM,IAAI,kBAAkB,EAAE,CAAC;QACxC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,6DAA6D;gBAC3E,4BAA4B,CAC/B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,qCAAqC,GAAG,IAAI;QAC1C,uDAAuD,CAC1D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snapshot } from '../file-source.js';
|
|
2
|
+
export interface DirectorySnapshotOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Predicate run against each POSIX-style relative path during walk and
|
|
5
|
+
* read. Returning `true` excludes the path. Used to hide internal dirs
|
|
6
|
+
* (`.git/` for git sources, `.lobu-cache/` for local sources rooted at
|
|
7
|
+
* the workspace).
|
|
8
|
+
*/
|
|
9
|
+
exclude?: (relativePath: string) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class DirectorySnapshot implements Snapshot {
|
|
12
|
+
#private;
|
|
13
|
+
readonly ref: string;
|
|
14
|
+
constructor(rootDir: string, ref: string, options?: DirectorySnapshotOptions);
|
|
15
|
+
walkFiles(glob: string): AsyncIterable<string>;
|
|
16
|
+
readFile(relativePath: string): Promise<Buffer>;
|
|
17
|
+
readText(relativePath: string): Promise<string>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/sources/snapshot.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;CAC7C;AAED,qBAAa,iBAAkB,YAAW,QAAQ;;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAMT,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;IAUzE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;IAO/C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI/C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA6CtD"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concrete `Snapshot` backed by a directory on disk. Reused by all three
|
|
3
|
+
* source implementations — the rootDir stays hidden from the connector.
|
|
4
|
+
*
|
|
5
|
+
* Two layers of defense:
|
|
6
|
+
*
|
|
7
|
+
* 1. Lexical: `#resolveSafe` rejects absolute paths and `..` escapes.
|
|
8
|
+
* 2. Runtime: `readFile` / `readText` resolve the *real* path (following
|
|
9
|
+
* symlinks) and verify it still falls under the snapshot root. This
|
|
10
|
+
* blocks symlink-based escape from extracted tarballs / local sources
|
|
11
|
+
* that happen to contain a `link -> /etc/passwd` symlink.
|
|
12
|
+
*
|
|
13
|
+
* An optional `exclude` predicate skips entries during `walkFiles` AND
|
|
14
|
+
* denies them in `readFile`/`readText` — used to hide `.git/` from git
|
|
15
|
+
* snapshots and `.lobu-cache/` from local-source snapshots rooted at the
|
|
16
|
+
* workspace.
|
|
17
|
+
*/
|
|
18
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
19
|
+
import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
20
|
+
import { matchesGlob, walkDirectoryRelative } from './glob.js';
|
|
21
|
+
export class DirectorySnapshot {
|
|
22
|
+
ref;
|
|
23
|
+
/** @internal — NOT exported on the public Snapshot interface. */
|
|
24
|
+
#rootDir;
|
|
25
|
+
#realRootDir;
|
|
26
|
+
#exclude;
|
|
27
|
+
constructor(rootDir, ref, options = {}) {
|
|
28
|
+
this.#rootDir = resolve(rootDir);
|
|
29
|
+
this.ref = ref;
|
|
30
|
+
this.#exclude = options.exclude ?? (() => false);
|
|
31
|
+
// Realpath the root once; symlink checks resolve against the canonical
|
|
32
|
+
// form so we don't reject a legitimate request just because the cache
|
|
33
|
+
// directory itself sits under a symlinked path.
|
|
34
|
+
this.#realRootDir = realpath(this.#rootDir).catch(() => this.#rootDir);
|
|
35
|
+
}
|
|
36
|
+
async *walkFiles(glob) {
|
|
37
|
+
for await (const rel of walkDirectoryRelative(this.#rootDir)) {
|
|
38
|
+
if (this.#exclude(rel))
|
|
39
|
+
continue;
|
|
40
|
+
if (matchesGlob(rel, glob))
|
|
41
|
+
yield rel;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async readFile(relativePath) {
|
|
45
|
+
return readFile(await this.#resolveSafe(relativePath));
|
|
46
|
+
}
|
|
47
|
+
async readText(relativePath) {
|
|
48
|
+
return readFile(await this.#resolveSafe(relativePath), 'utf8');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve `relativePath` inside `rootDir`. Layered defenses:
|
|
52
|
+
* 1. Reject absolute paths.
|
|
53
|
+
* 2. Reject `..` escapes (lexical).
|
|
54
|
+
* 3. Reject paths matched by the exclude predicate.
|
|
55
|
+
* 4. Resolve the path with `realpath()` (follows symlinks). Verify the
|
|
56
|
+
* canonical form still falls under the canonical root.
|
|
57
|
+
*/
|
|
58
|
+
async #resolveSafe(relativePath) {
|
|
59
|
+
if (isAbsolute(relativePath)) {
|
|
60
|
+
throw new Error(`Snapshot.readFile: absolute paths are not allowed (${relativePath})`);
|
|
61
|
+
}
|
|
62
|
+
const joined = normalize(join(this.#rootDir, relativePath));
|
|
63
|
+
const rel = relative(this.#rootDir, joined);
|
|
64
|
+
if (rel.startsWith('..') || isAbsolute(rel) || rel.split(sep).includes('..')) {
|
|
65
|
+
throw new Error(`Snapshot.readFile: path escapes snapshot root (${relativePath})`);
|
|
66
|
+
}
|
|
67
|
+
const posixRel = sep === '/' ? rel : rel.split(sep).join('/');
|
|
68
|
+
if (this.#exclude(posixRel)) {
|
|
69
|
+
throw new Error(`Snapshot.readFile: path is excluded from the snapshot (${relativePath})`);
|
|
70
|
+
}
|
|
71
|
+
// Runtime defense: follow symlinks and verify the target is under root.
|
|
72
|
+
let realTarget;
|
|
73
|
+
try {
|
|
74
|
+
realTarget = await realpath(joined);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
// File doesn't exist or isn't reachable — let readFile() surface the
|
|
78
|
+
// canonical ENOENT.
|
|
79
|
+
if (err.code === 'ENOENT')
|
|
80
|
+
return joined;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
const realRoot = await this.#realRootDir;
|
|
84
|
+
const relReal = relative(realRoot, realTarget);
|
|
85
|
+
if (relReal.startsWith('..') || isAbsolute(relReal)) {
|
|
86
|
+
throw new Error(`Snapshot.readFile: path resolves outside snapshot root via symlink (${relativePath})`);
|
|
87
|
+
}
|
|
88
|
+
return realTarget;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/sources/snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TarballFileSource — download a remote `.tar.gz`/`.tgz`, extract once,
|
|
3
|
+
* snapshot from the extraction directory.
|
|
4
|
+
*
|
|
5
|
+
* - Download via `undici.request` (Node built-in, no extra dep).
|
|
6
|
+
* - Extract via `tar` (npm `tar`, ~50KB) — streaming, won't buffer the
|
|
7
|
+
* archive in memory.
|
|
8
|
+
* - Atomic install: extract to `${root}/snapshot.tmp.<rand>` and rename
|
|
9
|
+
* over `${root}/snapshot/` once extraction completes. Partial fetches
|
|
10
|
+
* don't corrupt the cache.
|
|
11
|
+
* - `ref` is the canonical manifest hash; identical content → identical
|
|
12
|
+
* `ref` regardless of when/where it was fetched.
|
|
13
|
+
* - `diffSinceRef` re-fetches and compares against the per-ref manifest
|
|
14
|
+
* stored alongside the cache. Full tarballs have no incremental wire
|
|
15
|
+
* support — the gain is "did anything change?" not "send me the delta".
|
|
16
|
+
*/
|
|
17
|
+
import type { FileDelta, FileSystemSource, Snapshot } from '../file-source.js';
|
|
18
|
+
export interface TarballFileSourceOptions {
|
|
19
|
+
/** Strip the leading directory component during extraction (default 0). */
|
|
20
|
+
stripComponents?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class TarballFileSource implements FileSystemSource {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(uri: string, opts?: TarballFileSourceOptions);
|
|
25
|
+
fetch(): Promise<Snapshot>;
|
|
26
|
+
diffSinceRef(prevRef: string): Promise<FileDelta>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=tarball-file-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tarball-file-source.d.ts","sourceRoot":"","sources":["../../src/sources/tarball-file-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAkB/E,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,iBAAkB,YAAW,gBAAgB;;gBAK5C,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,wBAA6B;IAgB5D,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;IA8EpB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CA2BxD"}
|