@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,169 @@
1
+ /**
2
+ * Cache-layout helpers shared by all FileSystemSource implementations.
3
+ *
4
+ * ${WORKSPACE_DIR}/.lobu-cache/sources/<sha256(uri)[:32]>/
5
+ * ├── snapshot/ ← actual files (connector-visible via Snapshot.readFile)
6
+ * ├── manifest.json ← { ref, files: [{ path, sha256 }], fetched_at }
7
+ * └── meta.json ← { uri, kind }
8
+ *
9
+ * - `WORKSPACE_DIR` env var is the cache root; falls back to `process.cwd()`.
10
+ * - URI is hashed (sha256, first 32 hex chars = 128 bits) for filesystem-safe
11
+ * naming. 128 bits is collision-resistant even against adversarial URIs
12
+ * (1e19 URIs before 50% collision odds).
13
+ * - One cache directory per URI — same URI yields the same directory across
14
+ * runs so re-fetch is incremental for git, manifest-comparable for the rest.
15
+ * On reuse, `meta.json`'s `uri` field is verified to match — a mismatch
16
+ * (theoretical hash collision OR cache-root reuse across schemes) throws.
17
+ * - The cache layout is intentionally NOT exported on the public API; this
18
+ * module is internal to the SDK.
19
+ */
20
+ import { createHash } from 'node:crypto';
21
+ import { readFile, writeFile } from 'node:fs/promises';
22
+ import { join } from 'node:path';
23
+ /** Build the cache paths for `uri`, anchored under `cacheRoot`. */
24
+ export function cachePathsFor(uri, cacheRoot = defaultCacheRoot()) {
25
+ const hash = createHash('sha256').update(uri).digest('hex').slice(0, 32);
26
+ const root = join(cacheRoot, '.lobu-cache', 'sources', hash);
27
+ return {
28
+ root,
29
+ snapshotDir: join(root, 'snapshot'),
30
+ manifestPath: join(root, 'manifest.json'),
31
+ metaPath: join(root, 'meta.json'),
32
+ };
33
+ }
34
+ /**
35
+ * Read `meta.json` if present and assert it matches `uri`. Throws on URI
36
+ * mismatch (defends against an adversarial collision OR an operator copying
37
+ * a cache dir across sources). Returns `null` for a fresh cache.
38
+ */
39
+ export async function readAndVerifyMeta(metaPath, expectedUri) {
40
+ const meta = await readMeta(metaPath);
41
+ if (!meta)
42
+ return null;
43
+ if (meta.uri !== expectedUri) {
44
+ throw new Error(`FileSystemSource cache mismatch: ${metaPath} belongs to ${meta.uri}, ` +
45
+ `but ${expectedUri} was requested. Refusing to reuse cache directory.`);
46
+ }
47
+ return meta;
48
+ }
49
+ /**
50
+ * Like `readAndVerifyMeta`, but rejects when the cache is uninitialised.
51
+ * Use this from `diffSinceRef()` paths — calling diff before fetch is a
52
+ * caller bug, and a missing meta also means a stale/collided cache dir
53
+ * should not be silently consumed.
54
+ */
55
+ export async function requireMeta(metaPath, expectedUri) {
56
+ const meta = await readAndVerifyMeta(metaPath, expectedUri);
57
+ if (!meta) {
58
+ throw new Error(`FileSystemSource: source not fetched yet — call fetch() before diffSinceRef() (${expectedUri})`);
59
+ }
60
+ return meta;
61
+ }
62
+ /**
63
+ * Per-source mutex. Same URI → shared `Promise` chain so concurrent
64
+ * `fetch()` calls serialize. Process-local only — fine for the embedded
65
+ * worker model where one worker subprocess owns its cache.
66
+ *
67
+ * v1 limitation: two processes sharing the same
68
+ * `${WORKSPACE_DIR}/.lobu-cache` are NOT coordinated by this lock —
69
+ * each gets its own in-memory `_sourceLocks` map, and they can
70
+ * race-prune each other's per-ref dirs (see `pruneOldRefDirs` in each
71
+ * source impl). v1 assumes one cache owner per workspace. If we ever
72
+ * need multi-process sharing, replace this with a filesystem advisory
73
+ * lock (e.g. `proper-lockfile` against `${root}/.lock`) around
74
+ * fetch+prune.
75
+ *
76
+ * The map stores the *guarded* (error-swallowed) promise so a rejection in
77
+ * `fn` doesn't poison the chain. Identity comparison in `finally` uses the
78
+ * same stored reference, so cleanup actually removes the entry — an
79
+ * earlier draft created a fresh `.catch()` inside `finally` and leaked one
80
+ * entry per distinct URI.
81
+ */
82
+ const _sourceLocks = new Map();
83
+ export async function withSourceLock(uri, fn) {
84
+ const prev = _sourceLocks.get(uri) ?? Promise.resolve();
85
+ const next = prev.then(fn, fn);
86
+ const guarded = next.catch(() => undefined);
87
+ _sourceLocks.set(uri, guarded);
88
+ try {
89
+ return await next;
90
+ }
91
+ finally {
92
+ if (_sourceLocks.get(uri) === guarded) {
93
+ _sourceLocks.delete(uri);
94
+ }
95
+ }
96
+ }
97
+ /** Default cache root: `WORKSPACE_DIR` env, else `process.cwd()`. */
98
+ export function defaultCacheRoot() {
99
+ return process.env.WORKSPACE_DIR ?? process.cwd();
100
+ }
101
+ export async function readManifest(path) {
102
+ try {
103
+ const raw = await readFile(path, 'utf8');
104
+ return JSON.parse(raw);
105
+ }
106
+ catch (err) {
107
+ if (err.code === 'ENOENT')
108
+ return null;
109
+ throw err;
110
+ }
111
+ }
112
+ export async function writeManifest(path, manifest) {
113
+ await writeFile(path, JSON.stringify(manifest, null, 2), 'utf8');
114
+ }
115
+ export async function readMeta(path) {
116
+ try {
117
+ const raw = await readFile(path, 'utf8');
118
+ return JSON.parse(raw);
119
+ }
120
+ catch (err) {
121
+ if (err.code === 'ENOENT')
122
+ return null;
123
+ throw err;
124
+ }
125
+ }
126
+ export async function writeMeta(path, meta) {
127
+ await writeFile(path, JSON.stringify(meta, null, 2), 'utf8');
128
+ }
129
+ /**
130
+ * Diff two manifests by `(path, sha256)`. Order-independent.
131
+ */
132
+ export function diffManifests(prev, next) {
133
+ const prevMap = new Map(prev.files.map((f) => [f.path, f.sha256]));
134
+ const nextMap = new Map(next.files.map((f) => [f.path, f.sha256]));
135
+ const added = [];
136
+ const modified = [];
137
+ const removed = [];
138
+ for (const [path, sha] of nextMap) {
139
+ const prevSha = prevMap.get(path);
140
+ if (prevSha === undefined)
141
+ added.push(path);
142
+ else if (prevSha !== sha)
143
+ modified.push(path);
144
+ }
145
+ for (const path of prevMap.keys()) {
146
+ if (!nextMap.has(path))
147
+ removed.push(path);
148
+ }
149
+ return { added, modified, removed };
150
+ }
151
+ /**
152
+ * Canonicalize a manifest's `ref` from its file list:
153
+ *
154
+ * sha256 over lines of `<path>\0<sha256>\n`, sorted by path.
155
+ *
156
+ * Deterministic across runs and platforms.
157
+ */
158
+ export function canonicalManifestRef(files) {
159
+ const sorted = [...files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
160
+ const h = createHash('sha256');
161
+ for (const f of sorted) {
162
+ h.update(f.path);
163
+ h.update('\0');
164
+ h.update(f.sha256);
165
+ h.update('\n');
166
+ }
167
+ return h.digest('hex');
168
+ }
169
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/sources/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * GitFileSource — shallow git clone backed by `isomorphic-git`.
3
+ *
4
+ * - URI shape: `git+https://github.com/owner/repo.git[@<ref>]`. Ref may be
5
+ * a branch name, tag, or full commit SHA. If omitted, defaults to `main`.
6
+ * - Initial fetch: shallow clone (`depth: 1`, `singleBranch: true`).
7
+ * - Subsequent fetch: `git.fetch` against the same single branch with
8
+ * `depth: 1` — pulls only the new tip if upstream advanced.
9
+ * - `ref` = `resolveRef('HEAD')` (full commit SHA).
10
+ * - `diffSinceRef(prevRef)` walks two trees with `git.walk` and classifies
11
+ * each path by OID equality (`added`/`modified`/`removed`).
12
+ *
13
+ * Caveats called out in JSDoc rather than hidden:
14
+ *
15
+ * - With `depth: 1`, history before the current tip is NOT in the local
16
+ * repo. `git.walk` requires both trees to be reachable; if the caller
17
+ * passes a `prevRef` we no longer have on disk we throw a clear error.
18
+ * - Snapshot reads point at the working tree inside the cache. Git itself
19
+ * is not exposed.
20
+ */
21
+ import type { FileDelta, FileSystemSource, Snapshot } from '../file-source.js';
22
+ export interface ParsedGitUri {
23
+ url: string;
24
+ ref: string;
25
+ }
26
+ export declare function parseGitUri(uri: string): ParsedGitUri;
27
+ export declare class GitFileSource implements FileSystemSource {
28
+ #private;
29
+ constructor(uri: string);
30
+ fetch(): Promise<Snapshot>;
31
+ diffSinceRef(prevRef: string): Promise<FileDelta>;
32
+ }
33
+ //# sourceMappingURL=git-file-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-file-source.d.ts","sourceRoot":"","sources":["../../src/sources/git-file-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAiB/E,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAkBrD;AAED,qBAAa,aAAc,YAAW,gBAAgB;;gBAKxC,GAAG,EAAE,MAAM;IAMvB,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;IAmD1B,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CA2DlD"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * GitFileSource — shallow git clone backed by `isomorphic-git`.
3
+ *
4
+ * - URI shape: `git+https://github.com/owner/repo.git[@<ref>]`. Ref may be
5
+ * a branch name, tag, or full commit SHA. If omitted, defaults to `main`.
6
+ * - Initial fetch: shallow clone (`depth: 1`, `singleBranch: true`).
7
+ * - Subsequent fetch: `git.fetch` against the same single branch with
8
+ * `depth: 1` — pulls only the new tip if upstream advanced.
9
+ * - `ref` = `resolveRef('HEAD')` (full commit SHA).
10
+ * - `diffSinceRef(prevRef)` walks two trees with `git.walk` and classifies
11
+ * each path by OID equality (`added`/`modified`/`removed`).
12
+ *
13
+ * Caveats called out in JSDoc rather than hidden:
14
+ *
15
+ * - With `depth: 1`, history before the current tip is NOT in the local
16
+ * repo. `git.walk` requires both trees to be reachable; if the caller
17
+ * passes a `prevRef` we no longer have on disk we throw a clear error.
18
+ * - Snapshot reads point at the working tree inside the cache. Git itself
19
+ * is not exposed.
20
+ */
21
+ import { mkdir, readFile } from 'node:fs/promises';
22
+ import { join } from 'node:path';
23
+ import * as git from 'isomorphic-git';
24
+ // `fs` is passed as a plain Node fs module — isomorphic-git accepts it.
25
+ // We import the callback-style module so isomorphic-git's promisified
26
+ // adapter works out of the box (it auto-detects promises if present).
27
+ import nodeFs from 'node:fs';
28
+ import { cachePathsFor, readAndVerifyMeta, requireMeta, withSourceLock, writeMeta, } from './cache.js';
29
+ import { GitSnapshot } from './git-snapshot.js';
30
+ // Custom https-only http client — rejects plaintext redirects that the
31
+ // stock `isomorphic-git/http/node` (simple-get under the hood) would
32
+ // silently follow.
33
+ import { gitHttpsOnlyClient as http } from './git-http.js';
34
+ const DEFAULT_BRANCH = 'main';
35
+ export function parseGitUri(uri) {
36
+ if (uri.startsWith('git+http://')) {
37
+ throw new Error('GitFileSource: plaintext HTTP rejected, use git+https://');
38
+ }
39
+ if (!uri.startsWith('git+https://')) {
40
+ throw new Error(`GitFileSource: expected git+https:// URI, got ${uri}`);
41
+ }
42
+ const stripped = uri.slice('git+'.length); // → https://...
43
+ // Split on the LAST `@` that follows the host's `/`, not the user@host form.
44
+ const slashIdx = stripped.indexOf('/', stripped.indexOf('://') + 3);
45
+ const atIdx = slashIdx === -1 ? -1 : stripped.lastIndexOf('@');
46
+ if (atIdx > slashIdx) {
47
+ return {
48
+ url: stripped.slice(0, atIdx),
49
+ ref: stripped.slice(atIdx + 1) || DEFAULT_BRANCH,
50
+ };
51
+ }
52
+ return { url: stripped, ref: DEFAULT_BRANCH };
53
+ }
54
+ export class GitFileSource {
55
+ #uri;
56
+ #parsed;
57
+ #paths;
58
+ constructor(uri) {
59
+ this.#uri = uri;
60
+ this.#parsed = parseGitUri(uri);
61
+ this.#paths = cachePathsFor(uri);
62
+ }
63
+ fetch() {
64
+ return withSourceLock(this.#uri, () => this.#fetchLocked());
65
+ }
66
+ async #fetchLocked() {
67
+ await mkdir(this.#paths.root, { recursive: true });
68
+ await readAndVerifyMeta(this.#paths.metaPath, this.#uri);
69
+ const dir = this.#paths.snapshotDir;
70
+ // Detect whether we already have a clone.
71
+ const alreadyCloned = await pathExists(join(dir, '.git'));
72
+ if (!alreadyCloned) {
73
+ await mkdir(dir, { recursive: true });
74
+ await git.clone({
75
+ fs: nodeFs,
76
+ http,
77
+ dir,
78
+ url: this.#parsed.url,
79
+ ref: this.#parsed.ref,
80
+ singleBranch: true,
81
+ depth: 1,
82
+ });
83
+ }
84
+ else {
85
+ // Existing repo — fetch the same branch shallow.
86
+ await git.fetch({
87
+ fs: nodeFs,
88
+ http,
89
+ dir,
90
+ ref: this.#parsed.ref,
91
+ singleBranch: true,
92
+ depth: 1,
93
+ tags: false,
94
+ });
95
+ // Move HEAD/working tree to the fetched tip.
96
+ await git.checkout({
97
+ fs: nodeFs,
98
+ dir,
99
+ ref: this.#parsed.ref,
100
+ force: true,
101
+ });
102
+ }
103
+ const ref = await git.resolveRef({ fs: nodeFs, dir, ref: 'HEAD' });
104
+ await writeMeta(this.#paths.metaPath, { uri: this.#uri, kind: 'git' });
105
+ // Read from the captured commit's tree/blobs — immutable view even if a
106
+ // later fetch() moves HEAD or rewrites the working tree on disk.
107
+ return new GitSnapshot(dir, ref, nodeFs, { exclude: isGitInternalPath });
108
+ }
109
+ diffSinceRef(prevRef) {
110
+ return withSourceLock(this.#uri, () => this.#diffLocked(prevRef));
111
+ }
112
+ async #diffLocked(prevRef) {
113
+ await requireMeta(this.#paths.metaPath, this.#uri);
114
+ const dir = this.#paths.snapshotDir;
115
+ const currentRef = await git.resolveRef({ fs: nodeFs, dir, ref: 'HEAD' });
116
+ if (currentRef === prevRef)
117
+ return { added: [], modified: [], removed: [] };
118
+ // If `prevRef` isn't reachable in the local (shallow) repo, return a
119
+ // full-reingest delta — every current file as `added`. Matches the
120
+ // tarball and local-source contract: an unknown prevRef yields a
121
+ // re-ingest, never a thrown error.
122
+ let prevReachable = true;
123
+ try {
124
+ await git.readCommit({ fs: nodeFs, dir, oid: prevRef });
125
+ }
126
+ catch {
127
+ prevReachable = false;
128
+ }
129
+ if (!prevReachable) {
130
+ const allFiles = await listCurrentFiles(dir, currentRef);
131
+ return { added: allFiles, modified: [], removed: [] };
132
+ }
133
+ const added = [];
134
+ const modified = [];
135
+ const removed = [];
136
+ await git.walk({
137
+ fs: nodeFs,
138
+ dir,
139
+ trees: [git.TREE({ ref: prevRef }), git.TREE({ ref: currentRef })],
140
+ map: async (filepath, entries) => {
141
+ // The root entry has filepath '.'. Don't classify it but DO let the
142
+ // walk descend (returning null prunes the subtree).
143
+ if (filepath === '.')
144
+ return undefined;
145
+ if (!entries)
146
+ return undefined;
147
+ const [a, b] = entries;
148
+ // Skip directories — they're emitted as their own map() calls; we
149
+ // only classify file (blob) entries.
150
+ const aType = a ? await a.type() : null;
151
+ const bType = b ? await b.type() : null;
152
+ if (aType === 'tree' || bType === 'tree')
153
+ return undefined;
154
+ const aOid = aType ? await a.oid() : null;
155
+ const bOid = bType ? await b.oid() : null;
156
+ if (aOid === null && bOid !== null)
157
+ added.push(filepath);
158
+ else if (aOid !== null && bOid === null)
159
+ removed.push(filepath);
160
+ else if (aOid !== null && bOid !== null && aOid !== bOid)
161
+ modified.push(filepath);
162
+ return undefined;
163
+ },
164
+ });
165
+ return { added, modified, removed };
166
+ }
167
+ }
168
+ /** Walk a commit's tree and return every blob path (POSIX-separated). */
169
+ async function listCurrentFiles(dir, ref) {
170
+ const collected = [];
171
+ await git.walk({
172
+ fs: nodeFs,
173
+ dir,
174
+ trees: [git.TREE({ ref })],
175
+ map: async (filepath, entries) => {
176
+ if (filepath === '.')
177
+ return undefined;
178
+ if (!entries)
179
+ return undefined;
180
+ const [entry] = entries;
181
+ if (!entry)
182
+ return undefined;
183
+ const type = await entry.type();
184
+ if (type === 'blob')
185
+ collected.push(filepath);
186
+ return undefined;
187
+ },
188
+ });
189
+ return collected;
190
+ }
191
+ /** Filter for git internals — matches `.git` itself and anything under it. */
192
+ function isGitInternalPath(rel) {
193
+ return rel === '.git' || rel.startsWith('.git/');
194
+ }
195
+ async function pathExists(p) {
196
+ try {
197
+ await readFile(p).catch(async () => {
198
+ const { stat } = await import('node:fs/promises');
199
+ await stat(p);
200
+ });
201
+ return true;
202
+ }
203
+ catch {
204
+ return false;
205
+ }
206
+ }
207
+ //# sourceMappingURL=git-file-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-file-source.js","sourceRoot":"","sources":["../../src/sources/git-file-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * HttpClient for isomorphic-git that:
3
+ *
4
+ * - Talks https only — refuses to issue any request whose URL is not
5
+ * `https://...`.
6
+ * - Follows redirects manually (max 5 hops). Every `Location` is re-validated
7
+ * https before the next request — closes the https-to-http downgrade hole
8
+ * that an https endpoint replying with `Location: http://...` would
9
+ * otherwise open against the default `isomorphic-git/http/node` impl
10
+ * (which uses `simple-get`'s built-in redirect follower and does NOT
11
+ * enforce a scheme on the next hop).
12
+ *
13
+ * Implemented on top of Node's `https.request` so the SDK doesn't pull in
14
+ * an extra HTTP client. The body-as-async-iterator contract matches
15
+ * isomorphic-git's `GitHttpRequest` / `GitHttpResponse` types.
16
+ */
17
+ interface GitHttpRequest {
18
+ url: string;
19
+ method?: string;
20
+ headers?: Record<string, string>;
21
+ body?: AsyncIterable<Uint8Array> | Uint8Array | Buffer;
22
+ }
23
+ interface GitHttpResponse {
24
+ url: string;
25
+ method: string;
26
+ statusCode: number;
27
+ statusMessage: string;
28
+ headers: Record<string, string>;
29
+ body: AsyncIterableIterator<Uint8Array>;
30
+ }
31
+ /**
32
+ * Send `req`, manually following 3xx redirects up to MAX_REDIRECTS. Every
33
+ * hop validates the next URL is https. On a 30x→non-https Location, throws.
34
+ *
35
+ * Per RFC 7231 §6.4: 301/302/303 turn POST into GET and drop the request
36
+ * body; 307/308 preserve the method and body. isomorphic-git's traffic is
37
+ * GET (info/refs) and POST (upload-pack / receive-pack). For a POST that
38
+ * gets redirected with a method-changing status we drop the body — git
39
+ * smart servers don't typically issue method-changing redirects mid-flow,
40
+ * but if they did, the resulting GET would surface as a server-side error
41
+ * rather than silently completing.
42
+ */
43
+ export declare function gitHttpRequest(req: GitHttpRequest): Promise<GitHttpResponse>;
44
+ export declare const gitHttpsOnlyClient: {
45
+ request: typeof gitHttpRequest;
46
+ };
47
+ export {};
48
+ //# sourceMappingURL=git-http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-http.d.ts","sourceRoot":"","sources":["../../src/sources/git-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,MAAM,CAAC;CACxD;AAED,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;CACzC;AAgGD;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAkElF;AAED,eAAO,MAAM,kBAAkB;;CAA8B,CAAC"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * HttpClient for isomorphic-git that:
3
+ *
4
+ * - Talks https only — refuses to issue any request whose URL is not
5
+ * `https://...`.
6
+ * - Follows redirects manually (max 5 hops). Every `Location` is re-validated
7
+ * https before the next request — closes the https-to-http downgrade hole
8
+ * that an https endpoint replying with `Location: http://...` would
9
+ * otherwise open against the default `isomorphic-git/http/node` impl
10
+ * (which uses `simple-get`'s built-in redirect follower and does NOT
11
+ * enforce a scheme on the next hop).
12
+ *
13
+ * Implemented on top of Node's `https.request` so the SDK doesn't pull in
14
+ * an extra HTTP client. The body-as-async-iterator contract matches
15
+ * isomorphic-git's `GitHttpRequest` / `GitHttpResponse` types.
16
+ */
17
+ import { request as httpsRequest } from 'node:https';
18
+ import { Readable } from 'node:stream';
19
+ const MAX_REDIRECTS = 5;
20
+ async function bodyToBuffer(body) {
21
+ if (!body)
22
+ return undefined;
23
+ if (Buffer.isBuffer(body))
24
+ return body;
25
+ if (body instanceof Uint8Array)
26
+ return Buffer.from(body);
27
+ const chunks = [];
28
+ for await (const chunk of body) {
29
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
30
+ }
31
+ return Buffer.concat(chunks);
32
+ }
33
+ function flattenHeaders(raw) {
34
+ const out = {};
35
+ for (const [k, v] of Object.entries(raw)) {
36
+ if (v === undefined)
37
+ continue;
38
+ out[k] = Array.isArray(v) ? v.join(', ') : v;
39
+ }
40
+ return out;
41
+ }
42
+ function nodeStreamToAsyncIterableIterator(stream) {
43
+ // IncomingMessage is already AsyncIterable<Buffer>. Wrap it as an
44
+ // AsyncIterableIterator since that's what isomorphic-git's type
45
+ // demands (it has a `next()` method).
46
+ const iter = stream[Symbol.asyncIterator]();
47
+ return {
48
+ next: () => iter.next(),
49
+ return: iter.return ? (v) => iter.return(v) : undefined,
50
+ throw: iter.throw ? (e) => iter.throw(e) : undefined,
51
+ [Symbol.asyncIterator]() {
52
+ return this;
53
+ },
54
+ };
55
+ }
56
+ async function singleRequest(req) {
57
+ if (!req.url.startsWith('https://')) {
58
+ throw new Error(`GitFileSource: refusing non-https request: ${req.url}`);
59
+ }
60
+ const parsed = new URL(req.url);
61
+ const bodyBuf = await bodyToBuffer(req.body);
62
+ const method = (req.method ?? 'GET').toUpperCase();
63
+ const headers = { ...(req.headers ?? {}) };
64
+ if (bodyBuf) {
65
+ headers['content-length'] = bodyBuf.length;
66
+ }
67
+ return new Promise((resolve, reject) => {
68
+ const r = httpsRequest({
69
+ protocol: parsed.protocol,
70
+ hostname: parsed.hostname,
71
+ port: parsed.port || undefined,
72
+ path: `${parsed.pathname}${parsed.search}`,
73
+ method,
74
+ headers,
75
+ }, (res) => {
76
+ resolve({
77
+ statusCode: res.statusCode ?? 0,
78
+ statusMessage: res.statusMessage ?? '',
79
+ headers: flattenHeaders(res.headers),
80
+ bodyStream: res,
81
+ url: req.url,
82
+ });
83
+ });
84
+ r.on('error', reject);
85
+ if (bodyBuf)
86
+ r.write(bodyBuf);
87
+ r.end();
88
+ });
89
+ }
90
+ async function drain(stream) {
91
+ return new Promise((resolve) => {
92
+ stream.on('data', () => undefined);
93
+ stream.on('end', () => resolve());
94
+ stream.on('error', () => resolve());
95
+ stream.resume();
96
+ });
97
+ }
98
+ /**
99
+ * Send `req`, manually following 3xx redirects up to MAX_REDIRECTS. Every
100
+ * hop validates the next URL is https. On a 30x→non-https Location, throws.
101
+ *
102
+ * Per RFC 7231 §6.4: 301/302/303 turn POST into GET and drop the request
103
+ * body; 307/308 preserve the method and body. isomorphic-git's traffic is
104
+ * GET (info/refs) and POST (upload-pack / receive-pack). For a POST that
105
+ * gets redirected with a method-changing status we drop the body — git
106
+ * smart servers don't typically issue method-changing redirects mid-flow,
107
+ * but if they did, the resulting GET would surface as a server-side error
108
+ * rather than silently completing.
109
+ */
110
+ export async function gitHttpRequest(req) {
111
+ let cur = req;
112
+ let lastBuffered;
113
+ for (let i = 0; i <= MAX_REDIRECTS; i++) {
114
+ if (cur.body && !Buffer.isBuffer(cur.body) && !(cur.body instanceof Uint8Array)) {
115
+ // Eagerly buffer the body so a redirect can replay it without
116
+ // re-consuming an already-iterated source.
117
+ lastBuffered = await bodyToBuffer(cur.body);
118
+ cur = { ...cur, body: lastBuffered };
119
+ }
120
+ const res = await singleRequest(cur);
121
+ const status = res.statusCode;
122
+ const isRedirect = status >= 300 && status < 400 && res.headers['location'];
123
+ if (!isRedirect) {
124
+ return {
125
+ url: res.url,
126
+ method: cur.method ?? 'GET',
127
+ statusCode: status,
128
+ statusMessage: res.statusMessage,
129
+ headers: res.headers,
130
+ body: nodeStreamToAsyncIterableIterator(res.bodyStream),
131
+ };
132
+ }
133
+ const locHeader = res.headers['location'];
134
+ if (!locHeader) {
135
+ return {
136
+ url: res.url,
137
+ method: cur.method ?? 'GET',
138
+ statusCode: status,
139
+ statusMessage: res.statusMessage,
140
+ headers: res.headers,
141
+ body: nodeStreamToAsyncIterableIterator(res.bodyStream),
142
+ };
143
+ }
144
+ let nextUrl;
145
+ try {
146
+ nextUrl = new URL(locHeader, cur.url).toString();
147
+ }
148
+ catch {
149
+ throw new Error(`GitFileSource: invalid redirect location: ${locHeader}`);
150
+ }
151
+ if (!nextUrl.startsWith('https://')) {
152
+ throw new Error(`GitFileSource: redirect to plaintext URL rejected: ${nextUrl}`);
153
+ }
154
+ // Drain the redirect's body so the socket can be reused.
155
+ await drain(res.bodyStream);
156
+ // RFC 7231 §6.4.2/6.4.3: 301/302/303 strip the body and change the
157
+ // method to GET. 307/308 preserve method + body.
158
+ let nextMethod = cur.method ?? 'GET';
159
+ let nextBody = lastBuffered;
160
+ if (status === 301 || status === 302 || status === 303) {
161
+ if (nextMethod !== 'GET' && nextMethod !== 'HEAD') {
162
+ nextMethod = 'GET';
163
+ nextBody = undefined;
164
+ }
165
+ }
166
+ cur = {
167
+ url: nextUrl,
168
+ method: nextMethod,
169
+ headers: { ...(cur.headers ?? {}) },
170
+ body: nextBody,
171
+ };
172
+ }
173
+ throw new Error(`GitFileSource: too many redirects (>${MAX_REDIRECTS}) for ${req.url}`);
174
+ }
175
+ export const gitHttpsOnlyClient = { request: gitHttpRequest };
176
+ // Force Readable import to be retained (avoids unused-import elision and
177
+ // keeps the contract explicit for future stream-conversion changes).
178
+ void Readable;
179
+ //# sourceMappingURL=git-http.js.map