UNPKG

@lenne.tech/cli

Version:

lenne.Tech CLI: lt

137 lines (136 loc) 5.28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildIdentity = buildIdentity; exports.buildTestIdentity = buildTestIdentity; exports.buildTicketIdentity = buildTicketIdentity; exports.projectSlug = projectSlug; exports.slugify = slugify; /** * Project identity for `lt dev`. * * URL-first: every project has a slug derived from its package.json * "name", and a deterministic set of subdomains under `*.localhost`. * * Convention: * - `<slug>.localhost` → primary App * - `api.<slug>.localhost` → API * - `<other>.<slug>.localhost` → optional additional services * * The internal port behind each subdomain is opaque — Caddy proxies * arbitrary local ports. Developers and Claude only ever see the URL. */ const fs_1 = require("fs"); const path_1 = require("path"); /** * Build a complete identity from a project root. * * Detects monorepo subprojects under `projects/` automatically: * - `projects/api` → `api.<slug>.localhost` * - `projects/app` → `<slug>.localhost` (primary) * - `projects/<other>` → `<other>.<slug>.localhost` * * For standalone projects (single repo, no `projects/` directory): * - API project (config.env.ts present) → `api.<slug>.localhost` * - App project (nuxt.config.ts present) → `<slug>.localhost` */ function buildIdentity(root) { const slug = projectSlug(root); const subdomains = {}; const projectsDir = (0, path_1.join)(root, 'projects'); if ((0, fs_1.existsSync)(projectsDir)) { // Monorepo: enumerate projects/* subdirectories. const apiDir = (0, path_1.join)(projectsDir, 'api'); const appDir = (0, path_1.join)(projectsDir, 'app'); if ((0, fs_1.existsSync)(apiDir)) { subdomains.api = { hostname: `api.${slug}.localhost`, isPrimaryApp: false, subdir: 'projects/api', }; } if ((0, fs_1.existsSync)(appDir)) { subdomains.app = { hostname: `${slug}.localhost`, isPrimaryApp: true, subdir: 'projects/app', }; } } else { // Standalone — derive from project shape. const isApi = (0, fs_1.existsSync)((0, path_1.join)(root, 'src', 'config.env.ts')) || (0, fs_1.existsSync)((0, path_1.join)(root, 'nest-cli.json')); const isApp = (0, fs_1.existsSync)((0, path_1.join)(root, 'nuxt.config.ts')); if (isApi) { subdomains.api = { hostname: `api.${slug}.localhost`, isPrimaryApp: false, subdir: null }; } if (isApp) { subdomains.app = { hostname: `${slug}.localhost`, isPrimaryApp: true, subdir: null }; } } return { root, slug, subdomains }; } /** * Derive an ephemeral "test" identity from a base identity (used by * `lt dev test`). Suffixes the slug and every subdomain hostname with * `-test`, so the test stack runs on its own URLs / ports / Caddy block, * fully parallel to (and isolated from) the dev session. * * svl.localhost → svl-test.localhost * api.svl.localhost → api.svl-test.localhost */ function buildTestIdentity(base, suffix = '-test') { const slug = `${base.slug}${suffix}`; const subdomains = {}; for (const [sub, value] of Object.entries(base.subdomains)) { subdomains[sub] = Object.assign(Object.assign({}, value), { hostname: value.isPrimaryApp ? `${slug}.localhost` : `${sub}.${slug}.localhost` }); } return { root: base.root, slug, subdomains }; } /** * Derive a per-TICKET identity from a base identity (used by `lt ticket` / * `lt dev up --ticket`). Suffixes the slug + every subdomain hostname with the * ticket id, so each ticket worktree runs on its OWN URLs / ports / Caddy block * / DB — fully parallel to and isolated from every other ticket and the base * dev session. * * svl.localhost → svl-2200.localhost * api.svl.localhost → api.svl-2200.localhost * * Mechanically identical to {@link buildTestIdentity} (a named wrapper for * readability + intent at the call sites). `id` is already a clean slug (see * `deriveTicketId` in dev-ticket.ts). */ function buildTicketIdentity(base, id) { return buildTestIdentity(base, `-${id}`); } /** * Read the bare project name from package.json (scope stripped). * Falls back to directory basename if no package.json or no `name`. */ function projectSlug(root) { const fromPkg = readPackageName(root); const raw = fromPkg || (0, path_1.basename)(root); return slugify(raw); } /** Lowercase, alphanumerics + dashes only, trimmed dashes. */ function slugify(input) { return input .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); } /** Read `name` from package.json, scope-stripped (e.g. `@lenne.tech/foo` → `foo`). */ function readPackageName(dir) { const pkgPath = (0, path_1.join)(dir, 'package.json'); if (!(0, fs_1.existsSync)(pkgPath)) return null; try { const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8')); if (!pkg.name) return null; return pkg.name.includes('/') ? pkg.name.split('/').pop() : pkg.name; } catch (_a) { return null; } }