create-esmx 3.0.0-rc.104

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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/README.zh-CN.md +52 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.integration.test.d.ts +1 -0
  6. package/dist/cli.integration.test.mjs +238 -0
  7. package/dist/cli.mjs +166 -0
  8. package/dist/create.d.ts +2 -0
  9. package/dist/create.mjs +6 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.mjs +2 -0
  12. package/dist/project.d.ts +5 -0
  13. package/dist/project.mjs +46 -0
  14. package/dist/project.test.d.ts +1 -0
  15. package/dist/project.test.mjs +155 -0
  16. package/dist/template.d.ts +17 -0
  17. package/dist/template.mjs +76 -0
  18. package/dist/template.test.d.ts +1 -0
  19. package/dist/template.test.mjs +106 -0
  20. package/dist/types.d.ts +30 -0
  21. package/dist/types.mjs +0 -0
  22. package/dist/utils/index.d.ts +3 -0
  23. package/dist/utils/index.mjs +7 -0
  24. package/dist/utils/package-manager.d.ts +10 -0
  25. package/dist/utils/package-manager.mjs +49 -0
  26. package/dist/utils/package-manager.test.d.ts +4 -0
  27. package/dist/utils/package-manager.test.mjs +275 -0
  28. package/dist/utils/project-name.d.ts +48 -0
  29. package/dist/utils/project-name.mjs +42 -0
  30. package/dist/utils/project-name.test.d.ts +1 -0
  31. package/dist/utils/project-name.test.mjs +332 -0
  32. package/dist/utils/template.d.ts +19 -0
  33. package/dist/utils/template.mjs +8 -0
  34. package/dist/utils/template.test.d.ts +4 -0
  35. package/dist/utils/template.test.mjs +150 -0
  36. package/package.json +75 -0
  37. package/src/cli.integration.test.ts +289 -0
  38. package/src/cli.ts +214 -0
  39. package/src/create.ts +8 -0
  40. package/src/index.ts +3 -0
  41. package/src/project.test.ts +200 -0
  42. package/src/project.ts +75 -0
  43. package/src/template.test.ts +135 -0
  44. package/src/template.ts +117 -0
  45. package/src/types.ts +32 -0
  46. package/src/utils/index.ts +11 -0
  47. package/src/utils/package-manager.test.ts +540 -0
  48. package/src/utils/package-manager.ts +92 -0
  49. package/src/utils/project-name.test.ts +441 -0
  50. package/src/utils/project-name.ts +101 -0
  51. package/src/utils/template.test.ts +234 -0
  52. package/src/utils/template.ts +34 -0
  53. package/template/react-csr/README.md +81 -0
  54. package/template/react-csr/package.json +29 -0
  55. package/template/react-csr/src/app.css +98 -0
  56. package/template/react-csr/src/app.tsx +26 -0
  57. package/template/react-csr/src/components/hello-world.css +48 -0
  58. package/template/react-csr/src/components/hello-world.tsx +29 -0
  59. package/template/react-csr/src/create-app.tsx +9 -0
  60. package/template/react-csr/src/entry.client.ts +13 -0
  61. package/template/react-csr/src/entry.node.ts +35 -0
  62. package/template/react-csr/src/entry.server.tsx +27 -0
  63. package/template/react-csr/tsconfig.json +27 -0
  64. package/template/react-ssr/README.md +81 -0
  65. package/template/react-ssr/package.json +29 -0
  66. package/template/react-ssr/src/app.css +98 -0
  67. package/template/react-ssr/src/app.tsx +26 -0
  68. package/template/react-ssr/src/components/hello-world.css +48 -0
  69. package/template/react-ssr/src/components/hello-world.tsx +29 -0
  70. package/template/react-ssr/src/create-app.tsx +9 -0
  71. package/template/react-ssr/src/entry.client.ts +13 -0
  72. package/template/react-ssr/src/entry.node.ts +32 -0
  73. package/template/react-ssr/src/entry.server.tsx +36 -0
  74. package/template/react-ssr/tsconfig.json +27 -0
  75. package/template/shared-modules/README.md +85 -0
  76. package/template/shared-modules/package.json +28 -0
  77. package/template/shared-modules/src/entry.client.ts +50 -0
  78. package/template/shared-modules/src/entry.node.ts +67 -0
  79. package/template/shared-modules/src/entry.server.ts +299 -0
  80. package/template/shared-modules/src/index.ts +3 -0
  81. package/template/shared-modules/src/vue/index.ts +1 -0
  82. package/template/shared-modules/src/vue2/index.ts +1 -0
  83. package/template/shared-modules/tsconfig.json +26 -0
  84. package/template/vue-csr/README.md +80 -0
  85. package/template/vue-csr/package.json +26 -0
  86. package/template/vue-csr/src/app.vue +127 -0
  87. package/template/vue-csr/src/components/hello-world.vue +77 -0
  88. package/template/vue-csr/src/create-app.ts +9 -0
  89. package/template/vue-csr/src/entry.client.ts +5 -0
  90. package/template/vue-csr/src/entry.node.ts +35 -0
  91. package/template/vue-csr/src/entry.server.ts +26 -0
  92. package/template/vue-csr/tsconfig.json +26 -0
  93. package/template/vue-ssr/README.md +80 -0
  94. package/template/vue-ssr/package.json +27 -0
  95. package/template/vue-ssr/src/app.vue +127 -0
  96. package/template/vue-ssr/src/components/hello-world.vue +77 -0
  97. package/template/vue-ssr/src/create-app.ts +9 -0
  98. package/template/vue-ssr/src/entry.client.ts +5 -0
  99. package/template/vue-ssr/src/entry.node.ts +37 -0
  100. package/template/vue-ssr/src/entry.server.ts +30 -0
  101. package/template/vue-ssr/tsconfig.json +26 -0
  102. package/template/vue2-csr/README.md +80 -0
  103. package/template/vue2-csr/package.json +26 -0
  104. package/template/vue2-csr/src/app.vue +127 -0
  105. package/template/vue2-csr/src/components/hello-world.vue +77 -0
  106. package/template/vue2-csr/src/create-app.ts +11 -0
  107. package/template/vue2-csr/src/entry.client.ts +5 -0
  108. package/template/vue2-csr/src/entry.node.ts +35 -0
  109. package/template/vue2-csr/src/entry.server.ts +26 -0
  110. package/template/vue2-csr/tsconfig.json +26 -0
  111. package/template/vue2-ssr/README.md +80 -0
  112. package/template/vue2-ssr/package.json +27 -0
  113. package/template/vue2-ssr/src/app.vue +127 -0
  114. package/template/vue2-ssr/src/components/hello-world.vue +77 -0
  115. package/template/vue2-ssr/src/create-app.ts +11 -0
  116. package/template/vue2-ssr/src/entry.client.ts +5 -0
  117. package/template/vue2-ssr/src/entry.node.ts +32 -0
  118. package/template/vue2-ssr/src/entry.server.ts +37 -0
  119. package/template/vue2-ssr/tsconfig.json +26 -0
