UNPKG

@lenne.tech/cli

Version:

lenne.Tech CLI: lt

358 lines (357 loc) 17.9 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 path_1 = require("path"); const dev_identity_1 = require("../lib/dev-identity"); const dev_state_1 = require("../lib/dev-state"); const framework_detection_1 = require("../lib/framework-detection"); const frontend_framework_detection_1 = require("../lib/frontend-framework-detection"); const workspace_integration_1 = require("../lib/workspace-integration"); /** * Show project status and context */ const StatusCommand = { alias: ['st'], description: 'Show project status', hidden: false, name: 'status', run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c; const { filesystem, print: { colors, info, success, warning }, system, } = toolbox; const cwd = filesystem.cwd(); info(''); info(colors.bold('Project Status')); info(colors.dim('─'.repeat(50))); const projectInfo = { configFiles: [], frameworkMode: null, frontendFrameworkMode: null, gitBranch: null, gitRoot: null, hasGit: false, hasLtConfig: false, hasPackageJson: false, monorepoSubprojects: [], nodeVersion: null, npmVersion: null, packageName: null, packageVersion: null, projectType: 'unknown', // Probe layout at the workspace root if cwd is inside a // sub-project; otherwise probe at cwd. Without this, status // shown from `projects/api/src/` would report both halves // missing because there's no `projects/` underneath cwd. workspaceLayout: (() => { const ctx = (0, workspace_integration_1.detectSubProjectContext)(cwd, filesystem); return (0, workspace_integration_1.detectWorkspaceLayout)(ctx ? ctx.workspaceRoot : cwd, filesystem); })(), workspaceSubProject: (() => { const ctx = (0, workspace_integration_1.detectSubProjectContext)(cwd, filesystem); return ctx ? { kind: ctx.kind, root: ctx.workspaceRoot } : null; })(), }; // Check for lt.config const ltConfigFiles = ['lt.config.json', 'lt.config.yaml', 'lt.config']; for (const configFile of ltConfigFiles) { if (filesystem.exists((0, path_1.join)(cwd, configFile))) { projectInfo.hasLtConfig = true; projectInfo.configFiles.push(configFile); } } // Check for package.json const packageJsonPath = (0, path_1.join)(cwd, 'package.json'); if (filesystem.exists(packageJsonPath)) { projectInfo.hasPackageJson = true; try { const packageJson = JSON.parse(filesystem.read(packageJsonPath) || '{}'); projectInfo.packageName = packageJson.name || null; projectInfo.packageVersion = packageJson.version || null; // Detect project type const deps = Object.assign(Object.assign({}, packageJson.dependencies), packageJson.devDependencies); // A project is a nest-server project if it EITHER has the npm dep // (classic) OR has vendored the core/ directory. The frameworkMode // field records which of the two modes this project runs in. if (deps['@lenne.tech/nest-server'] || (0, framework_detection_1.isVendoredProject)(cwd)) { projectInfo.projectType = 'nest-server'; projectInfo.frameworkMode = (0, framework_detection_1.detectFrameworkMode)(cwd); } else if (deps['@nestjs/core']) { projectInfo.projectType = 'nestjs'; } else if (deps['nuxt']) { projectInfo.projectType = 'nuxt'; // Detect frontend framework mode if nuxt-extensions is present if (deps['@lenne.tech/nuxt-extensions'] || (0, frontend_framework_detection_1.isVendoredAppProject)(cwd)) { projectInfo.frontendFrameworkMode = (0, frontend_framework_detection_1.detectFrontendFrameworkMode)(cwd); } } else if (deps['@angular/core']) { projectInfo.projectType = 'angular'; } else if (deps['react']) { projectInfo.projectType = 'react'; } else if (deps['vue']) { projectInfo.projectType = 'vue'; } else if (deps['typescript']) { projectInfo.projectType = 'typescript'; } else if (packageJson.name) { projectInfo.projectType = 'node'; } } catch (_d) { // Ignore parse errors } } // Monorepo subproject detection: scan projects/api and projects/app for // framework modes so that `lt status` at the monorepo root surfaces // backend + frontend framework consumption modes even when the root // itself is not a Nest/Nuxt project. const monorepoCandidates = [ { kind: 'backend', path: (0, path_1.join)(cwd, 'projects', 'api') }, { kind: 'backend', path: (0, path_1.join)(cwd, 'packages', 'api') }, { kind: 'frontend', path: (0, path_1.join)(cwd, 'projects', 'app') }, { kind: 'frontend', path: (0, path_1.join)(cwd, 'packages', 'app') }, ]; for (const candidate of monorepoCandidates) { if (!filesystem.exists((0, path_1.join)(candidate.path, 'package.json'))) continue; if (candidate.kind === 'backend') { try { const subPkg = JSON.parse(filesystem.read((0, path_1.join)(candidate.path, 'package.json')) || '{}'); const subDeps = Object.assign(Object.assign({}, subPkg.dependencies), subPkg.devDependencies); if (subDeps['@lenne.tech/nest-server'] || (0, framework_detection_1.isVendoredProject)(candidate.path)) { projectInfo.monorepoSubprojects.push({ frameworkMode: (0, framework_detection_1.detectFrameworkMode)(candidate.path), kind: 'backend', path: candidate.path, }); } } catch (_e) { // ignore } } else { try { const subPkg = JSON.parse(filesystem.read((0, path_1.join)(candidate.path, 'package.json')) || '{}'); const subDeps = Object.assign(Object.assign({}, subPkg.dependencies), subPkg.devDependencies); if (subDeps['@lenne.tech/nuxt-extensions'] || (0, frontend_framework_detection_1.isVendoredAppProject)(candidate.path)) { projectInfo.monorepoSubprojects.push({ frameworkMode: (0, frontend_framework_detection_1.detectFrontendFrameworkMode)(candidate.path), kind: 'frontend', path: candidate.path, }); } } catch (_f) { // ignore } } } // Check for git try { const gitRoot = yield system.run('git rev-parse --show-toplevel 2>/dev/null'); if (gitRoot === null || gitRoot === void 0 ? void 0 : gitRoot.trim()) { projectInfo.hasGit = true; projectInfo.gitRoot = gitRoot.trim(); const branch = yield system.run('git rev-parse --abbrev-ref HEAD 2>/dev/null'); projectInfo.gitBranch = (branch === null || branch === void 0 ? void 0 : branch.trim()) || null; } } catch (_g) { // Not a git repository } // Get Node/npm versions try { projectInfo.nodeVersion = ((_a = (yield system.run('node --version 2>/dev/null'))) === null || _a === void 0 ? void 0 : _a.trim()) || null; projectInfo.npmVersion = ((_b = (yield system.run('npm --version 2>/dev/null'))) === null || _b === void 0 ? void 0 : _b.trim()) || null; } catch (_h) { // Ignore errors } // Display project info info(''); info(colors.bold('Directory:')); info(` ${cwd}`); if (projectInfo.hasPackageJson) { info(''); info(colors.bold('Package:')); if (projectInfo.packageName) { info(` Name: ${projectInfo.packageName}`); } if (projectInfo.packageVersion) { info(` Version: ${projectInfo.packageVersion}`); } info(` Type: ${formatProjectType(projectInfo.projectType)}`); if (projectInfo.frameworkMode) { const modeLabel = projectInfo.frameworkMode === 'vendor' ? 'vendor (src/core/, VENDOR.md)' : 'npm (@lenne.tech/nest-server dependency)'; info(` Framework: ${modeLabel}`); } if (projectInfo.frontendFrameworkMode) { const frontendModeLabel = projectInfo.frontendFrameworkMode === 'vendor' ? 'vendor (app/core/, VENDOR.md)' : 'npm (@lenne.tech/nuxt-extensions dependency)'; info(` Frontend Framework: ${frontendModeLabel}`); } } // Workspace overview — surfaces the layout that drives the // `add-api` / `add-app` / standalone gate decisions. Shown // whenever we're inside a workspace OR a sub-project of one, // so users immediately see what's missing and where the // workspace root is. if (projectInfo.workspaceLayout.hasWorkspace || projectInfo.workspaceSubProject) { info(''); info(colors.bold('Workspace:')); if (projectInfo.workspaceSubProject) { const { kind, root } = projectInfo.workspaceSubProject; warning(` You are inside projects/${kind}/ of a workspace.`); info(` Root: ${root}`); info(colors.dim(` Hint: cd to the workspace root for \`lt fullstack add-${kind === 'api' ? 'app' : 'api'}\``)); } else { success(' Detected (pnpm-workspace.yaml, package.json#workspaces, or projects/)'); } const apiMark = projectInfo.workspaceLayout.hasApi ? colors.green('✓') : colors.yellow('✗'); const appMark = projectInfo.workspaceLayout.hasApp ? colors.green('✓') : colors.yellow('✗'); info(` ${apiMark} projects/api/`); info(` ${appMark} projects/app/`); // Suggest the next step when only one half is present. if (projectInfo.workspaceLayout.hasApp && !projectInfo.workspaceLayout.hasApi) { info(colors.dim(' Hint: `lt fullstack add-api` to integrate a NestJS server.')); } else if (projectInfo.workspaceLayout.hasApi && !projectInfo.workspaceLayout.hasApp) { info(colors.dim(' Hint: `lt fullstack add-app` to integrate a Nuxt or Angular app.')); } } // Local dev orchestration registry — surface URLs if registered. // Helps users discover that `lt dev` is set up for this project, and // gives a quick inline answer to "what URLs does this project use?". { const registryRoot = ((_c = projectInfo.workspaceSubProject) === null || _c === void 0 ? void 0 : _c.root) || (projectInfo.workspaceLayout.hasWorkspace ? projectInfo.workspaceLayout.workspaceDir : cwd); if (registryRoot) { const slug = (0, dev_identity_1.projectSlug)(registryRoot); const entry = (0, dev_state_1.loadRegistry)().projects[slug]; if (entry) { info(''); info(colors.bold('Local dev orchestration (lt dev):')); for (const [sub, host] of Object.entries(entry.subdomains)) { info(` ${sub.padEnd(6)} https://${host}`); } if (entry.dbName) info(` db mongodb://127.0.0.1/${entry.dbName}`); const session = (0, dev_state_1.loadSession)(registryRoot); const apiAlive = (session === null || session === void 0 ? void 0 : session.pids.api) ? (0, dev_state_1.isPidAlive)(session.pids.api) : false; const appAlive = (session === null || session === void 0 ? void 0 : session.pids.app) ? (0, dev_state_1.isPidAlive)(session.pids.app) : false; if (apiAlive || appAlive) { info(` Running: api ${apiAlive ? colors.green('●') : colors.dim('○')} app ${appAlive ? colors.green('●') : colors.dim('○')}`); } else { info(colors.dim(' Hint: `lt dev up` to start API + App behind Caddy.')); } } } } // Show monorepo subprojects if we detected any (typically at monorepo root) if (projectInfo.monorepoSubprojects.length > 0) { info(''); info(colors.bold('Monorepo Subprojects:')); for (const sub of projectInfo.monorepoSubprojects) { const relPath = sub.path.replace(`${cwd}/`, ''); if (sub.kind === 'backend') { const label = sub.frameworkMode === 'vendor' ? 'vendor (src/core/, VENDOR.md)' : 'npm (@lenne.tech/nest-server dependency)'; info(` Backend: ${relPath}${label}`); } else { const label = sub.frameworkMode === 'vendor' ? 'vendor (app/core/, VENDOR.md)' : 'npm (@lenne.tech/nuxt-extensions dependency)'; info(` Frontend: ${relPath}${label}`); } } } if (projectInfo.hasGit) { info(''); info(colors.bold('Git:')); info(` Branch: ${projectInfo.gitBranch || 'unknown'}`); if (projectInfo.gitRoot !== cwd) { info(` Root: ${projectInfo.gitRoot}`); } } info(''); info(colors.bold('Configuration:')); if (projectInfo.hasLtConfig) { success(` lt.config: ${projectInfo.configFiles.join(', ')}`); } else { warning(' lt.config: Not found'); info(colors.dim(' Run "lt config init" to create one')); } info(''); info(colors.bold('Environment:')); if (projectInfo.nodeVersion) { info(` Node: ${projectInfo.nodeVersion}`); } if (projectInfo.npmVersion) { info(` npm: v${projectInfo.npmVersion}`); } const detectedPm = toolbox.pm.detect(); info(` Package Manager: ${detectedPm}`); // Show available commands based on project type info(''); info(colors.bold('Available Commands:')); const commands = getAvailableCommands(projectInfo.projectType); commands.forEach((cmd) => info(` ${colors.cyan(cmd.command)} - ${cmd.description}`)); info(''); // For tests return `status ${projectInfo.projectType}`; }), }; function formatProjectType(type) { const typeMap = { angular: 'Angular', 'nest-server': 'lenne.tech Nest Server', nestjs: 'NestJS', node: 'Node.js', nuxt: 'Nuxt', react: 'React', typescript: 'TypeScript', unknown: 'Unknown', vue: 'Vue', }; return typeMap[type] || type; } function getAvailableCommands(projectType) { const commonCommands = [ { command: 'lt git', description: 'Git workflow commands' }, { command: 'lt config', description: 'Configuration management' }, { command: 'lt npm', description: 'NPM utilities' }, ]; const typeCommands = { angular: [{ command: 'lt frontend angular', description: 'Angular tools' }], 'nest-server': [ { command: 'lt server module', description: 'Create server module' }, { command: 'lt server object', description: 'Create object type' }, { command: 'lt server add-property', description: 'Add property to module' }, ], nestjs: [{ command: 'lt server', description: 'NestJS server tools' }], nuxt: [{ command: 'lt frontend nuxt', description: 'Nuxt tools' }], }; return [...commonCommands, ...(typeCommands[projectType] || [])]; } exports.default = StatusCommand;