An experimental TypeSpec syntax for Lexicon

nits

+51 -36
+17 -1
packages/cli/src/cli.ts
··· 19 19 }); 20 20 }, 21 21 async (argv) => { 22 - await initCommand(argv.setup); 22 + // Extract any unknown flags to pass through to package manager 23 + const flags: string[] = []; 24 + const knownFlags = new Set(["setup", "_", "$0"]); 25 + 26 + for (const [key, value] of Object.entries(argv)) { 27 + if (!knownFlags.has(key)) { 28 + // Single letter = short flag, multiple letters = long flag 29 + const prefix = key.length === 1 ? "-" : "--"; 30 + if (typeof value === "boolean" && value) { 31 + flags.push(`${prefix}${key}`); 32 + } else if (value !== false && value !== undefined) { 33 + flags.push(`${prefix}${key}`, String(value)); 34 + } 35 + } 36 + } 37 + 38 + await initCommand(argv.setup, flags); 23 39 } 24 40 ) 25 41 .command(
+34 -35
packages/cli/src/commands/init.ts
··· 4 4 import { createInterface } from "readline"; 5 5 import pc from "picocolors"; 6 6 7 - // Gradient colors matching website (approximated with ANSI 256 colors) 8 - // Darker variant for better readability on white terminals 9 7 function gradientText(text: string): string { 10 - // Approximating: #4a9eff -> #7a8ef7 -> #ff85c1 -> #9b7ef7 11 - // Using darker ANSI 256 color codes for a blue->purple->pink gradient 12 8 const colors = [ 13 - '\x1b[38;5;33m', // darker blue 14 - '\x1b[38;5;69m', // blue-purple 15 - '\x1b[38;5;99m', // purple 16 - '\x1b[38;5;133m', // purple-pink 17 - '\x1b[38;5;170m', // pink 18 - '\x1b[38;5;170m', // pink 19 - '\x1b[38;5;133m', // purple-pink 9 + '\x1b[38;5;33m', 10 + '\x1b[38;5;69m', 11 + '\x1b[38;5;99m', 12 + '\x1b[38;5;133m', 13 + '\x1b[38;5;170m', 14 + '\x1b[38;5;170m', 15 + '\x1b[38;5;133m', 20 16 ]; 21 17 const reset = '\x1b[0m'; 22 18 ··· 60 56 }); 61 57 } 62 58 63 - /** 64 - * Initialize command - installs packages and hands off to local version 65 - * This is what gets called by npx 66 - */ 67 - export async function initCommand(isSetup: boolean = false): Promise<void> { 68 - const cwd = process.cwd(); 59 + export async function initCommand(isSetup: boolean = false, flags: string[] = []): Promise<void> { 60 + const originalCwd = process.cwd(); 61 + 62 + // Find nearest package.json upward 63 + let projectRoot = originalCwd; 64 + let dir = originalCwd; 65 + while (dir !== resolve(dir, "..")) { 66 + try { 67 + await access(resolve(dir, "package.json")); 68 + projectRoot = dir; 69 + break; 70 + } catch { 71 + dir = resolve(dir, ".."); 72 + } 73 + } 69 74 70 - // If this is the second pass (after handoff), run setup 71 75 if (isSetup) { 72 76 return initSetup(); 73 77 } 74 78 75 79 console.log(`Adding ${gradientText("typelex")}...\n`); 76 80 77 - // Detect package manager: walk up from cwd 81 + // Detect package manager 78 82 let packageManager = "npm"; 79 - let dir = cwd; 83 + dir = projectRoot; 80 84 while (dir !== resolve(dir, "..") && packageManager === "npm") { 81 85 try { 82 86 await access(resolve(dir, "pnpm-lock.yaml")); ··· 95 99 dir = resolve(dir, ".."); 96 100 } 97 101 98 - // Install dependencies (always use latest) 102 + // Install dependencies 99 103 await new Promise<void>((resolvePromise, reject) => { 100 104 const args = packageManager === "npm" 101 105 ? ["install", "--save-dev", "@typelex/cli@latest", "@typelex/emitter@latest"] 102 106 : ["add", "-D", "@typelex/cli@latest", "@typelex/emitter@latest"]; 103 107 108 + // Add any additional flags 109 + args.push(...flags); 110 + 104 111 const install = spawn(packageManager, args, { 105 - cwd, 112 + cwd: projectRoot, 106 113 stdio: "inherit", 107 114 }); 108 115 ··· 122 129 }); 123 130 }); 124 131 125 - // Hand off to locally installed version 126 - // Find where node_modules actually is (could be at workspace root with pnpm) 127 - let nodeModulesDir = resolve(cwd, "node_modules"); 128 - let searchDir = cwd; 129 - 130 - // Search upward for node_modules with typelex installed 132 + // Find node_modules 133 + let nodeModulesDir = resolve(projectRoot, "node_modules"); 134 + let searchDir = projectRoot; 131 135 while (searchDir !== resolve(searchDir, "..")) { 132 136 try { 133 137 const candidatePath = resolve(searchDir, "node_modules/.bin/typelex"); ··· 135 139 nodeModulesDir = resolve(searchDir, "node_modules"); 136 140 break; 137 141 } catch { 138 - // Not found, try parent 142 + searchDir = resolve(searchDir, ".."); 139 143 } 140 - searchDir = resolve(searchDir, ".."); 141 144 } 142 145 143 146 return new Promise((resolvePromise, reject) => { 144 147 const localCli = resolve(nodeModulesDir, ".bin/typelex"); 145 148 const setup = spawn(localCli, ["init", "--setup"], { 146 - cwd, 149 + cwd: projectRoot, 147 150 stdio: "inherit", 148 151 }); 149 152 ··· 162 165 }); 163 166 } 164 167 165 - /** 166 - * Setup function - called after packages are installed 167 - * This runs from the locally installed version 168 - */ 169 168 export async function initSetup(): Promise<void> { 170 169 const cwd = process.cwd(); 171 170 const typelexDir = resolve(cwd, "typelex");