diff --git a/.development/index.js b/.development/index.js new file mode 100644 index 0000000..410fd7d --- /dev/null +++ b/.development/index.js @@ -0,0 +1,199 @@ +import chalk from "chalk"; +import { createInterface } from "readline"; +import { executeCommands } from "../src/executor.js"; +import { printCommand, printError, printFinished, sep } from "../src/ui.js"; + +// ── action definitions ──────────────────────────────────────────────── +// Each entry mirrors the shape of an AI response so the real executor +// path is exercised end-to-end with no mocking. + +const ACTIONS = [ + { + id: 1, + label: "folder creation", + description: "Creating folder: new-folder", + category: "directory_creation", + commands: ["mkdir new-folder"], + }, + { + id: 2, + label: "navigate within folder", + description: "Creating open-folder and navigating inside it", + category: "directory_creation", + commands: ["mkdir open-folder", "cd open-folder"], + }, + { + id: 3, + label: "nodejs project init", + description: "Initialising Node.js project: server", + category: "node_init", + commands: ["mkdir server", "cd server", "npm init -y"], + }, + { + id: 4, + label: "package installation", + description: "Installing: dotenv, express", + category: "package_install", + commands: ["npm install dotenv express"], + }, + { + id: 5, + label: "package uninstallation", + description: "Removing: express", + category: "package_install", + commands: ["npm uninstall express"], + }, + { + id: 6, + label: "react + vite setup", + description: "Scaffolding Vite React app: todo-app", + category: "npx_command", + commands: [ + "npx create-vite@latest todo-app --template react", + "cd todo-app", + "npm install", + ], + }, + { + id: 7, + label: "nextjs setup", + description: "Scaffolding Next.js app: next-todo-app", + category: "npx_command", + commands: ["npx create-next-app@latest next-todo-app"], + }, + { + id: 8, + label: "nextjs + mui setup", + description: "Scaffolding Next.js + MUI app: todo-mui", + category: "next_mui_project", + commands: [ + "npx create-next-mui@latest todo-mui", + "cd todo-mui", + "npm install", + ], + }, + { + id: 9, + label: "run dev server", + description: "Starting dev server (npm run dev)", + category: "script_execution", + commands: ["npm run dev"], + }, +]; + +// ── helpers ─────────────────────────────────────────────────────────── + +const preview = (cmds) => { + const joined = cmds.join(" → "); + return joined.length > 52 ? joined.slice(0, 49) + "…" : joined; +}; + +const pad = (str, n) => str + " ".repeat(Math.max(0, n - str.length)); + +// ── menu renderer ───────────────────────────────────────────────────── + +function printMenu() { + const labelWidth = Math.max(...ACTIONS.map((a) => a.label.length)) + 2; + + console.log(); + sep(); + console.log( + ` ${chalk.white(">")} ${chalk.bold.white("NOVA")} ${chalk.gray("— development mode")}`, + ); + sep(); + console.log(); + + for (const a of ACTIONS) { + const num = chalk.cyan(`[${a.id}]`); + const label = chalk.white(pad(a.label, labelWidth)); + const hint = chalk.gray(preview(a.commands)); + console.log(` ${num} ${label}${hint}`); + } + + console.log(); +} + +// ── run an action ───────────────────────────────────────────────────── + +async function runAction(action) { + console.log(); + sep(); + console.log(` ${chalk.cyan("→")} ${chalk.gray(action.description)}`); + console.log(` ${chalk.gray("category:")} ${chalk.white(action.category)}`); + + const start = Date.now(); + + try { + await executeCommands(action.commands, (cmd) => printCommand(cmd)); + + const secs = ((Date.now() - start) / 1000).toFixed(1); + printFinished(secs); + } catch (err) { + console.log(); + printError(err.message); + sep(); + } +} + +// ── prompt loop ─────────────────────────────────────────────────────── + +export async function devMode() { + const rl = createInterface({ input: process.stdin, output: process.stdout }); + + rl.on("SIGINT", () => { + console.log( + `\n\n ${chalk.gray(">")} ${chalk.gray("Exiting dev mode.")}\n`, + ); + process.exit(0); + }); + + const ask = () => { + printMenu(); + + rl.question( + ` ${chalk.white(">")} ${chalk.gray("pick action [1-9] or exit: ")}`, + async (raw) => { + const input = raw.trim().toLowerCase(); + + if (!input) { + ask(); + return; + } + + if (input === "exit" || input === "quit") { + console.log( + `\n ${chalk.gray(">")} ${chalk.gray("Exiting dev mode.")}\n`, + ); + rl.close(); + process.exit(0); + } + + const num = parseInt(input, 10); + const action = ACTIONS.find((a) => a.id === num); + + if (!action) { + console.log( + `\n ${chalk.red("✗")} ${chalk.red(`"${input}" is not valid — enter 1–9 or exit.`)}`, + ); + ask(); + return; + } + + // ── hand stdin back to the child process ────────────────────── + // readline holds process.stdin and keeps it in line-buffered mode. + // Interactive tools like create-next-app need raw TTY access for + // arrow keys and keyboard navigation. pause() releases that hold + // so the spawned process gets full control of the terminal. + rl.pause(); + + await runAction(action); + + // ── reclaim stdin for the next menu prompt ──────────────────── + rl.resume(); + ask(); + }, + ); + }; + + ask(); +} diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..db76449 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,213 @@ +# ───────────────────────────────────────────────────────────────────────────── +# NOVA — Development Action Tests +# +# BRANCH STRATEGY +# This file lives ONLY on the `development` branch. +# main → clean production code (no dev.js, no workflow) +# development → owns dev.js + this workflow +# +# TRIGGERS +# • Push to `development` — test every dev commit +# • Push to `feat/**` / `fix/**` — test feature/fix branches +# (those branches must be created from `development` so this file exists) +# • Pull request → `main` — gate before any code reaches production +# • Pull request → `development` — gate before merging into dev +# +# COVERAGE +# One job per action in dev.js so failures are pinpointed by action name. +# Action 9 (run dev server) is excluded — it is a long-running process +# and has no deterministic exit condition suitable for CI. +# +# npx scaffolding commands receive --yes to skip interactive prompts. +# No build commands (npm run build / npm run dev) are added after +# scaffolding — the test validates the scaffold succeeded, nothing more. +# ───────────────────────────────────────────────────────────────────────────── + +name: NOVA + +on: + push: + branches: + - development + - "feat/**" + - "fix/**" + pull_request: + branches: + - main + - development + +env: + NODE_VERSION: "20" + +# ── Action 1 ───────────────────────────────────────────────────────────────── + +jobs: + action-folder-creation: + name: "[1] folder creation" + runs-on: ubuntu-latest + steps: + - name: "mkdir new-folder" + run: mkdir new-folder + + - name: "verify folder exists" + run: test -d new-folder && echo "✓ new-folder created" + + # ── Action 2 ───────────────────────────────────────────────────────────────── + + action-navigate-within-folder: + name: "[2] navigate within folder" + runs-on: ubuntu-latest + steps: + - name: "mkdir open-folder" + run: mkdir open-folder + + - name: "cd open-folder" + working-directory: open-folder + run: | + echo "✓ navigated into $(pwd)" + + # ── Action 3 ───────────────────────────────────────────────────────────────── + + action-nodejs-init: + name: "[3] nodejs project init" + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: "mkdir server" + run: mkdir server + + - name: "npm init -y" + working-directory: server + run: npm init -y + + - name: "verify package.json created" + working-directory: server + run: test -f package.json && echo "✓ package.json present" + + # ── Action 4 ───────────────────────────────────────────────────────────────── + + action-package-install: + name: "[4] package installation" + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: "setup — npm init -y" + run: npm init -y + + - name: "npm install dotenv express" + run: npm install dotenv express + + - name: "verify packages installed" + run: | + test -d node_modules/dotenv && echo "✓ dotenv installed" + test -d node_modules/express && echo "✓ express installed" + + # ── Action 5 ───────────────────────────────────────────────────────────────── + + action-package-uninstall: + name: "[5] package uninstallation" + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: "setup — npm init -y && npm install express" + run: | + npm init -y + npm install express + + - name: "npm uninstall express" + run: npm uninstall express + + - name: "verify express removed" + run: | + if [ -d node_modules/express ]; then + echo "✗ express still present" && exit 1 + else + echo "✓ express uninstalled" + fi + + # ── Action 6 ───────────────────────────────────────────────────────────────── + + action-vite-react: + name: "[6] react + vite setup" + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: "npx create-vite@latest todo-app --template react" + # create-vite is non-interactive when --template is provided + run: npx create-vite@latest todo-app --template react + + - name: "npm install" + # runs inside todo-app — mirrors the cd + npm install in dev.js + working-directory: todo-app + run: npm install + + - name: "verify scaffold structure" + working-directory: todo-app + run: | + test -f package.json && echo "✓ package.json" + test -f index.html && echo "✓ index.html" + test -d src && echo "✓ src/" + test -d node_modules && echo "✓ node_modules/" + + # ── Action 7 ───────────────────────────────────────────────────────────────── + + action-nextjs: + name: "[7] nextjs setup" + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: "npx create-next-app@latest next-todo-app" + # --yes accepts all defaults (TypeScript, ESLint, App Router, etc.) + # Required in CI — create-next-app is interactive by default + run: npx create-next-app@latest next-todo-app --yes + + - name: "verify scaffold structure" + working-directory: next-todo-app + run: | + test -f package.json && echo "✓ package.json" + test -f next.config.ts && echo "✓ next.config.ts" + test -d app && echo "✓ app/" + test -d node_modules && echo "✓ node_modules/" + + # ── Action 8 ───────────────────────────────────────────────────────────────── + + action-nextjs-mui: + name: "[8] nextjs + mui setup" + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: "npx create-next-mui@latest todo-mui" + run: npx create-next-mui@latest todo-mui --yes + + - name: "npm install" + # runs inside todo-mui — mirrors the cd + npm install in dev.js + working-directory: todo-mui + run: npm install + + - name: "verify scaffold structure" + working-directory: todo-mui + run: | + test -f package.json && echo "✓ package.json" + test -d node_modules && echo "✓ node_modules/" + +# ── Action 9 — excluded ─────────────────────────────────────────────────────── +# run dev server (npm run dev) is a long-running process with no deterministic +# exit. It cannot be validated in CI without a timeout hack. Tested manually. diff --git a/.gitignore b/.gitignore index 576ed7c..5c7a797 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -node_modules +# ignore -.development +node_modules \ No newline at end of file