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 +21 -0
- package/README.md +50 -0
- package/package.json +51 -0
- package/src/commands/create.js +144 -0
- package/src/constants.js +20 -0
- package/src/index.js +52 -0
- package/src/lib/project-creator.js +206 -0
- package/src/lib/prompts.js +136 -0
- package/src/lib/templates.js +56 -0
- package/src/lib/validation.js +155 -0
- package/tests/integration/cli.test.js +179 -0
- package/tests/integration/project-creator.test.js +215 -0
- package/tests/unit/prompts.test.js +207 -0
- package/tests/unit/templates.test.js +163 -0
- package/tests/unit/validation.test.js +192 -0
- package/vitest.config.js +22 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { TEMPLATES, TEMPLATES_DIR } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get template by name
|
|
7
|
+
* @param {string} templateName - The template name
|
|
8
|
+
* @returns {object|null} - Template object or null if not found
|
|
9
|
+
*/
|
|
10
|
+
export function getTemplate(templateName) {
|
|
11
|
+
return TEMPLATES.find((template) => template.name === templateName) || null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get all available template names
|
|
16
|
+
* @returns {string[]} - Array of template names
|
|
17
|
+
*/
|
|
18
|
+
export function getTemplateNames() {
|
|
19
|
+
return TEMPLATES.map((template) => template.name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get template choices for prompts
|
|
24
|
+
* @returns {object[]} - Array of choice objects for prompts
|
|
25
|
+
*/
|
|
26
|
+
export function getTemplateChoices() {
|
|
27
|
+
return TEMPLATES.map((template) => ({
|
|
28
|
+
title: template.display,
|
|
29
|
+
description: template.description,
|
|
30
|
+
value: template.name
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate if template exists
|
|
36
|
+
* @param {string} templateName - The template name to validate
|
|
37
|
+
* @returns {boolean} - True if template exists
|
|
38
|
+
*/
|
|
39
|
+
export function validateTemplate(templateName) {
|
|
40
|
+
if (!templateName) return false;
|
|
41
|
+
|
|
42
|
+
const template = getTemplate(templateName);
|
|
43
|
+
if (!template) return false;
|
|
44
|
+
|
|
45
|
+
const templatePath = join(TEMPLATES_DIR, templateName);
|
|
46
|
+
return existsSync(templatePath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get template directory path
|
|
51
|
+
* @param {string} templateName - The template name
|
|
52
|
+
* @returns {string} - Absolute path to template directory
|
|
53
|
+
*/
|
|
54
|
+
export function getTemplatePath(templateName) {
|
|
55
|
+
return join(TEMPLATES_DIR, templateName);
|
|
56
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
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-app');
|
|
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
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { createProject } from '../../src/lib/project-creator.js';
|
|
6
|
+
|
|
7
|
+
// Mock ora for cleaner test output
|
|
8
|
+
vi.mock('ora', () => ({
|
|
9
|
+
default: () => ({
|
|
10
|
+
start: () => ({ succeed: vi.fn(), fail: vi.fn(), warn: vi.fn() }),
|
|
11
|
+
succeed: vi.fn(),
|
|
12
|
+
fail: vi.fn(),
|
|
13
|
+
warn: vi.fn()
|
|
14
|
+
})
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock execSync to prevent actual git commands during tests
|
|
18
|
+
vi.mock('node:child_process', () => ({
|
|
19
|
+
default: {
|
|
20
|
+
execSync: vi.fn()
|
|
21
|
+
},
|
|
22
|
+
execSync: vi.fn()
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('createProject integration tests', () => {
|
|
26
|
+
let testDir;
|
|
27
|
+
let projectPath;
|
|
28
|
+
let templatePath;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
// Create a temporary test directory
|
|
32
|
+
testDir = join(tmpdir(), `create-ripple-test-${Date.now()}`);
|
|
33
|
+
mkdirSync(testDir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
projectPath = join(testDir, 'test-project');
|
|
36
|
+
templatePath = join(testDir, 'template');
|
|
37
|
+
|
|
38
|
+
// Create a mock template directory structure
|
|
39
|
+
mkdirSync(templatePath, { recursive: true });
|
|
40
|
+
mkdirSync(join(templatePath, 'src'), { recursive: true });
|
|
41
|
+
|
|
42
|
+
// Create mock template files
|
|
43
|
+
writeFileSync(
|
|
44
|
+
join(templatePath, 'package.json'),
|
|
45
|
+
JSON.stringify({
|
|
46
|
+
name: 'vite-template-ripple',
|
|
47
|
+
version: '0.0.0',
|
|
48
|
+
type: 'module',
|
|
49
|
+
scripts: {
|
|
50
|
+
dev: 'vite',
|
|
51
|
+
build: 'vite build'
|
|
52
|
+
},
|
|
53
|
+
dependencies: {
|
|
54
|
+
ripple: '^0.2.29'
|
|
55
|
+
},
|
|
56
|
+
devDependencies: {
|
|
57
|
+
'vite-plugin-ripple': '^0.2.29',
|
|
58
|
+
prettier: '^3.6.2'
|
|
59
|
+
}
|
|
60
|
+
}, null, 2)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
writeFileSync(join(templatePath, 'index.html'), '<!DOCTYPE html><html></html>');
|
|
64
|
+
writeFileSync(join(templatePath, 'src', 'App.ripple'), '<h1>Hello Ripple!</h1>');
|
|
65
|
+
writeFileSync(join(templatePath, 'README.md'), '# Template Project');
|
|
66
|
+
|
|
67
|
+
// Mock the getTemplatePath function
|
|
68
|
+
vi.doMock('../../src/lib/templates.js', () => ({
|
|
69
|
+
getTemplatePath: () => templatePath
|
|
70
|
+
}));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
// Clean up test directory
|
|
75
|
+
if (existsSync(testDir)) {
|
|
76
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
vi.clearAllMocks();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should create a project successfully', async () => {
|
|
82
|
+
await createProject({
|
|
83
|
+
projectName: 'test-project',
|
|
84
|
+
projectPath,
|
|
85
|
+
template: 'basic',
|
|
86
|
+
packageManager: 'npm',
|
|
87
|
+
typescript: true,
|
|
88
|
+
gitInit: false
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Verify project directory was created
|
|
92
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
93
|
+
|
|
94
|
+
// Verify files were copied
|
|
95
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
96
|
+
expect(existsSync(join(projectPath, 'index.html'))).toBe(true);
|
|
97
|
+
expect(existsSync(join(projectPath, 'src', 'App.ripple'))).toBe(true);
|
|
98
|
+
expect(existsSync(join(projectPath, 'README.md'))).toBe(true);
|
|
99
|
+
|
|
100
|
+
// Verify package.json was updated
|
|
101
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
102
|
+
expect(packageJson.name).toBe('test-project');
|
|
103
|
+
expect(packageJson.description).toBe('A Ripple application created with create-ripple-app');
|
|
104
|
+
expect(packageJson.version).toBe('1.0.0');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should update package.json with correct package manager', async () => {
|
|
108
|
+
await createProject({
|
|
109
|
+
projectName: 'test-pnpm-project',
|
|
110
|
+
projectPath,
|
|
111
|
+
template: 'basic',
|
|
112
|
+
packageManager: 'pnpm',
|
|
113
|
+
typescript: true,
|
|
114
|
+
gitInit: false
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
118
|
+
expect(packageJson.packageManager).toBe('pnpm@9.0.0');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should not add packageManager field for npm', async () => {
|
|
122
|
+
await createProject({
|
|
123
|
+
projectName: 'test-npm-project',
|
|
124
|
+
projectPath,
|
|
125
|
+
template: 'basic',
|
|
126
|
+
packageManager: 'npm',
|
|
127
|
+
typescript: true,
|
|
128
|
+
gitInit: false
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
132
|
+
expect(packageJson.packageManager).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should update dependency versions', async () => {
|
|
136
|
+
await createProject({
|
|
137
|
+
projectName: 'test-deps-project',
|
|
138
|
+
projectPath,
|
|
139
|
+
template: 'basic',
|
|
140
|
+
packageManager: 'npm',
|
|
141
|
+
typescript: true,
|
|
142
|
+
gitInit: false
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
146
|
+
expect(packageJson.dependencies.ripple).toBe('^0.2.35');
|
|
147
|
+
expect(packageJson.devDependencies['vite-plugin-ripple']).toBe('^0.2.29');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should handle missing template directory', async () => {
|
|
151
|
+
const invalidTemplatePath = join(testDir, 'non-existent-template');
|
|
152
|
+
|
|
153
|
+
vi.doMock('../../src/lib/templates.js', () => ({
|
|
154
|
+
getTemplatePath: () => invalidTemplatePath
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
await expect(
|
|
158
|
+
createProject({
|
|
159
|
+
projectName: 'test-project',
|
|
160
|
+
projectPath,
|
|
161
|
+
template: 'non-existent',
|
|
162
|
+
packageManager: 'npm',
|
|
163
|
+
typescript: true,
|
|
164
|
+
gitInit: false
|
|
165
|
+
})
|
|
166
|
+
).rejects.toThrow('Template "non-existent" not found');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should filter out unwanted files during copy', async () => {
|
|
170
|
+
// Add files that should be filtered out
|
|
171
|
+
mkdirSync(join(templatePath, 'node_modules'), { recursive: true });
|
|
172
|
+
writeFileSync(join(templatePath, 'node_modules', 'some-package.js'), 'module content');
|
|
173
|
+
writeFileSync(join(templatePath, 'package-lock.json'), '{}');
|
|
174
|
+
writeFileSync(join(templatePath, 'yarn.lock'), 'yarn lock content');
|
|
175
|
+
writeFileSync(join(templatePath, 'pnpm-lock.yaml'), 'pnpm lock content');
|
|
176
|
+
|
|
177
|
+
await createProject({
|
|
178
|
+
projectName: 'test-filter-project',
|
|
179
|
+
projectPath,
|
|
180
|
+
template: 'basic',
|
|
181
|
+
packageManager: 'npm',
|
|
182
|
+
typescript: true,
|
|
183
|
+
gitInit: false
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Verify filtered files were not copied
|
|
187
|
+
expect(existsSync(join(projectPath, 'node_modules'))).toBe(false);
|
|
188
|
+
expect(existsSync(join(projectPath, 'package-lock.json'))).toBe(false);
|
|
189
|
+
expect(existsSync(join(projectPath, 'yarn.lock'))).toBe(false);
|
|
190
|
+
expect(existsSync(join(projectPath, 'pnpm-lock.yaml'))).toBe(false);
|
|
191
|
+
|
|
192
|
+
// Verify other files were copied
|
|
193
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
194
|
+
expect(existsSync(join(projectPath, 'index.html'))).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle project creation in existing directory', async () => {
|
|
198
|
+
// Create the directory first
|
|
199
|
+
mkdirSync(projectPath, { recursive: true });
|
|
200
|
+
writeFileSync(join(projectPath, 'existing-file.txt'), 'existing content');
|
|
201
|
+
|
|
202
|
+
await createProject({
|
|
203
|
+
projectName: 'test-existing-project',
|
|
204
|
+
projectPath,
|
|
205
|
+
template: 'basic',
|
|
206
|
+
packageManager: 'npm',
|
|
207
|
+
typescript: true,
|
|
208
|
+
gitInit: false
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Verify project was created successfully
|
|
212
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
213
|
+
expect(existsSync(join(projectPath, 'existing-file.txt'))).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
});
|