Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-stable/src/address.ts

Snapshot at 34e3175

Containing directory

1,766 bytes · typescriptView raw
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