create-esmx 3.0.0-rc.35 → 3.0.0-rc.36
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/cli.d.ts +5 -2
- package/dist/{integration.test.mjs → cli.integration.test.mjs} +94 -27
- package/dist/cli.mjs +156 -6
- package/dist/create.d.ts +2 -0
- package/dist/create.mjs +6 -0
- package/dist/index.d.ts +1 -8
- package/dist/index.mjs +1 -276
- package/dist/project.d.ts +5 -0
- package/dist/project.mjs +46 -0
- package/dist/project.test.mjs +177 -0
- package/dist/template.d.ts +17 -0
- package/dist/template.mjs +76 -0
- package/dist/template.test.d.ts +1 -0
- package/dist/template.test.mjs +106 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.mjs +0 -0
- package/dist/utils/template.test.mjs +1 -1
- package/package.json +4 -4
- package/src/{integration.test.ts → cli.integration.test.ts} +107 -52
- package/src/cli.ts +198 -6
- package/src/create.ts +8 -0
- package/src/index.ts +1 -384
- package/src/project.test.ts +225 -0
- package/src/project.ts +72 -0
- package/src/template.test.ts +135 -0
- package/src/template.ts +117 -0
- package/src/types.ts +31 -0
- package/src/utils/template.test.ts +1 -1
- package/template/vue2/package.json +1 -1
- package/template/vue2/src/components/hello-world.vue +0 -2
- package/dist/index.test.mjs +0 -123
- package/src/index.test.ts +0 -159
- /package/dist/{index.test.d.ts → cli.integration.test.d.ts} +0 -0
- /package/dist/{integration.test.d.ts → project.test.d.ts} +0 -0
package/dist/cli.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
6
|
-
import {
|
|
7
|
+
import { cli } from "./cli.mjs";
|
|
8
|
+
import { getAvailableTemplates } from "./template.mjs";
|
|
7
9
|
async function createTempDir(prefix = "esmx-test-") {
|
|
8
10
|
return mkdtemp(join(tmpdir(), prefix));
|
|
9
11
|
}
|
|
@@ -14,7 +16,75 @@ async function cleanupTempDir(tempDir) {
|
|
|
14
16
|
console.warn(`Failed to cleanup temp directory: ${tempDir}`, error);
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
|
-
|
|
19
|
+
async function verifyProjectStructure(projectPath, projectName) {
|
|
20
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
21
|
+
expect(existsSync(join(projectPath, "src"))).toBe(true);
|
|
22
|
+
const requiredFiles = [
|
|
23
|
+
"package.json",
|
|
24
|
+
"tsconfig.json",
|
|
25
|
+
"README.md",
|
|
26
|
+
"src/entry.client.ts",
|
|
27
|
+
"src/entry.node.ts",
|
|
28
|
+
"src/create-app.ts"
|
|
29
|
+
];
|
|
30
|
+
for (const file of requiredFiles) {
|
|
31
|
+
expect(existsSync(join(projectPath, file))).toBe(true);
|
|
32
|
+
if (!existsSync(join(projectPath, file))) {
|
|
33
|
+
throw new Error(`Missing required file: ${file}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const packageJson = JSON.parse(
|
|
37
|
+
readFileSync(join(projectPath, "package.json"), "utf-8")
|
|
38
|
+
);
|
|
39
|
+
const typeCheckCommands = ["vue-tsc --noEmit", "tsc --noEmit"];
|
|
40
|
+
const typeGenCommands = [
|
|
41
|
+
"vue-tsc --declaration --emitDeclarationOnly --noEmit false --outDir dist/src && tsc-alias -p tsconfig.json --outDir dist/src",
|
|
42
|
+
"tsc --declaration --emitDeclarationOnly --outDir dist/src && tsc-alias -p tsconfig.json --outDir dist/src"
|
|
43
|
+
];
|
|
44
|
+
expect(packageJson).toMatchObject({
|
|
45
|
+
name: projectName,
|
|
46
|
+
type: "module",
|
|
47
|
+
private: true,
|
|
48
|
+
scripts: {
|
|
49
|
+
dev: "esmx dev",
|
|
50
|
+
build: "esmx build",
|
|
51
|
+
preview: "esmx preview",
|
|
52
|
+
start: "NODE_ENV=production node dist/index.mjs"
|
|
53
|
+
},
|
|
54
|
+
dependencies: {
|
|
55
|
+
"@esmx/core": expect.any(String)
|
|
56
|
+
},
|
|
57
|
+
devDependencies: {
|
|
58
|
+
typescript: expect.any(String),
|
|
59
|
+
"@types/node": expect.any(String),
|
|
60
|
+
"tsc-alias": expect.any(String)
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
expect(packageJson.scripts["lint:type"]).toBeOneOf(typeCheckCommands);
|
|
64
|
+
expect(packageJson.scripts["build:type"]).toBeOneOf(typeGenCommands);
|
|
65
|
+
const tsconfig = JSON.parse(
|
|
66
|
+
readFileSync(join(projectPath, "tsconfig.json"), "utf-8")
|
|
67
|
+
);
|
|
68
|
+
expect(tsconfig).toMatchObject({
|
|
69
|
+
compilerOptions: {
|
|
70
|
+
module: "ESNext",
|
|
71
|
+
moduleResolution: "node",
|
|
72
|
+
target: "ESNext",
|
|
73
|
+
strict: true,
|
|
74
|
+
baseUrl: ".",
|
|
75
|
+
paths: {
|
|
76
|
+
[`${projectName}/src/*`]: ["./src/*"],
|
|
77
|
+
[`${projectName}/*`]: ["./*"]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
include: ["src"],
|
|
81
|
+
exclude: ["dist", "node_modules"]
|
|
82
|
+
});
|
|
83
|
+
const readmeContent = readFileSync(join(projectPath, "README.md"), "utf-8");
|
|
84
|
+
expect(readmeContent.length).toBeGreaterThan(0);
|
|
85
|
+
expect(readmeContent).toContain(projectName);
|
|
86
|
+
}
|
|
87
|
+
describe("create-esmx CLI integration tests", () => {
|
|
18
88
|
let tmpDir;
|
|
19
89
|
beforeEach(async () => {
|
|
20
90
|
tmpDir = await createTempDir();
|
|
@@ -22,32 +92,29 @@ describe("create-esmx integration tests", () => {
|
|
|
22
92
|
afterEach(async () => {
|
|
23
93
|
await cleanupTempDir(tmpDir);
|
|
24
94
|
});
|
|
25
|
-
it("should create project with
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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);
|
|
95
|
+
it("should create project with all available templates", async () => {
|
|
96
|
+
const templates = getAvailableTemplates();
|
|
97
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
98
|
+
for (const template of templates) {
|
|
99
|
+
const projectName = `test-${template.folder}`;
|
|
100
|
+
const projectPath = join(tmpDir, projectName);
|
|
101
|
+
await cli({
|
|
102
|
+
argv: [projectName, "--template", template.folder],
|
|
103
|
+
cwd: tmpDir,
|
|
104
|
+
userAgent: "npm/test"
|
|
105
|
+
});
|
|
106
|
+
await verifyProjectStructure(projectPath, projectName);
|
|
107
|
+
}
|
|
41
108
|
});
|
|
42
109
|
it("should handle --force parameter correctly", async () => {
|
|
43
110
|
const projectPath = join(tmpDir, "test-project");
|
|
44
|
-
await
|
|
111
|
+
await cli({
|
|
45
112
|
argv: ["test-project", "--template", "vue2"],
|
|
46
113
|
cwd: tmpDir,
|
|
47
114
|
userAgent: "npm/test"
|
|
48
115
|
});
|
|
49
116
|
expect(existsSync(join(projectPath, "package.json"))).toBe(true);
|
|
50
|
-
await
|
|
117
|
+
await cli({
|
|
51
118
|
argv: ["test-project", "--template", "vue2", "--force"],
|
|
52
119
|
cwd: tmpDir,
|
|
53
120
|
userAgent: "npm/test"
|
|
@@ -66,7 +133,7 @@ describe("create-esmx integration tests", () => {
|
|
|
66
133
|
logOutput.push(args.join(" "));
|
|
67
134
|
};
|
|
68
135
|
try {
|
|
69
|
-
await
|
|
136
|
+
await cli({
|
|
70
137
|
argv: ["--help"],
|
|
71
138
|
cwd: tmpDir,
|
|
72
139
|
userAgent: "npm/test"
|
|
@@ -86,7 +153,7 @@ describe("create-esmx integration tests", () => {
|
|
|
86
153
|
logOutput.push(args.join(" "));
|
|
87
154
|
};
|
|
88
155
|
try {
|
|
89
|
-
await
|
|
156
|
+
await cli({
|
|
90
157
|
argv: ["--version"],
|
|
91
158
|
cwd: tmpDir,
|
|
92
159
|
userAgent: "npm/test"
|
|
@@ -99,7 +166,7 @@ describe("create-esmx integration tests", () => {
|
|
|
99
166
|
});
|
|
100
167
|
it("should handle creating directory when target directory does not exist", async () => {
|
|
101
168
|
const projectPath = join(tmpDir, "non-existent-parent", "test-project");
|
|
102
|
-
await
|
|
169
|
+
await cli({
|
|
103
170
|
argv: ["non-existent-parent/test-project", "--template", "vue2"],
|
|
104
171
|
cwd: tmpDir,
|
|
105
172
|
userAgent: "npm/test"
|
|
@@ -115,7 +182,7 @@ describe("create-esmx integration tests", () => {
|
|
|
115
182
|
join(projectPath, "existing-file.txt"),
|
|
116
183
|
"existing content"
|
|
117
184
|
);
|
|
118
|
-
await
|
|
185
|
+
await cli({
|
|
119
186
|
argv: ["test-project", "--template", "vue2", "--force"],
|
|
120
187
|
cwd: tmpDir,
|
|
121
188
|
userAgent: "npm/test"
|
|
@@ -126,7 +193,7 @@ describe("create-esmx integration tests", () => {
|
|
|
126
193
|
it("should handle force overwrite in current directory", async () => {
|
|
127
194
|
const testFile = join(tmpDir, "existing-file.txt");
|
|
128
195
|
await writeFile(testFile, "existing content");
|
|
129
|
-
await
|
|
196
|
+
await cli({
|
|
130
197
|
argv: [".", "--template", "vue2", "--force"],
|
|
131
198
|
cwd: tmpDir,
|
|
132
199
|
userAgent: "npm/test"
|
|
@@ -136,7 +203,7 @@ describe("create-esmx integration tests", () => {
|
|
|
136
203
|
expect(existsSync(join(tmpDir, "src/entry.client.ts"))).toBe(true);
|
|
137
204
|
});
|
|
138
205
|
it('should create project in current directory when target is "."', async () => {
|
|
139
|
-
await
|
|
206
|
+
await cli({
|
|
140
207
|
argv: [".", "--template", "vue2"],
|
|
141
208
|
cwd: tmpDir,
|
|
142
209
|
userAgent: "npm/test"
|
|
@@ -153,7 +220,7 @@ describe("create-esmx integration tests", () => {
|
|
|
153
220
|
];
|
|
154
221
|
for (const projectName of testCases) {
|
|
155
222
|
const projectPath = join(tmpDir, projectName);
|
|
156
|
-
await
|
|
223
|
+
await cli({
|
|
157
224
|
argv: [projectName, "--template", "vue2"],
|
|
158
225
|
cwd: tmpDir,
|
|
159
226
|
userAgent: "npm/test"
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,156 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import {
|
|
2
|
+
cancel,
|
|
3
|
+
intro,
|
|
4
|
+
isCancel,
|
|
5
|
+
note,
|
|
6
|
+
outro,
|
|
7
|
+
select,
|
|
8
|
+
text
|
|
9
|
+
} from "@clack/prompts";
|
|
10
|
+
import minimist from "minimist";
|
|
11
|
+
import color from "picocolors";
|
|
12
|
+
import { createProjectFromTemplate } from "./project.mjs";
|
|
13
|
+
import { getAvailableTemplates, getEsmxVersion } from "./template.mjs";
|
|
14
|
+
import { formatProjectName, getCommand } from "./utils/index.mjs";
|
|
15
|
+
function showHelp(userAgent) {
|
|
16
|
+
const createCmd = getCommand("create", userAgent);
|
|
17
|
+
console.log(`
|
|
18
|
+
${color.reset(color.bold(color.blue("\u{1F680} Create Esmx Project")))}
|
|
19
|
+
|
|
20
|
+
${color.bold("Usage:")}
|
|
21
|
+
${createCmd} [project-name]
|
|
22
|
+
${createCmd} [project-name] [options]
|
|
23
|
+
|
|
24
|
+
${color.bold("Options:")}
|
|
25
|
+
-t, --template <template> Template to use (default: vue2)
|
|
26
|
+
-n, --name <name> Project name or path
|
|
27
|
+
-f, --force Force overwrite existing directory
|
|
28
|
+
-h, --help Show help information
|
|
29
|
+
-v, --version Show version number
|
|
30
|
+
|
|
31
|
+
${color.bold("Examples:")}
|
|
32
|
+
${createCmd} my-project
|
|
33
|
+
${createCmd} my-project -t vue2
|
|
34
|
+
${createCmd} my-project --force
|
|
35
|
+
${createCmd} . -f -t vue2
|
|
36
|
+
|
|
37
|
+
${color.bold("Available Templates:")}
|
|
38
|
+
${getAvailableTemplates().map((t) => ` ${t.folder.padEnd(25)} ${t.description}`).join("\n")}
|
|
39
|
+
|
|
40
|
+
For more information, visit: ${color.cyan("https://esmnext.com")}
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
async function getProjectName(argName, positionalName) {
|
|
44
|
+
const providedName = argName || positionalName;
|
|
45
|
+
if (providedName) {
|
|
46
|
+
return providedName;
|
|
47
|
+
}
|
|
48
|
+
const projectName = await text({
|
|
49
|
+
message: "Project name or path:",
|
|
50
|
+
placeholder: "my-esmx-project",
|
|
51
|
+
validate: (value) => {
|
|
52
|
+
if (!value.trim()) {
|
|
53
|
+
return "Project name or path is required";
|
|
54
|
+
}
|
|
55
|
+
if (!/^[a-zA-Z0-9_.\/@-]+$/.test(value.trim())) {
|
|
56
|
+
return "Project name or path should only contain letters, numbers, hyphens, underscores, dots, and slashes";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return String(projectName).trim();
|
|
61
|
+
}
|
|
62
|
+
async function getTemplateType(argTemplate) {
|
|
63
|
+
const availableTemplates = getAvailableTemplates();
|
|
64
|
+
if (argTemplate && availableTemplates.some((t) => t.folder === argTemplate)) {
|
|
65
|
+
return argTemplate;
|
|
66
|
+
}
|
|
67
|
+
const options = availableTemplates.map((t) => ({
|
|
68
|
+
label: color.reset(color.gray(`${t.folder} - `) + color.bold(t.name)),
|
|
69
|
+
value: t.folder,
|
|
70
|
+
hint: t.description
|
|
71
|
+
}));
|
|
72
|
+
const template = await select({
|
|
73
|
+
message: "Select a template:",
|
|
74
|
+
options
|
|
75
|
+
});
|
|
76
|
+
return template;
|
|
77
|
+
}
|
|
78
|
+
export async function cli(options = {}) {
|
|
79
|
+
const { argv, cwd, userAgent } = options;
|
|
80
|
+
const commandLineArgs = argv || process.argv.slice(2);
|
|
81
|
+
const workingDir = cwd || process.cwd();
|
|
82
|
+
const parsedArgs = minimist(commandLineArgs, {
|
|
83
|
+
string: ["template", "name"],
|
|
84
|
+
boolean: ["help", "version", "force"],
|
|
85
|
+
alias: {
|
|
86
|
+
t: "template",
|
|
87
|
+
n: "name",
|
|
88
|
+
f: "force",
|
|
89
|
+
h: "help",
|
|
90
|
+
v: "version"
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (parsedArgs.help) {
|
|
94
|
+
showHelp(userAgent);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (parsedArgs.version) {
|
|
98
|
+
console.log(getEsmxVersion());
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
console.log();
|
|
102
|
+
intro(
|
|
103
|
+
color.reset(
|
|
104
|
+
color.bold(color.blue("\u{1F680} Welcome to Esmx Project Creator!"))
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
const projectNameInput = await getProjectName(
|
|
108
|
+
parsedArgs.name,
|
|
109
|
+
parsedArgs._[0]
|
|
110
|
+
);
|
|
111
|
+
if (isCancel(projectNameInput)) {
|
|
112
|
+
cancel("Operation cancelled");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const { packageName, targetDir } = formatProjectName(
|
|
116
|
+
projectNameInput,
|
|
117
|
+
workingDir
|
|
118
|
+
);
|
|
119
|
+
const templateType = await getTemplateType(parsedArgs.template);
|
|
120
|
+
if (isCancel(templateType)) {
|
|
121
|
+
cancel("Operation cancelled");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const installCommand = getCommand("install", userAgent);
|
|
125
|
+
const devCommand = getCommand("dev", userAgent);
|
|
126
|
+
const buildCommand = getCommand("build", userAgent);
|
|
127
|
+
const startCommand = getCommand("start", userAgent);
|
|
128
|
+
const buildTypeCommand = getCommand("build:type", userAgent);
|
|
129
|
+
const lintTypeCommand = getCommand("lint:type", userAgent);
|
|
130
|
+
await createProjectFromTemplate(
|
|
131
|
+
targetDir,
|
|
132
|
+
templateType,
|
|
133
|
+
workingDir,
|
|
134
|
+
parsedArgs.force,
|
|
135
|
+
{
|
|
136
|
+
projectName: packageName,
|
|
137
|
+
esmxVersion: getEsmxVersion(),
|
|
138
|
+
installCommand,
|
|
139
|
+
devCommand,
|
|
140
|
+
buildCommand,
|
|
141
|
+
startCommand,
|
|
142
|
+
buildTypeCommand,
|
|
143
|
+
lintTypeCommand
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
const installCmd = installCommand;
|
|
147
|
+
const devCmd = devCommand;
|
|
148
|
+
const nextSteps = [
|
|
149
|
+
color.reset(`1. ${color.cyan(`cd ${targetDir}`)}`),
|
|
150
|
+
color.reset(`2. ${color.cyan(installCmd)}`),
|
|
151
|
+
color.reset(`3. ${color.cyan("git init")} ${color.gray("(optional)")}`),
|
|
152
|
+
color.reset(`4. ${color.cyan(devCmd)}`)
|
|
153
|
+
];
|
|
154
|
+
note(nextSteps.join("\n"), "Next steps");
|
|
155
|
+
outro(color.reset(color.green("Happy coding! \u{1F389}")));
|
|
156
|
+
}
|
package/dist/create.d.ts
ADDED
package/dist/create.mjs
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
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;
|
|
1
|
+
export { cli } from './cli';
|
package/dist/index.mjs
CHANGED
|
@@ -1,276 +1 @@
|
|
|
1
|
-
|
|
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;
|
|
1
|
+
export { cli } from "./cli.mjs";
|