create-ripple 0.2.0 → 0.2.2
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 +21 -21
- package/README.md +50 -50
- package/dist/index.js +151 -0
- package/package.json +19 -16
- package/src/commands/create.js +0 -151
- package/src/constants.js +0 -17
- package/src/index.js +0 -52
- package/src/lib/project-creator.js +0 -316
- package/src/lib/prompts.js +0 -164
- package/src/lib/templates.js +0 -104
- package/src/lib/validation.js +0 -155
- package/tests/integration/cli.test.js +0 -179
- package/tests/integration/project-creator.test.js +0 -271
- package/tests/unit/prompts.test.js +0 -240
- package/tests/unit/templates.test.js +0 -160
- package/tests/unit/validation.test.js +0 -192
- package/vitest.config.js +0 -22
package/src/lib/prompts.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
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
|
-
}
|
|
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
|
-
}
|
package/src/lib/templates.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { dirname } from 'node:path';
|
|
6
|
-
import degit from 'degit';
|
|
7
|
-
import { GITHUB_REPO, GITHUB_TEMPLATES_DIRECTORY, TEMPLATES } from '../constants.js';
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get template by name
|
|
14
|
-
* @param {string} templateName - The template name
|
|
15
|
-
* @returns {object|null} - Template object or null if not found
|
|
16
|
-
*/
|
|
17
|
-
export function getTemplate(templateName) {
|
|
18
|
-
return TEMPLATES.find((template) => template.name === templateName) || null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Get all available template names
|
|
23
|
-
* @returns {string[]} - Array of template names
|
|
24
|
-
*/
|
|
25
|
-
export function getTemplateNames() {
|
|
26
|
-
return TEMPLATES.map((template) => template.name);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get template choices for prompts
|
|
31
|
-
* @returns {object[]} - Array of choice objects for prompts
|
|
32
|
-
*/
|
|
33
|
-
export function getTemplateChoices() {
|
|
34
|
-
return TEMPLATES.map((template) => ({
|
|
35
|
-
title: template.display,
|
|
36
|
-
description: template.description,
|
|
37
|
-
value: template.name
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Validate if template exists in our template list
|
|
43
|
-
* @param {string} templateName - The template name to validate
|
|
44
|
-
* @returns {boolean} - True if template exists in TEMPLATES list
|
|
45
|
-
*/
|
|
46
|
-
export function validateTemplate(templateName) {
|
|
47
|
-
if (!templateName) return false;
|
|
48
|
-
const template = getTemplate(templateName);
|
|
49
|
-
return template !== null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Download template from GitHub repository
|
|
54
|
-
* @param {string} templateName - The template name to download
|
|
55
|
-
* @returns {Promise<string>} - Path to downloaded template directory
|
|
56
|
-
*/
|
|
57
|
-
export async function downloadTemplate(templateName) {
|
|
58
|
-
if (!validateTemplate(templateName)) {
|
|
59
|
-
throw new Error(`Template "${templateName}" not found`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Create a temporary directory for the template
|
|
63
|
-
const tempDir = join(tmpdir(), `ripple-template-${templateName}-${Date.now()}`);
|
|
64
|
-
mkdirSync(tempDir, { recursive: true });
|
|
65
|
-
|
|
66
|
-
// Use degit to download the specific template from GitHub
|
|
67
|
-
const repoUrl = `${GITHUB_REPO}/${GITHUB_TEMPLATES_DIRECTORY}/${templateName}`;
|
|
68
|
-
const emitter = degit(repoUrl, {
|
|
69
|
-
cache: false,
|
|
70
|
-
force: true,
|
|
71
|
-
verbose: false
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
await emitter.clone(tempDir);
|
|
76
|
-
return tempDir;
|
|
77
|
-
} catch (error) {
|
|
78
|
-
throw new Error(`Failed to download template "${templateName}": ${error.message}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get template directory path (for local development)
|
|
84
|
-
* @param {string} templateName - The template name
|
|
85
|
-
* @returns {string} - Absolute path to template directory
|
|
86
|
-
*/
|
|
87
|
-
export function getLocalTemplatePath(templateName) {
|
|
88
|
-
// This is used for local development in the monorepo
|
|
89
|
-
// Navigate from packages/create-ripple/src/lib/ to templates/
|
|
90
|
-
const repoRoot = join(__dirname, '../../../../');
|
|
91
|
-
return join(repoRoot, 'templates', templateName);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Check if we're running in development mode (monorepo)
|
|
96
|
-
* @returns {boolean} - True if in development mode
|
|
97
|
-
*/
|
|
98
|
-
export function isLocalDevelopment() {
|
|
99
|
-
// Check if we're in the monorepo by looking for the templates directory
|
|
100
|
-
// Navigate from packages/create-ripple/src/lib/ to templates/
|
|
101
|
-
const repoRoot = join(__dirname, '../../../../');
|
|
102
|
-
const templatesDir = join(repoRoot, 'templates');
|
|
103
|
-
return existsSync(templatesDir);
|
|
104
|
-
}
|
package/src/lib/validation.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validation utilities for project creation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Validates a project name according to npm package naming rules
|
|
7
|
-
* @param {string} name - The project name to validate
|
|
8
|
-
* @returns {object} - Object with valid boolean and message string
|
|
9
|
-
*/
|
|
10
|
-
export function validateProjectName(name) {
|
|
11
|
-
if (typeof name !== 'string' || name === null || name === undefined) {
|
|
12
|
-
return {
|
|
13
|
-
valid: false,
|
|
14
|
-
message: 'Project name is required'
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
name = name.trim();
|
|
19
|
-
|
|
20
|
-
if (name.length === 0) {
|
|
21
|
-
return {
|
|
22
|
-
valid: false,
|
|
23
|
-
message: 'Project name cannot be empty'
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Check length (npm package names have a 214 character limit)
|
|
28
|
-
if (name.length > 214) {
|
|
29
|
-
return {
|
|
30
|
-
valid: false,
|
|
31
|
-
message: 'Project name must be less than 214 characters'
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Check for valid characters (npm allows lowercase letters, numbers, hyphens, and dots)
|
|
36
|
-
if (!/^[a-z0-9._-]+$/.test(name)) {
|
|
37
|
-
return {
|
|
38
|
-
valid: false,
|
|
39
|
-
message:
|
|
40
|
-
'Project name can only contain lowercase letters, numbers, hyphens, dots, and underscores'
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Cannot start with dot or underscore
|
|
45
|
-
if (name.startsWith('.') || name.startsWith('_')) {
|
|
46
|
-
return {
|
|
47
|
-
valid: false,
|
|
48
|
-
message: 'Project name cannot start with a dot or underscore'
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Cannot end with dot
|
|
53
|
-
if (name.endsWith('.')) {
|
|
54
|
-
return {
|
|
55
|
-
valid: false,
|
|
56
|
-
message: 'Project name cannot end with a dot'
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Cannot contain consecutive dots
|
|
61
|
-
if (name.includes('..')) {
|
|
62
|
-
return {
|
|
63
|
-
valid: false,
|
|
64
|
-
message: 'Project name cannot contain consecutive dots'
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Reserved names
|
|
69
|
-
const reservedNames = [
|
|
70
|
-
'node_modules',
|
|
71
|
-
'favicon.ico',
|
|
72
|
-
'con',
|
|
73
|
-
'prn',
|
|
74
|
-
'aux',
|
|
75
|
-
'nul',
|
|
76
|
-
'com1',
|
|
77
|
-
'com2',
|
|
78
|
-
'com3',
|
|
79
|
-
'com4',
|
|
80
|
-
'com5',
|
|
81
|
-
'com6',
|
|
82
|
-
'com7',
|
|
83
|
-
'com8',
|
|
84
|
-
'com9',
|
|
85
|
-
'lpt1',
|
|
86
|
-
'lpt2',
|
|
87
|
-
'lpt3',
|
|
88
|
-
'lpt4',
|
|
89
|
-
'lpt5',
|
|
90
|
-
'lpt6',
|
|
91
|
-
'lpt7',
|
|
92
|
-
'lpt8',
|
|
93
|
-
'lpt9'
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
if (reservedNames.includes(name.toLowerCase())) {
|
|
97
|
-
return {
|
|
98
|
-
valid: false,
|
|
99
|
-
message: `"${name}" is a reserved name and cannot be used`
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
valid: true,
|
|
105
|
-
message: ''
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Converts a project name to a valid directory name
|
|
111
|
-
* @param {string} name - The project name
|
|
112
|
-
* @returns {string} - A valid directory name
|
|
113
|
-
*/
|
|
114
|
-
export function sanitizeDirectoryName(name) {
|
|
115
|
-
return name
|
|
116
|
-
.toLowerCase()
|
|
117
|
-
.replace(/[^a-z0-9-]/g, '-')
|
|
118
|
-
.replace(/^-+|-+$/g, '')
|
|
119
|
-
.replace(/-+/g, '-');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Validates directory path and checks if it's writable
|
|
124
|
-
* @param {string} path - The directory path to validate
|
|
125
|
-
* @returns {object} - Object with valid boolean and message string
|
|
126
|
-
*/
|
|
127
|
-
export function validateDirectoryPath(path) {
|
|
128
|
-
if (!path || typeof path !== 'string') {
|
|
129
|
-
return {
|
|
130
|
-
valid: false,
|
|
131
|
-
message: 'Directory path is required'
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Check if path is absolute or relative
|
|
136
|
-
if (path.startsWith('/') && path.length < 2) {
|
|
137
|
-
return {
|
|
138
|
-
valid: false,
|
|
139
|
-
message: 'Cannot create project in root directory'
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Check for invalid characters in path
|
|
144
|
-
if (/[<>:"|?*]/.test(path)) {
|
|
145
|
-
return {
|
|
146
|
-
valid: false,
|
|
147
|
-
message: 'Directory path contains invalid characters'
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
valid: true,
|
|
153
|
-
message: ''
|
|
154
|
-
};
|
|
155
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { dirname } from 'node:path';
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
|
|
12
|
-
const CLI_PATH = join(__dirname, '../../src/index.js');
|
|
13
|
-
|
|
14
|
-
describe('CLI Integration Tests', () => {
|
|
15
|
-
let testDir;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
testDir = join(tmpdir(), `cli-test-${Date.now()}`);
|
|
19
|
-
mkdirSync(testDir, { recursive: true });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
if (existsSync(testDir)) {
|
|
24
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Helper function to run CLI commands
|
|
29
|
-
const runCLI = (args = [], input = '', timeout = 10000) => {
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
const child = spawn('node', [CLI_PATH, ...args], {
|
|
32
|
-
cwd: testDir,
|
|
33
|
-
stdio: 'pipe'
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
let stdout = '';
|
|
37
|
-
let stderr = '';
|
|
38
|
-
|
|
39
|
-
child.stdout.on('data', (data) => {
|
|
40
|
-
stdout += data.toString();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
child.stderr.on('data', (data) => {
|
|
44
|
-
stderr += data.toString();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
child.on('close', (code) => {
|
|
48
|
-
resolve({ code, stdout, stderr });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
child.on('error', reject);
|
|
52
|
-
|
|
53
|
-
if (input) {
|
|
54
|
-
child.stdin.write(input);
|
|
55
|
-
}
|
|
56
|
-
child.stdin.end();
|
|
57
|
-
|
|
58
|
-
setTimeout(() => {
|
|
59
|
-
child.kill();
|
|
60
|
-
reject(new Error('Command timed out'));
|
|
61
|
-
}, timeout);
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
it('should show help when --help flag is used', async () => {
|
|
66
|
-
const result = await runCLI(['--help']);
|
|
67
|
-
|
|
68
|
-
expect(result.code).toBe(0);
|
|
69
|
-
expect(result.stdout).toContain('Interactive CLI tool for creating Ripple applications');
|
|
70
|
-
expect(result.stdout).toContain('Usage: create-ripple');
|
|
71
|
-
expect(result.stdout).toContain('Arguments:');
|
|
72
|
-
expect(result.stdout).toContain('Options:');
|
|
73
|
-
expect(result.stdout).toContain('--template');
|
|
74
|
-
expect(result.stdout).toContain('--package-manager');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should show version when --version flag is used', async () => {
|
|
78
|
-
const result = await runCLI(['--version']);
|
|
79
|
-
|
|
80
|
-
expect(result.code).toBe(0);
|
|
81
|
-
expect(result.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should create project with all arguments provided', async () => {
|
|
85
|
-
const projectName = 'test-cli-project';
|
|
86
|
-
const result = await runCLI([
|
|
87
|
-
projectName,
|
|
88
|
-
'--template', 'basic',
|
|
89
|
-
'--package-manager', 'npm',
|
|
90
|
-
'--no-git',
|
|
91
|
-
'--yes'
|
|
92
|
-
]);
|
|
93
|
-
|
|
94
|
-
expect(result.code).toBe(0);
|
|
95
|
-
expect(result.stdout).toContain('Welcome to Create Ripple App');
|
|
96
|
-
expect(result.stdout).toContain('Creating Ripple app');
|
|
97
|
-
expect(result.stdout).toContain('Project created successfully');
|
|
98
|
-
expect(result.stdout).toContain('Next steps:');
|
|
99
|
-
expect(result.stdout).toContain(`cd ${projectName}`);
|
|
100
|
-
expect(result.stdout).toContain('npm install');
|
|
101
|
-
expect(result.stdout).toContain('npm run dev');
|
|
102
|
-
|
|
103
|
-
expect(existsSync(join(testDir, projectName))).toBe(true);
|
|
104
|
-
expect(existsSync(join(testDir, projectName, 'package.json'))).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should handle invalid template gracefully', async () => {
|
|
108
|
-
const result = await runCLI([
|
|
109
|
-
'test-project',
|
|
110
|
-
'--template', 'invalid-template',
|
|
111
|
-
'--yes'
|
|
112
|
-
]);
|
|
113
|
-
|
|
114
|
-
expect(result.code).toBe(1);
|
|
115
|
-
expect(result.stderr).toContain('Template "invalid-template" not found');
|
|
116
|
-
expect(result.stderr).toContain('Available templates:');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should handle invalid project name gracefully', async () => {
|
|
120
|
-
const result = await runCLI([
|
|
121
|
-
'Invalid Project Name!',
|
|
122
|
-
'--yes'
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
expect(result.code).toBe(1);
|
|
126
|
-
expect(result.stderr).toContain('Project name can only contain lowercase letters');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should show different package manager commands based on selection', async () => {
|
|
130
|
-
const projectName = 'test-pnpm-project';
|
|
131
|
-
const result = await runCLI([
|
|
132
|
-
projectName,
|
|
133
|
-
'--template', 'basic',
|
|
134
|
-
'--package-manager', 'pnpm',
|
|
135
|
-
'--yes'
|
|
136
|
-
]);
|
|
137
|
-
|
|
138
|
-
expect(result.code).toBe(0);
|
|
139
|
-
expect(result.stdout).toContain('pnpm install');
|
|
140
|
-
expect(result.stdout).toContain('pnpm dev');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should handle yarn package manager', async () => {
|
|
144
|
-
const projectName = 'test-yarn-project';
|
|
145
|
-
const result = await runCLI([
|
|
146
|
-
projectName,
|
|
147
|
-
'--template', 'basic',
|
|
148
|
-
'--package-manager', 'yarn',
|
|
149
|
-
'--yes'
|
|
150
|
-
]);
|
|
151
|
-
|
|
152
|
-
expect(result.code).toBe(0);
|
|
153
|
-
expect(result.stdout).toContain('yarn install');
|
|
154
|
-
expect(result.stdout).toContain('yarn dev');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should handle project directory that already exists (with --yes)', async () => {
|
|
158
|
-
const projectName = 'existing-project';
|
|
159
|
-
const projectPath = join(testDir, projectName);
|
|
160
|
-
|
|
161
|
-
mkdirSync(projectPath, { recursive: true });
|
|
162
|
-
writeFileSync(join(projectPath, 'existing-file.txt'), 'test');
|
|
163
|
-
|
|
164
|
-
const result = await runCLI([
|
|
165
|
-
projectName,
|
|
166
|
-
'--template', 'basic',
|
|
167
|
-
'--yes'
|
|
168
|
-
]);
|
|
169
|
-
|
|
170
|
-
expect(result.code).toBe(0);
|
|
171
|
-
expect(result.stdout).toContain('Project created successfully');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('should validate all required dependencies are available', async () => {
|
|
175
|
-
const result = await runCLI(['--help']);
|
|
176
|
-
expect(result.code).toBe(0);
|
|
177
|
-
expect(result.stderr).toBe('');
|
|
178
|
-
});
|
|
179
|
-
});
|