@open330/oac-repo 2026.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Open330
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,63 @@
1
+ interface RepoPermissions {
2
+ push: boolean;
3
+ pull: boolean;
4
+ admin: boolean;
5
+ }
6
+ interface ResolvedRepoMeta {
7
+ defaultBranch: string;
8
+ language: string | null;
9
+ languages: Record<string, number>;
10
+ size: number;
11
+ stars: number;
12
+ openIssuesCount: number;
13
+ topics: string[];
14
+ license: string | null;
15
+ isArchived: boolean;
16
+ isFork: boolean;
17
+ permissions: RepoPermissions;
18
+ }
19
+ interface ResolvedRepoGitState {
20
+ headSha: string;
21
+ remoteUrl: string;
22
+ isShallowClone: boolean;
23
+ }
24
+ interface ResolvedRepo {
25
+ fullName: string;
26
+ owner: string;
27
+ name: string;
28
+ localPath: string;
29
+ worktreePath: string;
30
+ meta: ResolvedRepoMeta;
31
+ git: ResolvedRepoGitState;
32
+ }
33
+
34
+ type RepoResolutionErrorCode = "INVALID_INPUT" | "NOT_FOUND" | "FORBIDDEN" | "ARCHIVED" | "UNKNOWN";
35
+ declare class RepoResolutionError extends Error {
36
+ readonly code: RepoResolutionErrorCode;
37
+ constructor(message: string, code: RepoResolutionErrorCode, cause?: unknown);
38
+ }
39
+ declare function resolveRepo(input: string): Promise<ResolvedRepo>;
40
+
41
+ declare const DEFAULT_REPO_CACHE_DIR: string;
42
+ declare function cloneRepo(repo: ResolvedRepo, cacheDir?: string): Promise<string>;
43
+
44
+ interface MetadataCacheOptions {
45
+ filePath?: string;
46
+ ttlMs?: number;
47
+ now?: () => number;
48
+ }
49
+ declare const DEFAULT_METADATA_CACHE_PATH: string;
50
+ declare const DEFAULT_METADATA_CACHE_TTL_MS: number;
51
+ declare class MetadataCache {
52
+ private readonly filePath;
53
+ private readonly ttlMs;
54
+ private readonly now;
55
+ constructor(options?: MetadataCacheOptions);
56
+ get(fullName: string): Promise<ResolvedRepo | null>;
57
+ set(fullName: string, repo: ResolvedRepo): Promise<void>;
58
+ invalidate(fullName?: string): Promise<void>;
59
+ private readCache;
60
+ private writeCache;
61
+ }
62
+
63
+ export { DEFAULT_METADATA_CACHE_PATH, DEFAULT_METADATA_CACHE_TTL_MS, DEFAULT_REPO_CACHE_DIR, MetadataCache, type MetadataCacheOptions, type RepoPermissions, RepoResolutionError, type RepoResolutionErrorCode, type ResolvedRepo, type ResolvedRepoGitState, type ResolvedRepoMeta, cloneRepo, resolveRepo };
package/dist/index.js ADDED
@@ -0,0 +1,442 @@
1
+ // src/resolver.ts
2
+ import { homedir as homedir2 } from "os";
3
+ import { join as join2 } from "path";
4
+ import { Octokit } from "@octokit/rest";
5
+
6
+ // src/metadata-cache.ts
7
+ import { constants as fsConstants } from "fs";
8
+ import { access, mkdir, readFile, rename, writeFile } from "fs/promises";
9
+ import { homedir } from "os";
10
+ import { dirname, join } from "path";
11
+ var DEFAULT_METADATA_CACHE_PATH = join(homedir(), ".oac", "cache", "repos.json");
12
+ var DEFAULT_METADATA_CACHE_TTL_MS = 60 * 60 * 1e3;
13
+ var EMPTY_CACHE = {
14
+ version: 1,
15
+ entries: {}
16
+ };
17
+ var MetadataCache = class {
18
+ filePath;
19
+ ttlMs;
20
+ now;
21
+ constructor(options = {}) {
22
+ this.filePath = expandHomePath(options.filePath ?? DEFAULT_METADATA_CACHE_PATH);
23
+ this.ttlMs = options.ttlMs ?? DEFAULT_METADATA_CACHE_TTL_MS;
24
+ this.now = options.now ?? Date.now;
25
+ }
26
+ async get(fullName) {
27
+ const cache = await this.readCache();
28
+ const key = normalizeCacheKey(fullName);
29
+ const entry = cache.entries[key];
30
+ if (!entry) {
31
+ return null;
32
+ }
33
+ if (this.now() - entry.cachedAt > this.ttlMs) {
34
+ delete cache.entries[key];
35
+ await this.writeCache(cache);
36
+ return null;
37
+ }
38
+ return entry.repo;
39
+ }
40
+ async set(fullName, repo) {
41
+ const cache = await this.readCache();
42
+ const key = normalizeCacheKey(fullName);
43
+ cache.entries[key] = {
44
+ cachedAt: this.now(),
45
+ repo
46
+ };
47
+ await this.writeCache(cache);
48
+ }
49
+ async invalidate(fullName) {
50
+ if (!fullName) {
51
+ await this.writeCache(EMPTY_CACHE);
52
+ return;
53
+ }
54
+ const cache = await this.readCache();
55
+ const key = normalizeCacheKey(fullName);
56
+ if (!(key in cache.entries)) {
57
+ return;
58
+ }
59
+ delete cache.entries[key];
60
+ await this.writeCache(cache);
61
+ }
62
+ async readCache() {
63
+ if (!await pathExists(this.filePath)) {
64
+ return { ...EMPTY_CACHE, entries: {} };
65
+ }
66
+ try {
67
+ const raw = await readFile(this.filePath, "utf8");
68
+ const parsed = JSON.parse(raw);
69
+ if (parsed.version !== 1 || typeof parsed.entries !== "object") {
70
+ return { ...EMPTY_CACHE, entries: {} };
71
+ }
72
+ return {
73
+ version: 1,
74
+ entries: parsed.entries
75
+ };
76
+ } catch {
77
+ return { ...EMPTY_CACHE, entries: {} };
78
+ }
79
+ }
80
+ async writeCache(cache) {
81
+ await mkdir(dirname(this.filePath), { recursive: true });
82
+ const tempPath = `${this.filePath}.tmp`;
83
+ await writeFile(tempPath, JSON.stringify(cache, null, 2), "utf8");
84
+ await rename(tempPath, this.filePath);
85
+ }
86
+ };
87
+ function normalizeCacheKey(fullName) {
88
+ return fullName.trim().toLowerCase();
89
+ }
90
+ function expandHomePath(path) {
91
+ if (path === "~") {
92
+ return homedir();
93
+ }
94
+ if (path.startsWith("~/")) {
95
+ return join(homedir(), path.slice(2));
96
+ }
97
+ return path;
98
+ }
99
+ async function pathExists(path) {
100
+ try {
101
+ await access(path, fsConstants.F_OK);
102
+ return true;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ // src/resolver.ts
109
+ var OWNER_REPO_PATTERN = /^(?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+?)(?:\.git)?$/;
110
+ var GITHUB_SSH_PATTERN = /^git@github\.com:(?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+?)(?:\.git)?$/;
111
+ var RepoResolutionError = class extends Error {
112
+ code;
113
+ constructor(message, code, cause) {
114
+ super(message, { cause });
115
+ this.name = "RepoResolutionError";
116
+ this.code = code;
117
+ }
118
+ };
119
+ async function resolveRepo(input) {
120
+ const parsed = parseRepoInput(input);
121
+ const cache = new MetadataCache();
122
+ const cacheKey = `${parsed.owner}/${parsed.name}`;
123
+ const cached = await cache.get(cacheKey);
124
+ if (cached) {
125
+ return cached;
126
+ }
127
+ const octokit = new Octokit({
128
+ auth: process.env.GITHUB_TOKEN
129
+ });
130
+ const repoData = await fetchRepo(octokit, parsed.owner, parsed.name);
131
+ if (repoData.archived) {
132
+ throw new RepoResolutionError(
133
+ `Repository "${repoData.full_name}" is archived and cannot be used for contributions.`,
134
+ "ARCHIVED"
135
+ );
136
+ }
137
+ const permissions = normalizePermissions(repoData.private, repoData.permissions);
138
+ if (!permissions.pull) {
139
+ throw new RepoResolutionError(
140
+ `Missing pull permission for "${repoData.full_name}".`,
141
+ "FORBIDDEN"
142
+ );
143
+ }
144
+ const [languages, headSha] = await Promise.all([
145
+ fetchLanguages(octokit, repoData.owner.login, repoData.name),
146
+ fetchHeadSha(octokit, repoData.owner.login, repoData.name, repoData.default_branch)
147
+ ]);
148
+ const localPath = defaultLocalPath(repoData.owner.login, repoData.name);
149
+ const resolved = {
150
+ fullName: repoData.full_name,
151
+ owner: repoData.owner.login,
152
+ name: repoData.name,
153
+ localPath,
154
+ worktreePath: join2(localPath, "..", ".oac-worktrees", repoData.default_branch),
155
+ meta: {
156
+ defaultBranch: repoData.default_branch,
157
+ language: repoData.language,
158
+ languages,
159
+ size: repoData.size,
160
+ stars: repoData.stargazers_count,
161
+ openIssuesCount: repoData.open_issues_count,
162
+ topics: repoData.topics ?? [],
163
+ license: normalizeLicense(repoData.license?.spdx_id ?? null),
164
+ isArchived: repoData.archived,
165
+ isFork: repoData.fork,
166
+ permissions
167
+ },
168
+ git: {
169
+ headSha,
170
+ remoteUrl: repoData.clone_url ?? `https://github.com/${repoData.full_name}.git`,
171
+ isShallowClone: true
172
+ }
173
+ };
174
+ await cache.set(resolved.fullName, resolved);
175
+ return resolved;
176
+ }
177
+ function parseRepoInput(input) {
178
+ const normalized = input.trim();
179
+ if (!normalized) {
180
+ throw new RepoResolutionError("Repository input cannot be empty.", "INVALID_INPUT");
181
+ }
182
+ const ownerRepoMatch = normalized.match(OWNER_REPO_PATTERN);
183
+ if (ownerRepoMatch?.groups) {
184
+ return {
185
+ owner: ownerRepoMatch.groups.owner,
186
+ name: ownerRepoMatch.groups.repo
187
+ };
188
+ }
189
+ const sshMatch = normalized.match(GITHUB_SSH_PATTERN);
190
+ if (sshMatch?.groups) {
191
+ return {
192
+ owner: sshMatch.groups.owner,
193
+ name: sshMatch.groups.repo
194
+ };
195
+ }
196
+ const normalizedUrlInput = normalized.startsWith("github.com/") ? `https://${normalized}` : normalized;
197
+ try {
198
+ const url = new URL(normalizedUrlInput);
199
+ if (!isGitHubHost(url.hostname)) {
200
+ throw new RepoResolutionError(
201
+ `Only github.com repository URLs are supported, received "${url.hostname}".`,
202
+ "INVALID_INPUT"
203
+ );
204
+ }
205
+ const pathParts = url.pathname.split("/").filter(Boolean);
206
+ if (pathParts.length < 2) {
207
+ throw new RepoResolutionError(`Invalid GitHub repository URL "${input}".`, "INVALID_INPUT");
208
+ }
209
+ const owner = pathParts[0];
210
+ const name = stripGitSuffix(pathParts[1]);
211
+ if (!owner || !name) {
212
+ throw new RepoResolutionError(`Invalid GitHub repository URL "${input}".`, "INVALID_INPUT");
213
+ }
214
+ return { owner, name };
215
+ } catch (error) {
216
+ if (error instanceof RepoResolutionError) {
217
+ throw error;
218
+ }
219
+ throw new RepoResolutionError(
220
+ `Expected "owner/repo" or a GitHub repository URL, received "${input}".`,
221
+ "INVALID_INPUT",
222
+ error
223
+ );
224
+ }
225
+ }
226
+ async function fetchRepo(octokit, owner, repo) {
227
+ try {
228
+ return (await octokit.repos.get({ owner, repo })).data;
229
+ } catch (error) {
230
+ throw toResolutionError(owner, repo, error);
231
+ }
232
+ }
233
+ async function fetchLanguages(octokit, owner, repo) {
234
+ try {
235
+ const response = await octokit.repos.listLanguages({ owner, repo });
236
+ return response.data;
237
+ } catch (error) {
238
+ throw toResolutionError(owner, repo, error);
239
+ }
240
+ }
241
+ async function fetchHeadSha(octokit, owner, repo, defaultBranch) {
242
+ try {
243
+ const branch = await octokit.repos.getBranch({
244
+ owner,
245
+ repo,
246
+ branch: defaultBranch
247
+ });
248
+ return branch.data.commit.sha;
249
+ } catch (error) {
250
+ throw toResolutionError(owner, repo, error);
251
+ }
252
+ }
253
+ function toResolutionError(owner, repo, error) {
254
+ const fullName = `${owner}/${repo}`;
255
+ const status = isApiError(error) ? error.status : void 0;
256
+ const message = typeof error === "object" && error && "message" in error ? String(error.message) : "unknown error";
257
+ if (status === 404) {
258
+ return new RepoResolutionError(
259
+ `Repository "${fullName}" was not found on GitHub.`,
260
+ "NOT_FOUND",
261
+ error
262
+ );
263
+ }
264
+ if (status === 403) {
265
+ return new RepoResolutionError(
266
+ `Access denied while resolving "${fullName}". Check GITHUB_TOKEN permissions.`,
267
+ "FORBIDDEN",
268
+ error
269
+ );
270
+ }
271
+ return new RepoResolutionError(
272
+ `Failed to resolve repository "${fullName}": ${message}`,
273
+ "UNKNOWN",
274
+ error
275
+ );
276
+ }
277
+ function normalizePermissions(isPrivateRepo, permissions) {
278
+ const pull = permissions?.pull ?? !isPrivateRepo;
279
+ return {
280
+ push: permissions?.push ?? false,
281
+ pull,
282
+ admin: permissions?.admin ?? false
283
+ };
284
+ }
285
+ function normalizeLicense(spdxId) {
286
+ if (!spdxId || spdxId === "NOASSERTION") {
287
+ return null;
288
+ }
289
+ return spdxId;
290
+ }
291
+ function isGitHubHost(hostname) {
292
+ const normalized = hostname.toLowerCase();
293
+ return normalized === "github.com" || normalized === "www.github.com";
294
+ }
295
+ function stripGitSuffix(repo) {
296
+ return repo.replace(/\.git$/i, "");
297
+ }
298
+ function isApiError(error) {
299
+ return typeof error === "object" && error !== null && "status" in error;
300
+ }
301
+ function defaultLocalPath(owner, name) {
302
+ return join2(homedir2(), ".oac", "cache", "repos", owner, name);
303
+ }
304
+
305
+ // src/cloner.ts
306
+ import { constants as fsConstants2 } from "fs";
307
+ import { access as access2, mkdir as mkdir2 } from "fs/promises";
308
+ import { homedir as homedir3 } from "os";
309
+ import { dirname as dirname2, join as join3, resolve } from "path";
310
+ import { simpleGit } from "simple-git";
311
+ var CLONE_RETRY_BACKOFF_MS = [1e3, 4e3, 16e3];
312
+ var DEFAULT_REPO_CACHE_DIR = join3(homedir3(), ".oac", "cache", "repos");
313
+ async function cloneRepo(repo, cacheDir = DEFAULT_REPO_CACHE_DIR) {
314
+ const cacheRoot = resolveCacheDir(cacheDir);
315
+ const localPath = join3(cacheRoot, repo.owner, repo.name);
316
+ await mkdir2(dirname2(localPath), { recursive: true });
317
+ if (await isGitRepository(localPath)) {
318
+ await pullExistingClone(repo, localPath);
319
+ } else if (await pathExists2(localPath)) {
320
+ throw new Error(
321
+ `Cannot clone "${repo.fullName}" into "${localPath}" because the directory exists and is not a git repository.`
322
+ );
323
+ } else {
324
+ await cloneNewRepository(repo, localPath);
325
+ }
326
+ const git = simpleGit(localPath);
327
+ repo.localPath = localPath;
328
+ repo.worktreePath = join3(localPath, "..", ".oac-worktrees", repo.meta.defaultBranch);
329
+ repo.git.headSha = (await git.revparse(["HEAD"])).trim();
330
+ repo.git.isShallowClone = await isShallowClone(git);
331
+ repo.git.remoteUrl = await getOriginUrl(git, repo.git.remoteUrl);
332
+ return localPath;
333
+ }
334
+ async function cloneNewRepository(repo, localPath) {
335
+ const git = simpleGit();
336
+ await retryGitOperation(
337
+ () => git.clone(repo.git.remoteUrl, localPath, [
338
+ "--depth",
339
+ "1",
340
+ "--branch",
341
+ repo.meta.defaultBranch
342
+ ]),
343
+ `clone ${repo.fullName}`
344
+ );
345
+ }
346
+ async function pullExistingClone(repo, localPath) {
347
+ const git = simpleGit(localPath);
348
+ await ensureOriginRemote(git, repo.git.remoteUrl);
349
+ await retryGitOperation(
350
+ () => git.fetch("origin", repo.meta.defaultBranch, ["--depth=1"]),
351
+ `fetch ${repo.fullName}`
352
+ );
353
+ await checkoutDefaultBranch(git, repo.meta.defaultBranch);
354
+ await retryGitOperation(
355
+ () => git.pull("origin", repo.meta.defaultBranch, ["--ff-only"]),
356
+ `pull ${repo.fullName}`
357
+ );
358
+ }
359
+ async function checkoutDefaultBranch(git, branchName) {
360
+ try {
361
+ await git.checkout(branchName);
362
+ } catch {
363
+ await git.raw(["checkout", "-B", branchName, `origin/${branchName}`]);
364
+ }
365
+ }
366
+ async function ensureOriginRemote(git, remoteUrl) {
367
+ const remotes = await git.getRemotes(true);
368
+ const origin = remotes.find((remote) => remote.name === "origin");
369
+ if (!origin) {
370
+ await git.addRemote("origin", remoteUrl);
371
+ return;
372
+ }
373
+ if (origin.refs.fetch !== remoteUrl && origin.refs.push !== remoteUrl) {
374
+ await git.remote(["set-url", "origin", remoteUrl]);
375
+ }
376
+ }
377
+ async function getOriginUrl(git, fallbackUrl) {
378
+ const remotes = await git.getRemotes(true);
379
+ const origin = remotes.find((remote) => remote.name === "origin");
380
+ return origin?.refs.fetch ?? fallbackUrl;
381
+ }
382
+ async function isShallowClone(git) {
383
+ const output = await git.raw(["rev-parse", "--is-shallow-repository"]);
384
+ return output.trim() === "true";
385
+ }
386
+ async function retryGitOperation(operation, operationName) {
387
+ let lastError;
388
+ for (let attempt = 0; attempt <= CLONE_RETRY_BACKOFF_MS.length; attempt += 1) {
389
+ try {
390
+ return await operation();
391
+ } catch (error) {
392
+ lastError = error;
393
+ if (attempt === CLONE_RETRY_BACKOFF_MS.length) {
394
+ break;
395
+ }
396
+ await sleep(CLONE_RETRY_BACKOFF_MS[attempt]);
397
+ }
398
+ }
399
+ throw new Error(
400
+ `Git operation failed after ${CLONE_RETRY_BACKOFF_MS.length + 1} attempts (${operationName}).`,
401
+ { cause: lastError }
402
+ );
403
+ }
404
+ function resolveCacheDir(cacheDir) {
405
+ const selected = cacheDir.trim().length > 0 ? cacheDir : DEFAULT_REPO_CACHE_DIR;
406
+ return resolve(expandHomePath2(selected));
407
+ }
408
+ function expandHomePath2(path) {
409
+ if (path === "~") {
410
+ return homedir3();
411
+ }
412
+ if (path.startsWith("~/")) {
413
+ return join3(homedir3(), path.slice(2));
414
+ }
415
+ return path;
416
+ }
417
+ async function pathExists2(path) {
418
+ try {
419
+ await access2(path, fsConstants2.F_OK);
420
+ return true;
421
+ } catch {
422
+ return false;
423
+ }
424
+ }
425
+ async function isGitRepository(path) {
426
+ return pathExists2(join3(path, ".git"));
427
+ }
428
+ function sleep(ms) {
429
+ return new Promise((resolvePromise) => {
430
+ setTimeout(resolvePromise, ms);
431
+ });
432
+ }
433
+ export {
434
+ DEFAULT_METADATA_CACHE_PATH,
435
+ DEFAULT_METADATA_CACHE_TTL_MS,
436
+ DEFAULT_REPO_CACHE_DIR,
437
+ MetadataCache,
438
+ RepoResolutionError,
439
+ cloneRepo,
440
+ resolveRepo
441
+ };
442
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolver.ts","../src/metadata-cache.ts","../src/cloner.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { Octokit } from \"@octokit/rest\";\nimport { MetadataCache } from \"./metadata-cache.js\";\nimport type { RepoPermissions, ResolvedRepo } from \"./types.js\";\n\nconst OWNER_REPO_PATTERN = /^(?<owner>[A-Za-z0-9_.-]+)\\/(?<repo>[A-Za-z0-9_.-]+?)(?:\\.git)?$/;\nconst GITHUB_SSH_PATTERN =\n /^git@github\\.com:(?<owner>[A-Za-z0-9_.-]+)\\/(?<repo>[A-Za-z0-9_.-]+?)(?:\\.git)?$/;\n\nexport type RepoResolutionErrorCode =\n | \"INVALID_INPUT\"\n | \"NOT_FOUND\"\n | \"FORBIDDEN\"\n | \"ARCHIVED\"\n | \"UNKNOWN\";\n\nexport class RepoResolutionError extends Error {\n public readonly code: RepoResolutionErrorCode;\n\n public constructor(message: string, code: RepoResolutionErrorCode, cause?: unknown) {\n super(message, { cause });\n this.name = \"RepoResolutionError\";\n this.code = code;\n }\n}\n\ninterface ParsedRepoInput {\n owner: string;\n name: string;\n}\n\nexport async function resolveRepo(input: string): Promise<ResolvedRepo> {\n const parsed = parseRepoInput(input);\n const cache = new MetadataCache();\n const cacheKey = `${parsed.owner}/${parsed.name}`;\n const cached = await cache.get(cacheKey);\n\n if (cached) {\n return cached;\n }\n\n const octokit = new Octokit({\n auth: process.env.GITHUB_TOKEN,\n });\n\n const repoData = await fetchRepo(octokit, parsed.owner, parsed.name);\n if (repoData.archived) {\n throw new RepoResolutionError(\n `Repository \"${repoData.full_name}\" is archived and cannot be used for contributions.`,\n \"ARCHIVED\",\n );\n }\n\n const permissions = normalizePermissions(repoData.private, repoData.permissions);\n if (!permissions.pull) {\n throw new RepoResolutionError(\n `Missing pull permission for \"${repoData.full_name}\".`,\n \"FORBIDDEN\",\n );\n }\n\n const [languages, headSha] = await Promise.all([\n fetchLanguages(octokit, repoData.owner.login, repoData.name),\n fetchHeadSha(octokit, repoData.owner.login, repoData.name, repoData.default_branch),\n ]);\n\n const localPath = defaultLocalPath(repoData.owner.login, repoData.name);\n const resolved: ResolvedRepo = {\n fullName: repoData.full_name,\n owner: repoData.owner.login,\n name: repoData.name,\n localPath,\n worktreePath: join(localPath, \"..\", \".oac-worktrees\", repoData.default_branch),\n meta: {\n defaultBranch: repoData.default_branch,\n language: repoData.language,\n languages,\n size: repoData.size,\n stars: repoData.stargazers_count,\n openIssuesCount: repoData.open_issues_count,\n topics: repoData.topics ?? [],\n license: normalizeLicense(repoData.license?.spdx_id ?? null),\n isArchived: repoData.archived,\n isFork: repoData.fork,\n permissions,\n },\n git: {\n headSha,\n remoteUrl: repoData.clone_url ?? `https://github.com/${repoData.full_name}.git`,\n isShallowClone: true,\n },\n };\n\n await cache.set(resolved.fullName, resolved);\n return resolved;\n}\n\nfunction parseRepoInput(input: string): ParsedRepoInput {\n const normalized = input.trim();\n if (!normalized) {\n throw new RepoResolutionError(\"Repository input cannot be empty.\", \"INVALID_INPUT\");\n }\n\n const ownerRepoMatch = normalized.match(OWNER_REPO_PATTERN);\n if (ownerRepoMatch?.groups) {\n return {\n owner: ownerRepoMatch.groups.owner,\n name: ownerRepoMatch.groups.repo,\n };\n }\n\n const sshMatch = normalized.match(GITHUB_SSH_PATTERN);\n if (sshMatch?.groups) {\n return {\n owner: sshMatch.groups.owner,\n name: sshMatch.groups.repo,\n };\n }\n\n const normalizedUrlInput = normalized.startsWith(\"github.com/\")\n ? `https://${normalized}`\n : normalized;\n\n try {\n const url = new URL(normalizedUrlInput);\n if (!isGitHubHost(url.hostname)) {\n throw new RepoResolutionError(\n `Only github.com repository URLs are supported, received \"${url.hostname}\".`,\n \"INVALID_INPUT\",\n );\n }\n\n const pathParts = url.pathname.split(\"/\").filter(Boolean);\n if (pathParts.length < 2) {\n throw new RepoResolutionError(`Invalid GitHub repository URL \"${input}\".`, \"INVALID_INPUT\");\n }\n\n const owner = pathParts[0];\n const name = stripGitSuffix(pathParts[1]);\n if (!owner || !name) {\n throw new RepoResolutionError(`Invalid GitHub repository URL \"${input}\".`, \"INVALID_INPUT\");\n }\n\n return { owner, name };\n } catch (error) {\n if (error instanceof RepoResolutionError) {\n throw error;\n }\n\n throw new RepoResolutionError(\n `Expected \"owner/repo\" or a GitHub repository URL, received \"${input}\".`,\n \"INVALID_INPUT\",\n error,\n );\n }\n}\n\nasync function fetchRepo(octokit: Octokit, owner: string, repo: string) {\n try {\n return (await octokit.repos.get({ owner, repo })).data;\n } catch (error) {\n throw toResolutionError(owner, repo, error);\n }\n}\n\nasync function fetchLanguages(\n octokit: Octokit,\n owner: string,\n repo: string,\n): Promise<Record<string, number>> {\n try {\n const response = await octokit.repos.listLanguages({ owner, repo });\n return response.data;\n } catch (error) {\n throw toResolutionError(owner, repo, error);\n }\n}\n\nasync function fetchHeadSha(\n octokit: Octokit,\n owner: string,\n repo: string,\n defaultBranch: string,\n): Promise<string> {\n try {\n const branch = await octokit.repos.getBranch({\n owner,\n repo,\n branch: defaultBranch,\n });\n return branch.data.commit.sha;\n } catch (error) {\n throw toResolutionError(owner, repo, error);\n }\n}\n\nfunction toResolutionError(owner: string, repo: string, error: unknown): RepoResolutionError {\n const fullName = `${owner}/${repo}`;\n const status = isApiError(error) ? error.status : undefined;\n const message =\n typeof error === \"object\" && error && \"message\" in error\n ? String(error.message)\n : \"unknown error\";\n\n if (status === 404) {\n return new RepoResolutionError(\n `Repository \"${fullName}\" was not found on GitHub.`,\n \"NOT_FOUND\",\n error,\n );\n }\n\n if (status === 403) {\n return new RepoResolutionError(\n `Access denied while resolving \"${fullName}\". Check GITHUB_TOKEN permissions.`,\n \"FORBIDDEN\",\n error,\n );\n }\n\n return new RepoResolutionError(\n `Failed to resolve repository \"${fullName}\": ${message}`,\n \"UNKNOWN\",\n error,\n );\n}\n\nfunction normalizePermissions(\n isPrivateRepo: boolean,\n permissions:\n | {\n admin?: boolean;\n push?: boolean;\n pull?: boolean;\n }\n | undefined,\n): RepoPermissions {\n const pull = permissions?.pull ?? !isPrivateRepo;\n\n return {\n push: permissions?.push ?? false,\n pull,\n admin: permissions?.admin ?? false,\n };\n}\n\nfunction normalizeLicense(spdxId: string | null): string | null {\n if (!spdxId || spdxId === \"NOASSERTION\") {\n return null;\n }\n\n return spdxId;\n}\n\nfunction isGitHubHost(hostname: string): boolean {\n const normalized = hostname.toLowerCase();\n return normalized === \"github.com\" || normalized === \"www.github.com\";\n}\n\nfunction stripGitSuffix(repo: string): string {\n return repo.replace(/\\.git$/i, \"\");\n}\n\nfunction isApiError(error: unknown): error is { status?: number } {\n return typeof error === \"object\" && error !== null && \"status\" in error;\n}\n\nfunction defaultLocalPath(owner: string, name: string): string {\n return join(homedir(), \".oac\", \"cache\", \"repos\", owner, name);\n}\n","import { constants as fsConstants } from \"node:fs\";\nimport { access, mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { ResolvedRepo } from \"./types.js\";\n\ninterface MetadataCacheEntry {\n cachedAt: number;\n repo: ResolvedRepo;\n}\n\ninterface MetadataCacheFile {\n version: 1;\n entries: Record<string, MetadataCacheEntry>;\n}\n\nexport interface MetadataCacheOptions {\n filePath?: string;\n ttlMs?: number;\n now?: () => number;\n}\n\nexport const DEFAULT_METADATA_CACHE_PATH = join(homedir(), \".oac\", \"cache\", \"repos.json\");\n\nexport const DEFAULT_METADATA_CACHE_TTL_MS = 60 * 60 * 1000;\n\nconst EMPTY_CACHE: MetadataCacheFile = {\n version: 1,\n entries: {},\n};\n\nexport class MetadataCache {\n private readonly filePath: string;\n private readonly ttlMs: number;\n private readonly now: () => number;\n\n public constructor(options: MetadataCacheOptions = {}) {\n this.filePath = expandHomePath(options.filePath ?? DEFAULT_METADATA_CACHE_PATH);\n this.ttlMs = options.ttlMs ?? DEFAULT_METADATA_CACHE_TTL_MS;\n this.now = options.now ?? Date.now;\n }\n\n public async get(fullName: string): Promise<ResolvedRepo | null> {\n const cache = await this.readCache();\n const key = normalizeCacheKey(fullName);\n const entry = cache.entries[key];\n\n if (!entry) {\n return null;\n }\n\n if (this.now() - entry.cachedAt > this.ttlMs) {\n delete cache.entries[key];\n await this.writeCache(cache);\n return null;\n }\n\n return entry.repo;\n }\n\n public async set(fullName: string, repo: ResolvedRepo): Promise<void> {\n const cache = await this.readCache();\n const key = normalizeCacheKey(fullName);\n cache.entries[key] = {\n cachedAt: this.now(),\n repo,\n };\n await this.writeCache(cache);\n }\n\n public async invalidate(fullName?: string): Promise<void> {\n if (!fullName) {\n await this.writeCache(EMPTY_CACHE);\n return;\n }\n\n const cache = await this.readCache();\n const key = normalizeCacheKey(fullName);\n if (!(key in cache.entries)) {\n return;\n }\n\n delete cache.entries[key];\n await this.writeCache(cache);\n }\n\n private async readCache(): Promise<MetadataCacheFile> {\n if (!(await pathExists(this.filePath))) {\n return { ...EMPTY_CACHE, entries: {} };\n }\n\n try {\n const raw = await readFile(this.filePath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<MetadataCacheFile>;\n\n if (parsed.version !== 1 || typeof parsed.entries !== \"object\") {\n return { ...EMPTY_CACHE, entries: {} };\n }\n\n return {\n version: 1,\n entries: parsed.entries as Record<string, MetadataCacheEntry>,\n };\n } catch {\n return { ...EMPTY_CACHE, entries: {} };\n }\n }\n\n private async writeCache(cache: MetadataCacheFile): Promise<void> {\n await mkdir(dirname(this.filePath), { recursive: true });\n const tempPath = `${this.filePath}.tmp`;\n await writeFile(tempPath, JSON.stringify(cache, null, 2), \"utf8\");\n await rename(tempPath, this.filePath);\n }\n}\n\nfunction normalizeCacheKey(fullName: string): string {\n return fullName.trim().toLowerCase();\n}\n\nfunction expandHomePath(path: string): string {\n if (path === \"~\") {\n return homedir();\n }\n\n if (path.startsWith(\"~/\")) {\n return join(homedir(), path.slice(2));\n }\n\n return path;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path, fsConstants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n","import { constants as fsConstants } from \"node:fs\";\nimport { access, mkdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { type SimpleGit, simpleGit } from \"simple-git\";\nimport type { ResolvedRepo } from \"./types.js\";\n\nconst CLONE_RETRY_BACKOFF_MS = [1000, 4000, 16000] as const;\n\nexport const DEFAULT_REPO_CACHE_DIR = join(homedir(), \".oac\", \"cache\", \"repos\");\n\nexport async function cloneRepo(\n repo: ResolvedRepo,\n cacheDir: string = DEFAULT_REPO_CACHE_DIR,\n): Promise<string> {\n const cacheRoot = resolveCacheDir(cacheDir);\n const localPath = join(cacheRoot, repo.owner, repo.name);\n await mkdir(dirname(localPath), { recursive: true });\n\n if (await isGitRepository(localPath)) {\n await pullExistingClone(repo, localPath);\n } else if (await pathExists(localPath)) {\n throw new Error(\n `Cannot clone \"${repo.fullName}\" into \"${localPath}\" because the directory exists and is not a git repository.`,\n );\n } else {\n await cloneNewRepository(repo, localPath);\n }\n\n const git = simpleGit(localPath);\n repo.localPath = localPath;\n repo.worktreePath = join(localPath, \"..\", \".oac-worktrees\", repo.meta.defaultBranch);\n repo.git.headSha = (await git.revparse([\"HEAD\"])).trim();\n repo.git.isShallowClone = await isShallowClone(git);\n repo.git.remoteUrl = await getOriginUrl(git, repo.git.remoteUrl);\n\n return localPath;\n}\n\nasync function cloneNewRepository(repo: ResolvedRepo, localPath: string): Promise<void> {\n const git = simpleGit();\n await retryGitOperation(\n () =>\n git.clone(repo.git.remoteUrl, localPath, [\n \"--depth\",\n \"1\",\n \"--branch\",\n repo.meta.defaultBranch,\n ]),\n `clone ${repo.fullName}`,\n );\n}\n\nasync function pullExistingClone(repo: ResolvedRepo, localPath: string): Promise<void> {\n const git = simpleGit(localPath);\n await ensureOriginRemote(git, repo.git.remoteUrl);\n\n await retryGitOperation(\n () => git.fetch(\"origin\", repo.meta.defaultBranch, [\"--depth=1\"]),\n `fetch ${repo.fullName}`,\n );\n\n await checkoutDefaultBranch(git, repo.meta.defaultBranch);\n\n await retryGitOperation(\n () => git.pull(\"origin\", repo.meta.defaultBranch, [\"--ff-only\"]),\n `pull ${repo.fullName}`,\n );\n}\n\nasync function checkoutDefaultBranch(git: SimpleGit, branchName: string): Promise<void> {\n try {\n await git.checkout(branchName);\n } catch {\n await git.raw([\"checkout\", \"-B\", branchName, `origin/${branchName}`]);\n }\n}\n\nasync function ensureOriginRemote(git: SimpleGit, remoteUrl: string): Promise<void> {\n const remotes = await git.getRemotes(true);\n const origin = remotes.find((remote) => remote.name === \"origin\");\n\n if (!origin) {\n await git.addRemote(\"origin\", remoteUrl);\n return;\n }\n\n if (origin.refs.fetch !== remoteUrl && origin.refs.push !== remoteUrl) {\n await git.remote([\"set-url\", \"origin\", remoteUrl]);\n }\n}\n\nasync function getOriginUrl(git: SimpleGit, fallbackUrl: string): Promise<string> {\n const remotes = await git.getRemotes(true);\n const origin = remotes.find((remote) => remote.name === \"origin\");\n return origin?.refs.fetch ?? fallbackUrl;\n}\n\nasync function isShallowClone(git: SimpleGit): Promise<boolean> {\n const output = await git.raw([\"rev-parse\", \"--is-shallow-repository\"]);\n return output.trim() === \"true\";\n}\n\nasync function retryGitOperation<T>(\n operation: () => Promise<T>,\n operationName: string,\n): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= CLONE_RETRY_BACKOFF_MS.length; attempt += 1) {\n try {\n return await operation();\n } catch (error) {\n lastError = error;\n if (attempt === CLONE_RETRY_BACKOFF_MS.length) {\n break;\n }\n await sleep(CLONE_RETRY_BACKOFF_MS[attempt]);\n }\n }\n\n throw new Error(\n `Git operation failed after ${CLONE_RETRY_BACKOFF_MS.length + 1} attempts (${operationName}).`,\n { cause: lastError },\n );\n}\n\nfunction resolveCacheDir(cacheDir: string): string {\n const selected = cacheDir.trim().length > 0 ? cacheDir : DEFAULT_REPO_CACHE_DIR;\n return resolve(expandHomePath(selected));\n}\n\nfunction expandHomePath(path: string): string {\n if (path === \"~\") {\n return homedir();\n }\n\n if (path.startsWith(\"~/\")) {\n return join(homedir(), path.slice(2));\n }\n\n return path;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path, fsConstants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function isGitRepository(path: string): Promise<boolean> {\n return pathExists(join(path, \".git\"));\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolvePromise) => {\n setTimeout(resolvePromise, ms);\n });\n}\n"],"mappings":"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","names":["homedir","join","join","homedir","fsConstants","access","mkdir","homedir","dirname","join","pathExists","expandHomePath"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@open330/oac-repo",
3
+ "version": "2026.2.17",
4
+ "description": "Repository resolution and cloning for OAC",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Open330/open-agent-contribution.git",
9
+ "directory": "packages/repo"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.js",
17
+ "types": "./dist/index.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "engines": {
27
+ "node": ">=22.0.0"
28
+ },
29
+ "dependencies": {
30
+ "@octokit/rest": "^21.1.1",
31
+ "simple-git": "^3.27.0",
32
+ "@open330/oac-core": "2026.2.17"
33
+ },
34
+ "devDependencies": {
35
+ "tsup": "^8.3.6",
36
+ "typescript": "^5.7.3"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup src/index.ts --format esm --dts",
40
+ "test": "vitest run",
41
+ "typecheck": "tsc --noEmit"
42
+ }
43
+ }