Files
vibn-agent-runner/vibn-frontend/lib/naming.ts

109 lines
3.8 KiB
TypeScript

/**
* Canonical name + domain derivation for workspace-scoped resources.
*
* AI-generated Coolify apps live under a single subdomain namespace
* per workspace:
*
* https://{app-slug}.{workspace-slug}.vibnai.com
*
* e.g. `api.mark.vibnai.com` for the `api` app in workspace `mark`.
*
* The DNS record `*.vibnai.com` (or its subdomain) must resolve to
* the Coolify server. Traefik picks up the Host header and Coolify's
* per-app ACME handshake provisions a Let's Encrypt cert per FQDN.
*/
const VIBN_BASE_DOMAIN = process.env.VIBN_BASE_DOMAIN ?? 'vibnai.com';
const SLUG_STRIP = /[^a-z0-9-]+/g;
/** Lowercase, dash-sanitize a free-form name into a DNS-safe slug. */
export function slugify(name: string): string {
return name
.toLowerCase()
.replace(/[_\s]+/g, '-')
.replace(SLUG_STRIP, '')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 40) || 'app';
}
/**
* The default public FQDN for an app inside a workspace, given the
* workspace's slug (e.g. `mark`) and an app slug (e.g. `my-api`).
*
* workspaceAppFqdn('mark', 'my-api') === 'my-api.mark.vibnai.com'
*/
export function workspaceAppFqdn(workspaceSlug: string, appSlug: string): string {
return `${appSlug}-${workspaceSlug}.${VIBN_BASE_DOMAIN}`;
}
/** `https://{fqdn}` — what Coolify's `domains` field expects. */
export function toDomainsString(fqdns: string[]): string {
return fqdns.map(f => (f.startsWith('http') ? f : `https://${f}`)).join(',');
}
/** Parse a Coolify `domains` CSV back into bare FQDNs. */
export function parseDomainsString(domains: string | null | undefined): string[] {
if (!domains) return [];
return domains
.split(/[,\s]+/)
.map(d => d.trim())
.filter(Boolean)
.map(d => d.replace(/^https?:\/\//, '').replace(/\/+$/, ''));
}
/** Guard against cross-workspace or disallowed domains. */
export function isDomainUnderWorkspace(fqdn: string, workspaceSlug: string): boolean {
const f = fqdn.replace(/^https?:\/\//, '').toLowerCase();
return f === `${workspaceSlug}.${VIBN_BASE_DOMAIN}` || f.endsWith(`-${workspaceSlug}.${VIBN_BASE_DOMAIN}`) || f.endsWith(`.${workspaceSlug}.${VIBN_BASE_DOMAIN}`);
}
/**
* Build a Gitea SSH clone URL for a repo in a workspace's org.
*
* NOTE: As of 2026-04 this is deprecated for Coolify-driven deploys on
* vibnai.com — Gitea's builtin SSH is bound to host port 22222 which is
* not publicly reachable, and the default port 22 hits Ubuntu's host
* sshd which doesn't know about Gitea keys. Use {@link giteaHttpsUrl}
* instead and embed the workspace bot's PAT. Kept for read-only places
* (e.g. UI display) that want the canonical "clone with SSH" form.
*/
export function giteaSshUrl(org: string, repo: string, giteaHost = 'git.vibnai.com'): string {
return `git@${giteaHost}:${org}/${repo}.git`;
}
/**
* Build a Gitea HTTPS clone URL with basic-auth credentials embedded.
*
* https://{username}:{token}@{host}/{org}/{repo}.git
*
* This is what we pass to Coolify's `git_repository` field for every
* Vibn-provisioned app. Works regardless of SSH topology and scopes
* access to whatever the bot user can see. The token is usually the
* per-workspace Gitea bot PAT.
*
* We URL-encode the username and token so PATs with special chars
* (`:`, `/`, `@`, `#`, `?`, etc.) don't break URL parsing.
*/
export function giteaHttpsUrl(
org: string,
repo: string,
username: string,
token: string,
giteaHost = 'git.vibnai.com'
): string {
const u = encodeURIComponent(username);
const t = encodeURIComponent(token);
return `https://${u}:${t}@${giteaHost}/${org}/${repo}.git`;
}
/**
* Redact the credentials from a Gitea HTTPS URL for safe logging /
* display. Leaves the repo path intact. No-op for non-HTTPS URLs.
*/
export function redactGiteaHttpsUrl(url: string): string {
return url.replace(/^(https?:\/\/)[^@]+@/, '$1***:***@');
}
export { VIBN_BASE_DOMAIN };