@lobu/connector-sdk 7.2.0 → 9.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 (67) hide show
  1. package/dist/connector-runtime.d.ts +23 -11
  2. package/dist/connector-runtime.d.ts.map +1 -1
  3. package/dist/connector-runtime.js +32 -8
  4. package/dist/connector-runtime.js.map +1 -1
  5. package/dist/connector-types.d.ts +27 -15
  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/identity-normalize.d.ts +12 -0
  14. package/dist/identity-normalize.d.ts.map +1 -1
  15. package/dist/identity-normalize.js +12 -0
  16. package/dist/identity-normalize.js.map +1 -1
  17. package/dist/index.d.ts +15 -9
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +14 -5
  20. package/dist/index.js.map +1 -1
  21. package/dist/reaction-client-types.d.ts +126 -0
  22. package/dist/reaction-client-types.d.ts.map +1 -0
  23. package/dist/reaction-client-types.js +2 -0
  24. package/dist/reaction-client-types.js.map +1 -0
  25. package/dist/sources/cache.d.ts +82 -0
  26. package/dist/sources/cache.d.ts.map +1 -0
  27. package/dist/sources/cache.js +169 -0
  28. package/dist/sources/cache.js.map +1 -0
  29. package/dist/sources/git-file-source.d.ts +33 -0
  30. package/dist/sources/git-file-source.d.ts.map +1 -0
  31. package/dist/sources/git-file-source.js +207 -0
  32. package/dist/sources/git-file-source.js.map +1 -0
  33. package/dist/sources/git-http.d.ts +48 -0
  34. package/dist/sources/git-http.d.ts.map +1 -0
  35. package/dist/sources/git-http.js +179 -0
  36. package/dist/sources/git-http.js.map +1 -0
  37. package/dist/sources/git-snapshot.d.ts +14 -0
  38. package/dist/sources/git-snapshot.d.ts.map +1 -0
  39. package/dist/sources/git-snapshot.js +96 -0
  40. package/dist/sources/git-snapshot.js.map +1 -0
  41. package/dist/sources/glob.d.ts +31 -0
  42. package/dist/sources/glob.d.ts.map +1 -0
  43. package/dist/sources/glob.js +129 -0
  44. package/dist/sources/glob.js.map +1 -0
  45. package/dist/sources/local-file-source.d.ts +29 -0
  46. package/dist/sources/local-file-source.d.ts.map +1 -0
  47. package/dist/sources/local-file-source.js +343 -0
  48. package/dist/sources/local-file-source.js.map +1 -0
  49. package/dist/sources/resolver.d.ts +6 -0
  50. package/dist/sources/resolver.d.ts.map +1 -0
  51. package/dist/sources/resolver.js +47 -0
  52. package/dist/sources/resolver.js.map +1 -0
  53. package/dist/sources/snapshot.d.ts +19 -0
  54. package/dist/sources/snapshot.d.ts.map +1 -0
  55. package/dist/sources/snapshot.js +91 -0
  56. package/dist/sources/snapshot.js.map +1 -0
  57. package/dist/sources/tarball-file-source.d.ts +28 -0
  58. package/dist/sources/tarball-file-source.d.ts.map +1 -0
  59. package/dist/sources/tarball-file-source.js +273 -0
  60. package/dist/sources/tarball-file-source.js.map +1 -0
  61. package/dist/types.d.ts +3 -65
  62. package/dist/types.d.ts.map +1 -1
  63. package/package.json +10 -3
  64. package/dist/event-taxonomy.d.ts +0 -3
  65. package/dist/event-taxonomy.d.ts.map +0 -1
  66. package/dist/event-taxonomy.js +0 -30
  67. package/dist/event-taxonomy.js.map +0 -1
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AACtC,wEAAwE;AACxE,sEAAsE;AACtE,sEAAsE;AACtE,OAAO,MAAM,MAAM,SAAS,CAAC;AAG7B,OAAO,EAEL,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,uEAAuE;AACvE,qEAAqE;AACrE,mBAAmB;AACnB,OAAO,EAAE,kBAAkB,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAE3D,MAAM,cAAc,GAAG,MAAM,CAAC;AAO9B,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,iDAAiD,GAAG,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB;IAC3D,6EAA6E;IAC7E,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,MAAM,KAAK,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/D,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;QACrB,OAAO;YACL,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;YAC7B,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,cAAc;SACjD,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,OAAO,aAAa;IACf,IAAI,CAAS;IACb,OAAO,CAAe;IACtB,MAAM,CAAa;IAE5B,YAAY,GAAW;QACrB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,KAAK;QACH,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,MAAM,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;QAEpC,0CAA0C;QAC1C,MAAM,aAAa,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QAE1D,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,MAAM,GAAG,CAAC,KAAK,CAAC;gBACd,EAAE,EAAE,MAAM;gBACV,IAAI;gBACJ,GAAG;gBACH,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;gBACrB,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;gBACrB,YAAY,EAAE,IAAI;gBAClB,KAAK,EAAE,CAAC;aACT,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,iDAAiD;YACjD,MAAM,GAAG,CAAC,KAAK,CAAC;gBACd,EAAE,EAAE,MAAM;gBACV,IAAI;gBACJ,GAAG;gBACH,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;gBACrB,YAAY,EAAE,IAAI;gBAClB,KAAK,EAAE,CAAC;gBACR,IAAI,EAAE,KAAK;aACZ,CAAC,CAAC;YACH,6CAA6C;YAC7C,MAAM,GAAG,CAAC,QAAQ,CAAC;gBACjB,EAAE,EAAE,MAAM;gBACV,GAAG;gBACH,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;gBACrB,KAAK,EAAE,IAAI;aACZ,CAAC,CAAC;QACL,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QACnE,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAEvE,wEAAwE;QACxE,iEAAiE;QACjE,OAAO,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,YAAY,CAAC,OAAe;QAC1B,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAe;QAC/B,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;QACpC,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1E,IAAI,UAAU,KAAK,OAAO;YAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAE5E,qEAAqE;QACrE,mEAAmE;QACnE,iEAAiE;QACjE,mCAAmC;QACnC,IAAI,aAAa,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,aAAa,GAAG,KAAK,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YACzD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACxD,CAAC;QAED,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAa,EAAE,CAAC;QAE7B,MAAM,GAAG,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,MAAM;YACV,GAAG;YACH,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;YAClE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;gBAC/B,oEAAoE;gBACpE,oDAAoD;gBACpD,IAAI,QAAQ,KAAK,GAAG;oBAAE,OAAO,SAAS,CAAC;gBACvC,IAAI,CAAC,OAAO;oBAAE,OAAO,SAAS,CAAC;gBAC/B,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC;gBAEvB,kEAAkE;gBAClE,qCAAqC;gBACrC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;gBACxC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;gBACxC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM;oBAAE,OAAO,SAAS,CAAC;gBAE3D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;gBAE3C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;qBACpD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;oBAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;qBAC3D,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;oBAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAElF,OAAO,SAAS,CAAC;YACnB,CAAC;SACF,CAAC,CAAC;QAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACtC,CAAC;CACF;AAED,yEAAyE;AACzE,KAAK,UAAU,gBAAgB,CAAC,GAAW,EAAE,GAAW;IACtD,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,GAAG,CAAC,IAAI,CAAC;QACb,EAAE,EAAE,MAAM;QACV,GAAG;QACH,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAC1B,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;YAC/B,IAAI,QAAQ,KAAK,GAAG;gBAAE,OAAO,SAAS,CAAC;YACvC,IAAI,CAAC,OAAO;gBAAE,OAAO,SAAS,CAAC;YAC/B,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;YACxB,IAAI,CAAC,KAAK;gBAAE,OAAO,SAAS,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,IAAI,KAAK,MAAM;gBAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC,CAAC;IACH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,SAAS,iBAAiB,CAAC,GAAW;IACpC,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,CAAS;IACjC,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE;YACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;YAClD,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-http.js","sourceRoot":"","sources":["../../src/sources/git-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAkBvC,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,KAAK,UAAU,YAAY,CACzB,IAAiE;IAEjE,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,IAAI,YAAY,UAAU;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,cAAc,CAAC,GAAmC;IACzD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QAC9B,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,iCAAiC,CACxC,MAAuB;IAEvB,kEAAkE;IAClE,gEAAgE;IAChE,sCAAsC;IACtC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAA+B,CAAC;IACzE,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;QACxD,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;QACrD,CAAC,MAAM,CAAC,aAAa,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,GAAmB;IAO9C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,8CAA8C,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACnD,MAAM,OAAO,GAAoC,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;IAC5E,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAC7C,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,GAAG,YAAY,CACpB;YACE,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,SAAS;YAC9B,IAAI,EAAE,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE;YAC1C,MAAM;YACN,OAAO;SACR,EACD,CAAC,GAAG,EAAE,EAAE;YACN,OAAO,CAAC;gBACN,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC;gBAC/B,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,EAAE;gBACtC,OAAO,EAAE,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;gBACpC,UAAU,EAAE,GAAG;gBACf,GAAG,EAAE,GAAG,CAAC,GAAG;aACb,CAAC,CAAC;QACL,CAAC,CACF,CAAC;QACF,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACtB,IAAI,OAAO;YAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC,CAAC,GAAG,EAAE,CAAC;IACV,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,MAAuB;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAmB;IACtD,IAAI,GAAG,GAAmB,GAAG,CAAC;IAC9B,IAAI,YAAgC,CAAC;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,YAAY,UAAU,CAAC,EAAE,CAAC;YAChF,8DAA8D;YAC9D,2CAA2C;YAC3C,YAAY,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC5C,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;QACvC,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC;QAC9B,MAAM,UAAU,GAAG,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC5E,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO;gBACL,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,KAAK;gBAC3B,UAAU,EAAE,MAAM;gBAClB,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI,EAAE,iCAAiC,CAAC,GAAG,CAAC,UAAU,CAAC;aACxD,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO;gBACL,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,KAAK;gBAC3B,UAAU,EAAE,MAAM;gBAClB,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI,EAAE,iCAAiC,CAAC,GAAG,CAAC,UAAU,CAAC;aACxD,CAAC;QACJ,CAAC;QACD,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,6CAA6C,SAAS,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CACb,sDAAsD,OAAO,EAAE,CAChE,CAAC;QACJ,CAAC;QACD,yDAAyD;QACzD,MAAM,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAE5B,mEAAmE;QACnE,iDAAiD;QACjD,IAAI,UAAU,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;QACrC,IAAI,QAAQ,GAAuB,YAAY,CAAC;QAChD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACvD,IAAI,UAAU,KAAK,KAAK,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;gBAClD,UAAU,GAAG,KAAK,CAAC;gBACnB,QAAQ,GAAG,SAAS,CAAC;YACvB,CAAC;QACH,CAAC;QACD,GAAG,GAAG;YACJ,GAAG,EAAE,OAAO;YACZ,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE;YACnC,IAAI,EAAE,QAAQ;SACf,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,uCAAuC,aAAa,SAAS,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;AAE9D,yEAAyE;AACzE,qEAAqE;AACrE,KAAK,QAAQ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type nodeFsType from 'node:fs';
2
+ import type { Snapshot } from '../file-source.js';
3
+ export interface GitSnapshotOptions {
4
+ exclude?: (relativePath: string) => boolean;
5
+ }
6
+ export declare class GitSnapshot implements Snapshot {
7
+ #private;
8
+ readonly ref: string;
9
+ constructor(dir: string, ref: string, fs: typeof nodeFsType, options?: GitSnapshotOptions);
10
+ walkFiles(glob: string): AsyncIterable<string>;
11
+ readFile(relativePath: string): Promise<Buffer>;
12
+ readText(relativePath: string): Promise<string>;
13
+ }
14
+ //# sourceMappingURL=git-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-snapshot.d.ts","sourceRoot":"","sources":["../../src/sources/git-snapshot.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,UAAU,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;CAC7C;AAED,qBAAa,WAAY,YAAW,QAAQ;;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAMnB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,OAAO,UAAU,EACrB,OAAO,GAAE,kBAAuB;IAQ3B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;IAsB/C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAW/C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAuBtD"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * GitSnapshot — Snapshot backed by an immutable git commit OID.
3
+ *
4
+ * Unlike `DirectorySnapshot`, this snapshot reads file contents via
5
+ * `git.readBlob({oid, filepath})` against the captured commit, NOT from
6
+ * the on-disk working tree. That gives us true immutability: a later
7
+ * `fetch()` may move HEAD or rewrite the working tree, but blob OIDs for
8
+ * the captured commit stay reachable in the shallow clone's object DB.
9
+ *
10
+ * Caveats:
11
+ * - `walkFiles` walks the captured commit's tree (`git.walk` with a single
12
+ * `TREE({ref})`). Result: files-as-they-existed-at-the-commit, not
13
+ * files-as-they-are-on-disk-right-now.
14
+ * - `readFile` / `readText` extract blob bytes from the object DB.
15
+ * - Path-security primitives mirror `DirectorySnapshot` lexically (absolute
16
+ * paths rejected, `..` escapes rejected, exclude predicate enforced).
17
+ * - No realpath/symlink defense — git's object model has no symlinks-as-
18
+ * filesystem-pointers. A blob with mode 120000 IS the link contents, not
19
+ * a follow-able pointer.
20
+ */
21
+ import * as git from 'isomorphic-git';
22
+ import { isAbsolute, join, normalize, relative, sep } from 'node:path';
23
+ import { matchesGlob } from './glob.js';
24
+ export class GitSnapshot {
25
+ ref;
26
+ #dir;
27
+ #fs;
28
+ #exclude;
29
+ constructor(dir, ref, fs, options = {}) {
30
+ this.#dir = dir;
31
+ this.ref = ref;
32
+ this.#fs = fs;
33
+ this.#exclude = options.exclude ?? (() => false);
34
+ }
35
+ async *walkFiles(glob) {
36
+ const collected = [];
37
+ await git.walk({
38
+ fs: this.#fs,
39
+ dir: this.#dir,
40
+ trees: [git.TREE({ ref: this.ref })],
41
+ map: async (filepath, entries) => {
42
+ if (filepath === '.')
43
+ return undefined;
44
+ if (!entries)
45
+ return undefined;
46
+ const [entry] = entries;
47
+ if (!entry)
48
+ return undefined;
49
+ const type = await entry.type();
50
+ if (type !== 'blob')
51
+ return undefined;
52
+ const posix = filepath.split(sep).join('/');
53
+ if (this.#exclude(posix))
54
+ return undefined;
55
+ if (matchesGlob(posix, glob))
56
+ collected.push(posix);
57
+ return undefined;
58
+ },
59
+ });
60
+ for (const p of collected)
61
+ yield p;
62
+ }
63
+ async readFile(relativePath) {
64
+ const posix = this.#validateRel(relativePath);
65
+ const { blob } = await git.readBlob({
66
+ fs: this.#fs,
67
+ dir: this.#dir,
68
+ oid: this.ref,
69
+ filepath: posix,
70
+ });
71
+ return Buffer.from(blob);
72
+ }
73
+ async readText(relativePath) {
74
+ const buf = await this.readFile(relativePath);
75
+ return buf.toString('utf8');
76
+ }
77
+ #validateRel(relativePath) {
78
+ if (isAbsolute(relativePath)) {
79
+ throw new Error(`Snapshot.readFile: absolute paths are not allowed (${relativePath})`);
80
+ }
81
+ // Use a virtual root so `normalize`/`relative` behave consistently across
82
+ // platforms without touching the real fs.
83
+ const virtualRoot = '/__snap__';
84
+ const joined = normalize(join(virtualRoot, relativePath));
85
+ const rel = relative(virtualRoot, joined);
86
+ if (rel.startsWith('..') || isAbsolute(rel) || rel.split(sep).includes('..')) {
87
+ throw new Error(`Snapshot.readFile: path escapes snapshot root (${relativePath})`);
88
+ }
89
+ const posix = sep === '/' ? rel : rel.split(sep).join('/');
90
+ if (this.#exclude(posix)) {
91
+ throw new Error(`Snapshot.readFile: path is excluded from the snapshot (${relativePath})`);
92
+ }
93
+ return posix;
94
+ }
95
+ }
96
+ //# sourceMappingURL=git-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-snapshot.js","sourceRoot":"","sources":["../../src/sources/git-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEvE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAMxC,MAAM,OAAO,WAAW;IACb,GAAG,CAAS;IACZ,IAAI,CAAS;IACb,GAAG,CAAoB;IACvB,QAAQ,CAAoC;IAErD,YACE,GAAW,EACX,GAAW,EACX,EAAqB,EACrB,UAA8B,EAAE;QAEhC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,CAAC,SAAS,CAAC,IAAY;QAC3B,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,MAAM,GAAG,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,IAAI,CAAC,GAAG;YACZ,GAAG,EAAE,IAAI,CAAC,IAAI;YACd,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACpC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;gBAC/B,IAAI,QAAQ,KAAK,GAAG;oBAAE,OAAO,SAAS,CAAC;gBACvC,IAAI,CAAC,OAAO;oBAAE,OAAO,SAAS,CAAC;gBAC/B,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;gBACxB,IAAI,CAAC,KAAK;oBAAE,OAAO,SAAS,CAAC;gBAC7B,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;gBAChC,IAAI,IAAI,KAAK,MAAM;oBAAE,OAAO,SAAS,CAAC;gBACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC5C,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAAE,OAAO,SAAS,CAAC;gBAC3C,IAAI,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC;oBAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACpD,OAAO,SAAS,CAAC;YACnB,CAAC;SACF,CAAC,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,YAAoB;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC9C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC;YAClC,EAAE,EAAE,IAAI,CAAC,GAAG;YACZ,GAAG,EAAE,IAAI,CAAC,IAAI;YACd,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,YAAoB;QACjC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC9C,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,YAAY,CAAC,YAAoB;QAC/B,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,sDAAsD,YAAY,GAAG,CAAC,CAAC;QACzF,CAAC;QACD,0EAA0E;QAC1E,0CAA0C;QAC1C,MAAM,WAAW,GAAG,WAAW,CAAC;QAChC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC1C,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7E,MAAM,IAAI,KAAK,CAAC,kDAAkD,YAAY,GAAG,CAAC,CAAC;QACrF,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,0DAA0D,YAAY,GAAG,CAAC,CAAC;QAC7F,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Minimal glob matcher + recursive directory walk.
3
+ *
4
+ * Supports the subset of glob syntax connectors actually need:
5
+ * - `*` matches any run of characters except `/`
6
+ * - `**` matches any run of characters including `/`
7
+ * - `?` matches exactly one character except `/`
8
+ * - Literal `.` `/` `_` `-` segments
9
+ *
10
+ * No brace-expansion, no `!` negation, no `[ ]` character classes. If a
11
+ * connector needs more, it can iterate `walkFiles("**\/*")` and filter in
12
+ * its own code — keeping the SDK surface small.
13
+ */
14
+ /**
15
+ * Recursive walk yielding POSIX-style relative paths (forward-slash).
16
+ * Skips dot-directories of the cache itself (`.lobu-cache`) but otherwise
17
+ * includes hidden files — git's `.git/` is excluded by callers that need
18
+ * to (git source uses a separate snapshot dir without `.git`).
19
+ */
20
+ export declare function walkDirectoryRelative(root: string): AsyncIterable<string>;
21
+ /**
22
+ * Compile a glob to a RegExp. Anchored on both ends.
23
+ *
24
+ * Implementation note: we walk the glob char-by-char rather than splitting on
25
+ * `*` so we can distinguish `*` from `**` without ambiguity, and so literal
26
+ * regex metacharacters get escaped exactly once.
27
+ */
28
+ export declare function globToRegExp(glob: string): RegExp;
29
+ /** Test a POSIX-style relative path against `glob`. */
30
+ export declare function matchesGlob(relativePath: string, glob: string): boolean;
31
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/sources/glob.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH;;;;;GAKG;AACH,wBAAuB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAwBhF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkDjD;AAYD,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKvE"}