create-esmx 3.0.0-rc.33

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.
Files changed (40) hide show
  1. package/dist/index.d.ts +8 -0
  2. package/dist/index.mjs +282 -0
  3. package/dist/index.test.d.ts +1 -0
  4. package/dist/index.test.mjs +123 -0
  5. package/dist/integration.test.d.ts +1 -0
  6. package/dist/integration.test.mjs +165 -0
  7. package/dist/utils/index.d.ts +3 -0
  8. package/dist/utils/index.mjs +7 -0
  9. package/dist/utils/package-manager.d.ts +10 -0
  10. package/dist/utils/package-manager.mjs +49 -0
  11. package/dist/utils/package-manager.test.d.ts +4 -0
  12. package/dist/utils/package-manager.test.mjs +275 -0
  13. package/dist/utils/project-name.d.ts +30 -0
  14. package/dist/utils/project-name.mjs +17 -0
  15. package/dist/utils/project-name.test.d.ts +4 -0
  16. package/dist/utils/project-name.test.mjs +186 -0
  17. package/dist/utils/template.d.ts +19 -0
  18. package/dist/utils/template.mjs +8 -0
  19. package/dist/utils/template.test.d.ts +4 -0
  20. package/dist/utils/template.test.mjs +150 -0
  21. package/package.json +71 -0
  22. package/src/index.test.ts +159 -0
  23. package/src/index.ts +391 -0
  24. package/src/integration.test.ts +226 -0
  25. package/src/utils/index.ts +11 -0
  26. package/src/utils/package-manager.test.ts +540 -0
  27. package/src/utils/package-manager.ts +92 -0
  28. package/src/utils/project-name.test.ts +345 -0
  29. package/src/utils/project-name.ts +55 -0
  30. package/src/utils/template.test.ts +234 -0
  31. package/src/utils/template.ts +34 -0
  32. package/template/vue2/README.md +80 -0
  33. package/template/vue2/package.json +27 -0
  34. package/template/vue2/src/app.vue +127 -0
  35. package/template/vue2/src/components/hello-world.vue +79 -0
  36. package/template/vue2/src/create-app.ts +11 -0
  37. package/template/vue2/src/entry.client.ts +5 -0
  38. package/template/vue2/src/entry.node.ts +29 -0
  39. package/template/vue2/src/entry.server.ts +37 -0
  40. package/template/vue2/tsconfig.json +26 -0
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Unit tests for project-name utilities
3
+ */
4
+
5
+ import { basename } from 'node:path';
6
+ import { describe, expect, it } from 'vitest';
7
+ import { formatProjectName } from './project-name';
8
+ import type { ProjectNameResult } from './project-name';
9
+
10
+ describe('project-name utilities', () => {
11
+ describe('formatProjectName', () => {
12
+ it('should handle simple project name', () => {
13
+ // Arrange
14
+ const input = 'foo';
15
+
16
+ // Act
17
+ const result: ProjectNameResult = formatProjectName(input);
18
+
19
+ // Assert
20
+ expect(result.targetDir).toBe('foo');
21
+ expect(result.packageName).toBe('foo');
22
+ });
23
+
24
+ it('should handle nested path project name', () => {
25
+ // Arrange
26
+ const input = 'foo/bar';
27
+
28
+ // Act
29
+ const result: ProjectNameResult = formatProjectName(input);
30
+
31
+ // Assert
32
+ expect(result.targetDir).toBe('foo/bar');
33
+ expect(result.packageName).toBe('bar');
34
+ });
35
+
36
+ it('should handle scoped package name', () => {
37
+ // Arrange
38
+ const input = '@scope/foo';
39
+
40
+ // Act
41
+ const result: ProjectNameResult = formatProjectName(input);
42
+
43
+ // Assert
44
+ expect(result.targetDir).toBe('@scope/foo');
45
+ expect(result.packageName).toBe('@scope/foo');
46
+ });
47
+
48
+ it('should handle relative path project name', () => {
49
+ // Arrange
50
+ const input = './foo/bar';
51
+
52
+ // Act
53
+ const result: ProjectNameResult = formatProjectName(input);
54
+
55
+ // Assert
56
+ expect(result.targetDir).toBe('./foo/bar');
57
+ expect(result.packageName).toBe('bar');
58
+ });
59
+
60
+ it('should handle absolute path project name', () => {
61
+ // Arrange
62
+ const input = '/root/path/to/foo';
63
+
64
+ // Act
65
+ const result: ProjectNameResult = formatProjectName(input);
66
+
67
+ // Assert
68
+ expect(result.targetDir).toBe('/root/path/to/foo');
69
+ expect(result.packageName).toBe('foo');
70
+ });
71
+
72
+ it('should handle current directory', () => {
73
+ // Arrange
74
+ const input = '.';
75
+ const expectedPackageName = basename(process.cwd());
76
+
77
+ // Act
78
+ const result: ProjectNameResult = formatProjectName(input);
79
+
80
+ // Assert
81
+ expect(result.targetDir).toBe('.');
82
+ expect(result.packageName).toBe(expectedPackageName);
83
+ });
84
+
85
+ it('should handle project name with trailing slashes', () => {
86
+ // Arrange
87
+ const input = 'foo/bar///';
88
+
89
+ // Act
90
+ const result: ProjectNameResult = formatProjectName(input);
91
+
92
+ // Assert
93
+ expect(result.targetDir).toBe('foo/bar');
94
+ expect(result.packageName).toBe('bar');
95
+ });
96
+
97
+ it('should handle project name with multiple trailing slashes', () => {
98
+ // Arrange
99
+ const input = 'my-project/////';
100
+
101
+ // Act
102
+ const result: ProjectNameResult = formatProjectName(input);
103
+
104
+ // Assert
105
+ expect(result.targetDir).toBe('my-project');
106
+ expect(result.packageName).toBe('my-project');
107
+ });
108
+
109
+ it('should handle deep nested path', () => {
110
+ // Arrange
111
+ const input = 'projects/web/frontend/my-app';
112
+
113
+ // Act
114
+ const result: ProjectNameResult = formatProjectName(input);
115
+
116
+ // Assert
117
+ expect(result.targetDir).toBe('projects/web/frontend/my-app');
118
+ expect(result.packageName).toBe('my-app');
119
+ });
120
+
121
+ it('should handle scoped package with nested path', () => {
122
+ // Arrange
123
+ const input = 'org/@scope/package-name';
124
+
125
+ // Act
126
+ const result: ProjectNameResult = formatProjectName(input);
127
+
128
+ // Assert
129
+ expect(result.targetDir).toBe('org/@scope/package-name');
130
+ expect(result.packageName).toBe('package-name');
131
+ });
132
+
133
+ it('should handle scoped package starting with @', () => {
134
+ // Arrange
135
+ const input = '@company/ui-library';
136
+
137
+ // Act
138
+ const result: ProjectNameResult = formatProjectName(input);
139
+
140
+ // Assert
141
+ expect(result.targetDir).toBe('@company/ui-library');
142
+ expect(result.packageName).toBe('@company/ui-library');
143
+ });
144
+
145
+ it('should handle empty path segment', () => {
146
+ // Arrange
147
+ const input = 'foo//bar';
148
+
149
+ // Act
150
+ const result: ProjectNameResult = formatProjectName(input);
151
+
152
+ // Assert
153
+ expect(result.targetDir).toBe('foo//bar');
154
+ expect(result.packageName).toBe('bar');
155
+ });
156
+
157
+ it('should handle single character project name', () => {
158
+ // Arrange
159
+ const input = 'a';
160
+
161
+ // Act
162
+ const result: ProjectNameResult = formatProjectName(input);
163
+
164
+ // Assert
165
+ expect(result.targetDir).toBe('a');
166
+ expect(result.packageName).toBe('a');
167
+ });
168
+
169
+ it('should handle project name with numbers and hyphens', () => {
170
+ // Arrange
171
+ const input = 'my-project-v2';
172
+
173
+ // Act
174
+ const result: ProjectNameResult = formatProjectName(input);
175
+
176
+ // Assert
177
+ expect(result.targetDir).toBe('my-project-v2');
178
+ expect(result.packageName).toBe('my-project-v2');
179
+ });
180
+
181
+ it('should handle project name ending with slash', () => {
182
+ // Arrange
183
+ const input = 'path/to/project/';
184
+
185
+ // Act
186
+ const result: ProjectNameResult = formatProjectName(input);
187
+
188
+ // Assert
189
+ expect(result.targetDir).toBe('path/to/project');
190
+ expect(result.packageName).toBe('project');
191
+ });
192
+
193
+ it('should fall back to default name when path ends with slash and no name', () => {
194
+ // Arrange
195
+ const input = '/';
196
+
197
+ // Act
198
+ const result: ProjectNameResult = formatProjectName(input);
199
+
200
+ // Assert
201
+ expect(result.targetDir).toBe('');
202
+ expect(result.packageName).toBe('esmx-project');
203
+ });
204
+
205
+ it('should handle Windows-style paths', () => {
206
+ // Arrange
207
+ const input = 'C:\\projects\\my-app';
208
+
209
+ // Act
210
+ const result: ProjectNameResult = formatProjectName(input);
211
+
212
+ // Assert
213
+ expect(result.targetDir).toBe('C:\\projects\\my-app');
214
+ // Windows paths use backslashes, so the function returns the full path as package name
215
+ expect(result.packageName).toBe('C:\\projects\\my-app');
216
+ });
217
+
218
+ it('should handle mixed path separators', () => {
219
+ // Arrange
220
+ const input = 'path\\to/project';
221
+
222
+ // Act
223
+ const result: ProjectNameResult = formatProjectName(input);
224
+
225
+ // Assert
226
+ expect(result.targetDir).toBe('path\\to/project');
227
+ expect(result.packageName).toBe('project');
228
+ });
229
+ });
230
+
231
+ describe('formatProjectName with cwd parameter', () => {
232
+ it('should use custom cwd when input is "."', () => {
233
+ // Arrange
234
+ const input = '.';
235
+ const customCwd = '/custom/working/directory';
236
+
237
+ // Act
238
+ const result: ProjectNameResult = formatProjectName(
239
+ input,
240
+ customCwd
241
+ );
242
+
243
+ // Assert
244
+ expect(result.targetDir).toBe('.');
245
+ expect(result.packageName).toBe('directory');
246
+ });
247
+
248
+ it('should fallback to process.cwd() when cwd is not provided', () => {
249
+ // Arrange
250
+ const input = '.';
251
+ const expectedPackageName = basename(process.cwd());
252
+
253
+ // Act
254
+ const result: ProjectNameResult = formatProjectName(input);
255
+
256
+ // Assert
257
+ expect(result.targetDir).toBe('.');
258
+ expect(result.packageName).toBe(expectedPackageName);
259
+ });
260
+
261
+ it('should ignore cwd parameter when input is not "."', () => {
262
+ // Arrange
263
+ const input = 'my-project';
264
+ const customCwd = '/custom/working/directory';
265
+
266
+ // Act
267
+ const result: ProjectNameResult = formatProjectName(
268
+ input,
269
+ customCwd
270
+ );
271
+
272
+ // Assert
273
+ expect(result.targetDir).toBe('my-project');
274
+ expect(result.packageName).toBe('my-project');
275
+ });
276
+
277
+ it('should handle scoped packages with custom cwd', () => {
278
+ // Arrange
279
+ const input = '@scope/package';
280
+ const customCwd = '/any/directory';
281
+
282
+ // Act
283
+ const result: ProjectNameResult = formatProjectName(
284
+ input,
285
+ customCwd
286
+ );
287
+
288
+ // Assert
289
+ expect(result.targetDir).toBe('@scope/package');
290
+ expect(result.packageName).toBe('@scope/package');
291
+ });
292
+
293
+ it('should handle nested paths with custom cwd', () => {
294
+ // Arrange
295
+ const input = 'org/team/project';
296
+ const customCwd = '/any/directory';
297
+
298
+ // Act
299
+ const result: ProjectNameResult = formatProjectName(
300
+ input,
301
+ customCwd
302
+ );
303
+
304
+ // Assert
305
+ expect(result.targetDir).toBe('org/team/project');
306
+ expect(result.packageName).toBe('project');
307
+ });
308
+
309
+ it('should handle Unix-style absolute paths in cwd', () => {
310
+ // Arrange
311
+ const input = '.';
312
+ const customCwd = '/home/user/projects/my-awesome-app';
313
+
314
+ // Act
315
+ const result: ProjectNameResult = formatProjectName(
316
+ input,
317
+ customCwd
318
+ );
319
+
320
+ // Assert
321
+ expect(result.targetDir).toBe('.');
322
+ expect(result.packageName).toBe('my-awesome-app');
323
+ });
324
+
325
+ it('should handle Windows-style absolute paths in cwd', () => {
326
+ // Arrange
327
+ const input = '.';
328
+ const customCwd = 'C:\\Users\\Developer\\Projects\\WindowsApp';
329
+
330
+ // Act
331
+ const result: ProjectNameResult = formatProjectName(
332
+ input,
333
+ customCwd
334
+ );
335
+
336
+ // Assert
337
+ expect(result.targetDir).toBe('.');
338
+ // On Unix systems, backslashes are treated as regular characters
339
+ // On Windows systems, basename would correctly extract 'WindowsApp'
340
+ // This test verifies the current behavior on the running platform
341
+ const expectedPackageName = basename(customCwd);
342
+ expect(result.packageName).toBe(expectedPackageName);
343
+ });
344
+ });
345
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Project name utilities
3
+ */
4
+
5
+ import { basename } from 'node:path';
6
+
7
+ export interface ProjectNameResult {
8
+ packageName: string;
9
+ targetDir: string;
10
+ }
11
+
12
+ /**
13
+ * Format project name and determine target directory
14
+ *
15
+ * Examples:
16
+ * 1. Input: 'foo'
17
+ * Output: folder `<cwd>/foo`, `package.json#name` -> `foo`
18
+ *
19
+ * 2. Input: 'foo/bar'
20
+ * Output: folder -> `<cwd>/foo/bar` folder, `package.json#name` -> `bar`
21
+ *
22
+ * 3. Input: '@scope/foo'
23
+ * Output: folder -> `<cwd>/@scope/foo` folder, `package.json#name` -> `@scope/foo`
24
+ *
25
+ * 4. Input: './foo/bar'
26
+ * Output: folder -> `<cwd>/foo/bar` folder, `package.json#name` -> `bar`
27
+ *
28
+ * 5. Input: '/root/path/to/foo'
29
+ * Output: folder -> `'/root/path/to/foo'` folder, `package.json#name` -> `foo`
30
+ *
31
+ * 6. Input: '.'
32
+ * Output: folder -> `<cwd>` folder, `package.json#name` -> `<current-dir-name>`
33
+ */
34
+ export function formatProjectName(
35
+ input: string,
36
+ cwd?: string
37
+ ): ProjectNameResult {
38
+ const targetDir = input.replace(/\/+$/g, '');
39
+
40
+ let packageName: string;
41
+ if (targetDir === '.') {
42
+ // Use current directory name as package name
43
+ const workingDir = cwd || process.cwd();
44
+ packageName = basename(workingDir);
45
+ } else if (targetDir.startsWith('@')) {
46
+ packageName = targetDir;
47
+ } else {
48
+ packageName = targetDir.split('/').pop() || 'esmx-project';
49
+ }
50
+
51
+ return {
52
+ packageName,
53
+ targetDir
54
+ };
55
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Unit tests for template variable replacement utilities
3
+ */
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+ import { replaceTemplateVariables } from './template';
7
+
8
+ describe('replaceTemplateVariables', () => {
9
+ describe('basic functionality', () => {
10
+ it('should replace single variable', () => {
11
+ // Arrange
12
+ const content = 'Hello {{name}}!';
13
+ const variables = { name: 'World' };
14
+
15
+ // Act
16
+ const result = replaceTemplateVariables(content, variables);
17
+
18
+ // Assert
19
+ expect(result).toBe('Hello World!');
20
+ });
21
+
22
+ it('should replace multiple different variables', () => {
23
+ // Arrange
24
+ const content = 'Project {{projectName}} version {{version}}';
25
+ const variables = {
26
+ projectName: 'my-app',
27
+ version: '1.0.0'
28
+ };
29
+
30
+ // Act
31
+ const result = replaceTemplateVariables(content, variables);
32
+
33
+ // Assert
34
+ expect(result).toBe('Project my-app version 1.0.0');
35
+ });
36
+
37
+ it('should replace same variable multiple times', () => {
38
+ // Arrange
39
+ const content = '{{greeting}} {{name}}, {{greeting}} again!';
40
+ const variables = {
41
+ greeting: 'Hello',
42
+ name: 'World'
43
+ };
44
+
45
+ // Act
46
+ const result = replaceTemplateVariables(content, variables);
47
+
48
+ // Assert
49
+ expect(result).toBe('Hello World, Hello again!');
50
+ });
51
+ });
52
+
53
+ describe('edge cases', () => {
54
+ it('should handle content without variables', () => {
55
+ // Arrange
56
+ const content = 'No variables here';
57
+ const variables = { name: 'World' };
58
+
59
+ // Act
60
+ const result = replaceTemplateVariables(content, variables);
61
+
62
+ // Assert
63
+ expect(result).toBe('No variables here');
64
+ });
65
+
66
+ it('should handle empty variables object', () => {
67
+ // Arrange
68
+ const content = 'Hello {{name}}!';
69
+ const variables = {};
70
+
71
+ // Act
72
+ const result = replaceTemplateVariables(content, variables);
73
+
74
+ // Assert
75
+ expect(result).toBe('Hello {{name}}!');
76
+ });
77
+
78
+ it('should handle empty content', () => {
79
+ // Arrange
80
+ const content = '';
81
+ const variables = { name: 'World' };
82
+
83
+ // Act
84
+ const result = replaceTemplateVariables(content, variables);
85
+
86
+ // Assert
87
+ expect(result).toBe('');
88
+ });
89
+
90
+ it('should handle variables with special characters', () => {
91
+ // Arrange
92
+ const content = 'Command: {{installCommand}}';
93
+ const variables = { installCommand: 'npm install --save-dev' };
94
+
95
+ // Act
96
+ const result = replaceTemplateVariables(content, variables);
97
+
98
+ // Assert
99
+ expect(result).toBe('Command: npm install --save-dev');
100
+ });
101
+
102
+ it('should handle variables with regex special characters', () => {
103
+ // Arrange
104
+ const content = 'Pattern: {{pattern}}';
105
+ const variables = { pattern: '[a-z]+.*$' };
106
+
107
+ // Act
108
+ const result = replaceTemplateVariables(content, variables);
109
+
110
+ // Assert
111
+ expect(result).toBe('Pattern: [a-z]+.*$');
112
+ });
113
+ });
114
+
115
+ describe('real-world scenarios', () => {
116
+ it('should handle all template variables from create-esmx', () => {
117
+ // Arrange
118
+ const content = `# {{projectName}}
119
+
120
+ Install dependencies:
121
+ \`\`\`bash
122
+ {{installCommand}}
123
+ \`\`\`
124
+
125
+ Start development:
126
+ \`\`\`bash
127
+ {{devCommand}}
128
+ \`\`\`
129
+
130
+ Build for production:
131
+ \`\`\`bash
132
+ {{buildCommand}}
133
+ \`\`\`
134
+
135
+ Start production server:
136
+ \`\`\`bash
137
+ {{startCommand}}
138
+ \`\`\`
139
+
140
+ Esmx version: {{esmxVersion}}`;
141
+
142
+ const variables = {
143
+ projectName: 'my-awesome-app',
144
+ installCommand: 'pnpm install',
145
+ devCommand: 'pnpm dev',
146
+ buildCommand: 'pnpm build',
147
+ startCommand: 'pnpm start',
148
+ esmxVersion: '3.0.0-rc.33'
149
+ };
150
+
151
+ // Act
152
+ const result = replaceTemplateVariables(content, variables);
153
+
154
+ // Assert
155
+ expect(result).toContain('# my-awesome-app');
156
+ expect(result).toContain('pnpm install');
157
+ expect(result).toContain('pnpm dev');
158
+ expect(result).toContain('pnpm build');
159
+ expect(result).toContain('pnpm start');
160
+ expect(result).toContain('3.0.0-rc.33');
161
+ expect(result).not.toContain('{{');
162
+ expect(result).not.toContain('}}');
163
+ });
164
+
165
+ it('should handle package.json template', () => {
166
+ // Arrange
167
+ const content = `{
168
+ "name": "{{projectName}}",
169
+ "version": "1.0.0",
170
+ "scripts": {
171
+ "dev": "esmx dev",
172
+ "build": "esmx build",
173
+ "start": "esmx start"
174
+ },
175
+ "dependencies": {
176
+ "esmx": "{{esmxVersion}}"
177
+ }
178
+ }`;
179
+
180
+ const variables = {
181
+ projectName: '@scope/my-package',
182
+ esmxVersion: '^3.0.0'
183
+ };
184
+
185
+ // Act
186
+ const result = replaceTemplateVariables(content, variables);
187
+
188
+ // Assert
189
+ expect(result).toContain('"name": "@scope/my-package"');
190
+ expect(result).toContain('"esmx": "^3.0.0"');
191
+ });
192
+ });
193
+
194
+ describe('variable name validation', () => {
195
+ it('should handle variables with underscores', () => {
196
+ // Arrange
197
+ const content = 'Command: {{install_command}}';
198
+ const variables = { install_command: 'npm install' };
199
+
200
+ // Act
201
+ const result = replaceTemplateVariables(content, variables);
202
+
203
+ // Assert
204
+ expect(result).toBe('Command: npm install');
205
+ });
206
+
207
+ it('should handle variables with numbers', () => {
208
+ // Arrange
209
+ const content = 'Node version: {{node18}}';
210
+ const variables = { node18: 'v18.12.0' };
211
+
212
+ // Act
213
+ const result = replaceTemplateVariables(content, variables);
214
+
215
+ // Assert
216
+ expect(result).toBe('Node version: v18.12.0');
217
+ });
218
+
219
+ it('should be case sensitive', () => {
220
+ // Arrange
221
+ const content = 'Hello {{Name}} and {{name}}!';
222
+ const variables = {
223
+ Name: 'Alice',
224
+ name: 'Bob'
225
+ };
226
+
227
+ // Act
228
+ const result = replaceTemplateVariables(content, variables);
229
+
230
+ // Assert
231
+ expect(result).toBe('Hello Alice and Bob!');
232
+ });
233
+ });
234
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Template variable replacement utilities
3
+ */
4
+
5
+ /**
6
+ * Replace template variables in content using mustache-style syntax {{variableName}}
7
+ *
8
+ * @param content - The content string containing template variables
9
+ * @param variables - Object containing variable names and their replacement values
10
+ * @returns Content with all template variables replaced
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const content = "Hello {{name}}, version {{version}}!";
15
+ * const variables = { name: "World", version: "1.0.0" };
16
+ * const result = replaceTemplateVariables(content, variables);
17
+ * // Result: "Hello World, version 1.0.0!"
18
+ * ```
19
+ */
20
+ export function replaceTemplateVariables(
21
+ content: string,
22
+ variables: Record<string, string>
23
+ ): string {
24
+ let result = content;
25
+
26
+ // Iterate through all variables and replace them
27
+ for (const [key, value] of Object.entries(variables)) {
28
+ // Create regex pattern for {{variableName}} with global flag
29
+ const pattern = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
30
+ result = result.replace(pattern, value);
31
+ }
32
+
33
+ return result;
34
+ }