forked from farhoodlabs/paperclip
refactor(portability): migrate to git-source; delete github-fetch.ts
Mirrors the skills refactor: company-portability was the second user of
the per-host REST shim (its own parallel parseGitHubSourceUrl + fetch
helpers + raw.githubusercontent URL builder), so importing a company
package from a non-github URL hit the same Gitea 404 the skills path did.
- Extend git-source.ts:
- parseGitSourceUrl: also recognises query-string shape
(?ref=...&path=...) used by portability URLs, with precedence over
path-style segments when both are present.
- RepoSnapshot: add readBinary (Uint8Array for the company logo
fetch) and readFileOptional (null on NotFoundError, for the
COMPANY.md probe + main->master fallback).
- Rewrite resolveSource in company-portability.ts to open a single
in-memory snapshot per import and serve all reads (COMPANY.md,
candidate tree, includes, logo) from it. Drops fetchText/fetchJson/
fetchBinary/fetchOptionalText.
- parseGitHubSourceUrl stays exported with its original return shape
({hostname, owner, repo, ref, basePath, companyPath}) so the existing
test suite passes unchanged. It now delegates URL parsing to
parseGitSourceUrl and layers companyPath derivation on top.
- Delete server/src/services/github-fetch.ts: zero remaining callers.
Test coverage:
- 7 new git-source tests (query-string parse variants, query-string
precedence over path style, readBinary, readFileOptional NotFound
null + non-NotFound rethrow) — 34/34 passing.
- 52 existing company-portability tests still pass via the
parseGitHubSourceUrl shim contract.
- Smoke-tested end-to-end against https://git.farh.net/.../?ref=main:
ref resolves, snapshot opens, readFile/readBinary/readFileOptional
all return expected results.
Note: two pre-existing failures in company-skills-routes.test.ts
("does not expose a skill reference...") exist on dev too and are
unrelated to this change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,8 @@ export type RepoSnapshot = {
|
||||
sha: string;
|
||||
listFiles(): Promise<string[]>;
|
||||
readFile(repoPath: string): Promise<string>;
|
||||
readFileOptional(repoPath: string): Promise<string | null>;
|
||||
readBinary(repoPath: string): Promise<Uint8Array>;
|
||||
};
|
||||
|
||||
const SHA_REGEX = /^[0-9a-f]{40}$/i;
|
||||
@@ -50,6 +52,25 @@ export function parseGitSourceUrl(rawUrl: string): ParsedGitSource {
|
||||
const owner = segments[0]!;
|
||||
const repo = segments[1]!.replace(/\.git$/i, "");
|
||||
|
||||
// Query-string shape: /{owner}/{repo}?ref=...&path=...
|
||||
// Used by company portability URLs. Takes precedence over path-based parsing
|
||||
// so a URL with both shapes (rare) prefers the explicit query params.
|
||||
const queryRef = url.searchParams.get("ref")?.trim() ?? null;
|
||||
const queryPath = url.searchParams.get("path")?.trim() ?? null;
|
||||
if (queryRef || queryPath) {
|
||||
const normalizedPath = (queryPath ?? "").replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
||||
return {
|
||||
cloneUrl: buildCloneUrl(url.hostname, owner, repo),
|
||||
hostname: url.hostname,
|
||||
owner,
|
||||
repo,
|
||||
ref: queryRef || null,
|
||||
basePath: normalizedPath,
|
||||
filePath: null,
|
||||
explicitRef: Boolean(queryRef),
|
||||
};
|
||||
}
|
||||
|
||||
let ref: string | null = null;
|
||||
let basePath = "";
|
||||
let filePath: string | null = null;
|
||||
@@ -233,11 +254,29 @@ export async function openRepoSnapshot(
|
||||
return out;
|
||||
}
|
||||
|
||||
async function readFile(repoPath: string): Promise<string> {
|
||||
async function readBinary(repoPath: string): Promise<Uint8Array> {
|
||||
const normalized = repoPath.replace(/^\/+/, "");
|
||||
const { blob } = await git.readBlob({ fs, dir, oid: sha, filepath: normalized });
|
||||
return blob;
|
||||
}
|
||||
|
||||
async function readFile(repoPath: string): Promise<string> {
|
||||
const blob = await readBinary(repoPath);
|
||||
return new TextDecoder("utf-8").decode(blob);
|
||||
}
|
||||
|
||||
return { sha, listFiles, readFile };
|
||||
async function readFileOptional(repoPath: string): Promise<string | null> {
|
||||
try {
|
||||
return await readFile(repoPath);
|
||||
} catch (err) {
|
||||
// isomorphic-git throws NotFoundError when the path is missing from the tree.
|
||||
const name = (err as { code?: string; name?: string } | null)?.code
|
||||
?? (err as { name?: string } | null)?.name
|
||||
?? "";
|
||||
if (/NotFound/i.test(name)) return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return { sha, listFiles, readFile, readFileOptional, readBinary };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user