Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-stable/src/address.ts
Snapshot at 34e3175
1export interface ForkAddressInput {
2 /** Canonical HTTPS origin from the connected account's configuration. */
3 origin: string;
4 project: string;
5 repoName: string;
6}
7
8/** Matches core's project slug grammar, without accepting a trailing newline. */
9export function isProjectSlug(value: unknown): value is string {
10 return typeof value === "string" && value.length <= 48 &&
11 /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value) && !/[^a-z0-9-]/.test(value);
12}
13
14/** No normalization: a caller must supply the configured origin, never a URL path. */
15export function isHttpsOrigin(value: unknown): value is string {
16 if (typeof value !== "string" || value.length > 2048) return false;
17 try {
18 const url = new URL(value);
19 return url.protocol === "https:" && url.origin === value &&
20 !url.username && !url.password;
21 } catch {
22 return false;
23 }
24}
25
26/** Address construction only; ownership and project access remain server checks. */
27export function forkAddress({ origin, project, repoName }: ForkAddressInput): string {
28 if (!isHttpsOrigin(origin)) throw new Error("A canonical HTTPS origin is required.");
29 if (!isProjectSlug(project)) throw new Error("Invalid project slug.");
30 // Core creates names as project--agent-counter. Require exactly one delimiter;
31 // the suffix is a slug, while allocation and counter semantics remain in core.
32 if (typeof repoName !== "string" || repoName.length > 128 ||
33 !repoName.startsWith(`${project}--`)) throw new Error("Invalid fork repository name.");
34 const suffix = repoName.slice(project.length + 2);
35 if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(suffix) || /[^a-z0-9-]/.test(suffix)) {
36 throw new Error("Invalid fork repository name.");
37 }
38 return `${origin}/projects/${project}/forks/${repoName}`;
39}
40