chore: added ability to select typescript / framework options
81cb55d6
1 file(s) · +114 −48
| 76 | 76 | } |
|
| 77 | 77 | } |
|
| 78 | 78 | ||
| 79 | + | interface MenuItem { |
|
| 80 | + | value: string; |
|
| 81 | + | label: string; |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | async function selectFromMenu( |
|
| 85 | + | question: string, |
|
| 86 | + | items: MenuItem[], |
|
| 87 | + | defaultIndex = 0, |
|
| 88 | + | ): Promise<string> { |
|
| 89 | + | return new Promise((resolve) => { |
|
| 90 | + | let selectedIndex = defaultIndex; |
|
| 91 | + | let isFirstRender = true; |
|
| 92 | + | ||
| 93 | + | const renderMenu = () => { |
|
| 94 | + | // Clear previous menu (move cursor up and clear lines) |
|
| 95 | + | if (!isFirstRender) { |
|
| 96 | + | // Move cursor up by the number of lines we printed |
|
| 97 | + | // +2 for the blank line before question and the question itself |
|
| 98 | + | process.stdout.write(`\x1b[${items.length + 2}A`); |
|
| 99 | + | // Clear from cursor to end of screen |
|
| 100 | + | process.stdout.write("\x1b[0J"); |
|
| 101 | + | } |
|
| 102 | + | isFirstRender = false; |
|
| 103 | + | ||
| 104 | + | // Use process.stdout.write for precise control |
|
| 105 | + | process.stdout.write(colors.blue(`\n${question}\n`)); |
|
| 106 | + | items.forEach((item, index) => { |
|
| 107 | + | const prefix = index === selectedIndex ? colors.green("❯ ") : " "; |
|
| 108 | + | const text = |
|
| 109 | + | index === selectedIndex ? colors.cyan(item.label) : item.label; |
|
| 110 | + | process.stdout.write(prefix + text + "\n"); |
|
| 111 | + | }); |
|
| 112 | + | }; |
|
| 113 | + | ||
| 114 | + | const onKeyPress = (str: string, key: any) => { |
|
| 115 | + | if (key.name === "return" || key.name === "enter") { |
|
| 116 | + | cleanup(); |
|
| 117 | + | resolve(items[selectedIndex].value); |
|
| 118 | + | return; |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | if (key.name === "up" || str === "k") { |
|
| 122 | + | selectedIndex = |
|
| 123 | + | selectedIndex > 0 ? selectedIndex - 1 : items.length - 1; |
|
| 124 | + | renderMenu(); |
|
| 125 | + | } else if (key.name === "down" || str === "j") { |
|
| 126 | + | selectedIndex = |
|
| 127 | + | selectedIndex < items.length - 1 ? selectedIndex + 1 : 0; |
|
| 128 | + | renderMenu(); |
|
| 129 | + | } else if (key.ctrl && key.name === "c") { |
|
| 130 | + | cleanup(); |
|
| 131 | + | process.exit(0); |
|
| 132 | + | } |
|
| 133 | + | }; |
|
| 134 | + | ||
| 135 | + | const cleanup = () => { |
|
| 136 | + | if (process.stdin.isTTY && process.stdin.setRawMode) { |
|
| 137 | + | process.stdin.setRawMode(false); |
|
| 138 | + | } |
|
| 139 | + | process.stdin.removeListener("keypress", onKeyPress); |
|
| 140 | + | process.stdin.pause(); |
|
| 141 | + | console.log(); // Add newline after selection |
|
| 142 | + | }; |
|
| 143 | + | ||
| 144 | + | // Enable keypress events |
|
| 145 | + | const readline = require("readline"); |
|
| 146 | + | readline.emitKeypressEvents(process.stdin); |
|
| 147 | + | ||
| 148 | + | if (process.stdin.isTTY && process.stdin.setRawMode) { |
|
| 149 | + | process.stdin.setRawMode(true); |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | process.stdin.resume(); // Ensure stdin is resumed |
|
| 153 | + | process.stdin.on("keypress", onKeyPress); |
|
| 154 | + | renderMenu(); |
|
| 155 | + | }); |
|
| 156 | + | } |
|
| 157 | + | ||
| 79 | 158 | async function init() { |
|
| 80 | 159 | console.log(colors.yellow("∅ Initializing norns project...")); |
|
| 81 | 160 | ||
| 112 | 191 | // Get framework selection |
|
| 113 | 192 | let framework: "typescript" | "react" | "svelte" | "vue" = "typescript"; |
|
| 114 | 193 | if (includeTypes) { |
|
| 115 | - | console.log(colors.blue("\n▸ Select your framework:")); |
|
| 116 | - | console.log(colors.cyan(" 1. TypeScript (standard)")); |
|
| 117 | - | console.log(colors.cyan(" 2. React")); |
|
| 118 | - | console.log(colors.cyan(" 3. Svelte")); |
|
| 119 | - | console.log(colors.cyan(" 4. Vue")); |
|
| 120 | - | ||
| 121 | - | const frameworkChoice = await promptUser("Enter your choice (1-4)", "1"); |
|
| 122 | - | ||
| 123 | - | switch (frameworkChoice) { |
|
| 124 | - | case "1": |
|
| 125 | - | framework = "typescript"; |
|
| 126 | - | break; |
|
| 127 | - | case "2": |
|
| 128 | - | framework = "react"; |
|
| 129 | - | break; |
|
| 130 | - | case "3": |
|
| 131 | - | framework = "svelte"; |
|
| 132 | - | break; |
|
| 133 | - | case "4": |
|
| 134 | - | framework = "vue"; |
|
| 135 | - | break; |
|
| 136 | - | default: |
|
| 137 | - | console.log( |
|
| 138 | - | colors.yellow(`▸ Invalid choice, defaulting to TypeScript`), |
|
| 139 | - | ); |
|
| 140 | - | framework = "typescript"; |
|
| 141 | - | } |
|
| 194 | + | const frameworkChoice = await selectFromMenu( |
|
| 195 | + | "Select your framework (use arrow keys or j/k, press Enter to select):", |
|
| 196 | + | [ |
|
| 197 | + | { value: "typescript", label: "TypeScript (standard)" }, |
|
| 198 | + | { value: "react", label: "React" }, |
|
| 199 | + | { value: "svelte", label: "Svelte" }, |
|
| 200 | + | { value: "vue", label: "Vue" }, |
|
| 201 | + | ], |
|
| 202 | + | 0, |
|
| 203 | + | ); |
|
| 204 | + | framework = frameworkChoice as "typescript" | "react" | "svelte" | "vue"; |
|
| 142 | 205 | } |
|
| 143 | 206 | ||
| 144 | 207 | // Create the configuration |
|
| 393 | 456 | const command = positionals[0]; |
|
| 394 | 457 | const componentName = positionals[1]; |
|
| 395 | 458 | ||
| 396 | - | if (values.help) { |
|
| 397 | - | showHelp(); |
|
| 398 | - | } else { |
|
| 399 | - | switch (command) { |
|
| 400 | - | case "init": |
|
| 401 | - | await init(); |
|
| 402 | - | break; |
|
| 403 | - | case "add": |
|
| 404 | - | await addComponent(componentName); |
|
| 405 | - | break; |
|
| 406 | - | case "help": |
|
| 407 | - | showHelp(); |
|
| 408 | - | break; |
|
| 409 | - | default: |
|
| 410 | - | if (!command) { |
|
| 459 | + | // Wrap in async IIFE to avoid top-level await warning |
|
| 460 | + | (async () => { |
|
| 461 | + | if (values.help) { |
|
| 462 | + | showHelp(); |
|
| 463 | + | } else { |
|
| 464 | + | switch (command) { |
|
| 465 | + | case "init": |
|
| 466 | + | await init(); |
|
| 467 | + | break; |
|
| 468 | + | case "add": |
|
| 469 | + | await addComponent(componentName); |
|
| 470 | + | break; |
|
| 471 | + | case "help": |
|
| 411 | 472 | showHelp(); |
|
| 412 | - | } else { |
|
| 413 | - | console.error(colors.red(`✗ Unknown command: ${command}`)); |
|
| 414 | - | showHelp(); |
|
| 415 | - | process.exit(1); |
|
| 416 | - | } |
|
| 473 | + | break; |
|
| 474 | + | default: |
|
| 475 | + | if (!command) { |
|
| 476 | + | showHelp(); |
|
| 477 | + | } else { |
|
| 478 | + | console.error(colors.red(`✗ Unknown command: ${command}`)); |
|
| 479 | + | showHelp(); |
|
| 480 | + | process.exit(1); |
|
| 481 | + | } |
|
| 482 | + | } |
|
| 417 | 483 | } |
|
| 418 | - | } |
|
| 484 | + | })(); |
|