create-ripple 0.1.2 → 0.2.0
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/README.md +7 -7
- package/package.json +1 -1
- package/src/commands/create.js +9 -2
- package/src/index.js +1 -1
- package/src/lib/project-creator.js +86 -13
- package/src/lib/prompts.js +28 -0
- package/tests/integration/cli.test.js +1 -1
- package/tests/integration/project-creator.test.js +43 -2
- package/tests/unit/prompts.test.js +34 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# create-ripple
|
|
1
|
+
# create-ripple
|
|
2
2
|
|
|
3
3
|
Interactive CLI tool for creating new Ripple applications.
|
|
4
4
|
|
|
@@ -7,21 +7,21 @@ Interactive CLI tool for creating new Ripple applications.
|
|
|
7
7
|
### Interactive Mode
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm create ripple
|
|
10
|
+
npm create ripple
|
|
11
11
|
# or
|
|
12
|
-
npx create-ripple
|
|
12
|
+
npx create-ripple
|
|
13
13
|
# or
|
|
14
|
-
yarn create ripple
|
|
14
|
+
yarn create ripple
|
|
15
15
|
# or
|
|
16
|
-
pnpm create ripple
|
|
16
|
+
pnpm create ripple
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
### With Arguments
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm create ripple
|
|
22
|
+
npm create ripple my-app
|
|
23
23
|
# or
|
|
24
|
-
npx create-ripple
|
|
24
|
+
npx create-ripple my-app
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Features
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
promptTemplate,
|
|
9
9
|
promptOverwrite,
|
|
10
10
|
promptPackageManager,
|
|
11
|
-
promptGitInit
|
|
11
|
+
promptGitInit,
|
|
12
|
+
promptStylingFramework
|
|
12
13
|
} from '../lib/prompts.js';
|
|
13
14
|
import { createProject } from '../lib/project-creator.js';
|
|
14
15
|
|
|
@@ -71,6 +72,11 @@ export async function createCommand(projectName, options) {
|
|
|
71
72
|
gitInit = false;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
let stylingFramework = 'vanilla';
|
|
76
|
+
if (!options.yes) {
|
|
77
|
+
stylingFramework = await promptStylingFramework();
|
|
78
|
+
}
|
|
79
|
+
|
|
74
80
|
// Step 6: Create the project
|
|
75
81
|
console.log();
|
|
76
82
|
console.log(`Creating Ripple app in ${green(projectPath)}...`);
|
|
@@ -83,7 +89,8 @@ export async function createCommand(projectName, options) {
|
|
|
83
89
|
template,
|
|
84
90
|
packageManager,
|
|
85
91
|
typescript: true,
|
|
86
|
-
gitInit
|
|
92
|
+
gitInit,
|
|
93
|
+
stylingFramework
|
|
87
94
|
});
|
|
88
95
|
|
|
89
96
|
showNextSteps(projectName, packageManager);
|
package/src/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'),
|
|
|
14
14
|
const program = new Command();
|
|
15
15
|
|
|
16
16
|
program
|
|
17
|
-
.name('create-ripple
|
|
17
|
+
.name('create-ripple')
|
|
18
18
|
.description('Interactive CLI tool for creating Ripple applications')
|
|
19
19
|
.version(packageJson.version)
|
|
20
20
|
.helpOption('-h, --help', 'Display help for command');
|
|
@@ -13,16 +13,16 @@ import { downloadTemplate, getLocalTemplatePath, isLocalDevelopment } from './te
|
|
|
13
13
|
* @param {string} options.projectPath - Absolute path where project will be created
|
|
14
14
|
* @param {string} options.template - Template to use
|
|
15
15
|
* @param {string} options.packageManager - Package manager to use
|
|
16
|
-
* @param {boolean} options.typescript - Whether to use TypeScript
|
|
17
16
|
* @param {boolean} options.gitInit - Whether to initialize Git
|
|
17
|
+
* @param {string} options.stylingFramework - Styling framework to use
|
|
18
18
|
*/
|
|
19
19
|
export async function createProject({
|
|
20
20
|
projectName,
|
|
21
21
|
projectPath,
|
|
22
22
|
template,
|
|
23
23
|
packageManager = 'npm',
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
gitInit = true,
|
|
25
|
+
stylingFramework = 'vanilla'
|
|
26
26
|
}) {
|
|
27
27
|
console.log(dim(`Creating project: ${projectName}`));
|
|
28
28
|
console.log(dim(`Template: ${template}`));
|
|
@@ -95,7 +95,7 @@ export async function createProject({
|
|
|
95
95
|
// Step 4: Update package.json
|
|
96
96
|
const spinner4 = ora('Configuring package.json...').start();
|
|
97
97
|
try {
|
|
98
|
-
updatePackageJson(projectPath, projectName, packageManager,
|
|
98
|
+
updatePackageJson(projectPath, projectName, packageManager, stylingFramework);
|
|
99
99
|
spinner4.succeed('Package.json configured');
|
|
100
100
|
} catch (error) {
|
|
101
101
|
spinner4.fail('Failed to configure package.json');
|
|
@@ -105,16 +105,29 @@ export async function createProject({
|
|
|
105
105
|
throw error;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
// Step 5:
|
|
108
|
+
// Step 5: Configure styling
|
|
109
|
+
const spinner5 = ora('Configuring styling framework...').start();
|
|
110
|
+
try {
|
|
111
|
+
configureStyling(projectPath, stylingFramework);
|
|
112
|
+
spinner5.succeed('Styling framework configured');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
spinner5.fail('Failed to configure styling framework');
|
|
115
|
+
if (isTemporary) {
|
|
116
|
+
rmSync(templatePath, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
// Step 6: Initialize Git (if requested)
|
|
109
124
|
if (gitInit) {
|
|
110
|
-
const
|
|
125
|
+
const spinner6 = ora('Initializing Git repository...').start();
|
|
111
126
|
try {
|
|
112
127
|
execSync('git init', { cwd: projectPath, stdio: 'ignore' });
|
|
113
|
-
|
|
114
|
-
execSync('git commit -m "Initial commit"', { cwd: projectPath, stdio: 'ignore' });
|
|
115
|
-
spinner5.succeed('Git repository initialized');
|
|
128
|
+
spinner6.succeed('Git repository initialized');
|
|
116
129
|
} catch (error) {
|
|
117
|
-
|
|
130
|
+
spinner6.warn('Git initialization failed (optional)');
|
|
118
131
|
}
|
|
119
132
|
}
|
|
120
133
|
|
|
@@ -136,9 +149,9 @@ export async function createProject({
|
|
|
136
149
|
* @param {string} projectPath - Path to the project
|
|
137
150
|
* @param {string} projectName - Name of the project
|
|
138
151
|
* @param {string} packageManager - Package manager being used
|
|
139
|
-
* @param {
|
|
152
|
+
* @param {string} stylingFramework - Styling framework being used
|
|
140
153
|
*/
|
|
141
|
-
function updatePackageJson(projectPath, projectName, packageManager,
|
|
154
|
+
function updatePackageJson(projectPath, projectName, packageManager, stylingFramework) {
|
|
142
155
|
const packageJsonPath = join(projectPath, 'package.json');
|
|
143
156
|
|
|
144
157
|
if (!existsSync(packageJsonPath)) {
|
|
@@ -156,13 +169,27 @@ function updatePackageJson(projectPath, projectName, packageManager, typescript)
|
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
// Update description
|
|
159
|
-
packageJson.description = `A Ripple application created with create-ripple
|
|
172
|
+
packageJson.description = `A Ripple application created with create-ripple`;
|
|
160
173
|
|
|
161
174
|
// Add package manager field if not npm
|
|
162
175
|
if (packageManager !== 'npm') {
|
|
163
176
|
packageJson.packageManager = getPackageManagerVersion(packageManager);
|
|
164
177
|
}
|
|
165
178
|
|
|
179
|
+
// Add styling dependencies
|
|
180
|
+
if (stylingFramework === 'tailwind') {
|
|
181
|
+
packageJson.devDependencies = {
|
|
182
|
+
...packageJson.devDependencies,
|
|
183
|
+
'tailwindcss': '^4.1.12',
|
|
184
|
+
'@tailwindcss/vite': '^4.1.12'
|
|
185
|
+
};
|
|
186
|
+
} else if (stylingFramework === 'bootstrap') {
|
|
187
|
+
packageJson.dependencies = {
|
|
188
|
+
...packageJson.dependencies,
|
|
189
|
+
'bootstrap': '^5.3.0'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
166
193
|
// Ensure we're using the latest versions
|
|
167
194
|
updateDependencyVersions(packageJson);
|
|
168
195
|
|
|
@@ -172,6 +199,52 @@ function updatePackageJson(projectPath, projectName, packageManager, typescript)
|
|
|
172
199
|
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
173
200
|
}
|
|
174
201
|
|
|
202
|
+
function configureStyling(projectPath, stylingFramework) {
|
|
203
|
+
if (stylingFramework === 'tailwind') {
|
|
204
|
+
const tailwindConfig = `import type { Config } from 'tailwindcss';
|
|
205
|
+
export default {
|
|
206
|
+
content: [
|
|
207
|
+
"./index.html",
|
|
208
|
+
"./src/**/*.{ts,ripple}",
|
|
209
|
+
],
|
|
210
|
+
theme: {
|
|
211
|
+
extend: {},
|
|
212
|
+
},
|
|
213
|
+
plugins: []
|
|
214
|
+
} satisfies Config
|
|
215
|
+
`;
|
|
216
|
+
writeFileSync(join(projectPath, 'tailwind.config.ts'), tailwindConfig);
|
|
217
|
+
const mainCss = `@import "tailwindcss";
|
|
218
|
+
@config "./tailwind.config.ts";`;
|
|
219
|
+
writeFileSync(join(projectPath, 'src', 'index.css'), mainCss);
|
|
220
|
+
|
|
221
|
+
const mainTs = readFileSync(join(projectPath, 'src', 'index.ts'), 'utf-8');
|
|
222
|
+
const newMainTs = "import './index.css';\n" + mainTs;
|
|
223
|
+
writeFileSync(join(projectPath, 'src', 'index.ts'), newMainTs);
|
|
224
|
+
|
|
225
|
+
if (existsSync(join(projectPath, 'vite.config.js'))) {
|
|
226
|
+
rmSync(join(projectPath, 'vite.config.js'));
|
|
227
|
+
}
|
|
228
|
+
const viteConfig = `import { defineConfig } from 'vite';
|
|
229
|
+
import { ripple } from 'vite-plugin-ripple';
|
|
230
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
231
|
+
|
|
232
|
+
export default defineConfig({
|
|
233
|
+
plugins: [ripple(), tailwindcss()],
|
|
234
|
+
server: {
|
|
235
|
+
port: 3000
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
`;
|
|
239
|
+
writeFileSync(join(projectPath, 'vite.config.js'), viteConfig);
|
|
240
|
+
|
|
241
|
+
} else if (stylingFramework === 'bootstrap') {
|
|
242
|
+
const mainTs = readFileSync(join(projectPath, 'src', 'index.ts'), 'utf-8');
|
|
243
|
+
const newMainTs = "import 'bootstrap/dist/css/bootstrap.min.css';\n" + mainTs;
|
|
244
|
+
writeFileSync(join(projectPath, 'src', 'index.ts'), newMainTs);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
175
248
|
/**
|
|
176
249
|
* Update dependency versions to latest
|
|
177
250
|
* @param {object} packageJson - Package.json object
|
package/src/lib/prompts.js
CHANGED
|
@@ -134,3 +134,31 @@ export async function promptGitInit() {
|
|
|
134
134
|
|
|
135
135
|
return response.gitInit;
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
export async function promptStylingFramework() {
|
|
139
|
+
const response = await prompts({
|
|
140
|
+
type: 'select',
|
|
141
|
+
name: 'stylingFramework',
|
|
142
|
+
message: 'Which styling framework would you like to integrate with Ripple?',
|
|
143
|
+
choices: [{
|
|
144
|
+
title: 'Vanilla CSS',
|
|
145
|
+
value: 'vanilla',
|
|
146
|
+
description: 'Use Vanilla CSS for styling your components'
|
|
147
|
+
}, {
|
|
148
|
+
title: 'Bootstrap',
|
|
149
|
+
value: 'bootstrap',
|
|
150
|
+
description: 'Use Bootstrap classes to style your components'
|
|
151
|
+
}, {
|
|
152
|
+
title: 'TailwindCSS',
|
|
153
|
+
value: 'tailwind',
|
|
154
|
+
description: 'Use TailwindCSS to style your components'
|
|
155
|
+
}]
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (response.stylingFramework === undefined) {
|
|
159
|
+
console.log(red('✖ Operation cancelled'));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return response.stylingFramework;
|
|
164
|
+
}
|
|
@@ -67,7 +67,7 @@ describe('CLI Integration Tests', () => {
|
|
|
67
67
|
|
|
68
68
|
expect(result.code).toBe(0);
|
|
69
69
|
expect(result.stdout).toContain('Interactive CLI tool for creating Ripple applications');
|
|
70
|
-
expect(result.stdout).toContain('Usage: create-ripple
|
|
70
|
+
expect(result.stdout).toContain('Usage: create-ripple');
|
|
71
71
|
expect(result.stdout).toContain('Arguments:');
|
|
72
72
|
expect(result.stdout).toContain('Options:');
|
|
73
73
|
expect(result.stdout).toContain('--template');
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { createProject } from '../../src/lib/project-creator.js';
|
|
5
|
+
import { createProject, updatePackageJson, configureStyling } from '../../src/lib/project-creator.js';
|
|
6
6
|
import { getLocalTemplatePath, isLocalDevelopment, validateTemplate } from '../../src/lib/templates.js';
|
|
7
7
|
|
|
8
8
|
// Mock ora for cleaner test output
|
|
@@ -116,7 +116,7 @@ describe('createProject integration tests', () => {
|
|
|
116
116
|
// Verify package.json was updated
|
|
117
117
|
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
118
118
|
expect(packageJson.name).toBe('test-project');
|
|
119
|
-
expect(packageJson.description).toBe('A Ripple application created with create-ripple
|
|
119
|
+
expect(packageJson.description).toBe('A Ripple application created with create-ripple');
|
|
120
120
|
expect(packageJson.version).toBe('1.0.0');
|
|
121
121
|
});
|
|
122
122
|
|
|
@@ -227,4 +227,45 @@ describe('createProject integration tests', () => {
|
|
|
227
227
|
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
228
228
|
expect(existsSync(join(projectPath, 'existing-file.txt'))).toBe(true);
|
|
229
229
|
});
|
|
230
|
+
it('should configure Tailwind CSS correctly', async () => {
|
|
231
|
+
writeFileSync(join(templatePath, 'src', 'index.ts'), 'console.log("Hello, World!");');
|
|
232
|
+
await createProject({
|
|
233
|
+
projectName: 'test-tailwind-project',
|
|
234
|
+
projectPath,
|
|
235
|
+
template: 'basic',
|
|
236
|
+
packageManager: 'npm',
|
|
237
|
+
typescript: true,
|
|
238
|
+
gitInit: false,
|
|
239
|
+
stylingFramework: 'tailwind'
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
243
|
+
expect(packageJson.devDependencies).toHaveProperty('tailwindcss');
|
|
244
|
+
expect(packageJson.devDependencies).toHaveProperty('@tailwindcss/vite');
|
|
245
|
+
|
|
246
|
+
expect(existsSync(join(projectPath, 'tailwind.config.ts'))).toBe(true);
|
|
247
|
+
expect(readFileSync(join(projectPath, 'src', 'index.ts'), 'utf-8')).toContain("import './index.css';\n");
|
|
248
|
+
expect(existsSync(join(projectPath, 'src', 'index.css'))).toBe(true);
|
|
249
|
+
expect(readFileSync(join(projectPath, 'src', 'index.css'), 'utf-8')).toContain('@import "tailwindcss"');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should configure Bootstrap correctly', async () => {
|
|
253
|
+
writeFileSync(join(templatePath, 'src', 'index.ts'), 'console.log("Hello, World!");');
|
|
254
|
+
|
|
255
|
+
await createProject({
|
|
256
|
+
projectName: 'test-bootstrap-project',
|
|
257
|
+
projectPath,
|
|
258
|
+
template: 'basic',
|
|
259
|
+
packageManager: 'npm',
|
|
260
|
+
typescript: true,
|
|
261
|
+
gitInit: false,
|
|
262
|
+
stylingFramework: 'bootstrap'
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
266
|
+
expect(packageJson.dependencies).toHaveProperty('bootstrap');
|
|
267
|
+
|
|
268
|
+
const mainTsContent = readFileSync(join(projectPath, 'src', 'index.ts'), 'utf-8');
|
|
269
|
+
expect(mainTsContent).toContain("import 'bootstrap/dist/css/bootstrap.min.css';");
|
|
270
|
+
});
|
|
230
271
|
});
|
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
promptOverwrite,
|
|
29
29
|
promptPackageManager,
|
|
30
30
|
promptTypeScript,
|
|
31
|
-
promptGitInit
|
|
31
|
+
promptGitInit,
|
|
32
|
+
promptStylingFramework
|
|
32
33
|
} from '../../src/lib/prompts.js';
|
|
33
34
|
|
|
34
35
|
describe('Prompts', () => {
|
|
@@ -204,4 +205,36 @@ describe('Prompts', () => {
|
|
|
204
205
|
expect(mockExit).toHaveBeenCalledWith(1);
|
|
205
206
|
});
|
|
206
207
|
});
|
|
208
|
+
describe('promptStylingFramework', () => {
|
|
209
|
+
it('should return selected styling framework', async () => {
|
|
210
|
+
prompts.default.mockResolvedValue({ stylingFramework: 'tailwind' });
|
|
211
|
+
|
|
212
|
+
const result = await promptStylingFramework();
|
|
213
|
+
expect(result).toBe('tailwind');
|
|
214
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
215
|
+
type: 'select',
|
|
216
|
+
name: 'stylingFramework',
|
|
217
|
+
message: 'Which styling framework would you like to integrate with Ripple?',
|
|
218
|
+
choices: [{
|
|
219
|
+
title: 'Vanilla CSS',
|
|
220
|
+
value: 'vanilla',
|
|
221
|
+
description: 'Use Vanilla CSS for styling your components'
|
|
222
|
+
}, {
|
|
223
|
+
title: 'Bootstrap',
|
|
224
|
+
value: 'bootstrap',
|
|
225
|
+
description: 'Use Bootstrap classes to style your components'
|
|
226
|
+
}, {
|
|
227
|
+
title: 'TailwindCSS',
|
|
228
|
+
value: 'tailwind',
|
|
229
|
+
description: 'Use TailwindCSS to style your components'
|
|
230
|
+
}]
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should return undefined when user cancels', async () => {
|
|
235
|
+
prompts.default.mockResolvedValue({ stylingFramework: undefined });
|
|
236
|
+
const result = await promptStylingFramework();
|
|
237
|
+
expect(result).toBeUndefined();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
207
240
|
});
|