From 64ed089ed42822b81dc2d35909b29439765b7608 Mon Sep 17 00:00:00 2001 From: strausr Date: Fri, 1 May 2026 11:34:07 -0700 Subject: [PATCH] fix: add headless mode validation and documentation --- README.md | 25 ++++++++++++++++ cli.js | 89 +++++++++++++++++++++++++++++++------------------------ 2 files changed, 75 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index e4fb758..6424c65 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,31 @@ The CLI will guide you through: 3. **Upload Preset** (Optional): handling unsigned uploads 4. **AI Assistant**: generating custom rules for your tool of choice (Cursor, VS Code, etc.) +## āš™ļø Headless Mode + +For CI/CD pipelines, scripts, or automated workflows, pass `--headless` along with all options as flags to skip the interactive prompts: + +```bash +npx create-cloudinary-react -- --headless \ + --cloudName your-cloud-name \ + --projectName my-app +``` + +**All options:** + +| Flag | Type | Default | Description | +|---|---|---|---| +| `--cloudName` | string | *(required)* | Your Cloudinary cloud name | +| `--projectName` | string | `my-cloudinary-app` | Output directory name | +| `--hasUploadPreset` | boolean | `false` | Set if you have an unsigned upload preset | +| `--uploadPreset` | string | — | Your unsigned upload preset name (required if `--hasUploadPreset`) | +| `--aiTools` | string (repeatable) | `cursor` | AI tools to configure: `cursor`, `copilot`, `claude`, `generic` | +| `--installDeps` | boolean | `true` | Install dependencies after scaffolding | +| `--startDev` | boolean | `false` | Start the dev server after install | +| `--packageManager` | string | *(auto-detected)* | `npm`, `pnpm`, `yarn`, or `bun` | + +> **Note:** If a flag value starts with a dash, use `--flag=value` syntax (e.g. `--cloudName=-myvalue`). Shell variables should be quoted: `--cloudName "$CLOUD_NAME"`. + ## šŸ› ļø What's Included Your new project comes with: diff --git a/cli.js b/cli.js index 3c76694..c790690 100755 --- a/cli.js +++ b/cli.js @@ -88,47 +88,58 @@ async function main() { if (process.argv.includes('--headless')) { - const { values, positionals } = parseArgs({ - options: { - headless: { - type: 'boolean' - }, - projectName: { - type: 'string', - default: 'my-cloudinary-app' - }, - cloudName: { - type: 'string' - }, - hasUploadPreset: { - type: 'boolean', - default: false - }, - uploadPreset: { - type: 'string' - }, - aiTools: { - type: 'string', - multiple: true, - default: ['cursor'] - }, - installDeps: { - type: 'boolean', - default: true - }, - startDev: { - type: 'boolean', - default: false - }, - packageManager: { - type: 'string', + let values; + try { + ({ values } = parseArgs({ + args: process.argv.slice(2).filter(a => a !== '--'), + options: { + headless: { type: 'boolean' }, + projectName: { type: 'string', default: 'my-cloudinary-app' }, + cloudName: { type: 'string' }, + hasUploadPreset: { type: 'boolean', default: false }, + uploadPreset: { type: 'string' }, + aiTools: { type: 'string', multiple: true, default: ['cursor'] }, + installDeps: { type: 'boolean', default: true }, + startDev: { type: 'boolean', default: false }, + packageManager: { type: 'string' }, }, - }, - allowPositionals: true, - }); - + allowPositionals: true, + })); + } catch (e) { + console.error(chalk.red(`Error: ${e.message}`)); + console.error(chalk.gray('Tip: If a flag value starts with a dash, use --flag=value syntax (e.g., --cloudName=-myvalue)')); + process.exit(1); + } + Object.assign(answers, values); - + + const errors = []; + + if (!values.projectName) { + errors.push('--projectName is required'); + } else if (!isValidProjectName(values.projectName)) { + errors.push('--projectName can only contain letters, numbers, hyphens, and underscores'); + } else if (existsSync(values.projectName)) { + errors.push(`Directory "${values.projectName}" already exists. Please choose a different name.`); + } + + if (!values.cloudName) { + errors.push('--cloudName is required'); + } else if (!isValidCloudName(values.cloudName)) { + errors.push('--cloudName can only contain lowercase letters, numbers, hyphens, and underscores'); + } + + if (values.hasUploadPreset && !values.uploadPreset) { + errors.push('--uploadPreset is required when --hasUploadPreset is set'); + } + + if (errors.length > 0) { + for (const err of errors) { + console.error(chalk.red(`Error: ${err}`)); + } + process.exit(1); + } + } else { console.log(chalk.cyan.bold('\nšŸš€ Cloudinary React Starter Kit\n'));