create-ripple 0.1.0 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ripple",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Interactive CLI tool for creating Ripple applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
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
- // Get the root directory of the monorepo
19
- export const REPO_ROOT = resolve(__dirname, '../../../');
20
- export const TEMPLATES_DIR = join(REPO_ROOT, 'templates');
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 { getTemplatePath } from './templates.js';
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
- const templatePath = getTemplatePath(template);
32
+ let templatePath;
33
+ let isTemporary = false;
33
34
 
34
- if (!existsSync(templatePath)) {
35
- throw new Error(`Template "${template}" not found at ${templatePath}`);
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 1: Create project directory
39
- const spinner1 = ora('Creating project directory...').start();
57
+ // Step 2: Create project directory
58
+ const spinner2 = ora('Creating project directory...').start();
40
59
  try {
41
60
  mkdirSync(projectPath, { recursive: true });
42
- spinner1.succeed('Project directory created');
61
+ spinner2.succeed('Project directory created');
43
62
  } catch (error) {
44
- spinner1.fail('Failed to create project directory');
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 2: Copy template files
49
- const spinner2 = ora('Copying template files...').start();
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
- spinner2.succeed('Template files copied');
86
+ spinner3.succeed('Template files copied');
65
87
  } catch (error) {
66
- spinner2.fail('Failed to copy template files');
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 3: Update package.json
71
- const spinner3 = ora('Configuring package.json...').start();
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
- spinner3.succeed('Package.json configured');
99
+ spinner4.succeed('Package.json configured');
75
100
  } catch (error) {
76
- spinner3.fail('Failed to configure package.json');
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 4: Initialize Git (if requested)
108
+ // Step 5: Initialize Git (if requested)
81
109
  if (gitInit) {
82
- const spinner4 = ora('Initializing Git repository...').start();
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
- spinner4.succeed('Git repository initialized');
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
- spinner4.warn('Git initialization failed (optional)');
126
+ // Ignore cleanup errors
90
127
  }
91
128
  }
92
129
 
@@ -1,6 +1,13 @@
1
1
  import { join } from 'node:path';
2
- import { existsSync } from 'node:fs';
3
- import { TEMPLATES, TEMPLATES_DIR } from '../constants.js';
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);
4
11
 
5
12
  /**
6
13
  * Get template by name
@@ -32,25 +39,66 @@ export function getTemplateChoices() {
32
39
  }
33
40
 
34
41
  /**
35
- * Validate if template exists
42
+ * Validate if template exists in our template list
36
43
  * @param {string} templateName - The template name to validate
37
- * @returns {boolean} - True if template exists
44
+ * @returns {boolean} - True if template exists in TEMPLATES list
38
45
  */
39
46
  export function validateTemplate(templateName) {
40
47
  if (!templateName) return false;
41
-
42
48
  const template = getTemplate(templateName);
43
- if (!template) return false;
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
+ }
44
61
 
45
- const templatePath = join(TEMPLATES_DIR, templateName);
46
- return existsSync(templatePath);
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
+ }
47
80
  }
48
81
 
49
82
  /**
50
- * Get template directory path
83
+ * Get template directory path (for local development)
51
84
  * @param {string} templateName - The template name
52
85
  * @returns {string} - Absolute path to template directory
53
86
  */
54
- export function getTemplatePath(templateName) {
55
- return join(TEMPLATES_DIR, templateName);
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);
56
104
  }
@@ -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
- // Mock the getTemplatePath function
68
- vi.doMock('../../src/lib/templates.js', () => ({
69
- getTemplatePath: () => templatePath
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
- vi.doMock('../../src/lib/templates.js', () => ({
154
- getTemplatePath: () => invalidTemplatePath
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: 'non-existent',
176
+ template: 'basic',
162
177
  packageManager: 'npm',
163
178
  typescript: true,
164
179
  gitInit: false
165
180
  })
166
- ).rejects.toThrow('Template "non-existent" not found');
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
- getTemplatePath,
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
- beforeEach(() => {
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('getTemplatePath', () => {
149
- it('should return correct template path', () => {
150
- const path = getTemplatePath('basic');
151
- expect(path).toBe('/mock/templates/basic');
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 = getTemplatePath('non-existent');
156
- expect(path).toBe('/mock/templates/non-existent');
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 = getTemplatePath('my-template.name');
161
- expect(path).toBe('/mock/templates/my-template.name');
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
@@ -16,7 +16,7 @@ export default defineConfig({
16
16
  '**/*.config.js'
17
17
  ]
18
18
  },
19
- testTimeout: 30000, // 30 seconds for integration tests
19
+ testTimeout: 60000, // 60 seconds for integration tests
20
20
  hookTimeout: 10000 // 10 seconds for setup/teardown
21
21
  }
22
22
  });