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,8 @@
1
+ #!/usr/bin/env node
2
+ interface CreateProjectOptions {
3
+ argv?: string[];
4
+ cwd?: string;
5
+ userAgent?: string;
6
+ }
7
+ export declare function createProject(options?: CreateProjectOptions): Promise<void>;
8
+ export default createProject;
package/dist/index.mjs ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ statSync,
8
+ writeFileSync
9
+ } from "node:fs";
10
+ import { dirname, join, resolve } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import {
13
+ cancel,
14
+ confirm,
15
+ intro,
16
+ isCancel,
17
+ note,
18
+ outro,
19
+ select,
20
+ text
21
+ } from "@clack/prompts";
22
+ import minimist from "minimist";
23
+ import color from "picocolors";
24
+ import {
25
+ formatProjectName,
26
+ getCommand,
27
+ replaceTemplateVariables
28
+ } from "./utils/index.mjs";
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+ function getEsmxVersion() {
31
+ try {
32
+ const packageJsonPath = resolve(__dirname, "../package.json");
33
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
34
+ return packageJson.version || "latest";
35
+ } catch (error) {
36
+ console.warn("Failed to read esmx version, using latest version");
37
+ return "latest";
38
+ }
39
+ }
40
+ function getAvailableTemplates() {
41
+ const templateDir = resolve(__dirname, "../template");
42
+ const templates = [];
43
+ const templateFolders = readdirSync(templateDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
44
+ for (const folder of templateFolders) {
45
+ const name = folder;
46
+ const packageJsonPath = resolve(templateDir, folder, "package.json");
47
+ let description = `${name} template`;
48
+ if (existsSync(packageJsonPath)) {
49
+ try {
50
+ const packageJson = JSON.parse(
51
+ readFileSync(packageJsonPath, "utf-8")
52
+ );
53
+ if (packageJson.description) {
54
+ description = packageJson.description;
55
+ }
56
+ templates.push({
57
+ folder,
58
+ name,
59
+ description
60
+ });
61
+ } catch (error) {
62
+ console.warn(
63
+ `Warning: Failed to parse package.json for template '${folder}', skipping.`
64
+ );
65
+ }
66
+ }
67
+ }
68
+ return templates.sort((a, b) => a.name.localeCompare(b.name));
69
+ }
70
+ function showHelp(userAgent) {
71
+ const createCmd = getCommand("create", userAgent);
72
+ console.log(`
73
+ ${color.reset(color.bold(color.blue("\u{1F680} Create Esmx Project")))}
74
+
75
+ ${color.bold("Usage:")}
76
+ ${createCmd} [project-name]
77
+ ${createCmd} [project-name] [options]
78
+
79
+ ${color.bold("Options:")}
80
+ -t, --template <template> Template to use (default: vue2)
81
+ -n, --name <name> Project name or path
82
+ -f, --force Force overwrite existing directory
83
+ -h, --help Show help information
84
+ -v, --version Show version number
85
+
86
+ ${color.bold("Examples:")}
87
+ ${createCmd} my-project
88
+ ${createCmd} my-project -t vue2
89
+ ${createCmd} my-project --force
90
+ ${createCmd} . -f -t vue2
91
+
92
+ ${color.bold("Available Templates:")}
93
+ ${getAvailableTemplates().map((t) => ` ${t.folder.padEnd(25)} ${t.description}`).join("\n")}
94
+
95
+ For more information, visit: ${color.cyan("https://esmnext.com")}
96
+ `);
97
+ }
98
+ export async function createProject(options = {}) {
99
+ const { argv, cwd, userAgent } = options;
100
+ const commandLineArgs = argv || process.argv.slice(2);
101
+ const workingDir = cwd || process.cwd();
102
+ const parsedArgs = minimist(commandLineArgs, {
103
+ string: ["template", "name"],
104
+ boolean: ["help", "version", "force"],
105
+ alias: {
106
+ t: "template",
107
+ n: "name",
108
+ f: "force",
109
+ h: "help",
110
+ v: "version"
111
+ }
112
+ });
113
+ if (parsedArgs.help) {
114
+ showHelp(userAgent);
115
+ return;
116
+ }
117
+ if (parsedArgs.version) {
118
+ console.log(getEsmxVersion());
119
+ return;
120
+ }
121
+ console.log();
122
+ intro(
123
+ color.reset(
124
+ color.bold(color.blue("\u{1F680} Welcome to Esmx Project Creator!"))
125
+ )
126
+ );
127
+ const projectNameInput = await getProjectName(
128
+ parsedArgs.name,
129
+ parsedArgs._[0]
130
+ );
131
+ if (isCancel(projectNameInput)) {
132
+ cancel("Operation cancelled");
133
+ return;
134
+ }
135
+ const { packageName, targetDir } = formatProjectName(
136
+ projectNameInput,
137
+ workingDir
138
+ );
139
+ const templateType = await getTemplateType(parsedArgs.template);
140
+ if (isCancel(templateType)) {
141
+ cancel("Operation cancelled");
142
+ return;
143
+ }
144
+ const installCommand = getCommand("install", userAgent);
145
+ const devCommand = getCommand("dev", userAgent);
146
+ const buildCommand = getCommand("build", userAgent);
147
+ const startCommand = getCommand("start", userAgent);
148
+ const buildTypeCommand = getCommand("build:type", userAgent);
149
+ const lintTypeCommand = getCommand("lint:type", userAgent);
150
+ await createProjectFromTemplate(
151
+ targetDir,
152
+ templateType,
153
+ workingDir,
154
+ parsedArgs.force,
155
+ {
156
+ projectName: packageName,
157
+ esmxVersion: getEsmxVersion(),
158
+ installCommand,
159
+ devCommand,
160
+ buildCommand,
161
+ startCommand,
162
+ buildTypeCommand,
163
+ lintTypeCommand
164
+ }
165
+ );
166
+ const installCmd = installCommand;
167
+ const devCmd = devCommand;
168
+ const nextSteps = [
169
+ color.reset(`1. ${color.cyan(`cd ${targetDir}`)}`),
170
+ color.reset(`2. ${color.cyan(installCmd)}`),
171
+ color.reset(`3. ${color.cyan("git init")} ${color.gray("(optional)")}`),
172
+ color.reset(`4. ${color.cyan(devCmd)}`)
173
+ ];
174
+ note(nextSteps.join("\n"), "Next steps");
175
+ outro(color.reset(color.green("Happy coding! \u{1F389}")));
176
+ }
177
+ async function getProjectName(argName, positionalName) {
178
+ const providedName = argName || positionalName;
179
+ if (providedName) {
180
+ return providedName;
181
+ }
182
+ const projectName = await text({
183
+ message: "Project name or path:",
184
+ placeholder: "my-esmx-project",
185
+ validate: (value) => {
186
+ if (!value.trim()) {
187
+ return "Project name or path is required";
188
+ }
189
+ if (!/^[a-zA-Z0-9_.\/@-]+$/.test(value.trim())) {
190
+ return "Project name or path should only contain letters, numbers, hyphens, underscores, dots, and slashes";
191
+ }
192
+ }
193
+ });
194
+ return String(projectName).trim();
195
+ }
196
+ async function getTemplateType(argTemplate) {
197
+ const availableTemplates = getAvailableTemplates();
198
+ if (argTemplate && availableTemplates.some((t) => t.folder === argTemplate)) {
199
+ return argTemplate;
200
+ }
201
+ const options = availableTemplates.map((t) => ({
202
+ label: color.reset(color.gray(`${t.folder} - `) + color.bold(t.name)),
203
+ value: t.folder,
204
+ hint: t.description
205
+ }));
206
+ const template = await select({
207
+ message: "Select a template:",
208
+ options
209
+ });
210
+ return template;
211
+ }
212
+ function isDirectoryEmpty(dirPath) {
213
+ if (!existsSync(dirPath)) {
214
+ return true;
215
+ }
216
+ const files = readdirSync(dirPath);
217
+ const nonHiddenFiles = files.filter((file) => !file.startsWith("."));
218
+ return nonHiddenFiles.length === 0;
219
+ }
220
+ async function createProjectFromTemplate(targetDir, templateType, workingDir, force, variables) {
221
+ const templatePath = resolve(__dirname, "../template", templateType);
222
+ const targetPath = targetDir === "." ? workingDir : resolve(workingDir, targetDir);
223
+ if (!existsSync(templatePath)) {
224
+ throw new Error(`Template "${templateType}" not found`);
225
+ }
226
+ if (targetDir !== "." && existsSync(targetPath)) {
227
+ if (!isDirectoryEmpty(targetPath)) {
228
+ if (!force) {
229
+ const shouldOverwrite = await confirm({
230
+ message: `Directory "${targetDir}" is not empty. Do you want to overwrite it?`
231
+ });
232
+ if (isCancel(shouldOverwrite)) {
233
+ cancel("Operation cancelled");
234
+ return;
235
+ }
236
+ if (!shouldOverwrite) {
237
+ throw new Error("Operation cancelled by user");
238
+ }
239
+ }
240
+ }
241
+ } else if (targetDir !== ".") {
242
+ mkdirSync(targetPath, { recursive: true });
243
+ }
244
+ if (targetDir === "." && !isDirectoryEmpty(targetPath)) {
245
+ if (!force) {
246
+ const shouldOverwrite = await confirm({
247
+ message: "Current directory is not empty. Do you want to overwrite existing files?"
248
+ });
249
+ if (isCancel(shouldOverwrite)) {
250
+ cancel("Operation cancelled");
251
+ return;
252
+ }
253
+ if (!shouldOverwrite) {
254
+ throw new Error("Operation cancelled by user");
255
+ }
256
+ }
257
+ }
258
+ copyTemplateFiles(templatePath, targetPath, variables);
259
+ }
260
+ function copyTemplateFiles(templatePath, targetPath, variables) {
261
+ const files = readdirSync(templatePath);
262
+ for (const file of files) {
263
+ const filePath = join(templatePath, file);
264
+ const targetFilePath = join(targetPath, file);
265
+ const stat = statSync(filePath);
266
+ if (stat.isDirectory()) {
267
+ mkdirSync(targetFilePath, { recursive: true });
268
+ copyTemplateFiles(filePath, targetFilePath, variables);
269
+ } else {
270
+ let content = readFileSync(filePath, "utf-8");
271
+ content = replaceTemplateVariables(content, variables);
272
+ writeFileSync(targetFilePath, content);
273
+ }
274
+ }
275
+ }
276
+ export default createProject;
277
+ if (import.meta.url === `file://${process.argv[1]}`) {
278
+ createProject().catch((error) => {
279
+ console.error("Error creating project:", error);
280
+ process.exit(1);
281
+ });
282
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,123 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import { createProject } from "./index.mjs";
7
+ async function createTempDir(prefix = "esmx-unit-test-") {
8
+ return mkdtemp(join(tmpdir(), prefix));
9
+ }
10
+ async function cleanupTempDir(tempDir) {
11
+ try {
12
+ await rm(tempDir, { recursive: true, force: true });
13
+ } catch (error) {
14
+ console.warn(`Failed to cleanup temp directory: ${tempDir}`, error);
15
+ }
16
+ }
17
+ describe("createProject unit tests", () => {
18
+ let tmpDir;
19
+ beforeEach(async () => {
20
+ tmpDir = await createTempDir();
21
+ });
22
+ afterEach(async () => {
23
+ await cleanupTempDir(tmpDir);
24
+ });
25
+ it("should handle isDirectoryEmpty edge cases", async () => {
26
+ const hiddenFilesDir = join(tmpDir, "hidden-files-dir");
27
+ await mkdir(hiddenFilesDir, { recursive: true });
28
+ await writeFile(join(hiddenFilesDir, ".hidden-file"), "hidden content");
29
+ await writeFile(join(hiddenFilesDir, ".gitignore"), "node_modules/");
30
+ await createProject({
31
+ argv: ["hidden-files-dir", "--template", "vue2"],
32
+ cwd: tmpDir,
33
+ userAgent: "npm/test"
34
+ });
35
+ expect(existsSync(join(hiddenFilesDir, "package.json"))).toBe(true);
36
+ });
37
+ it("should handle directory creation for nested paths", async () => {
38
+ const deepPath = join(
39
+ tmpDir,
40
+ "very",
41
+ "deep",
42
+ "nested",
43
+ "path",
44
+ "project"
45
+ );
46
+ await createProject({
47
+ argv: ["very/deep/nested/path/project", "--template", "vue2"],
48
+ cwd: tmpDir,
49
+ userAgent: "npm/test"
50
+ });
51
+ expect(existsSync(deepPath)).toBe(true);
52
+ expect(existsSync(join(deepPath, "package.json"))).toBe(true);
53
+ });
54
+ it("should handle file copy with template variable replacement", async () => {
55
+ const projectPath = join(tmpDir, "variable-test");
56
+ await createProject({
57
+ argv: ["variable-test", "--template", "vue2"],
58
+ cwd: tmpDir,
59
+ userAgent: "npm/test"
60
+ });
61
+ const packageJsonPath = join(projectPath, "package.json");
62
+ expect(existsSync(packageJsonPath)).toBe(true);
63
+ const packageContent = require("node:fs").readFileSync(
64
+ packageJsonPath,
65
+ "utf-8"
66
+ );
67
+ const packageJson = JSON.parse(packageContent);
68
+ expect(packageJson.name).toBe("variable-test");
69
+ });
70
+ it("should handle empty directory detection correctly", async () => {
71
+ const emptyDir = join(tmpDir, "empty-dir");
72
+ await mkdir(emptyDir, { recursive: true });
73
+ await createProject({
74
+ argv: ["empty-dir", "--template", "vue2"],
75
+ cwd: tmpDir,
76
+ userAgent: "npm/test"
77
+ });
78
+ expect(existsSync(join(emptyDir, "package.json"))).toBe(true);
79
+ });
80
+ it("should handle mixed file types in directory", async () => {
81
+ const mixedDir = join(tmpDir, "mixed-dir");
82
+ await mkdir(mixedDir, { recursive: true });
83
+ await writeFile(join(mixedDir, ".dotfile"), "hidden");
84
+ await writeFile(join(mixedDir, "regular-file.txt"), "visible");
85
+ await createProject({
86
+ argv: ["mixed-dir", "--template", "vue2", "--force"],
87
+ cwd: tmpDir,
88
+ userAgent: "npm/test"
89
+ });
90
+ expect(existsSync(join(mixedDir, "package.json"))).toBe(true);
91
+ });
92
+ it("should handle various package manager user agents", async () => {
93
+ const testCases = ["npm", "yarn", "pnpm", "bun"];
94
+ for (const userAgent of testCases) {
95
+ const projectName = `test-${userAgent}`;
96
+ const projectPath = join(tmpDir, projectName);
97
+ await createProject({
98
+ argv: [projectName, "--template", "vue2"],
99
+ cwd: tmpDir,
100
+ userAgent: `${userAgent}/test-version`
101
+ });
102
+ expect(existsSync(projectPath)).toBe(true);
103
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
104
+ }
105
+ });
106
+ it("should handle special characters in project names", async () => {
107
+ const specialNames = [
108
+ "project-with-dashes",
109
+ "project_with_underscores",
110
+ "project.with.dots"
111
+ ];
112
+ for (const projectName of specialNames) {
113
+ const projectPath = join(tmpDir, projectName);
114
+ await createProject({
115
+ argv: [projectName, "--template", "vue2"],
116
+ cwd: tmpDir,
117
+ userAgent: "npm/test"
118
+ });
119
+ expect(existsSync(projectPath)).toBe(true);
120
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
121
+ }
122
+ });
123
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,165 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import { createProject } from "./index.mjs";
7
+ async function createTempDir(prefix = "esmx-test-") {
8
+ return mkdtemp(join(tmpdir(), prefix));
9
+ }
10
+ async function cleanupTempDir(tempDir) {
11
+ try {
12
+ await rm(tempDir, { recursive: true, force: true });
13
+ } catch (error) {
14
+ console.warn(`Failed to cleanup temp directory: ${tempDir}`, error);
15
+ }
16
+ }
17
+ describe("create-esmx integration tests", () => {
18
+ let tmpDir;
19
+ beforeEach(async () => {
20
+ tmpDir = await createTempDir();
21
+ });
22
+ afterEach(async () => {
23
+ await cleanupTempDir(tmpDir);
24
+ });
25
+ it("should create project with vue2 template", async () => {
26
+ const projectPath = join(tmpDir, "test-project");
27
+ await createProject({
28
+ argv: ["test-project", "--template", "vue2"],
29
+ cwd: tmpDir,
30
+ userAgent: "npm/test"
31
+ });
32
+ expect(existsSync(projectPath)).toBe(true);
33
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
34
+ expect(existsSync(join(projectPath, "tsconfig.json"))).toBe(true);
35
+ expect(existsSync(join(projectPath, "README.md"))).toBe(true);
36
+ expect(existsSync(join(projectPath, "src"))).toBe(true);
37
+ expect(existsSync(join(projectPath, "src/entry.client.ts"))).toBe(true);
38
+ expect(existsSync(join(projectPath, "src/entry.node.ts"))).toBe(true);
39
+ expect(existsSync(join(projectPath, "src/entry.server.ts"))).toBe(true);
40
+ expect(existsSync(join(projectPath, "src/create-app.ts"))).toBe(true);
41
+ });
42
+ it("should handle --force parameter correctly", async () => {
43
+ const projectPath = join(tmpDir, "test-project");
44
+ await createProject({
45
+ argv: ["test-project", "--template", "vue2"],
46
+ cwd: tmpDir,
47
+ userAgent: "npm/test"
48
+ });
49
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
50
+ await createProject({
51
+ argv: ["test-project", "--template", "vue2", "--force"],
52
+ cwd: tmpDir,
53
+ userAgent: "npm/test"
54
+ });
55
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
56
+ expect(existsSync(join(projectPath, "src"))).toBe(true);
57
+ expect(existsSync(join(projectPath, "src/entry.client.ts"))).toBe(true);
58
+ expect(existsSync(join(projectPath, "src/entry.node.ts"))).toBe(true);
59
+ expect(existsSync(join(projectPath, "src/entry.server.ts"))).toBe(true);
60
+ expect(existsSync(join(projectPath, "src/create-app.ts"))).toBe(true);
61
+ });
62
+ it("should show help information", async () => {
63
+ const originalLog = console.log;
64
+ const logOutput = [];
65
+ console.log = (...args) => {
66
+ logOutput.push(args.join(" "));
67
+ };
68
+ try {
69
+ await createProject({
70
+ argv: ["--help"],
71
+ cwd: tmpDir,
72
+ userAgent: "npm/test"
73
+ });
74
+ const output = logOutput.join("\n");
75
+ expect(output).toContain("Usage");
76
+ expect(output).toContain("Options");
77
+ expect(output).toContain("Examples");
78
+ } finally {
79
+ console.log = originalLog;
80
+ }
81
+ });
82
+ it("should show version information", async () => {
83
+ const originalLog = console.log;
84
+ const logOutput = [];
85
+ console.log = (...args) => {
86
+ logOutput.push(args.join(" "));
87
+ };
88
+ try {
89
+ await createProject({
90
+ argv: ["--version"],
91
+ cwd: tmpDir,
92
+ userAgent: "npm/test"
93
+ });
94
+ const output = logOutput.join("\n");
95
+ expect(output).toMatch(/^\d+\.\d+\.\d+/);
96
+ } finally {
97
+ console.log = originalLog;
98
+ }
99
+ });
100
+ it("should handle creating directory when target directory does not exist", async () => {
101
+ const projectPath = join(tmpDir, "non-existent-parent", "test-project");
102
+ await createProject({
103
+ argv: ["non-existent-parent/test-project", "--template", "vue2"],
104
+ cwd: tmpDir,
105
+ userAgent: "npm/test"
106
+ });
107
+ expect(existsSync(projectPath)).toBe(true);
108
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
109
+ expect(existsSync(join(projectPath, "src"))).toBe(true);
110
+ });
111
+ it("should handle force overwrite for non-empty directory", async () => {
112
+ const projectPath = join(tmpDir, "test-project");
113
+ await mkdir(projectPath, { recursive: true });
114
+ await writeFile(
115
+ join(projectPath, "existing-file.txt"),
116
+ "existing content"
117
+ );
118
+ await createProject({
119
+ argv: ["test-project", "--template", "vue2", "--force"],
120
+ cwd: tmpDir,
121
+ userAgent: "npm/test"
122
+ });
123
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
124
+ expect(existsSync(join(projectPath, "src"))).toBe(true);
125
+ });
126
+ it("should handle force overwrite in current directory", async () => {
127
+ const testFile = join(tmpDir, "existing-file.txt");
128
+ await writeFile(testFile, "existing content");
129
+ await createProject({
130
+ argv: [".", "--template", "vue2", "--force"],
131
+ cwd: tmpDir,
132
+ userAgent: "npm/test"
133
+ });
134
+ expect(existsSync(join(tmpDir, "package.json"))).toBe(true);
135
+ expect(existsSync(join(tmpDir, "src"))).toBe(true);
136
+ expect(existsSync(join(tmpDir, "src/entry.client.ts"))).toBe(true);
137
+ });
138
+ it('should create project in current directory when target is "."', async () => {
139
+ await createProject({
140
+ argv: [".", "--template", "vue2"],
141
+ cwd: tmpDir,
142
+ userAgent: "npm/test"
143
+ });
144
+ expect(existsSync(join(tmpDir, "package.json"))).toBe(true);
145
+ expect(existsSync(join(tmpDir, "src"))).toBe(true);
146
+ expect(existsSync(join(tmpDir, "src/entry.client.ts"))).toBe(true);
147
+ });
148
+ it("should handle various project name formats", async () => {
149
+ const testCases = [
150
+ "simple-name",
151
+ "nested/project-name",
152
+ "deep/nested/project-name"
153
+ ];
154
+ for (const projectName of testCases) {
155
+ const projectPath = join(tmpDir, projectName);
156
+ await createProject({
157
+ argv: [projectName, "--template", "vue2"],
158
+ cwd: tmpDir,
159
+ userAgent: "npm/test"
160
+ });
161
+ expect(existsSync(projectPath)).toBe(true);
162
+ expect(existsSync(join(projectPath, "package.json"))).toBe(true);
163
+ }
164
+ });
165
+ });
@@ -0,0 +1,3 @@
1
+ export { getCommand, type CommandType } from './package-manager';
2
+ export { formatProjectName, type ProjectNameResult } from './project-name';
3
+ export { replaceTemplateVariables } from './template';
@@ -0,0 +1,7 @@
1
+ export {
2
+ getCommand
3
+ } from "./package-manager.mjs";
4
+ export {
5
+ formatProjectName
6
+ } from "./project-name.mjs";
7
+ export { replaceTemplateVariables } from "./template.mjs";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Package manager detection and command generation utilities
3
+ */
4
+ export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
5
+ export type CommandType = 'install' | 'dev' | 'build' | 'start' | 'create' | 'build:type' | 'lint:type';
6
+ /**
7
+ * Get a specific command for the detected package manager
8
+ * Business logic only needs to specify what command to execute
9
+ */
10
+ export declare function getCommand(commandType: CommandType, userAgent?: string): string;
@@ -0,0 +1,49 @@
1
+ const PACKAGE_MANAGER_CONFIG = {
2
+ npm: {
3
+ install: "npm install",
4
+ dev: "npm run dev",
5
+ build: "npm run build",
6
+ start: "npm start",
7
+ create: "npm create esmx@latest",
8
+ "build:type": "npm run build:type",
9
+ "lint:type": "npm run lint:type"
10
+ },
11
+ yarn: {
12
+ install: "yarn install",
13
+ dev: "yarn dev",
14
+ build: "yarn build",
15
+ start: "yarn start",
16
+ create: "yarn create esmx",
17
+ "build:type": "yarn build:type",
18
+ "lint:type": "yarn lint:type"
19
+ },
20
+ pnpm: {
21
+ install: "pnpm install",
22
+ dev: "pnpm dev",
23
+ build: "pnpm build",
24
+ start: "pnpm start",
25
+ create: "pnpm create esmx",
26
+ "build:type": "pnpm build:type",
27
+ "lint:type": "pnpm lint:type"
28
+ },
29
+ bun: {
30
+ install: "bun install",
31
+ dev: "bun dev",
32
+ build: "bun run build",
33
+ start: "bun start",
34
+ create: "bun create esmx",
35
+ "build:type": "bun run build:type",
36
+ "lint:type": "bun run lint:type"
37
+ }
38
+ };
39
+ function detectPackageManager(userAgent) {
40
+ const agent = userAgent || process.env.npm_config_user_agent || "";
41
+ if (agent.includes("pnpm")) return "pnpm";
42
+ if (agent.includes("yarn")) return "yarn";
43
+ if (agent.includes("bun")) return "bun";
44
+ return "npm";
45
+ }
46
+ export function getCommand(commandType, userAgent) {
47
+ const packageManager = detectPackageManager(userAgent);
48
+ return PACKAGE_MANAGER_CONFIG[packageManager][commandType];
49
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for package-manager utilities
3
+ */
4
+ export {};