create-cloudinary-react 1.0.0-beta.1
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/.github/CODEOWNERS +4 -0
- package/.github/workflows/release.yml +45 -0
- package/.husky/commit-msg +1 -0
- package/.releaserc.json +23 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/cli.js +283 -0
- package/commitlint.config.js +23 -0
- package/package.json +46 -0
- package/templates/.cursor/mcp.json.template +10 -0
- package/templates/.cursorrules.template +525 -0
- package/templates/.env.example.template +10 -0
- package/templates/.env.template +5 -0
- package/templates/.gitignore.template +29 -0
- package/templates/README.md.template +46 -0
- package/templates/eslint.config.js.template +23 -0
- package/templates/index.html.template +13 -0
- package/templates/package.json.template +32 -0
- package/templates/src/App.css.template +94 -0
- package/templates/src/App.tsx.template +80 -0
- package/templates/src/cloudinary/UploadWidget.tsx.template +112 -0
- package/templates/src/cloudinary/config.ts.template +21 -0
- package/templates/src/index.css.template +29 -0
- package/templates/src/main.tsx.template +10 -0
- package/templates/tsconfig.app.json.template +24 -0
- package/templates/tsconfig.json.template +7 -0
- package/templates/tsconfig.node.json.template +22 -0
- package/templates/vite.config.ts.template +7 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write
|
|
8
|
+
id-token: write
|
|
9
|
+
issues: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Verify admin permissions
|
|
17
|
+
run: |
|
|
18
|
+
PERMISSION=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
|
19
|
+
"https://api.github.com/repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission" \
|
|
20
|
+
| grep -o '"permission":"[^"]*"' | cut -d'"' -f4)
|
|
21
|
+
|
|
22
|
+
if [ "$PERMISSION" != "admin" ]; then
|
|
23
|
+
echo "Error: Only repository admins can trigger releases. Current permission: $PERMISSION"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo "ā Verified admin permission for ${{ github.actor }}"
|
|
28
|
+
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
|
|
33
|
+
- uses: actions/setup-node@v4
|
|
34
|
+
with:
|
|
35
|
+
node-version: '18'
|
|
36
|
+
registry-url: 'https://registry.npmjs.org'
|
|
37
|
+
|
|
38
|
+
- run: npm ci
|
|
39
|
+
|
|
40
|
+
- run: npm test --if-present
|
|
41
|
+
|
|
42
|
+
- name: Release
|
|
43
|
+
env:
|
|
44
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
run: npx semantic-release
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx --no -- commitlint --edit ${1}
|
package/.releaserc.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": [
|
|
3
|
+
"main",
|
|
4
|
+
{
|
|
5
|
+
"name": "beta",
|
|
6
|
+
"prerelease": true
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"plugins": [
|
|
10
|
+
"@semantic-release/commit-analyzer",
|
|
11
|
+
"@semantic-release/release-notes-generator",
|
|
12
|
+
"@semantic-release/changelog",
|
|
13
|
+
"@semantic-release/npm",
|
|
14
|
+
[
|
|
15
|
+
"@semantic-release/git",
|
|
16
|
+
{
|
|
17
|
+
"assets": ["package.json", "CHANGELOG.md"],
|
|
18
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"@semantic-release/github"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cloudinary
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# create-cloudinary-react
|
|
2
|
+
|
|
3
|
+
> **Beta Release** - This is a beta version. We welcome feedback and bug reports!
|
|
4
|
+
|
|
5
|
+
Part of the [Cloudinary Developers](https://github.com/cloudinary-devs) organization.
|
|
6
|
+
|
|
7
|
+
Scaffold a Cloudinary React + Vite + TypeScript project with interactive setup.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- Node.js 18+ installed
|
|
12
|
+
- A Cloudinary account (free tier available)
|
|
13
|
+
- [Sign up for free](https://cloudinary.com/users/register/free)
|
|
14
|
+
- Your cloud name is in your [dashboard](https://console.cloudinary.com/app/home/dashboard)
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx create-cloudinary-react
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The CLI will prompt you for:
|
|
23
|
+
- Project name
|
|
24
|
+
- **Cloudinary cloud name** (found in your [dashboard](https://console.cloudinary.com/app/home/dashboard))
|
|
25
|
+
- Upload preset (optional - required for uploads, but transformations work without it)
|
|
26
|
+
- AI coding assistant(s) you're using (Cursor, GitHub Copilot, Claude, etc.)
|
|
27
|
+
- Whether to install dependencies
|
|
28
|
+
- Whether to start dev server
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- ā
Interactive setup with validation
|
|
33
|
+
- ā
Pre-configured Cloudinary React SDK
|
|
34
|
+
- ā
TypeScript + Vite + React 19
|
|
35
|
+
- ā
Typed Upload Widget component
|
|
36
|
+
- ā
Environment variables with VITE_ prefix
|
|
37
|
+
- ā
Multi-tool AI assistant support (Cursor, GitHub Copilot, Claude, and more)
|
|
38
|
+
- ā
MCP configuration for Cloudinary integration
|
|
39
|
+
- ā
ESLint + TypeScript configured
|
|
40
|
+
|
|
41
|
+
## AI Assistant Support
|
|
42
|
+
|
|
43
|
+
During setup, you'll be asked which AI coding assistant(s) you're using. The CLI will generate the appropriate configuration files:
|
|
44
|
+
|
|
45
|
+
- ā
**Cursor** ā `.cursorrules` + `.cursor/mcp.json` (if selected)
|
|
46
|
+
- ā
**GitHub Copilot** ā `.github/copilot-instructions.md`
|
|
47
|
+
- ā
**Claude Code / Claude Desktop** ā `.claude`, `claude.md` + `.cursor/mcp.json` (if selected)
|
|
48
|
+
- ā
**Generic AI tools** ā `AI_INSTRUCTIONS.md`, `PROMPT.md`
|
|
49
|
+
|
|
50
|
+
**MCP Configuration**: The `.cursor/mcp.json` file is automatically generated if you select Cursor or Claude, as it works with both tools.
|
|
51
|
+
|
|
52
|
+
These rules help AI assistants understand Cloudinary React SDK patterns, common errors, and best practices. The generated app also includes an "AI Prompts" section with ready-to-use suggestions for your AI assistant.
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
This project uses [Conventional Commits](https://www.conventionalcommits.org/) for version management.
|
|
57
|
+
|
|
58
|
+
### Commit Format
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
<type>(<scope>): <subject>
|
|
62
|
+
|
|
63
|
+
<body>
|
|
64
|
+
|
|
65
|
+
<footer>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Types:**
|
|
69
|
+
- `feat`: New feature
|
|
70
|
+
- `fix`: Bug fix
|
|
71
|
+
- `docs`: Documentation changes
|
|
72
|
+
- `refactor`: Code refactoring
|
|
73
|
+
- `perf`: Performance improvements
|
|
74
|
+
- `chore`: Other changes
|
|
75
|
+
|
package/cli.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
const TEMPLATES_DIR = join(__dirname, 'templates');
|
|
15
|
+
|
|
16
|
+
// Validate cloud name format
|
|
17
|
+
function isValidCloudName(name) {
|
|
18
|
+
return /^[a-z0-9_-]+$/.test(name) && name.length > 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Validate project name
|
|
22
|
+
function isValidProjectName(name) {
|
|
23
|
+
return /^[a-z0-9_-]+$/i.test(name) && name.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
console.log(chalk.cyan.bold('\nš Cloudinary React + Vite Boilerplate\n'));
|
|
28
|
+
console.log(chalk.gray('š” Need a Cloudinary account? Sign up for free: https://cloudinary.com/users/register/free\n'));
|
|
29
|
+
|
|
30
|
+
const answers = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: 'input',
|
|
33
|
+
name: 'projectName',
|
|
34
|
+
message: 'Project name:',
|
|
35
|
+
default: 'my-cloudinary-app',
|
|
36
|
+
validate: (input) => {
|
|
37
|
+
if (!input.trim()) {
|
|
38
|
+
return 'Project name cannot be empty';
|
|
39
|
+
}
|
|
40
|
+
if (!isValidProjectName(input)) {
|
|
41
|
+
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
42
|
+
}
|
|
43
|
+
if (existsSync(input)) {
|
|
44
|
+
return `Directory "${input}" already exists. Please choose a different name.`;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'cloudName',
|
|
52
|
+
message: 'Cloudinary Cloud Name:',
|
|
53
|
+
description: chalk.gray(
|
|
54
|
+
'Your cloud name is shown in your dashboard: https://console.cloudinary.com/app/home/dashboard'
|
|
55
|
+
),
|
|
56
|
+
validate: (input) => {
|
|
57
|
+
if (!input.trim()) {
|
|
58
|
+
return chalk.yellow(
|
|
59
|
+
'Cloud name is required.\n' +
|
|
60
|
+
' ā Sign up: https://cloudinary.com/users/register/free\n' +
|
|
61
|
+
' ā Find your cloud name: https://console.cloudinary.com/app/home/dashboard'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (!isValidCloudName(input)) {
|
|
65
|
+
return 'Cloud name can only contain lowercase letters, numbers, hyphens, and underscores';
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'confirm',
|
|
72
|
+
name: 'hasUploadPreset',
|
|
73
|
+
message: 'Do you have an unsigned upload preset? (Required for uploads, optional for transformations)',
|
|
74
|
+
default: false,
|
|
75
|
+
description: chalk.gray(
|
|
76
|
+
'Upload presets enable client-side uploads. You can set one up later at:\n' +
|
|
77
|
+
'https://console.cloudinary.com/app/settings/upload/presets'
|
|
78
|
+
),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: 'input',
|
|
82
|
+
name: 'uploadPreset',
|
|
83
|
+
message: 'Upload Preset Name:',
|
|
84
|
+
when: (answers) => answers.hasUploadPreset,
|
|
85
|
+
validate: (input) => {
|
|
86
|
+
if (!input.trim()) {
|
|
87
|
+
return 'Upload preset name cannot be empty';
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'checkbox',
|
|
94
|
+
name: 'aiTools',
|
|
95
|
+
message: 'Which AI coding assistant(s) are you using? (Select all that apply)',
|
|
96
|
+
choices: [
|
|
97
|
+
{ name: 'Cursor', value: 'cursor' },
|
|
98
|
+
{ name: 'GitHub Copilot', value: 'copilot' },
|
|
99
|
+
{ name: 'Claude Code / Claude Desktop', value: 'claude' },
|
|
100
|
+
{ name: 'Other / Generic AI tools', value: 'generic' },
|
|
101
|
+
],
|
|
102
|
+
default: ['cursor'],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'confirm',
|
|
106
|
+
name: 'installDeps',
|
|
107
|
+
message: 'Install dependencies now?',
|
|
108
|
+
default: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'confirm',
|
|
112
|
+
name: 'startDev',
|
|
113
|
+
message: 'Start development server?',
|
|
114
|
+
default: false,
|
|
115
|
+
when: (answers) => answers.installDeps,
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const { projectName, cloudName, uploadPreset, aiTools, installDeps, startDev } = answers;
|
|
120
|
+
|
|
121
|
+
console.log(chalk.blue('\nš¦ Creating project...\n'));
|
|
122
|
+
|
|
123
|
+
// Create project directory
|
|
124
|
+
const projectPath = join(process.cwd(), projectName);
|
|
125
|
+
mkdirSync(projectPath, { recursive: true });
|
|
126
|
+
|
|
127
|
+
// Template replacement function
|
|
128
|
+
function replaceTemplate(content, vars) {
|
|
129
|
+
let result = content;
|
|
130
|
+
Object.keys(vars).forEach((key) => {
|
|
131
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
132
|
+
result = result.replace(regex, vars[key]);
|
|
133
|
+
});
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Template variables
|
|
138
|
+
const templateVars = {
|
|
139
|
+
PROJECT_NAME: projectName,
|
|
140
|
+
CLOUD_NAME: cloudName,
|
|
141
|
+
UPLOAD_PRESET: uploadPreset || '',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Function to copy template file
|
|
145
|
+
function copyTemplate(relativePath, outputPath = null) {
|
|
146
|
+
const templatePath = join(TEMPLATES_DIR, relativePath);
|
|
147
|
+
const finalPath = outputPath || join(projectPath, relativePath.replace('.template', ''));
|
|
148
|
+
|
|
149
|
+
// Create directory if needed
|
|
150
|
+
const finalDir = dirname(finalPath);
|
|
151
|
+
if (!existsSync(finalDir)) {
|
|
152
|
+
mkdirSync(finalDir, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (existsSync(templatePath)) {
|
|
156
|
+
const content = readFileSync(templatePath, 'utf-8');
|
|
157
|
+
const processed = replaceTemplate(content, templateVars);
|
|
158
|
+
writeFileSync(finalPath, processed);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Copy all template files
|
|
163
|
+
const filesToCopy = [
|
|
164
|
+
'package.json.template',
|
|
165
|
+
'vite.config.ts.template',
|
|
166
|
+
'tsconfig.json.template',
|
|
167
|
+
'tsconfig.app.json.template',
|
|
168
|
+
'tsconfig.node.json.template',
|
|
169
|
+
'eslint.config.js.template',
|
|
170
|
+
'.gitignore.template',
|
|
171
|
+
'.env.template',
|
|
172
|
+
'index.html.template',
|
|
173
|
+
'README.md.template',
|
|
174
|
+
'src/cloudinary/config.ts.template',
|
|
175
|
+
'src/cloudinary/UploadWidget.tsx.template',
|
|
176
|
+
'src/App.tsx.template',
|
|
177
|
+
'src/main.tsx.template',
|
|
178
|
+
'src/index.css.template',
|
|
179
|
+
'src/App.css.template',
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
filesToCopy.forEach((file) => {
|
|
183
|
+
copyTemplate(file);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Create AI rules based on user's tool selection
|
|
187
|
+
const aiRulesTemplatePath = join(TEMPLATES_DIR, '.cursorrules.template');
|
|
188
|
+
if (existsSync(aiRulesTemplatePath) && aiTools && aiTools.length > 0) {
|
|
189
|
+
const aiRulesContent = replaceTemplate(
|
|
190
|
+
readFileSync(aiRulesTemplatePath, 'utf-8'),
|
|
191
|
+
templateVars
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Generate files based on selected tools
|
|
195
|
+
if (aiTools.includes('cursor')) {
|
|
196
|
+
writeFileSync(join(projectPath, '.cursorrules'), aiRulesContent);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (aiTools.includes('copilot')) {
|
|
200
|
+
const githubDir = join(projectPath, '.github');
|
|
201
|
+
mkdirSync(githubDir, { recursive: true });
|
|
202
|
+
writeFileSync(join(githubDir, 'copilot-instructions.md'), aiRulesContent);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (aiTools.includes('claude')) {
|
|
206
|
+
writeFileSync(join(projectPath, '.claude'), aiRulesContent);
|
|
207
|
+
writeFileSync(join(projectPath, 'claude.md'), aiRulesContent);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (aiTools.includes('generic')) {
|
|
211
|
+
writeFileSync(join(projectPath, 'AI_INSTRUCTIONS.md'), aiRulesContent);
|
|
212
|
+
writeFileSync(join(projectPath, 'PROMPT.md'), aiRulesContent);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Generate MCP configuration if using Cursor or Claude (MCP works with both)
|
|
216
|
+
if (aiTools.includes('cursor') || aiTools.includes('claude')) {
|
|
217
|
+
const mcpTemplatePath = join(TEMPLATES_DIR, '.cursor/mcp.json.template');
|
|
218
|
+
if (existsSync(mcpTemplatePath)) {
|
|
219
|
+
const cursorDir = join(projectPath, '.cursor');
|
|
220
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
221
|
+
const mcpContent = replaceTemplate(
|
|
222
|
+
readFileSync(mcpTemplatePath, 'utf-8'),
|
|
223
|
+
templateVars
|
|
224
|
+
);
|
|
225
|
+
writeFileSync(join(cursorDir, 'mcp.json'), mcpContent);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Copy vite.svg to public directory
|
|
231
|
+
const viteSvgPath = join(projectPath, 'public', 'vite.svg');
|
|
232
|
+
mkdirSync(join(projectPath, 'public'), { recursive: true });
|
|
233
|
+
const viteSvg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>';
|
|
234
|
+
writeFileSync(viteSvgPath, viteSvg);
|
|
235
|
+
|
|
236
|
+
console.log(chalk.green('ā
Project created successfully!\n'));
|
|
237
|
+
|
|
238
|
+
if (!answers.hasUploadPreset) {
|
|
239
|
+
console.log(chalk.yellow('\nš Note: Upload preset not configured'));
|
|
240
|
+
console.log(chalk.gray(' ⢠Transformations will work with sample images'));
|
|
241
|
+
console.log(chalk.gray(' ⢠Uploads require an unsigned upload preset'));
|
|
242
|
+
console.log(chalk.cyan('\n To enable uploads:'));
|
|
243
|
+
console.log(chalk.cyan(' 1. Go to https://console.cloudinary.com/app/settings/upload/presets'));
|
|
244
|
+
console.log(chalk.cyan(' 2. Click "Add upload preset"'));
|
|
245
|
+
console.log(chalk.cyan(' 3. Set it to "Unsigned" mode'));
|
|
246
|
+
console.log(chalk.cyan(' 4. Add the preset name to your .env file\n'));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (installDeps) {
|
|
250
|
+
console.log(chalk.blue('š¦ Installing dependencies...\n'));
|
|
251
|
+
try {
|
|
252
|
+
process.chdir(projectPath);
|
|
253
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
254
|
+
console.log(chalk.green('\nā
Dependencies installed!\n'));
|
|
255
|
+
|
|
256
|
+
if (startDev) {
|
|
257
|
+
console.log(chalk.blue('š Starting development server...\n'));
|
|
258
|
+
execSync('npm run dev', { stdio: 'inherit' });
|
|
259
|
+
} else {
|
|
260
|
+
console.log(chalk.cyan(`\nš Project created at: ${projectPath}`));
|
|
261
|
+
console.log(chalk.cyan(`\nNext steps:`));
|
|
262
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
263
|
+
console.log(chalk.cyan(` npm run dev\n`));
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error(chalk.red('\nā Error installing dependencies:'), error.message);
|
|
267
|
+
console.log(chalk.cyan(`\nYou can install manually:`));
|
|
268
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
269
|
+
console.log(chalk.cyan(` npm install\n`));
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
console.log(chalk.cyan(`\nš Project created at: ${projectPath}`));
|
|
273
|
+
console.log(chalk.cyan(`\nNext steps:`));
|
|
274
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
275
|
+
console.log(chalk.cyan(` npm install`));
|
|
276
|
+
console.log(chalk.cyan(` npm run dev\n`));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
main().catch((error) => {
|
|
281
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
extends: ['@commitlint/config-conventional'],
|
|
3
|
+
rules: {
|
|
4
|
+
'type-enum': [
|
|
5
|
+
2,
|
|
6
|
+
'always',
|
|
7
|
+
[
|
|
8
|
+
'feat', // New feature
|
|
9
|
+
'fix', // Bug fix
|
|
10
|
+
'docs', // Documentation changes
|
|
11
|
+
'style', // Code style changes (formatting, etc.)
|
|
12
|
+
'refactor', // Code refactoring
|
|
13
|
+
'perf', // Performance improvements
|
|
14
|
+
'test', // Adding or updating tests
|
|
15
|
+
'build', // Build system changes
|
|
16
|
+
'ci', // CI configuration changes
|
|
17
|
+
'chore', // Other changes that don't modify src or test files
|
|
18
|
+
'revert', // Revert a previous commit
|
|
19
|
+
],
|
|
20
|
+
],
|
|
21
|
+
'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
|
|
22
|
+
},
|
|
23
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-cloudinary-react",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Scaffold a Cloudinary React + Vite + TypeScript project with interactive setup",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-cloudinary-react": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"cloudinary",
|
|
11
|
+
"react",
|
|
12
|
+
"vite",
|
|
13
|
+
"typescript",
|
|
14
|
+
"boilerplate",
|
|
15
|
+
"scaffold"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/cloudinary-devs/create-cloudinary-react.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/cloudinary-devs/create-cloudinary-react#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/cloudinary-devs/create-cloudinary-react/issues"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"inquirer": "^9.2.15",
|
|
29
|
+
"chalk": "^5.3.0",
|
|
30
|
+
"fs-extra": "^11.2.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"semantic-release": "semantic-release"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@commitlint/cli": "^19.6.0",
|
|
40
|
+
"@commitlint/config-conventional": "^19.6.0",
|
|
41
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
42
|
+
"@semantic-release/git": "^10.0.1",
|
|
43
|
+
"husky": "^9.1.7",
|
|
44
|
+
"semantic-release": "^23.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|