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.
- package/dist/index.d.ts +8 -0
- package/dist/index.mjs +282 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.mjs +123 -0
- package/dist/integration.test.d.ts +1 -0
- package/dist/integration.test.mjs +165 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.mjs +7 -0
- package/dist/utils/package-manager.d.ts +10 -0
- package/dist/utils/package-manager.mjs +49 -0
- package/dist/utils/package-manager.test.d.ts +4 -0
- package/dist/utils/package-manager.test.mjs +275 -0
- package/dist/utils/project-name.d.ts +30 -0
- package/dist/utils/project-name.mjs +17 -0
- package/dist/utils/project-name.test.d.ts +4 -0
- package/dist/utils/project-name.test.mjs +186 -0
- package/dist/utils/template.d.ts +19 -0
- package/dist/utils/template.mjs +8 -0
- package/dist/utils/template.test.d.ts +4 -0
- package/dist/utils/template.test.mjs +150 -0
- package/package.json +71 -0
- package/src/index.test.ts +159 -0
- package/src/index.ts +391 -0
- package/src/integration.test.ts +226 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/package-manager.test.ts +540 -0
- package/src/utils/package-manager.ts +92 -0
- package/src/utils/project-name.test.ts +345 -0
- package/src/utils/project-name.ts +55 -0
- package/src/utils/template.test.ts +234 -0
- package/src/utils/template.ts +34 -0
- package/template/vue2/README.md +80 -0
- package/template/vue2/package.json +27 -0
- package/template/vue2/src/app.vue +127 -0
- package/template/vue2/src/components/hello-world.vue +79 -0
- package/template/vue2/src/create-app.ts +11 -0
- package/template/vue2/src/entry.client.ts +5 -0
- package/template/vue2/src/entry.node.ts +29 -0
- package/template/vue2/src/entry.server.ts +37 -0
- 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
|
+
}
|