create-ripple 0.1.0 → 0.1.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/package.json +1 -1
- package/src/constants.js +6 -9
- package/src/lib/project-creator.js +58 -21
- package/src/lib/templates.js +52 -11
- package/tests/integration/project-creator.test.js +24 -9
- package/tests/unit/templates.test.js +32 -35
- package/vitest.config.js +1 -1
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
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
1
|
/**
|
|
8
2
|
* Available templates configuration
|
|
9
3
|
*/
|
|
@@ -15,6 +9,9 @@ export const TEMPLATES = [
|
|
|
15
9
|
}
|
|
16
10
|
];
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
4
|
import { green, dim } from 'kleur/colors';
|
|
5
5
|
import ora from 'ora';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { downloadTemplate, getLocalTemplatePath, isLocalDevelopment } from './templates.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Create a new Ripple project
|
|
@@ -29,24 +29,46 @@ export async function createProject({
|
|
|
29
29
|
console.log(dim(`Package manager: ${packageManager}`));
|
|
30
30
|
console.log();
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
let templatePath;
|
|
33
|
+
let isTemporary = false;
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
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;
|
|
36
55
|
}
|
|
37
56
|
|
|
38
|
-
// Step
|
|
39
|
-
const
|
|
57
|
+
// Step 2: Create project directory
|
|
58
|
+
const spinner2 = ora('Creating project directory...').start();
|
|
40
59
|
try {
|
|
41
60
|
mkdirSync(projectPath, { recursive: true });
|
|
42
|
-
|
|
61
|
+
spinner2.succeed('Project directory created');
|
|
43
62
|
} catch (error) {
|
|
44
|
-
|
|
63
|
+
spinner2.fail('Failed to create project directory');
|
|
64
|
+
if (isTemporary) {
|
|
65
|
+
rmSync(templatePath, { recursive: true, force: true });
|
|
66
|
+
}
|
|
45
67
|
throw error;
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
// Step
|
|
49
|
-
const
|
|
70
|
+
// Step 3: Copy template files
|
|
71
|
+
const spinner3 = ora('Copying template files...').start();
|
|
50
72
|
try {
|
|
51
73
|
cpSync(templatePath, projectPath, {
|
|
52
74
|
recursive: true,
|
|
@@ -61,32 +83,47 @@ export async function createProject({
|
|
|
61
83
|
);
|
|
62
84
|
}
|
|
63
85
|
});
|
|
64
|
-
|
|
86
|
+
spinner3.succeed('Template files copied');
|
|
65
87
|
} catch (error) {
|
|
66
|
-
|
|
88
|
+
spinner3.fail('Failed to copy template files');
|
|
89
|
+
if (isTemporary) {
|
|
90
|
+
rmSync(templatePath, { recursive: true, force: true });
|
|
91
|
+
}
|
|
67
92
|
throw error;
|
|
68
93
|
}
|
|
69
94
|
|
|
70
|
-
// Step
|
|
71
|
-
const
|
|
95
|
+
// Step 4: Update package.json
|
|
96
|
+
const spinner4 = ora('Configuring package.json...').start();
|
|
72
97
|
try {
|
|
73
98
|
updatePackageJson(projectPath, projectName, packageManager, typescript);
|
|
74
|
-
|
|
99
|
+
spinner4.succeed('Package.json configured');
|
|
75
100
|
} catch (error) {
|
|
76
|
-
|
|
101
|
+
spinner4.fail('Failed to configure package.json');
|
|
102
|
+
if (isTemporary) {
|
|
103
|
+
rmSync(templatePath, { recursive: true, force: true });
|
|
104
|
+
}
|
|
77
105
|
throw error;
|
|
78
106
|
}
|
|
79
107
|
|
|
80
|
-
// Step
|
|
108
|
+
// Step 5: Initialize Git (if requested)
|
|
81
109
|
if (gitInit) {
|
|
82
|
-
const
|
|
110
|
+
const spinner5 = ora('Initializing Git repository...').start();
|
|
83
111
|
try {
|
|
84
112
|
execSync('git init', { cwd: projectPath, stdio: 'ignore' });
|
|
85
113
|
execSync('git add .', { cwd: projectPath, stdio: 'ignore' });
|
|
86
114
|
execSync('git commit -m "Initial commit"', { cwd: projectPath, stdio: 'ignore' });
|
|
87
|
-
|
|
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 });
|
|
88
125
|
} catch (error) {
|
|
89
|
-
|
|
126
|
+
// Ignore cleanup errors
|
|
90
127
|
}
|
|
91
128
|
}
|
|
92
129
|
|
package/src/lib/templates.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import {
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import degit from 'degit';
|
|
5
|
+
import { GITHUB_REPO, GITHUB_TEMPLATES_DIRECTORY, TEMPLATES } from '../constants.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Get template by name
|
|
@@ -32,25 +34,64 @@ export function getTemplateChoices() {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
* Validate if template exists
|
|
37
|
+
* Validate if template exists in our template list
|
|
36
38
|
* @param {string} templateName - The template name to validate
|
|
37
|
-
* @returns {boolean} - True if template exists
|
|
39
|
+
* @returns {boolean} - True if template exists in TEMPLATES list
|
|
38
40
|
*/
|
|
39
41
|
export function validateTemplate(templateName) {
|
|
40
42
|
if (!templateName) return false;
|
|
41
|
-
|
|
42
43
|
const template = getTemplate(templateName);
|
|
43
|
-
|
|
44
|
+
return template !== null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Download template from GitHub repository
|
|
49
|
+
* @param {string} templateName - The template name to download
|
|
50
|
+
* @returns {Promise<string>} - Path to downloaded template directory
|
|
51
|
+
*/
|
|
52
|
+
export async function downloadTemplate(templateName) {
|
|
53
|
+
if (!validateTemplate(templateName)) {
|
|
54
|
+
throw new Error(`Template "${templateName}" not found`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Create a temporary directory for the template
|
|
58
|
+
const tempDir = join(tmpdir(), `ripple-template-${templateName}-${Date.now()}`);
|
|
59
|
+
mkdirSync(tempDir, { recursive: true });
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
// Use degit to download the specific template from GitHub
|
|
62
|
+
const repoUrl = `${GITHUB_REPO}/${GITHUB_TEMPLATES_DIRECTORY}/${templateName}`;
|
|
63
|
+
const emitter = degit(repoUrl, {
|
|
64
|
+
cache: false,
|
|
65
|
+
force: true,
|
|
66
|
+
verbose: false
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await emitter.clone(tempDir);
|
|
71
|
+
return tempDir;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new Error(`Failed to download template "${templateName}": ${error.message}`);
|
|
74
|
+
}
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
/**
|
|
50
|
-
* Get template directory path
|
|
78
|
+
* Get template directory path (for local development)
|
|
51
79
|
* @param {string} templateName - The template name
|
|
52
80
|
* @returns {string} - Absolute path to template directory
|
|
53
81
|
*/
|
|
54
|
-
export function
|
|
55
|
-
|
|
82
|
+
export function getLocalTemplatePath(templateName) {
|
|
83
|
+
// This is used for local development in the monorepo
|
|
84
|
+
const repoRoot = join(process.cwd(), '../../../');
|
|
85
|
+
return join(repoRoot, 'templates', templateName);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if we're running in development mode (monorepo)
|
|
90
|
+
* @returns {boolean} - True if in development mode
|
|
91
|
+
*/
|
|
92
|
+
export function isLocalDevelopment() {
|
|
93
|
+
// Check if we're in the monorepo by looking for the templates directory
|
|
94
|
+
const repoRoot = join(process.cwd(), '../../../');
|
|
95
|
+
const templatesDir = join(repoRoot, 'templates');
|
|
96
|
+
return existsSync(templatesDir);
|
|
56
97
|
}
|
|
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { createProject } from '../../src/lib/project-creator.js';
|
|
6
|
+
import { getLocalTemplatePath, isLocalDevelopment, validateTemplate } from '../../src/lib/templates.js';
|
|
6
7
|
|
|
7
8
|
// Mock ora for cleaner test output
|
|
8
9
|
vi.mock('ora', () => ({
|
|
@@ -22,6 +23,21 @@ vi.mock('node:child_process', () => ({
|
|
|
22
23
|
execSync: vi.fn()
|
|
23
24
|
}));
|
|
24
25
|
|
|
26
|
+
// Mock degit to prevent actual network calls
|
|
27
|
+
vi.mock('degit', () => ({
|
|
28
|
+
default: vi.fn(() => ({
|
|
29
|
+
clone: vi.fn().mockResolvedValue(undefined)
|
|
30
|
+
}))
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Mock template functions globally
|
|
34
|
+
vi.mock('../../src/lib/templates.js', () => ({
|
|
35
|
+
getLocalTemplatePath: vi.fn(),
|
|
36
|
+
isLocalDevelopment: vi.fn(),
|
|
37
|
+
downloadTemplate: vi.fn(),
|
|
38
|
+
validateTemplate: vi.fn()
|
|
39
|
+
}));
|
|
40
|
+
|
|
25
41
|
describe('createProject integration tests', () => {
|
|
26
42
|
let testDir;
|
|
27
43
|
let projectPath;
|
|
@@ -64,10 +80,10 @@ describe('createProject integration tests', () => {
|
|
|
64
80
|
writeFileSync(join(templatePath, 'src', 'App.ripple'), '<h1>Hello Ripple!</h1>');
|
|
65
81
|
writeFileSync(join(templatePath, 'README.md'), '# Template Project');
|
|
66
82
|
|
|
67
|
-
//
|
|
68
|
-
vi.
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
// Set up mocks for each test
|
|
84
|
+
vi.mocked(getLocalTemplatePath).mockReturnValue(templatePath);
|
|
85
|
+
vi.mocked(isLocalDevelopment).mockReturnValue(true);
|
|
86
|
+
vi.mocked(validateTemplate).mockReturnValue(true);
|
|
71
87
|
});
|
|
72
88
|
|
|
73
89
|
afterEach(() => {
|
|
@@ -150,20 +166,19 @@ describe('createProject integration tests', () => {
|
|
|
150
166
|
it('should handle missing template directory', async () => {
|
|
151
167
|
const invalidTemplatePath = join(testDir, 'non-existent-template');
|
|
152
168
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}));
|
|
169
|
+
// Override the mock for this specific test
|
|
170
|
+
vi.mocked(getLocalTemplatePath).mockReturnValue(invalidTemplatePath);
|
|
156
171
|
|
|
157
172
|
await expect(
|
|
158
173
|
createProject({
|
|
159
174
|
projectName: 'test-project',
|
|
160
175
|
projectPath,
|
|
161
|
-
template: '
|
|
176
|
+
template: 'basic',
|
|
162
177
|
packageManager: 'npm',
|
|
163
178
|
typescript: true,
|
|
164
179
|
gitInit: false
|
|
165
180
|
})
|
|
166
|
-
).rejects.toThrow('
|
|
181
|
+
).rejects.toThrow('Local template "basic" not found');
|
|
167
182
|
});
|
|
168
183
|
|
|
169
184
|
it('should filter out unwanted files during copy', async () => {
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
getTemplateNames,
|
|
6
6
|
getTemplateChoices,
|
|
7
7
|
validateTemplate,
|
|
8
|
-
|
|
8
|
+
getLocalTemplatePath,
|
|
9
|
+
isLocalDevelopment,
|
|
10
|
+
downloadTemplate
|
|
9
11
|
} from '../../src/lib/templates.js';
|
|
10
12
|
|
|
11
13
|
// Mock the constants
|
|
@@ -94,70 +96,65 @@ describe('getTemplateChoices', () => {
|
|
|
94
96
|
});
|
|
95
97
|
|
|
96
98
|
describe('validateTemplate', () => {
|
|
97
|
-
|
|
98
|
-
vi.clearAllMocks();
|
|
99
|
-
// Reset the mock to a default state for CI compatibility
|
|
100
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
101
|
-
mockExistsSync.mockReturnValue(false);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should return true for valid existing template', () => {
|
|
105
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
106
|
-
mockExistsSync.mockReturnValue(true);
|
|
99
|
+
it('should return true for valid template', () => {
|
|
107
100
|
const isValid = validateTemplate('basic');
|
|
108
101
|
expect(isValid).toBe(true);
|
|
109
|
-
expect(mockExistsSync).toHaveBeenCalledWith('/mock/templates/basic');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should return false for valid template that does not exist on filesystem', () => {
|
|
113
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
114
|
-
mockExistsSync.mockReturnValue(false);
|
|
115
|
-
const isValid = validateTemplate('basic');
|
|
116
|
-
expect(isValid).toBe(false);
|
|
117
102
|
});
|
|
118
103
|
|
|
119
104
|
it('should return false for invalid template name', () => {
|
|
120
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
121
105
|
const isValid = validateTemplate('non-existent');
|
|
122
106
|
expect(isValid).toBe(false);
|
|
123
|
-
expect(mockExistsSync).not.toHaveBeenCalled();
|
|
124
107
|
});
|
|
125
108
|
|
|
126
109
|
it('should return false for undefined template name', () => {
|
|
127
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
128
110
|
const isValid = validateTemplate();
|
|
129
111
|
expect(isValid).toBe(false);
|
|
130
|
-
expect(mockExistsSync).not.toHaveBeenCalled();
|
|
131
112
|
});
|
|
132
113
|
|
|
133
114
|
it('should return false for null template name', () => {
|
|
134
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
135
115
|
const isValid = validateTemplate(null);
|
|
136
116
|
expect(isValid).toBe(false);
|
|
137
|
-
expect(mockExistsSync).not.toHaveBeenCalled();
|
|
138
117
|
});
|
|
139
118
|
|
|
140
119
|
it('should return false for empty string template name', () => {
|
|
141
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
142
120
|
const isValid = validateTemplate('');
|
|
143
121
|
expect(isValid).toBe(false);
|
|
144
|
-
expect(mockExistsSync).not.toHaveBeenCalled();
|
|
145
122
|
});
|
|
146
123
|
});
|
|
147
124
|
|
|
148
|
-
describe('
|
|
149
|
-
it('should return correct template path', () => {
|
|
150
|
-
const path =
|
|
151
|
-
expect(path).
|
|
125
|
+
describe('getLocalTemplatePath', () => {
|
|
126
|
+
it('should return correct local template path', () => {
|
|
127
|
+
const path = getLocalTemplatePath('basic');
|
|
128
|
+
expect(path).toContain('templates/basic');
|
|
152
129
|
});
|
|
153
130
|
|
|
154
131
|
it('should return path even for non-existent template', () => {
|
|
155
|
-
const path =
|
|
156
|
-
expect(path).
|
|
132
|
+
const path = getLocalTemplatePath('non-existent');
|
|
133
|
+
expect(path).toContain('templates/non-existent');
|
|
157
134
|
});
|
|
158
135
|
|
|
159
136
|
it('should handle special characters in template name', () => {
|
|
160
|
-
const path =
|
|
161
|
-
expect(path).
|
|
137
|
+
const path = getLocalTemplatePath('my-template.name');
|
|
138
|
+
expect(path).toContain('templates/my-template.name');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('isLocalDevelopment', () => {
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
vi.clearAllMocks();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return true when templates directory exists', () => {
|
|
148
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
149
|
+
mockExistsSync.mockReturnValue(true);
|
|
150
|
+
const isDev = isLocalDevelopment();
|
|
151
|
+
expect(isDev).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should return false when templates directory does not exist', () => {
|
|
155
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
156
|
+
mockExistsSync.mockReturnValue(false);
|
|
157
|
+
const isDev = isLocalDevelopment();
|
|
158
|
+
expect(isDev).toBe(false);
|
|
162
159
|
});
|
|
163
160
|
});
|
package/vitest.config.js
CHANGED