Skip to content

Commit cbad233

Browse files
authored
Merge pull request #12 from cloudinary-devs/detect-invoking-package-manager
fix: detect invoking package manager
2 parents 4061546 + 5dd6f00 commit cbad233

5 files changed

Lines changed: 100 additions & 16 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
Scaffold a modern, production-ready Cloudinary application with React 19, Vite 6, and TypeScript 5. Features interactive setup, automatic environment configuration, and built-in AI coding assistance.
1010

11-
- Node.js 18+ installed
11+
- **Node.js** — use a [current LTS](https://nodejs.org/) release. The generated app lists supported versions under `engines` in its `package.json`.
1212
- A Cloudinary account (free tier available)
1313
- [Sign up for free](https://cld.media/reactregister)
1414
- Your cloud name is in your [dashboard](https://console.cloudinary.com/app/home/dashboard)
@@ -36,7 +36,7 @@ Part of the [Cloudinary Developers](https://github.com/cloudinary-devs) organiza
3636

3737
## 🚀 Quick Start
3838

39-
Ensure you have Node.js 18+ installed.
39+
Use a current **Node.js LTS** release. If installs fail, match the `engines` range in the generated project’s `package.json`.
4040

4141
```bash
4242
npx create-cloudinary-react
@@ -80,7 +80,7 @@ During setup, select your AI tool to generate **Context Rules**. These rules tea
8080

8181
## 📋 Prerequisites
8282

83-
- **Node.js 18+**
83+
- **Node.js** — LTS recommended; exact ranges for the scaffolded app are in `package.json``engines`.
8484
- **Cloudinary Account**: [Sign up for free](https://cloudinary.com/users/register/free) if you haven't already.
8585

8686
## 🤝 Contributing

cli.js

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { fileURLToPath } from 'url';
44
import { dirname, join } from 'path';
55
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
6-
import { execSync } from 'child_process';
6+
import { spawnSync } from 'child_process';
77
import inquirer from 'inquirer';
88
import chalk from 'chalk';
99
import fs from 'fs-extra';
@@ -14,6 +14,64 @@ const __dirname = dirname(__filename);
1414

1515
const TEMPLATES_DIR = join(__dirname, 'templates');
1616

17+
/** Package managers we know how to drive for install / dev scripts */
18+
const SUPPORTED_PACKAGE_MANAGERS = new Set(['npm', 'pnpm', 'yarn', 'bun']);
19+
20+
/**
21+
* Parse npm_config_user_agent (set by npm, pnpm, yarn, bun when they run a package).
22+
* Same approach as create-vite: first space-separated segment is "name/version".
23+
*/
24+
function pkgFromUserAgent(userAgent) {
25+
if (!userAgent) return undefined;
26+
const pkgSpec = userAgent.split(' ')[0];
27+
const [name, version = ''] = pkgSpec.split('/');
28+
if (!name) return undefined;
29+
return { name, version };
30+
}
31+
32+
/**
33+
* Resolve which client to run for install / dev.
34+
* Only npm, pnpm, yarn, and bun have explicit command mappings below; anything else
35+
* (missing env, exotic clients, parse quirks) falls back to npm as the safe default that
36+
* ships with Node and matches the install instructions most users expect.
37+
*/
38+
function detectPackageManager() {
39+
const pkg = pkgFromUserAgent(process.env.npm_config_user_agent);
40+
if (pkg && SUPPORTED_PACKAGE_MANAGERS.has(pkg.name)) {
41+
return pkg.name;
42+
}
43+
return 'npm';
44+
}
45+
46+
function getInstallCommand(packageManager) {
47+
if (packageManager === 'yarn') {
48+
return ['yarn'];
49+
}
50+
return [packageManager, 'install'];
51+
}
52+
53+
function getRunDevCommand(packageManager) {
54+
switch (packageManager) {
55+
case 'yarn':
56+
case 'pnpm':
57+
case 'bun':
58+
return [packageManager, 'dev'];
59+
default:
60+
return ['npm', 'run', 'dev'];
61+
}
62+
}
63+
64+
function runPackageManagerCommand(args, cwd) {
65+
const [command, ...cmdArgs] = args;
66+
const result = spawnSync(command, cmdArgs, { stdio: 'inherit', cwd, shell: false });
67+
if (result.error) {
68+
throw result.error;
69+
}
70+
if (result.status !== 0) {
71+
throw new Error(`Command failed: ${args.join(' ')}`);
72+
}
73+
}
74+
1775
// Validate cloud name format
1876
function isValidCloudName(name) {
1977
return /^[a-z0-9_-]+$/.test(name) && name.length > 0;
@@ -61,8 +119,12 @@ async function main() {
61119
startDev: {
62120
type: 'boolean',
63121
default: false
64-
}
65-
}
122+
},
123+
packageManager: {
124+
type: 'string',
125+
},
126+
},
127+
allowPositionals: true,
66128
});
67129

68130
Object.assign(answers, values);
@@ -166,6 +228,17 @@ async function main() {
166228

167229
const { projectName, cloudName, uploadPreset, aiTools, installDeps, startDev } = answers;
168230

231+
let packageManager = answers.packageManager;
232+
if (packageManager && !SUPPORTED_PACKAGE_MANAGERS.has(packageManager)) {
233+
console.warn(
234+
chalk.yellow(
235+
`Unknown package manager "${packageManager}". Use npm, pnpm, yarn, or bun. Ignoring --packageManager; using npm_config_user_agent when it names a supported client, otherwise npm.`
236+
)
237+
);
238+
packageManager = undefined;
239+
}
240+
packageManager = packageManager || detectPackageManager();
241+
169242
console.log(chalk.blue('\n📦 Creating project...\n'));
170243

171244
// Create project directory
@@ -311,34 +384,38 @@ async function main() {
311384
console.log(chalk.cyan(' 5. Save the file and restart the dev server so it loads correctly\n'));
312385
}
313386

387+
const installCmd = getInstallCommand(packageManager);
388+
const devCmd = getRunDevCommand(packageManager);
389+
const installCmdStr = installCmd.join(' ');
390+
const devCmdStr = devCmd.join(' ');
391+
314392
if (installDeps) {
315-
console.log(chalk.blue('📦 Installing dependencies...\n'));
393+
console.log(chalk.blue(`📦 Installing dependencies with ${packageManager}...\n`));
316394
try {
317-
process.chdir(projectPath);
318-
execSync('npm install', { stdio: 'inherit' });
395+
runPackageManagerCommand(installCmd, projectPath);
319396
console.log(chalk.green('\n✅ Dependencies installed!\n'));
320397

321398
if (startDev) {
322399
console.log(chalk.blue('🚀 Starting development server...\n'));
323-
execSync('npm run dev', { stdio: 'inherit' });
400+
runPackageManagerCommand(devCmd, projectPath);
324401
} else {
325402
console.log(chalk.cyan(`\n📁 Project created at: ${projectPath}`));
326403
console.log(chalk.cyan(`\nNext steps:`));
327404
console.log(chalk.cyan(` cd ${projectName}`));
328-
console.log(chalk.cyan(` npm run dev\n`));
405+
console.log(chalk.cyan(` ${devCmdStr}\n`));
329406
}
330407
} catch (error) {
331408
console.error(chalk.red('\n❌ Error installing dependencies:'), error.message);
332409
console.log(chalk.cyan(`\nYou can install manually:`));
333410
console.log(chalk.cyan(` cd ${projectName}`));
334-
console.log(chalk.cyan(` npm install\n`));
411+
console.log(chalk.cyan(` ${installCmdStr}\n`));
335412
}
336413
} else {
337414
console.log(chalk.cyan(`\n📁 Project created at: ${projectPath}`));
338415
console.log(chalk.cyan(`\nNext steps:`));
339416
console.log(chalk.cyan(` cd ${projectName}`));
340-
console.log(chalk.cyan(` npm install`));
341-
console.log(chalk.cyan(` npm run dev\n`));
417+
console.log(chalk.cyan(` ${installCmdStr}`));
418+
console.log(chalk.cyan(` ${devCmdStr}\n`));
342419
}
343420
}
344421

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/README.md.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
A Cloudinary React + Vite + TypeScript project scaffolded with [create-cloudinary-react](https://github.com/cloudinary-devs/create-cloudinary-react).
44

5+
## Prerequisites
6+
7+
- **Node.js** — use a current LTS release. Supported ranges are listed under `engines` in this `package.json`.
8+
59
## Quick Start
610

711
```bash

templates/package.json.template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"private": true,
44
"version": "0.0.0",
55
"type": "module",
6+
"engines": {
7+
"node": "^20.19.0 || >=22.12.0"
8+
},
69
"scripts": {
710
"dev": "vite",
811
"build": "tsc -b && vite build",

0 commit comments

Comments
 (0)