@@ -0,0 +1,441 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { ProjectNameResult } from './project-name';
3
+ import { formatProjectName } from './project-name';
4
+
5
+ const isWindows = process.platform === 'win32';
6
+ const isUnix = !isWindows;
7
+
8
+ describe('project-name utilities', () => {
9
+ describe('formatProjectName', () => {
10
+ it('should handle simple project name', () => {
11
+ const input = 'foo';
12
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
13
+
14
+ const result: ProjectNameResult = formatProjectName(input, cwd);
15
+
16
+ expect(result.name).toBe('foo');
17
+ if (isWindows) {
18
+ expect(result.root).toBe('C:\\workspace\\foo');
19
+ } else {
20
+ expect(result.root).toBe('/home/user/foo');
21
+ }
22
+ });
23
+
24
+ it('should handle nested path project name', () => {
25
+ const input = isWindows ? 'foo\\bar' : 'foo/bar';
26
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
27
+
28
+ const result: ProjectNameResult = formatProjectName(input, cwd);
29
+
30
+ expect(result.name).toBe('bar');
31
+ if (isWindows) {
32
+ expect(result.root).toBe('C:\\workspace\\foo\\bar');
33
+ } else {
34
+ expect(result.root).toBe('/home/user/foo/bar');
35
+ }
36
+ });
37
+
38
+ it('should handle scoped package name', () => {
39
+ const input = '@scope/foo';
40
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
41
+
42
+ const result: ProjectNameResult = formatProjectName(input, cwd);
43
+
44
+ expect(result.name).toBe('@scope/foo');
45
+ if (isWindows) {
46
+ expect(result.root).toBe('C:\\workspace\\@scope\\foo');
47
+ } else {
48
+ expect(result.root).toBe('/home/user/@scope/foo');
49
+ }
50
+ });
51
+
52
+ it('should handle relative path project name', () => {
53
+ const input = isWindows ? '.\\foo\\bar' : './foo/bar';
54
+ const cwd = isWindows
55
+ ? 'C:\\workspace\\current'
56
+ : '/home/user/current';
57
+
58
+ const result: ProjectNameResult = formatProjectName(input, cwd);
59
+
60
+ expect(result.name).toBe('bar');
61
+ if (isWindows) {
62
+ expect(result.root).toBe('C:\\workspace\\current\\foo\\bar');
63
+ } else {
64
+ expect(result.root).toBe('/home/user/current/foo/bar');
65
+ }
66
+ });
67
+
68
+ it('should handle absolute path project name', () => {
69
+ const input = isWindows
70
+ ? 'C:\\projects\\my-app'
71
+ : '/root/path/to/foo';
72
+ const cwd = isWindows ? 'D:\\workspace' : '/home/user';
73
+
74
+ const result: ProjectNameResult = formatProjectName(input, cwd);
75
+
76
+ if (isWindows) {
77
+ expect(result.name).toBe('my-app');
78
+ expect(result.root).toBe('C:\\projects\\my-app');
79
+ } else {
80
+ expect(result.name).toBe('foo');
81
+ expect(result.root).toBe('/root/path/to/foo');
82
+ }
83
+ });
84
+
85
+ it('should handle current directory', () => {
86
+ const input = '.';
87
+ const cwd = isWindows
88
+ ? 'C:\\Users\\Developer\\Projects\\WindowsApp'
89
+ : '/home/user/projects/my-app';
90
+
91
+ const result: ProjectNameResult = formatProjectName(input, cwd);
92
+
93
+ if (isWindows) {
94
+ expect(result.name).toBe('WindowsApp');
95
+ expect(result.root).toBe(
96
+ 'C:\\Users\\Developer\\Projects\\WindowsApp'
97
+ );
98
+ } else {
99
+ expect(result.name).toBe('my-app');
100
+ expect(result.root).toBe('/home/user/projects/my-app');
101
+ }
102
+ });
103
+
104
+ it('should handle project name with trailing slashes', () => {
105
+ const input = isWindows ? 'foo\\' : 'foo/';
106
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
107
+
108
+ const result: ProjectNameResult = formatProjectName(input, cwd);
109
+
110
+ expect(result.name).toBe('foo');
111
+ if (isWindows) {
112
+ expect(result.root).toBe('C:\\workspace\\foo');
113
+ } else {
114
+ expect(result.root).toBe('/home/user/foo');
115
+ }
116
+ });
117
+
118
+ it('should handle project name with multiple trailing slashes', () => {
119
+ const input = isWindows ? 'foo\\\\\\' : 'foo///';
120
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
121
+
122
+ const result: ProjectNameResult = formatProjectName(input, cwd);
123
+
124
+ expect(result.name).toBe('foo');
125
+ if (isWindows) {
126
+ expect(result.root).toBe('C:\\workspace\\foo');
127
+ } else {
128
+ expect(result.root).toBe('/home/user/foo');
129
+ }
130
+ });
131
+
132
+ it('should handle deep nested path', () => {
133
+ const input = isWindows
134
+ ? 'path\\to\\nested\\project'
135
+ : 'path/to/nested/project';
136
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
137
+
138
+ const result: ProjectNameResult = formatProjectName(input, cwd);
139
+
140
+ expect(result.name).toBe('project');
141
+ if (isWindows) {
142
+ expect(result.root).toBe(
143
+ 'C:\\workspace\\path\\to\\nested\\project'
144
+ );
145
+ } else {
146
+ expect(result.root).toBe('/home/user/path/to/nested/project');
147
+ }
148
+ });
149
+
150
+ it('should handle scoped package with nested path', () => {
151
+ const input = isWindows
152
+ ? '@company\\ui\\library'
153
+ : '@company/ui/library';
154
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
155
+
156
+ const result: ProjectNameResult = formatProjectName(input, cwd);
157
+
158
+ expect(result.name).toBe(input);
159
+ if (isWindows) {
160
+ expect(result.root).toBe(
161
+ 'C:\\workspace\\@company\\ui\\library'
162
+ );
163
+ } else {
164
+ expect(result.root).toBe('/home/user/@company/ui/library');
165
+ }
166
+ });
167
+
168
+ it('should handle scoped package starting with @', () => {
169
+ const input = '@my-org/my-package';
170
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
171
+
172
+ const result: ProjectNameResult = formatProjectName(input, cwd);
173
+
174
+ expect(result.name).toBe('@my-org/my-package');
175
+ if (isWindows) {
176
+ expect(result.root).toBe('C:\\workspace\\@my-org\\my-package');
177
+ } else {
178
+ expect(result.root).toBe('/home/user/@my-org/my-package');
179
+ }
180
+ });
181
+
182
+ it('should handle empty path segment', () => {
183
+ const input = isWindows
184
+ ? 'path\\\\with\\\\segments'
185
+ : 'path//with//segments';
186
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
187
+
188
+ const result: ProjectNameResult = formatProjectName(input, cwd);
189
+
190
+ expect(result.name).toBe('segments');
191
+ if (isWindows) {
192
+ expect(result.root).toBe('C:\\workspace\\path\\with\\segments');
193
+ } else {
194
+ expect(result.root).toBe('/home/user/path/with/segments');
195
+ }
196
+ });
197
+
198
+ it('should handle single character project name', () => {
199
+ const input = 'a';
200
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
201
+
202
+ const result: ProjectNameResult = formatProjectName(input, cwd);
203
+
204
+ expect(result.name).toBe('a');
205
+ if (isWindows) {
206
+ expect(result.root).toBe('C:\\workspace\\a');
207
+ } else {
208
+ expect(result.root).toBe('/home/user/a');
209
+ }
210
+ });
211
+
212
+ it('should handle project name with numbers and hyphens', () => {
213
+ const input = 'my-project-123';
214
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
215
+
216
+ const result: ProjectNameResult = formatProjectName(input, cwd);
217
+
218
+ expect(result.name).toBe('my-project-123');
219
+ if (isWindows) {
220
+ expect(result.root).toBe('C:\\workspace\\my-project-123');
221
+ } else {
222
+ expect(result.root).toBe('/home/user/my-project-123');
223
+ }
224
+ });
225
+
226
+ it('should handle project name ending with slash', () => {
227
+ const input = isWindows ? 'my-project\\' : 'my-project/';
228
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
229
+
230
+ const result: ProjectNameResult = formatProjectName(input, cwd);
231
+
232
+ expect(result.name).toBe('my-project');
233
+ if (isWindows) {
234
+ expect(result.root).toBe('C:\\workspace\\my-project');
235
+ } else {
236
+ expect(result.root).toBe('/home/user/my-project');
237
+ }
238
+ });
239
+
240
+ it('should fall back to default name when path ends with slash and no name', () => {
241
+ const input = '/';
242
+ const cwd = isWindows ? 'C:\\workspace' : '/home/user';
243
+
244
+ const result: ProjectNameResult = formatProjectName(input, cwd);
245
+
246
+ expect(result.root).toBe('/');
247
+ expect(result.name).toBe('esmx-project');
248
+ });
249
+
250
+ it.runIf(isWindows)(
251
+ 'should handle Windows-style absolute paths',
252
+ () => {
253
+ const input = 'C:\\projects\\my-app';
254
+ const cwd = 'D:\\workspace';
255
+
256
+ const result: ProjectNameResult = formatProjectName(input, cwd);
257
+
258
+ expect(result.name).toBe('my-app');
259
+ expect(result.root).toMatch(/projects[\\/]my-app/);
260
+ }
261
+ );
262
+
263
+ it.runIf(isWindows)('should handle Windows UNC paths', () => {
264
+ const input = '\\\\server\\share\\project';
265
+ const cwd = 'C:\\workspace';
266
+
267
+ const result: ProjectNameResult = formatProjectName(input, cwd);
268
+
269
+ expect(result.name).toBe('project');
270
+ expect(result.root).toMatch(/project$/);
271
+ });
272
+
273
+ it.runIf(isUnix)('should handle mixed path separators on Unix', () => {
274
+ const input = 'path\\to/mixed/separators';
275
+ const cwd = '/home/user';
276
+
277
+ const result: ProjectNameResult = formatProjectName(input, cwd);
278
+
279
+ expect(result.name).toBe('separators');
280
+ expect(result.root).toBe('/home/user/path\\to/mixed/separators');
281
+ });
282
+
283
+ it.runIf(isWindows)(
284
+ 'should handle Windows-style relative paths',
285
+ () => {
286
+ const input = 'foo\\bar';
287
+ const cwd = 'C:\\workspace';
288
+
289
+ const result: ProjectNameResult = formatProjectName(input, cwd);
290
+
291
+ expect(result.name).toBe('bar');
292
+ expect(result.root).toMatch(/workspace[\\/]foo[\\/]bar/);
293
+ }
294
+ );
295
+
296
+ it.runIf(isWindows)(
297
+ 'should handle Windows-style trailing backslashes',
298
+ () => {
299
+ const input = 'foo\\bar\\\\\\';
300
+ const cwd = 'C:\\workspace';
301
+
302
+ const result: ProjectNameResult = formatProjectName(input, cwd);
303
+
304
+ expect(result.name).toBe('bar');
305
+ expect(result.root).toMatch(/workspace[\\/]foo[\\/]bar/);
306
+ }
307
+ );
308
+ });
309
+
310
+ describe('formatProjectName with cwd parameter', () => {
311
+ it('should use custom cwd when input is "."', () => {
312
+ const input = '.';
313
+ const customCwd = isWindows
314
+ ? 'C:\\custom\\path\\to\\project'
315
+ : '/custom/path/to/project';
316
+
317
+ const result: ProjectNameResult = formatProjectName(
318
+ input,
319
+ customCwd
320
+ );
321
+
322
+ expect(result.name).toBe('project');
323
+ expect(result.root).toBe(customCwd);
324
+ });
325
+
326
+ it('should fallback to process.cwd() when cwd is not provided', () => {
327
+ const input = 'test-project';
328
+
329
+ const result: ProjectNameResult = formatProjectName(input);
330
+
331
+ expect(result.name).toBe('test-project');
332
+ expect(result.root).toMatch(/test-project$/);
333
+ });
334
+
335
+ it('should ignore cwd parameter when input is not "."', () => {
336
+ const input = 'my-project';
337
+ const customCwd = isWindows ? 'C:\\custom\\path' : '/custom/path';
338
+
339
+ const result: ProjectNameResult = formatProjectName(
340
+ input,
341
+ customCwd
342
+ );
343
+
344
+ expect(result.name).toBe('my-project');
345
+ if (isWindows) {
346
+ expect(result.root).toBe('C:\\custom\\path\\my-project');
347
+ } else {
348
+ expect(result.root).toBe('/custom/path/my-project');
349
+ }
350
+ });
351
+
352
+ it('should handle scoped packages with custom cwd', () => {
353
+ const input = '@scope/foo';
354
+ const customCwd = isWindows
355
+ ? 'C:\\custom\\workspace'
356
+ : '/custom/workspace';
357
+
358
+ const result: ProjectNameResult = formatProjectName(
359
+ input,
360
+ customCwd
361
+ );
362
+
363
+ expect(result.name).toBe('@scope/foo');
364
+ if (isWindows) {
365
+ expect(result.root).toBe('C:\\custom\\workspace\\@scope\\foo');
366
+ } else {
367
+ expect(result.root).toBe('/custom/workspace/@scope/foo');
368
+ }
369
+ });
370
+
371
+ it('should handle nested paths with custom cwd', () => {
372
+ const input = isWindows ? 'foo\\bar' : 'foo/bar';
373
+ const customCwd = isWindows
374
+ ? 'C:\\custom\\workspace'
375
+ : '/custom/workspace';
376
+
377
+ const result: ProjectNameResult = formatProjectName(
378
+ input,
379
+ customCwd
380
+ );
381
+
382
+ expect(result.name).toBe('bar');
383
+ if (isWindows) {
384
+ expect(result.root).toBe('C:\\custom\\workspace\\foo\\bar');
385
+ } else {
386
+ expect(result.root).toBe('/custom/workspace/foo/bar');
387
+ }
388
+ });
389
+
390
+ it.runIf(isUnix)(
391
+ 'should handle Unix-style absolute paths in cwd',
392
+ () => {
393
+ const input = '/root/path/to/foo';
394
+ const customCwd = '/this/should/be/ignored';
395
+
396
+ const result: ProjectNameResult = formatProjectName(
397
+ input,
398
+ customCwd
399
+ );
400
+
401
+ expect(result.name).toBe('foo');
402
+ expect(result.root).toBe('/root/path/to/foo');
403
+ }
404
+ );
405
+
406
+ it.runIf(isWindows)(
407
+ 'should handle Windows-style absolute paths in cwd',
408
+ () => {
409
+ const input = 'C:\\projects\\my-app';
410
+ const customCwd = 'D:\\this\\should\\be\\ignored';
411
+
412
+ const result: ProjectNameResult = formatProjectName(
413
+ input,
414
+ customCwd
415
+ );
416
+
417
+ expect(result.name).toBe('my-app');
418
+ expect(result.root).toBe('C:\\projects\\my-app');
419
+ }
420
+ );
421
+
422
+ it('should handle absolute input paths and ignore cwd', () => {
423
+ const input = isWindows
424
+ ? 'C:\\projects\\absolute-project'
425
+ : '/projects/absolute-project';
426
+ const customCwd = isWindows ? 'D:\\ignored' : '/ignored';
427
+
428
+ const result: ProjectNameResult = formatProjectName(
429
+ input,
430
+ customCwd
431
+ );
432
+
433
+ expect(result.name).toBe('absolute-project');
434
+ if (isWindows) {
435
+ expect(result.root).toBe('C:\\projects\\absolute-project');
436
+ } else {
437
+ expect(result.root).toBe('/projects/absolute-project');
438
+ }
439
+ });
440
+ });
441
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Project name utilities
3
+ */
4
+
5
+ import { basename, isAbsolute, normalize, resolve } from 'node:path';
6
+
7
+ export interface ProjectNameResult {
8
+ name: string;
9
+ root: string;
10
+ }
11
+
12
+ /**
13
+ * Format project name and determine target directory
14
+ *
15
+ * Examples:
16
+ * 1. Input: 'foo', cwd: '/home/user' (Unix)
17
+ * Output: { name: 'foo', root: '/home/user/foo' }
18
+ * Input: 'foo', cwd: 'C:\\workspace' (Windows)
19
+ * Output: { name: 'foo', root: 'C:\\workspace\\foo' }
20
+ *
21
+ * 2. Input: 'foo/bar', cwd: '/home/user' (Unix)
22
+ * Output: { name: 'bar', root: '/home/user/foo/bar' }
23
+ * Input: 'foo\\bar', cwd: 'C:\\workspace' (Windows)
24
+ * Output: { name: 'bar', root: 'C:\\workspace\\foo\\bar' }
25
+ *
26
+ * 3. Input: '@scope/foo', cwd: '/home/user' (Unix)
27
+ * Output: { name: '@scope/foo', root: '/home/user/@scope/foo' }
28
+ * Input: '@scope/foo', cwd: 'C:\\workspace' (Windows)
29
+ * Output: { name: '@scope/foo', root: 'C:\\workspace\\@scope\\foo' }
30
+ *
31
+ * 4. Input: './foo/bar', cwd: '/home/user/current' (Unix)
32
+ * Output: { name: 'bar', root: '/home/user/current/foo/bar' }
33
+ * Input: '.\\foo\\bar', cwd: 'C:\\workspace\\current' (Windows)
34
+ * Output: { name: 'bar', root: 'C:\\workspace\\current\\foo\\bar' }
35
+ *
36
+ * 5. Input: '/root/path/to/foo', cwd: '/home/user' (Unix absolute)
37
+ * Output: { name: 'foo', root: '/root/path/to/foo' }
38
+ * Input: 'C:\\projects\\my-app', cwd: 'D:\\workspace' (Windows absolute)
39
+ * Output: { name: 'my-app', root: 'C:\\projects\\my-app' }
40
+ *
41
+ * 6. Input: '.', cwd: '/home/user/projects/my-app' (Unix current dir)
42
+ * Output: { name: 'my-app', root: '/home/user/projects/my-app' }
43
+ * Input: '.', cwd: 'C:\\Users\\Developer\\Projects\\WindowsApp' (Windows current dir)
44
+ * Output: { name: 'WindowsApp', root: 'C:\\Users\\Developer\\Projects\\WindowsApp' }
45
+ *
46
+ * 7. Input: '\\\\server\\share\\project', cwd: 'C:\\workspace' (Windows UNC)
47
+ * Output: { name: 'project', root: '\\\\server\\share\\project' }
48
+ *
49
+ * 8. Input: 'path\\to/project', cwd: '/home/user' (Mixed separators)
50
+ * Output: { name: 'project', root: '/home/user/path/to/project' }
51
+ */
52
+ export function formatProjectName(
53
+ input: string,
54
+ cwd?: string
55
+ ): ProjectNameResult {
56
+ const workingDir = cwd || process.cwd();
57
+
58
+ let cleanInput = input;
59
+ if (input !== '/' && input !== '\\' && input.length > 1) {
60
+ cleanInput = input.replace(/[\\/]+$/, '');
61
+ }
62
+
63
+ if (cleanInput === '.') {
64
+ return {
65
+ name: basename(workingDir),
66
+ root: workingDir
67
+ };
68
+ }
69
+
70
+ if (cleanInput === '' || cleanInput === '/' || cleanInput === '\\') {
71
+ if (cleanInput === '/') {
72
+ return {
73
+ name: 'esmx-project',
74
+ root: '/'
75
+ };
76
+ }
77
+ return {
78
+ name: 'esmx-project',
79
+ root: resolve(workingDir, 'esmx-project')
80
+ };
81
+ }
82
+
83
+ let root: string;
84
+ if (isAbsolute(cleanInput)) {
85
+ root = normalize(cleanInput);
86
+ } else {
87
+ root = resolve(workingDir, cleanInput);
88
+ }
89
+
90
+ let name: string;
91
+ if (cleanInput.startsWith('@')) {
92
+ name = cleanInput;
93
+ } else {
94
+ name = basename(normalize(cleanInput)) || 'esmx-project';
95
+ }
96
+
97
+ return {
98
+ name,
99
+ root
100
+ };
101
+ }