@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.
Files changed (59) hide show
  1. package/dist/connector-runtime.d.ts +10 -2
  2. package/dist/connector-runtime.d.ts.map +1 -1
  3. package/dist/connector-runtime.js +21 -1
  4. package/dist/connector-runtime.js.map +1 -1
  5. package/dist/connector-types.d.ts +0 -6
  6. package/dist/connector-types.d.ts.map +1 -1
  7. package/dist/connector-types.js +0 -7
  8. package/dist/connector-types.js.map +1 -1
  9. package/dist/file-source.d.ts +112 -0
  10. package/dist/file-source.d.ts.map +1 -0
  11. package/dist/file-source.js +40 -0
  12. package/dist/file-source.js.map +1 -0
  13. package/dist/index.d.ts +12 -9
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7 -5
  16. package/dist/index.js.map +1 -1
  17. package/dist/sources/cache.d.ts +82 -0
  18. package/dist/sources/cache.d.ts.map +1 -0
  19. package/dist/sources/cache.js +169 -0
  20. package/dist/sources/cache.js.map +1 -0
  21. package/dist/sources/git-file-source.d.ts +33 -0
  22. package/dist/sources/git-file-source.d.ts.map +1 -0
  23. package/dist/sources/git-file-source.js +207 -0
  24. package/dist/sources/git-file-source.js.map +1 -0
  25. package/dist/sources/git-http.d.ts +48 -0
  26. package/dist/sources/git-http.d.ts.map +1 -0
  27. package/dist/sources/git-http.js +179 -0
  28. package/dist/sources/git-http.js.map +1 -0
  29. package/dist/sources/git-snapshot.d.ts +14 -0
  30. package/dist/sources/git-snapshot.d.ts.map +1 -0
  31. package/dist/sources/git-snapshot.js +96 -0
  32. package/dist/sources/git-snapshot.js.map +1 -0
  33. package/dist/sources/glob.d.ts +31 -0
  34. package/dist/sources/glob.d.ts.map +1 -0
  35. package/dist/sources/glob.js +129 -0
  36. package/dist/sources/glob.js.map +1 -0
  37. package/dist/sources/local-file-source.d.ts +29 -0
  38. package/dist/sources/local-file-source.d.ts.map +1 -0
  39. package/dist/sources/local-file-source.js +343 -0
  40. package/dist/sources/local-file-source.js.map +1 -0
  41. package/dist/sources/resolver.d.ts +6 -0
  42. package/dist/sources/resolver.d.ts.map +1 -0
  43. package/dist/sources/resolver.js +47 -0
  44. package/dist/sources/resolver.js.map +1 -0
  45. package/dist/sources/snapshot.d.ts +19 -0
  46. package/dist/sources/snapshot.d.ts.map +1 -0
  47. package/dist/sources/snapshot.js +91 -0
  48. package/dist/sources/snapshot.js.map +1 -0
  49. package/dist/sources/tarball-file-source.d.ts +28 -0
  50. package/dist/sources/tarball-file-source.d.ts.map +1 -0
  51. package/dist/sources/tarball-file-source.js +273 -0
  52. package/dist/sources/tarball-file-source.js.map +1 -0
  53. package/dist/types.d.ts +3 -65
  54. package/dist/types.d.ts.map +1 -1
  55. package/package.json +6 -3
  56. package/dist/event-taxonomy.d.ts +0 -3
  57. package/dist/event-taxonomy.d.ts.map +0 -1
  58. package/dist/event-taxonomy.js +0 -30
  59. 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,6 @@
1
+ /**
2
+ * URI → FileSystemSource resolver. Imported by `file-source.ts`.
3
+ */
4
+ import type { FileSystemSource } from '../file-source.js';
5
+ export declare function resolveUri(uri: string): FileSystemSource;
6
+ //# sourceMappingURL=resolver.d.ts.map
@@ -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"}