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 +21 -0
- package/README.md +50 -0
- package/package.json +51 -0
- package/src/commands/create.js +144 -0
- package/src/constants.js +17 -0
- package/src/index.js +52 -0
- package/src/lib/project-creator.js +243 -0
- package/src/lib/prompts.js +136 -0
- package/src/lib/templates.js +97 -0
- package/src/lib/validation.js +155 -0
- package/tests/integration/cli.test.js +179 -0
- package/tests/integration/project-creator.test.js +230 -0
- package/tests/unit/prompts.test.js +207 -0
- package/tests/unit/templates.test.js +160 -0
- package/tests/unit/validation.test.js +192 -0
- package/vitest.config.js +22 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateProjectName, sanitizeDirectoryName, validateDirectoryPath } from '../../src/lib/validation.js';
|
|
3
|
+
|
|
4
|
+
describe('validateProjectName', () => {
|
|
5
|
+
it('should validate correct project names', () => {
|
|
6
|
+
const validNames = [
|
|
7
|
+
'my-app',
|
|
8
|
+
'my.app',
|
|
9
|
+
'my_app',
|
|
10
|
+
'myapp',
|
|
11
|
+
'my-awesome-app',
|
|
12
|
+
'app123',
|
|
13
|
+
'a',
|
|
14
|
+
'a'.repeat(214) // max length
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
validNames.forEach(name => {
|
|
18
|
+
const result = validateProjectName(name);
|
|
19
|
+
expect(result.valid).toBe(true);
|
|
20
|
+
expect(result.message).toBe('');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should reject invalid project names', () => {
|
|
25
|
+
const invalidCases = [
|
|
26
|
+
{ name: '', expectedMessage: 'Project name cannot be empty' },
|
|
27
|
+
{ name: ' ', expectedMessage: 'Project name cannot be empty' },
|
|
28
|
+
{ name: null, expectedMessage: 'Project name is required' },
|
|
29
|
+
{ name: undefined, expectedMessage: 'Project name is required' },
|
|
30
|
+
{ name: 123, expectedMessage: 'Project name is required' },
|
|
31
|
+
{ name: 'a'.repeat(215), expectedMessage: 'Project name must be less than 214 characters' }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
invalidCases.forEach(({ name, expectedMessage }) => {
|
|
35
|
+
const result = validateProjectName(name);
|
|
36
|
+
expect(result.valid).toBe(false);
|
|
37
|
+
expect(result.message).toBe(expectedMessage);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should reject names with invalid characters', () => {
|
|
42
|
+
const invalidNames = [
|
|
43
|
+
'My-App', // uppercase
|
|
44
|
+
'my app', // space
|
|
45
|
+
'my@app', // special character
|
|
46
|
+
'my/app', // slash
|
|
47
|
+
'my\\app', // backslash
|
|
48
|
+
'my:app', // colon
|
|
49
|
+
'my*app', // asterisk
|
|
50
|
+
'my?app', // question mark
|
|
51
|
+
'my"app', // quote
|
|
52
|
+
'my<app', // less than
|
|
53
|
+
'my>app' // greater than
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
invalidNames.forEach(name => {
|
|
57
|
+
const result = validateProjectName(name);
|
|
58
|
+
expect(result.valid).toBe(false);
|
|
59
|
+
expect(result.message).toBe(
|
|
60
|
+
'Project name can only contain lowercase letters, numbers, hyphens, dots, and underscores'
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should reject names starting with dot or underscore', () => {
|
|
66
|
+
const invalidNames = ['.my-app', '_my-app'];
|
|
67
|
+
|
|
68
|
+
invalidNames.forEach(name => {
|
|
69
|
+
const result = validateProjectName(name);
|
|
70
|
+
expect(result.valid).toBe(false);
|
|
71
|
+
expect(result.message).toBe('Project name cannot start with a dot or underscore');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should reject names ending with dot', () => {
|
|
76
|
+
const result = validateProjectName('my-app.');
|
|
77
|
+
expect(result.valid).toBe(false);
|
|
78
|
+
expect(result.message).toBe('Project name cannot end with a dot');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should reject names with consecutive dots', () => {
|
|
82
|
+
const result = validateProjectName('my..app');
|
|
83
|
+
expect(result.valid).toBe(false);
|
|
84
|
+
expect(result.message).toBe('Project name cannot contain consecutive dots');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should reject reserved names', () => {
|
|
88
|
+
const reservedNames = [
|
|
89
|
+
'node_modules',
|
|
90
|
+
'favicon.ico',
|
|
91
|
+
'con',
|
|
92
|
+
'prn',
|
|
93
|
+
'aux',
|
|
94
|
+
'nul',
|
|
95
|
+
'com1',
|
|
96
|
+
'com2',
|
|
97
|
+
'lpt1',
|
|
98
|
+
'lpt9'
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
reservedNames.forEach(name => {
|
|
102
|
+
const result = validateProjectName(name);
|
|
103
|
+
expect(result.valid).toBe(false);
|
|
104
|
+
expect(result.message).toBe(`"${name}" is a reserved name and cannot be used`);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle case insensitive reserved names', () => {
|
|
109
|
+
const result = validateProjectName('con'); // use lowercase since validation requires lowercase
|
|
110
|
+
expect(result.valid).toBe(false);
|
|
111
|
+
expect(result.message).toBe('"con" is a reserved name and cannot be used');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('sanitizeDirectoryName', () => {
|
|
116
|
+
it('should sanitize directory names correctly', () => {
|
|
117
|
+
const testCases = [
|
|
118
|
+
{ input: 'My App', expected: 'my-app' },
|
|
119
|
+
{ input: 'my@app#name', expected: 'my-app-name' },
|
|
120
|
+
{ input: '---my-app---', expected: 'my-app' },
|
|
121
|
+
{ input: 'my___app', expected: 'my-app' },
|
|
122
|
+
{ input: 'MY-APP', expected: 'my-app' },
|
|
123
|
+
{ input: 'app123!@#', expected: 'app123' },
|
|
124
|
+
{ input: ' spaces ', expected: 'spaces' },
|
|
125
|
+
{ input: 'special$%^chars', expected: 'special-chars' }
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
testCases.forEach(({ input, expected }) => {
|
|
129
|
+
const result = sanitizeDirectoryName(input);
|
|
130
|
+
expect(result).toBe(expected);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle edge cases', () => {
|
|
135
|
+
expect(sanitizeDirectoryName('')).toBe('');
|
|
136
|
+
expect(sanitizeDirectoryName('---')).toBe('');
|
|
137
|
+
expect(sanitizeDirectoryName('123')).toBe('123');
|
|
138
|
+
expect(sanitizeDirectoryName('a')).toBe('a');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('validateDirectoryPath', () => {
|
|
143
|
+
it('should validate correct directory paths', () => {
|
|
144
|
+
const validPaths = [
|
|
145
|
+
'my-app',
|
|
146
|
+
'./my-app',
|
|
147
|
+
'../my-app',
|
|
148
|
+
'path/to/my-app',
|
|
149
|
+
'/home/user/projects/my-app'
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
validPaths.forEach(path => {
|
|
153
|
+
const result = validateDirectoryPath(path);
|
|
154
|
+
expect(result.valid).toBe(true);
|
|
155
|
+
expect(result.message).toBe('');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should reject invalid directory paths', () => {
|
|
160
|
+
const invalidCases = [
|
|
161
|
+
{ path: '', expectedMessage: 'Directory path is required' },
|
|
162
|
+
{ path: null, expectedMessage: 'Directory path is required' },
|
|
163
|
+
{ path: undefined, expectedMessage: 'Directory path is required' },
|
|
164
|
+
{ path: 123, expectedMessage: 'Directory path is required' },
|
|
165
|
+
{ path: '/', expectedMessage: 'Cannot create project in root directory' }
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
invalidCases.forEach(({ path, expectedMessage }) => {
|
|
169
|
+
const result = validateDirectoryPath(path);
|
|
170
|
+
expect(result.valid).toBe(false);
|
|
171
|
+
expect(result.message).toBe(expectedMessage);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should reject paths with invalid characters', () => {
|
|
176
|
+
const invalidPaths = [
|
|
177
|
+
'my<app',
|
|
178
|
+
'my>app',
|
|
179
|
+
'my:app',
|
|
180
|
+
'my"app',
|
|
181
|
+
'my|app',
|
|
182
|
+
'my?app',
|
|
183
|
+
'my*app'
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
invalidPaths.forEach(path => {
|
|
187
|
+
const result = validateDirectoryPath(path);
|
|
188
|
+
expect(result.valid).toBe(false);
|
|
189
|
+
expect(result.message).toBe('Directory path contains invalid characters');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
package/vitest.config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
include: ['tests/**/*.test.js'],
|
|
6
|
+
environment: 'node',
|
|
7
|
+
globals: true,
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'json', 'html'],
|
|
11
|
+
exclude: [
|
|
12
|
+
'node_modules/',
|
|
13
|
+
'tests/',
|
|
14
|
+
'coverage/',
|
|
15
|
+
'**/*.test.js',
|
|
16
|
+
'**/*.config.js'
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
testTimeout: 60000, // 60 seconds for integration tests
|
|
20
|
+
hookTimeout: 10000 // 10 seconds for setup/teardown
|
|
21
|
+
}
|
|
22
|
+
});
|