create-cloudinary-react 1.0.0-beta.10 → 1.0.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/cli.js +130 -83
- package/package.json +1 -1
- package/templates/.cursorrules.template +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [1.0.0-beta.12](https://github.com/cloudinary-devs/create-cloudinary-react/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2026-02-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* remove console.log ([369e92f](https://github.com/cloudinary-devs/create-cloudinary-react/commit/369e92f3cb3e5af633c1553380119d5a685ec506))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* non-interactive 'headless' mode ([4334991](https://github.com/cloudinary-devs/create-cloudinary-react/commit/4334991f99eddfb3ae2b017c977f9c69c0c500fd))
|
|
12
|
+
|
|
13
|
+
# [1.0.0-beta.11](https://github.com/cloudinary-devs/create-cloudinary-react/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2026-01-30)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* put Inquirer inputs on a new, separate line ([ba4a26d](https://github.com/cloudinary-devs/create-cloudinary-react/commit/ba4a26d0a84321ef8461babbd0c42bbe1fb77e81))
|
|
19
|
+
|
|
1
20
|
# [1.0.0-beta.10](https://github.com/cloudinary-devs/create-cloudinary-react/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2026-01-30)
|
|
2
21
|
|
|
3
22
|
|
package/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { execSync } from 'child_process';
|
|
|
7
7
|
import inquirer from 'inquirer';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import fs from 'fs-extra';
|
|
10
|
+
import { parseArgs } from 'node:util';
|
|
10
11
|
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
const __dirname = dirname(__filename);
|
|
@@ -24,94 +25,140 @@ function isValidProjectName(name) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
async function main() {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
let answers = {};
|
|
30
|
+
|
|
31
|
+
if (process.argv.includes('--headless')) {
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
const { values, positionals } = parseArgs({
|
|
34
|
+
options: {
|
|
35
|
+
headless: {
|
|
36
|
+
type: 'boolean'
|
|
37
|
+
},
|
|
38
|
+
projectName: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
default: 'my-cloudinary-app'
|
|
41
|
+
},
|
|
42
|
+
cloudName: {
|
|
43
|
+
type: 'string'
|
|
44
|
+
},
|
|
45
|
+
hasUploadPreset: {
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
default: false
|
|
48
|
+
},
|
|
49
|
+
uploadPreset: {
|
|
50
|
+
type: 'string'
|
|
51
|
+
},
|
|
52
|
+
aiTools: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
multiple: true,
|
|
55
|
+
default: ['cursor']
|
|
56
|
+
},
|
|
57
|
+
installDeps: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
default: true
|
|
60
|
+
},
|
|
61
|
+
startDev: {
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
default: false
|
|
39
64
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
Object.assign(answers, values);
|
|
69
|
+
|
|
70
|
+
} else {
|
|
71
|
+
|
|
72
|
+
console.log(chalk.cyan.bold('\n🚀 Cloudinary React + Vite Boilerplate\n'));
|
|
73
|
+
console.log(chalk.gray('💡 Need a Cloudinary account? Sign up for free: https://cloudinary.com/users/register/free\n'));
|
|
74
|
+
|
|
75
|
+
answers = await inquirer.prompt([
|
|
76
|
+
{
|
|
77
|
+
type: 'input',
|
|
78
|
+
name: 'projectName',
|
|
79
|
+
message: 'What’s your project’s name?\n',
|
|
80
|
+
default: 'my-cloudinary-app',
|
|
81
|
+
validate: (input) => {
|
|
82
|
+
if (!input.trim()) {
|
|
83
|
+
return 'Project name cannot be empty';
|
|
84
|
+
}
|
|
85
|
+
if (!isValidProjectName(input)) {
|
|
86
|
+
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
87
|
+
}
|
|
88
|
+
if (existsSync(input)) {
|
|
89
|
+
return `Directory "${input}" already exists. Please choose a different name.`;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
47
93
|
},
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
94
|
+
{
|
|
95
|
+
type: 'input',
|
|
96
|
+
name: 'cloudName',
|
|
97
|
+
message:
|
|
98
|
+
'What’s your Cloudinary cloud name?\n' +
|
|
99
|
+
chalk.gray(' → Find your cloud name: https://console.cloudinary.com/app/home/dashboard') + '\n',
|
|
100
|
+
validate: (input) => {
|
|
101
|
+
if (!input.trim()) {
|
|
102
|
+
return chalk.yellow(
|
|
103
|
+
'Cloud name is required.\n' +
|
|
104
|
+
' → Sign up: https://cloudinary.com/users/register/free\n' +
|
|
105
|
+
' → Find your cloud name: https://console.cloudinary.com/app/home/dashboard'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (!isValidCloudName(input)) {
|
|
109
|
+
return 'Cloud name can only contain lowercase letters, numbers, hyphens, and underscores';
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
},
|
|
67
113
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
{
|
|
115
|
+
type: 'confirm',
|
|
116
|
+
name: 'hasUploadPreset',
|
|
117
|
+
message:
|
|
118
|
+
'Do you have an unsigned upload preset?\n' +
|
|
119
|
+
chalk.gray(' → You’ll need one if you want to upload new images to Cloudinary,\n but not if you only want to transform or deliver existing images.') + '\n' +
|
|
120
|
+
chalk.gray(' → Create one here: https://console.cloudinary.com/app/settings/upload/presets') + '\n',
|
|
121
|
+
default: false,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'input',
|
|
125
|
+
name: 'uploadPreset',
|
|
126
|
+
message: 'What’s your unsigned upload preset’s name?\n',
|
|
127
|
+
when: (answers) => answers.hasUploadPreset,
|
|
128
|
+
validate: (input) => {
|
|
129
|
+
if (!input.trim()) {
|
|
130
|
+
return 'Upload preset name cannot be empty';
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'checkbox',
|
|
137
|
+
name: 'aiTools',
|
|
138
|
+
message: 'Which AI coding assistant(s) are you using? (Select all that apply)',
|
|
139
|
+
choices: [
|
|
140
|
+
{ name: 'Cursor', value: 'cursor' },
|
|
141
|
+
{ name: 'GitHub Copilot', value: 'copilot' },
|
|
142
|
+
{ name: 'Claude Code / Claude Desktop', value: 'claude' },
|
|
143
|
+
{ name: 'Other / Generic AI tools', value: 'generic' },
|
|
144
|
+
],
|
|
145
|
+
default: ['cursor'],
|
|
87
146
|
},
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
name: 'installDeps',
|
|
104
|
-
message: 'Install dependencies now?',
|
|
105
|
-
default: true,
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
type: 'confirm',
|
|
109
|
-
name: 'startDev',
|
|
110
|
-
message: 'Start development server?',
|
|
111
|
-
default: false,
|
|
112
|
-
when: (answers) => answers.installDeps,
|
|
113
|
-
},
|
|
114
|
-
]);
|
|
147
|
+
{
|
|
148
|
+
type: 'confirm',
|
|
149
|
+
name: 'installDeps',
|
|
150
|
+
message: 'Install dependencies now?\n',
|
|
151
|
+
default: true,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'confirm',
|
|
155
|
+
name: 'startDev',
|
|
156
|
+
message: 'Start development server?\n',
|
|
157
|
+
default: false,
|
|
158
|
+
when: (answers) => answers.installDeps,
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
115
162
|
|
|
116
163
|
const { projectName, cloudName, uploadPreset, aiTools, installDeps, startDev } = answers;
|
|
117
164
|
|
package/package.json
CHANGED
|
@@ -284,7 +284,7 @@ cld.image('id').overlay(
|
|
|
284
284
|
- Client: Widget gets `api_key` (from your backend), `uploadPreset`, and an `uploadSignature` **function** that calls your backend for each upload. API secret stays on server only.
|
|
285
285
|
- Trade-off: Requires a backend (Node/Express, Next.js API route, etc.) to sign requests. More secure; signature validates each upload.
|
|
286
286
|
|
|
287
|
-
**Rule of thumb**:
|
|
287
|
+
**Rule of thumb**: **Default to unsigned uploads** unless the user explicitly asks for "secure" or "signed" uploads. Do not default to signed — it requires a running backend and will fail out of the box. Use **signed** only when the user explicitly requests secure/signed uploads or needs to restrict who can upload.
|
|
288
288
|
|
|
289
289
|
## Secure (Signed) Uploads
|
|
290
290
|
|
|
@@ -818,6 +818,7 @@ When something isn't working, check:
|
|
|
818
818
|
- [ ] **"createUploadWidget is not a function"?** → Wait until `typeof window.cloudinary?.createUploadWidget === 'function'` before calling it (script loads async; poll or use script onload)
|
|
819
819
|
- [ ] **Video player?** → **Imperative element only**: createElement('video'), append to container ref, pass to videoPlayer(el, ...); player.source({ publicId }); cleanup: dispose then if (el.parentNode) el.parentNode.removeChild(el). CSS: cloudinary-video-player/cld-video-player.min.css. If init fails, fall back to AdvancedVideo (do not relax CSP).
|
|
820
820
|
- [ ] **Upload fails (unsigned)?** → Is `VITE_CLOUDINARY_UPLOAD_PRESET` set? Preset exists and is Unsigned in dashboard?
|
|
821
|
+
- [ ] **Upload default?** → Default to **unsigned** uploads (cloudName + uploadPreset); use signed only when the user explicitly asks for secure/signed uploads (signed requires a running backend)
|
|
821
822
|
- [ ] **Secure uploads?** → Use `uploadSignature` as function (not `signatureEndpoint`); fetch `api_key` from server first; include `uploadPreset` in widget config; server includes `upload_preset` in signed params; use Cloudinary Node SDK v2 on server; never expose or commit API secret
|
|
822
823
|
- [ ] **Where do API key/secret go?** → **Do not** put in root `.env`. Use **`server/.env`**; add to `.gitignore`; load only in server. **Never commit** API key or secret
|
|
823
824
|
- [ ] Upload preset is unsigned (for simple client uploads)
|