UNPKG

@lenne.tech/cli

Version:

lenne.Tech CLI: lt

142 lines (141 loc) 7.91 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = require("fs"); const dev_identity_1 = require("../../lib/dev-identity"); const dev_process_1 = require("../../lib/dev-process"); const dev_project_1 = require("../../lib/dev-project"); const dev_state_1 = require("../../lib/dev-state"); const dev_ticket_1 = require("../../lib/dev-ticket"); /** * `lt ticket start <name>` — spin up a fully-isolated parallel dev environment * for a ticket or feature, in seconds: * * 1. `git fetch` + create a git WORKTREE on a fresh branch from `origin/dev` * (so every ticket starts independent from the latest dev) at a sibling * folder `<parent>/<slug>-<id>`, * 2. tag it with a `.lt-dev/ticket` marker (makes every `lt dev *` in it * ticket-aware), * 3. `pnpm install` (hard-links from the shared store → fast), * 4. `lt dev up` → own URLs (`<slug>-<id>.localhost` / `api.<slug>-<id>…`), * own ports, own Caddy block, own EMPTY DB (`<base>-<id>`). * * lt ticket start DEV-2200 → svl-2200.localhost (branch feat/DEV-2200) * lt ticket start checkout-refactor → svl-checkout-refactor.localhost * lt ticket start DEV-2200 --as cof → svl-cof.localhost */ const StartCommand = { description: 'Start an isolated parallel dev env for a ticket/feature (worktree + lt dev up)', name: 'start', run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () { var _a; const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox; // `String(...)` — a purely-numeric arg (e.g. a ticket "9991") is parsed as a // JS number by the option parser, on which `.trim()` would throw. const name = String((_a = parameters.first) !== null && _a !== void 0 ? _a : '').trim(); if (!name) { error('Usage: lt ticket start <ticket-or-name> [--as <id>] [--branch <branch>] [--base <ref>]'); info(colors.dim(' e.g. lt ticket start DEV-2200 | lt ticket start checkout-refactor')); if (!parameters.options.fromGluegunMenu) process.exit(1); return 'ticket start: missing name'; } // Anchor to the MAIN repo (works even when invoked from inside a worktree). const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem); let mainRepoRoot; try { mainRepoRoot = (0, dev_ticket_1.gitMainRepoRoot)(layout.root); } catch (_b) { error('Not inside a git repository — `lt ticket` needs a git project.'); if (!parameters.options.fromGluegunMenu) process.exit(1); return 'ticket start: not a git repo'; } const base = (0, dev_identity_1.buildIdentity)(mainRepoRoot); const id = (0, dev_ticket_1.deriveTicketId)(name, parameters.options.as != null ? String(parameters.options.as) : undefined); const branch = typeof parameters.options.branch === 'string' ? parameters.options.branch : (0, dev_ticket_1.defaultTicketBranch)(name); const baseRef = typeof parameters.options.base === 'string' ? parameters.options.base : 'origin/dev'; const ticketIdentity = (0, dev_identity_1.buildTicketIdentity)(base, id); const dbName = (0, dev_project_1.deriveTicketDbName)((0, dev_project_1.deriveDbName)(layout.apiDir, base.slug), id); const worktreePath = (0, dev_ticket_1.worktreePathFor)(mainRepoRoot, base.slug, id); // Pre-flight: nothing already there + slug free. if ((0, fs_1.existsSync)(worktreePath)) { error(`Target folder already exists: ${worktreePath}`); info(colors.dim(' Use a different id (`--as <id>`) or remove it with `lt ticket stop`.')); if (!parameters.options.fromGluegunMenu) process.exit(1); return 'ticket start: path exists'; } if ((0, dev_state_1.loadRegistry)().projects[ticketIdentity.slug]) { warning(`An env named "${ticketIdentity.slug}" is already registered — choose another id with --as.`); } info(''); info(colors.bold(`Starting ticket "${id}" → ${ticketIdentity.slug}`)); info(colors.dim(` branch: ${branch} (from ${baseRef})`)); info(colors.dim(` worktree: ${worktreePath}`)); info(''); // 1. fetch + worktree (fresh branch from origin/dev → independent). try { info(colors.dim(`Fetching + creating worktree from ${baseRef} …`)); (0, dev_ticket_1.gitFetch)(mainRepoRoot); (0, dev_ticket_1.worktreeAdd)(mainRepoRoot, worktreePath, branch, baseRef); } catch (e) { error(`git worktree setup failed: ${e.message}`); if (!parameters.options.fromGluegunMenu) process.exit(1); return 'ticket start: worktree failed'; } // 2. tag the worktree with its ticket id (makes lt dev * ticket-aware). (0, dev_ticket_1.writeTicketMarker)(worktreePath, id); // 3. install deps (pnpm hard-links from the shared store → fast). if (parameters.options.install !== false) { info(colors.dim('Installing dependencies (pnpm) …')); try { (0, dev_ticket_1.pnpmInstall)(worktreePath); } catch (e) { warning(`pnpm install failed (${e.message}) — continuing; run it manually in the worktree.`); } } // 4. bring the isolated stack up (re-invokes THIS lt build so the marker is // honoured even before a global recompile). `--no-up` just scaffolds. if (parameters.options.up !== false) { info(''); info(colors.dim('Bringing up the isolated stack (lt dev up) …')); const code = yield (0, dev_process_1.runChildInherit)(process.execPath, [process.argv[1], 'dev', 'up'], { cwd: worktreePath, env: process.env, }); if (code !== 0) warning(`lt dev up exited ${code} — inspect with \`cd ${worktreePath} && lt dev status\`.`); } // Summary — the URLs the user works with (always re-viewable via `lt ticket list`). info(''); success(`Ticket "${id}" ready.`); if (ticketIdentity.subdomains.app) info(` app: https://${ticketIdentity.subdomains.app.hostname}`); if (ticketIdentity.subdomains.api) info(` api: https://${ticketIdentity.subdomains.api.hostname}`); info(` db: mongodb://127.0.0.1/${dbName} (empty)`); info(` dir: ${worktreePath}`); info(''); info(colors.dim(`Open it: code ${worktreePath} (or: lt ticket switch ${id})`)); info(colors.dim(`Test it: cd ${worktreePath} && lt dev test (or: lt ticket test ${id})`)); info(colors.dim('All envs: lt ticket list')); info(colors.dim(`Stop it: lt ticket stop ${id}`)); if (!parameters.options.fromGluegunMenu) process.exit(); return `ticket start: ${id}`; }), }; module.exports = StartCommand;