From 64ed089ed42822b81dc2d35909b29439765b7608 Mon Sep 17 00:00:00 2001 From: strausr Date: Fri, 1 May 2026 11:34:07 -0700 Subject: [PATCH 1/2] 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')); From 157c94c9ecbd86bcb2156b32876bfcf71b0590d8 Mon Sep 17 00:00:00 2001 From: strausr Date: Mon, 11 May 2026 10:38:08 -0700 Subject: [PATCH 2/2] fix: headless mode assertions --- README.md | 4 ++-- cli.js | 5 +---- package-lock.json | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6424c65..47effbd 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ The CLI will guide you through: 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 \ +npx create-cloudinary-react --headless \ --cloudName your-cloud-name \ --projectName my-app ``` @@ -72,7 +72,7 @@ npx create-cloudinary-react -- --headless \ | `--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"`. +> **Note:** Shell variables should be quoted to prevent unexpected expansion: `--cloudName "$CLOUD_NAME"`. ## šŸ› ļø What's Included diff --git a/cli.js b/cli.js index c790690..23597f1 100755 --- a/cli.js +++ b/cli.js @@ -107,7 +107,6 @@ async function main() { })); } 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); } @@ -115,9 +114,7 @@ async function main() { const errors = []; - if (!values.projectName) { - errors.push('--projectName is required'); - } else if (!isValidProjectName(values.projectName)) { + 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.`); diff --git a/package-lock.json b/package-lock.json index 1d228d6..1f0d9e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-cloudinary-react", - "version": "1.0.0-beta.21", + "version": "1.0.0-beta.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-cloudinary-react", - "version": "1.0.0-beta.21", + "version": "1.0.0-beta.23", "license": "MIT", "dependencies": { "chalk": "^5.3.0",