create-ripple 0.1.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/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",
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,20 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { dirname, join, resolve } from 'node:path';
3
+
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+
7
+ /**
8
+ * Available templates configuration
9
+ */
10
+ export const TEMPLATES = [
11
+ {
12
+ name: 'basic',
13
+ display: 'Basic Ripple App',
14
+ description: 'A minimal Ripple application with Vite and TypeScript'
15
+ }
16
+ ];
17
+
18
+ // Get the root directory of the monorepo
19
+ export const REPO_ROOT = resolve(__dirname, '../../../');
20
+ export const TEMPLATES_DIR = join(REPO_ROOT, '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,206 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync } 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 { getTemplatePath } 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
+ const templatePath = getTemplatePath(template);
33
+
34
+ if (!existsSync(templatePath)) {
35
+ throw new Error(`Template "${template}" not found at ${templatePath}`);
36
+ }
37
+
38
+ // Step 1: Create project directory
39
+ const spinner1 = ora('Creating project directory...').start();
40
+ try {
41
+ mkdirSync(projectPath, { recursive: true });
42
+ spinner1.succeed('Project directory created');
43
+ } catch (error) {
44
+ spinner1.fail('Failed to create project directory');
45
+ throw error;
46
+ }
47
+
48
+ // Step 2: Copy template files
49
+ const spinner2 = ora('Copying template files...').start();
50
+ try {
51
+ cpSync(templatePath, projectPath, {
52
+ recursive: true,
53
+ filter: (src) => {
54
+ // Skip node_modules and any lock files from template
55
+ const relativePath = src.replace(templatePath, '');
56
+ return (
57
+ !relativePath.includes('node_modules') &&
58
+ !relativePath.includes('package-lock.json') &&
59
+ !relativePath.includes('yarn.lock') &&
60
+ !relativePath.includes('pnpm-lock.yaml')
61
+ );
62
+ }
63
+ });
64
+ spinner2.succeed('Template files copied');
65
+ } catch (error) {
66
+ spinner2.fail('Failed to copy template files');
67
+ throw error;
68
+ }
69
+
70
+ // Step 3: Update package.json
71
+ const spinner3 = ora('Configuring package.json...').start();
72
+ try {
73
+ updatePackageJson(projectPath, projectName, packageManager, typescript);
74
+ spinner3.succeed('Package.json configured');
75
+ } catch (error) {
76
+ spinner3.fail('Failed to configure package.json');
77
+ throw error;
78
+ }
79
+
80
+ // Step 4: Initialize Git (if requested)
81
+ if (gitInit) {
82
+ const spinner4 = ora('Initializing Git repository...').start();
83
+ try {
84
+ execSync('git init', { cwd: projectPath, stdio: 'ignore' });
85
+ execSync('git add .', { cwd: projectPath, stdio: 'ignore' });
86
+ execSync('git commit -m "Initial commit"', { cwd: projectPath, stdio: 'ignore' });
87
+ spinner4.succeed('Git repository initialized');
88
+ } catch (error) {
89
+ spinner4.warn('Git initialization failed (optional)');
90
+ }
91
+ }
92
+
93
+ console.log();
94
+ console.log(green('✓ Project created successfully!'));
95
+ }
96
+
97
+ /**
98
+ * Update package.json with project-specific configurations
99
+ * @param {string} projectPath - Path to the project
100
+ * @param {string} projectName - Name of the project
101
+ * @param {string} packageManager - Package manager being used
102
+ * @param {boolean} typescript - Whether TypeScript is enabled
103
+ */
104
+ function updatePackageJson(projectPath, projectName, packageManager, typescript) {
105
+ const packageJsonPath = join(projectPath, 'package.json');
106
+
107
+ if (!existsSync(packageJsonPath)) {
108
+ throw new Error('package.json not found in template');
109
+ }
110
+
111
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
112
+
113
+ // Update package name
114
+ packageJson.name = projectName;
115
+
116
+ // Remove version if it exists (since this is a new project)
117
+ if (packageJson.version === '0.0.0') {
118
+ packageJson.version = '1.0.0';
119
+ }
120
+
121
+ // Update description
122
+ packageJson.description = `A Ripple application created with create-ripple-app`;
123
+
124
+ // Add package manager field if not npm
125
+ if (packageManager !== 'npm') {
126
+ packageJson.packageManager = getPackageManagerVersion(packageManager);
127
+ }
128
+
129
+ // Ensure we're using the latest versions
130
+ updateDependencyVersions(packageJson);
131
+
132
+ // Update scripts based on package manager
133
+ updateScripts(packageJson, packageManager);
134
+
135
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
136
+ }
137
+
138
+ /**
139
+ * Update dependency versions to latest
140
+ * @param {object} packageJson - Package.json object
141
+ */
142
+ function updateDependencyVersions(packageJson) {
143
+ // Use the latest versions for Ripple packages
144
+ const latestVersions = {
145
+ ripple: '^0.2.35',
146
+ 'vite-plugin-ripple': '^0.2.29',
147
+ 'prettier-plugin-ripple': '^0.2.29'
148
+ };
149
+
150
+ // Update dependencies
151
+ if (packageJson.dependencies) {
152
+ for (const [pkg, version] of Object.entries(latestVersions)) {
153
+ if (packageJson.dependencies[pkg]) {
154
+ packageJson.dependencies[pkg] = version;
155
+ }
156
+ }
157
+ }
158
+
159
+ // Update devDependencies
160
+ if (packageJson.devDependencies) {
161
+ for (const [pkg, version] of Object.entries(latestVersions)) {
162
+ if (packageJson.devDependencies[pkg]) {
163
+ packageJson.devDependencies[pkg] = version;
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Update scripts based on package manager
171
+ * @param {object} packageJson - Package.json object
172
+ * @param {string} packageManager - Package manager being used
173
+ */
174
+ function updateScripts(packageJson, packageManager) {
175
+ if (!packageJson.scripts) return;
176
+
177
+ // Add package manager specific scripts
178
+ const pmCommands = {
179
+ npm: 'npm run',
180
+ yarn: 'yarn',
181
+ pnpm: 'pnpm'
182
+ };
183
+
184
+ const pm = pmCommands[packageManager] || 'npm run';
185
+
186
+ // Update format scripts to use the correct package manager
187
+ if (packageJson.scripts.format) {
188
+ packageJson.scripts.format = 'prettier --write .';
189
+ }
190
+ if (packageJson.scripts['format:check']) {
191
+ packageJson.scripts['format:check'] = 'prettier --check .';
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Get package manager version string
197
+ * @param {string} packageManager - Package manager name
198
+ * @returns {string} - Package manager with version
199
+ */
200
+ function getPackageManagerVersion(packageManager) {
201
+ const versions = {
202
+ yarn: 'yarn@4.0.0',
203
+ pnpm: 'pnpm@9.0.0'
204
+ };
205
+ return versions[packageManager] || packageManager;
206
+ }
@@ -0,0 +1,136 @@
1
+ import prompts from 'prompts';
2
+ import { validateProjectName } from './validation.js';
3
+ import { getTemplateChoices } from './templates.js';
4
+ import { red } from 'kleur/colors';
5
+
6
+ /**
7
+ * Prompt for project name
8
+ * @param {string} defaultName - Default project name
9
+ * @returns {Promise<string>} - Project name
10
+ */
11
+ export async function promptProjectName(defaultName = 'my-ripple-app') {
12
+ const response = await prompts({
13
+ type: 'text',
14
+ name: 'projectName',
15
+ message: 'What is your project named?',
16
+ initial: defaultName,
17
+ validate: (value) => {
18
+ const validation = validateProjectName(value);
19
+ return validation.valid || validation.message;
20
+ }
21
+ });
22
+
23
+ if (!response.projectName) {
24
+ console.log(red('✖ Operation cancelled'));
25
+ process.exit(1);
26
+ }
27
+
28
+ return response.projectName;
29
+ }
30
+
31
+ /**
32
+ * Prompt for template selection
33
+ * @returns {Promise<string>} - Selected template name
34
+ */
35
+ export async function promptTemplate() {
36
+ const response = await prompts({
37
+ type: 'select',
38
+ name: 'template',
39
+ message: 'Which template would you like to use?',
40
+ choices: getTemplateChoices(),
41
+ initial: 0
42
+ });
43
+
44
+ if (!response.template) {
45
+ console.log(red('✖ Operation cancelled'));
46
+ process.exit(1);
47
+ }
48
+
49
+ return response.template;
50
+ }
51
+
52
+ /**
53
+ * Prompt for directory overwrite confirmation
54
+ * @param {string} projectName - The project name
55
+ * @returns {Promise<boolean>} - Whether to overwrite
56
+ */
57
+ export async function promptOverwrite(projectName) {
58
+ const response = await prompts({
59
+ type: 'confirm',
60
+ name: 'overwrite',
61
+ message: `Directory "${projectName}" already exists. Continue anyway?`,
62
+ initial: false
63
+ });
64
+
65
+ if (response.overwrite === undefined) {
66
+ console.log(red('✖ Operation cancelled'));
67
+ process.exit(1);
68
+ }
69
+
70
+ return response.overwrite;
71
+ }
72
+
73
+ /**
74
+ * Prompt for package manager selection
75
+ * @returns {Promise<string>} - Selected package manager
76
+ */
77
+ export async function promptPackageManager() {
78
+ const response = await prompts({
79
+ type: 'select',
80
+ name: 'packageManager',
81
+ message: 'Which package manager would you like to use?',
82
+ choices: [
83
+ { title: 'npm', value: 'npm', description: 'Use npm for dependency management' },
84
+ { title: 'yarn', value: 'yarn', description: 'Use Yarn for dependency management' },
85
+ { title: 'pnpm', value: 'pnpm', description: 'Use pnpm for dependency management' }
86
+ ],
87
+ initial: 0
88
+ });
89
+
90
+ if (!response.packageManager) {
91
+ console.log(red('✖ Operation cancelled'));
92
+ process.exit(1);
93
+ }
94
+
95
+ return response.packageManager;
96
+ }
97
+
98
+ /**
99
+ * Prompt for TypeScript usage
100
+ * @returns {Promise<boolean>} - Whether to use TypeScript
101
+ */
102
+ export async function promptTypeScript() {
103
+ const response = await prompts({
104
+ type: 'confirm',
105
+ name: 'typescript',
106
+ message: 'Would you like to use TypeScript?',
107
+ initial: true
108
+ });
109
+
110
+ if (response.typescript === undefined) {
111
+ console.log(red('✖ Operation cancelled'));
112
+ process.exit(1);
113
+ }
114
+
115
+ return response.typescript;
116
+ }
117
+
118
+ /**
119
+ * Prompt for Git initialization
120
+ * @returns {Promise<boolean>} - Whether to initialize Git
121
+ */
122
+ export async function promptGitInit() {
123
+ const response = await prompts({
124
+ type: 'confirm',
125
+ name: 'gitInit',
126
+ message: 'Initialize a new Git repository?',
127
+ initial: true
128
+ });
129
+
130
+ if (response.gitInit === undefined) {
131
+ console.log(red('✖ Operation cancelled'));
132
+ process.exit(1);
133
+ }
134
+
135
+ return response.gitInit;
136
+ }