create-ripple 0.1.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ripple
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,50 @@
1
+ # create-ripple-app
2
+
3
+ Interactive CLI tool for creating new Ripple applications.
4
+
5
+ ## Usage
6
+
7
+ ### Interactive Mode
8
+
9
+ ```bash
10
+ npm create ripple-app
11
+ # or
12
+ npx create-ripple-app
13
+ # or
14
+ yarn create ripple-app
15
+ # or
16
+ pnpm create ripple-app
17
+ ```
18
+
19
+ ### With Arguments
20
+
21
+ ```bash
22
+ npm create ripple-app my-app
23
+ # or
24
+ npx create-ripple-app my-app basic
25
+ ```
26
+
27
+ ## Features
28
+
29
+ - 🎯 **Interactive prompts** - Guides you through project setup
30
+ - 📁 **Template selection** - Choose from predefined templates
31
+ - ✅ **Project validation** - Ensures valid project names
32
+ - 🎨 **Beautiful CLI** - Colored output with progress indicators
33
+ - ⚡ **Fast setup** - Quickly scaffold new Ripple projects
34
+
35
+ ## Templates
36
+
37
+ ### Basic
38
+ A minimal Ripple application with:
39
+ - Vite for development and building
40
+ - TypeScript support
41
+ - Prettier for code formatting
42
+ - Basic project structure
43
+
44
+ ## Requirements
45
+
46
+ - Node.js 18.0.0 or higher
47
+
48
+ ## License
49
+
50
+ MIT
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "create-ripple",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Interactive CLI tool for creating Ripple applications",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Ripple Team",
8
+ "keywords": [
9
+ "ripple",
10
+ "cli",
11
+ "scaffold",
12
+ "template",
13
+ "create-app"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/trueadm/ripple.git",
18
+ "directory": "packages/create-ripple"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/trueadm/ripple/issues"
22
+ },
23
+ "homepage": "https://ripplejs.com",
24
+ "bin": {
25
+ "create-ripple": "./src/index.js"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ },
30
+ "scripts": {
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "test:coverage": "vitest run --coverage",
34
+ "test:ui": "vitest --ui",
35
+ "format": "prettier --write .",
36
+ "format:check": "prettier --check ."
37
+ },
38
+ "dependencies": {
39
+ "commander": "^12.1.0",
40
+ "prompts": "^2.4.2",
41
+ "kleur": "^4.1.5",
42
+ "degit": "^2.8.4",
43
+ "ora": "^8.1.0"
44
+ },
45
+ "devDependencies": {
46
+ "prettier": "^3.6.2",
47
+ "vitest": "catalog:",
48
+ "jsdom": "catalog:",
49
+ "@types/prompts": "^2.4.9"
50
+ }
51
+ }
@@ -0,0 +1,144 @@
1
+ import { resolve } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { green, cyan, dim, red } from 'kleur/colors';
4
+ import { validateProjectName } from '../lib/validation.js';
5
+ import { validateTemplate, getTemplateNames } from '../lib/templates.js';
6
+ import {
7
+ promptProjectName,
8
+ promptTemplate,
9
+ promptOverwrite,
10
+ promptPackageManager,
11
+ promptGitInit
12
+ } from '../lib/prompts.js';
13
+ import { createProject } from '../lib/project-creator.js';
14
+
15
+ /**
16
+ * Create command handler
17
+ * @param {string} projectName - Project name (optional)
18
+ * @param {object} options - Command options
19
+ */
20
+ export async function createCommand(projectName, options) {
21
+ console.log();
22
+ console.log(cyan('🌊 Welcome to Create Ripple App!'));
23
+ console.log(dim("Let's create a new Ripple application"));
24
+ console.log();
25
+
26
+ // Step 1: Get or validate project name
27
+ if (!projectName) {
28
+ projectName = await promptProjectName();
29
+ } else {
30
+ const validation = validateProjectName(projectName);
31
+ if (!validation.valid) {
32
+ console.error(red(`✖ ${validation.message}`));
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ // Step 2: Get template
38
+ let template = options.template;
39
+ if (!template) {
40
+ template = await promptTemplate();
41
+ } else {
42
+ // Validate template
43
+ if (!validateTemplate(template)) {
44
+ console.error(red(`✖ Template "${template}" not found`));
45
+ console.error(`Available templates: ${getTemplateNames().join(', ')}`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+
50
+ // Step 3: Get package manager
51
+ let packageManager = options.packageManager || 'npm';
52
+ if (!options.packageManager && !options.yes) {
53
+ packageManager = await promptPackageManager();
54
+ }
55
+
56
+ // Step 4: Check directory and handle conflicts
57
+ const projectPath = resolve(process.cwd(), projectName);
58
+ if (existsSync(projectPath) && !options.yes) {
59
+ const shouldOverwrite = await promptOverwrite(projectName);
60
+ if (!shouldOverwrite) {
61
+ console.log(red('✖ Operation cancelled'));
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ // Step 5: Git initialization preference
67
+ let gitInit = true;
68
+ if (!options.git && !options.yes) {
69
+ gitInit = await promptGitInit();
70
+ } else if (options.git === false) {
71
+ gitInit = false;
72
+ }
73
+
74
+ // Step 6: Create the project
75
+ console.log();
76
+ console.log(`Creating Ripple app in ${green(projectPath)}...`);
77
+ console.log();
78
+
79
+ try {
80
+ await createProject({
81
+ projectName,
82
+ projectPath,
83
+ template,
84
+ packageManager,
85
+ typescript: true,
86
+ gitInit
87
+ });
88
+
89
+ showNextSteps(projectName, packageManager);
90
+ } catch (error) {
91
+ console.error(red('✖ Failed to create project:'));
92
+ console.error(error.message);
93
+ process.exit(1);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Show next steps to the user
99
+ * @param {string} projectName - The created project name
100
+ * @param {string} packageManager - Package manager used
101
+ */
102
+ function showNextSteps(projectName, packageManager) {
103
+ const installCommand = getInstallCommand(packageManager);
104
+ const devCommand = getDevCommand(packageManager);
105
+
106
+ console.log();
107
+ console.log(green('🎉 Success! Your Ripple app is ready to go.'));
108
+ console.log();
109
+ console.log('Next steps:');
110
+ console.log(` ${dim('cd')} ${projectName}`);
111
+ console.log(` ${dim(installCommand)}`);
112
+ console.log(` ${dim(devCommand)}`);
113
+ console.log();
114
+ console.log('Happy coding! 🌊');
115
+ console.log();
116
+ }
117
+
118
+ /**
119
+ * Get install command for package manager
120
+ * @param {string} packageManager - Package manager name
121
+ * @returns {string} - Install command
122
+ */
123
+ function getInstallCommand(packageManager) {
124
+ const commands = {
125
+ npm: 'npm install',
126
+ yarn: 'yarn install',
127
+ pnpm: 'pnpm install'
128
+ };
129
+ return commands[packageManager] || 'npm install';
130
+ }
131
+
132
+ /**
133
+ * Get dev command for package manager
134
+ * @param {string} packageManager - Package manager name
135
+ * @returns {string} - Dev command
136
+ */
137
+ function getDevCommand(packageManager) {
138
+ const commands = {
139
+ npm: 'npm run dev',
140
+ yarn: 'yarn dev',
141
+ pnpm: 'pnpm dev'
142
+ };
143
+ return commands[packageManager] || 'npm run dev';
144
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Available templates configuration
3
+ */
4
+ export const TEMPLATES = [
5
+ {
6
+ name: 'basic',
7
+ display: 'Basic Ripple App',
8
+ description: 'A minimal Ripple application with Vite and TypeScript'
9
+ }
10
+ ];
11
+
12
+ /**
13
+ * GitHub repository configuration
14
+ */
15
+ export const GITHUB_REPO = 'trueadm/ripple';
16
+ export const GITHUB_BRANCH = 'main'; // or whatever the default branch is
17
+ export const GITHUB_TEMPLATES_DIRECTORY = 'templates';
package/src/index.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { readFileSync } from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname, join } from 'node:path';
7
+ import { red } from 'kleur/colors';
8
+ import { createCommand } from './commands/create.js';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
14
+ const program = new Command();
15
+
16
+ program
17
+ .name('create-ripple-app')
18
+ .description('Interactive CLI tool for creating Ripple applications')
19
+ .version(packageJson.version)
20
+ .helpOption('-h, --help', 'Display help for command');
21
+
22
+ program
23
+ .argument('[project-name]', 'Name of the project to create')
24
+ .option('-t, --template <template>', 'Template to use (default: basic)')
25
+ .option('-p, --package-manager <pm>', 'Package manager to use (npm, yarn, pnpm)', 'npm')
26
+ .option('--no-git', 'Skip Git repository initialization')
27
+ .option('-y, --yes', 'Skip all prompts and use defaults')
28
+ .action(async (projectName, options) => {
29
+ try {
30
+ await createCommand(projectName, options);
31
+ } catch (error) {
32
+ console.error(red('✖ Unexpected error:'));
33
+ console.error(error.message);
34
+ process.exit(1);
35
+ }
36
+ });
37
+
38
+ // Handle unhandled promise rejections
39
+ process.on('unhandledRejection', (err) => {
40
+ console.error(red('✖ Unhandled error:'));
41
+ console.error(err);
42
+ process.exit(1);
43
+ });
44
+
45
+ // Handle SIGINT (Ctrl+C)
46
+ process.on('SIGINT', () => {
47
+ console.log();
48
+ console.log(red('✖ Operation cancelled'));
49
+ process.exit(1);
50
+ });
51
+
52
+ program.parse();
@@ -0,0 +1,243 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { execSync } from 'node:child_process';
4
+ import { green, dim } from 'kleur/colors';
5
+ import ora from 'ora';
6
+
7
+ import { downloadTemplate, getLocalTemplatePath, isLocalDevelopment } from './templates.js';
8
+
9
+ /**
10
+ * Create a new Ripple project
11
+ * @param {object} options - Project creation options
12
+ * @param {string} options.projectName - Name of the project
13
+ * @param {string} options.projectPath - Absolute path where project will be created
14
+ * @param {string} options.template - Template to use
15
+ * @param {string} options.packageManager - Package manager to use
16
+ * @param {boolean} options.typescript - Whether to use TypeScript
17
+ * @param {boolean} options.gitInit - Whether to initialize Git
18
+ */
19
+ export async function createProject({
20
+ projectName,
21
+ projectPath,
22
+ template,
23
+ packageManager = 'npm',
24
+ typescript = true,
25
+ gitInit = true
26
+ }) {
27
+ console.log(dim(`Creating project: ${projectName}`));
28
+ console.log(dim(`Template: ${template}`));
29
+ console.log(dim(`Package manager: ${packageManager}`));
30
+ console.log();
31
+
32
+ let templatePath;
33
+ let isTemporary = false;
34
+
35
+ // Step 1: Get or download template
36
+ const spinner1 = ora('Preparing template...').start();
37
+ try {
38
+ if (isLocalDevelopment()) {
39
+ // Use local template for development
40
+ templatePath = getLocalTemplatePath(template);
41
+ if (!existsSync(templatePath)) {
42
+ throw new Error(`Local template "${template}" not found at ${templatePath}`);
43
+ }
44
+ spinner1.succeed('Local template located');
45
+ } else {
46
+ // Download template from GitHub
47
+ spinner1.text = 'Downloading template from GitHub...';
48
+ templatePath = await downloadTemplate(template);
49
+ isTemporary = true;
50
+ spinner1.succeed('Template downloaded');
51
+ }
52
+ } catch (error) {
53
+ spinner1.fail('Failed to prepare template');
54
+ throw error;
55
+ }
56
+
57
+ // Step 2: Create project directory
58
+ const spinner2 = ora('Creating project directory...').start();
59
+ try {
60
+ mkdirSync(projectPath, { recursive: true });
61
+ spinner2.succeed('Project directory created');
62
+ } catch (error) {
63
+ spinner2.fail('Failed to create project directory');
64
+ if (isTemporary) {
65
+ rmSync(templatePath, { recursive: true, force: true });
66
+ }
67
+ throw error;
68
+ }
69
+
70
+ // Step 3: Copy template files
71
+ const spinner3 = ora('Copying template files...').start();
72
+ try {
73
+ cpSync(templatePath, projectPath, {
74
+ recursive: true,
75
+ filter: (src) => {
76
+ // Skip node_modules and any lock files from template
77
+ const relativePath = src.replace(templatePath, '');
78
+ return (
79
+ !relativePath.includes('node_modules') &&
80
+ !relativePath.includes('package-lock.json') &&
81
+ !relativePath.includes('yarn.lock') &&
82
+ !relativePath.includes('pnpm-lock.yaml')
83
+ );
84
+ }
85
+ });
86
+ spinner3.succeed('Template files copied');
87
+ } catch (error) {
88
+ spinner3.fail('Failed to copy template files');
89
+ if (isTemporary) {
90
+ rmSync(templatePath, { recursive: true, force: true });
91
+ }
92
+ throw error;
93
+ }
94
+
95
+ // Step 4: Update package.json
96
+ const spinner4 = ora('Configuring package.json...').start();
97
+ try {
98
+ updatePackageJson(projectPath, projectName, packageManager, typescript);
99
+ spinner4.succeed('Package.json configured');
100
+ } catch (error) {
101
+ spinner4.fail('Failed to configure package.json');
102
+ if (isTemporary) {
103
+ rmSync(templatePath, { recursive: true, force: true });
104
+ }
105
+ throw error;
106
+ }
107
+
108
+ // Step 5: Initialize Git (if requested)
109
+ if (gitInit) {
110
+ const spinner5 = ora('Initializing Git repository...').start();
111
+ try {
112
+ execSync('git init', { cwd: projectPath, stdio: 'ignore' });
113
+ execSync('git add .', { cwd: projectPath, stdio: 'ignore' });
114
+ execSync('git commit -m "Initial commit"', { cwd: projectPath, stdio: 'ignore' });
115
+ spinner5.succeed('Git repository initialized');
116
+ } catch (error) {
117
+ spinner5.warn('Git initialization failed (optional)');
118
+ }
119
+ }
120
+
121
+ // Clean up temporary template directory
122
+ if (isTemporary) {
123
+ try {
124
+ rmSync(templatePath, { recursive: true, force: true });
125
+ } catch (error) {
126
+ // Ignore cleanup errors
127
+ }
128
+ }
129
+
130
+ console.log();
131
+ console.log(green('✓ Project created successfully!'));
132
+ }
133
+
134
+ /**
135
+ * Update package.json with project-specific configurations
136
+ * @param {string} projectPath - Path to the project
137
+ * @param {string} projectName - Name of the project
138
+ * @param {string} packageManager - Package manager being used
139
+ * @param {boolean} typescript - Whether TypeScript is enabled
140
+ */
141
+ function updatePackageJson(projectPath, projectName, packageManager, typescript) {
142
+ const packageJsonPath = join(projectPath, 'package.json');
143
+
144
+ if (!existsSync(packageJsonPath)) {
145
+ throw new Error('package.json not found in template');
146
+ }
147
+
148
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
149
+
150
+ // Update package name
151
+ packageJson.name = projectName;
152
+
153
+ // Remove version if it exists (since this is a new project)
154
+ if (packageJson.version === '0.0.0') {
155
+ packageJson.version = '1.0.0';
156
+ }
157
+
158
+ // Update description
159
+ packageJson.description = `A Ripple application created with create-ripple-app`;
160
+
161
+ // Add package manager field if not npm
162
+ if (packageManager !== 'npm') {
163
+ packageJson.packageManager = getPackageManagerVersion(packageManager);
164
+ }
165
+
166
+ // Ensure we're using the latest versions
167
+ updateDependencyVersions(packageJson);
168
+
169
+ // Update scripts based on package manager
170
+ updateScripts(packageJson, packageManager);
171
+
172
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
173
+ }
174
+
175
+ /**
176
+ * Update dependency versions to latest
177
+ * @param {object} packageJson - Package.json object
178
+ */
179
+ function updateDependencyVersions(packageJson) {
180
+ // Use the latest versions for Ripple packages
181
+ const latestVersions = {
182
+ ripple: '^0.2.35',
183
+ 'vite-plugin-ripple': '^0.2.29',
184
+ 'prettier-plugin-ripple': '^0.2.29'
185
+ };
186
+
187
+ // Update dependencies
188
+ if (packageJson.dependencies) {
189
+ for (const [pkg, version] of Object.entries(latestVersions)) {
190
+ if (packageJson.dependencies[pkg]) {
191
+ packageJson.dependencies[pkg] = version;
192
+ }
193
+ }
194
+ }
195
+
196
+ // Update devDependencies
197
+ if (packageJson.devDependencies) {
198
+ for (const [pkg, version] of Object.entries(latestVersions)) {
199
+ if (packageJson.devDependencies[pkg]) {
200
+ packageJson.devDependencies[pkg] = version;
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Update scripts based on package manager
208
+ * @param {object} packageJson - Package.json object
209
+ * @param {string} packageManager - Package manager being used
210
+ */
211
+ function updateScripts(packageJson, packageManager) {
212
+ if (!packageJson.scripts) return;
213
+
214
+ // Add package manager specific scripts
215
+ const pmCommands = {
216
+ npm: 'npm run',
217
+ yarn: 'yarn',
218
+ pnpm: 'pnpm'
219
+ };
220
+
221
+ const pm = pmCommands[packageManager] || 'npm run';
222
+
223
+ // Update format scripts to use the correct package manager
224
+ if (packageJson.scripts.format) {
225
+ packageJson.scripts.format = 'prettier --write .';
226
+ }
227
+ if (packageJson.scripts['format:check']) {
228
+ packageJson.scripts['format:check'] = 'prettier --check .';
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Get package manager version string
234
+ * @param {string} packageManager - Package manager name
235
+ * @returns {string} - Package manager with version
236
+ */
237
+ function getPackageManagerVersion(packageManager) {
238
+ const versions = {
239
+ yarn: 'yarn@4.0.0',
240
+ pnpm: 'pnpm@9.0.0'
241
+ };
242
+ return versions[packageManager] || packageManager;
243
+ }