create-esmx 3.0.0-rc.35 → 3.0.0-rc.37
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/README.md +52 -0
- package/README.zh-CN.md +52 -0
- 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/README.md +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
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdir, mkdtemp, rm } 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 {
|
|
7
|
+
copyTemplateFiles,
|
|
8
|
+
getAvailableTemplates,
|
|
9
|
+
getEsmxVersion,
|
|
10
|
+
isDirectoryEmpty
|
|
11
|
+
} from './template';
|
|
12
|
+
|
|
13
|
+
// Test utilities
|
|
14
|
+
async function createTempDir(prefix = 'esmx-template-test-'): Promise<string> {
|
|
15
|
+
return mkdtemp(join(tmpdir(), prefix));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function cleanupTempDir(tempDir: string): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.warn(`Failed to cleanup temp directory: ${tempDir}`, error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('template unit tests', () => {
|
|
27
|
+
let tmpDir: string;
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
tmpDir = await createTempDir();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await cleanupTempDir(tmpDir);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should get Esmx version from package.json', () => {
|
|
38
|
+
const version = getEsmxVersion();
|
|
39
|
+
// 版本可能是 x.y.z 格式,也可能是 x.y.z-rc.n 格式,或 'latest'
|
|
40
|
+
expect(typeof version).toBe('string');
|
|
41
|
+
expect(version.length).toBeGreaterThan(0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should get available templates', () => {
|
|
45
|
+
const templates = getAvailableTemplates();
|
|
46
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
47
|
+
expect(templates[0]).toHaveProperty('folder');
|
|
48
|
+
expect(templates[0]).toHaveProperty('name');
|
|
49
|
+
expect(templates[0]).toHaveProperty('description');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should detect empty directory', async () => {
|
|
53
|
+
// Create empty directory
|
|
54
|
+
const emptyDir = join(tmpDir, 'empty');
|
|
55
|
+
await mkdir(emptyDir);
|
|
56
|
+
expect(isDirectoryEmpty(emptyDir)).toBe(true);
|
|
57
|
+
|
|
58
|
+
// Create directory with only hidden files
|
|
59
|
+
const hiddenDir = join(tmpDir, 'hidden');
|
|
60
|
+
await mkdir(hiddenDir);
|
|
61
|
+
writeFileSync(join(hiddenDir, '.hiddenfile'), 'hidden');
|
|
62
|
+
expect(isDirectoryEmpty(hiddenDir)).toBe(true);
|
|
63
|
+
|
|
64
|
+
// Create directory with visible files
|
|
65
|
+
const nonEmptyDir = join(tmpDir, 'non-empty');
|
|
66
|
+
await mkdir(nonEmptyDir);
|
|
67
|
+
writeFileSync(join(nonEmptyDir, 'visible.txt'), 'visible');
|
|
68
|
+
expect(isDirectoryEmpty(nonEmptyDir)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should copy template files with variable replacement', () => {
|
|
72
|
+
// Create a simple template
|
|
73
|
+
const templateDir = join(tmpDir, 'template');
|
|
74
|
+
const targetDir = join(tmpDir, 'target');
|
|
75
|
+
|
|
76
|
+
mkdirSync(templateDir, { recursive: true });
|
|
77
|
+
mkdirSync(join(templateDir, 'src'), { recursive: true });
|
|
78
|
+
mkdirSync(targetDir, { recursive: true });
|
|
79
|
+
|
|
80
|
+
// Create template files with variables
|
|
81
|
+
writeFileSync(
|
|
82
|
+
join(templateDir, 'package.json'),
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
name: '{{projectName}}',
|
|
85
|
+
version: '1.0.0',
|
|
86
|
+
dependencies: {
|
|
87
|
+
esmx: '{{esmxVersion}}'
|
|
88
|
+
},
|
|
89
|
+
scripts: {
|
|
90
|
+
dev: '{{devCommand}}',
|
|
91
|
+
build: '{{buildCommand}}'
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
writeFileSync(
|
|
97
|
+
join(templateDir, 'src', 'index.ts'),
|
|
98
|
+
'console.log("Welcome to {{projectName}}!");'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Copy with variable replacement
|
|
102
|
+
copyTemplateFiles(templateDir, targetDir, {
|
|
103
|
+
projectName: 'test-project',
|
|
104
|
+
esmxVersion: '1.2.3',
|
|
105
|
+
devCommand: 'npm run dev',
|
|
106
|
+
buildCommand: 'npm run build',
|
|
107
|
+
installCommand: 'npm install',
|
|
108
|
+
startCommand: 'npm start',
|
|
109
|
+
buildTypeCommand: 'npm run build:type',
|
|
110
|
+
lintTypeCommand: 'npm run lint:type'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Check that files were created
|
|
114
|
+
expect(existsSync(join(targetDir, 'package.json'))).toBe(true);
|
|
115
|
+
expect(existsSync(join(targetDir, 'src', 'index.ts'))).toBe(true);
|
|
116
|
+
|
|
117
|
+
// Check variable replacement in package.json
|
|
118
|
+
const packageJson = JSON.parse(
|
|
119
|
+
require('node:fs').readFileSync(
|
|
120
|
+
join(targetDir, 'package.json'),
|
|
121
|
+
'utf-8'
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
expect(packageJson.name).toBe('test-project');
|
|
125
|
+
expect(packageJson.dependencies.esmx).toBe('1.2.3');
|
|
126
|
+
expect(packageJson.scripts.dev).toBe('npm run dev');
|
|
127
|
+
|
|
128
|
+
// Check variable replacement in source file
|
|
129
|
+
const indexContent = require('node:fs').readFileSync(
|
|
130
|
+
join(targetDir, 'src', 'index.ts'),
|
|
131
|
+
'utf-8'
|
|
132
|
+
);
|
|
133
|
+
expect(indexContent).toBe('console.log("Welcome to test-project!");');
|
|
134
|
+
});
|
|
135
|
+
});
|
package/src/template.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
statSync,
|
|
7
|
+
writeFileSync
|
|
8
|
+
} from 'node:fs';
|
|
9
|
+
import { dirname, join, resolve } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import type { TemplateInfo, TemplateVariables } from './types';
|
|
12
|
+
import { replaceTemplateVariables } from './utils/index';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get version of esmx from package.json
|
|
18
|
+
*/
|
|
19
|
+
export function getEsmxVersion(): string {
|
|
20
|
+
try {
|
|
21
|
+
const packageJsonPath = resolve(__dirname, '../package.json');
|
|
22
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
23
|
+
|
|
24
|
+
return packageJson.version || 'latest';
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.warn('Failed to read esmx version, using latest version');
|
|
27
|
+
return 'latest';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get list of available templates
|
|
33
|
+
*/
|
|
34
|
+
export function getAvailableTemplates(): TemplateInfo[] {
|
|
35
|
+
const templateDir = resolve(__dirname, '../template');
|
|
36
|
+
|
|
37
|
+
const templates: TemplateInfo[] = [];
|
|
38
|
+
const templateFolders = readdirSync(templateDir, { withFileTypes: true })
|
|
39
|
+
.filter((dirent) => dirent.isDirectory())
|
|
40
|
+
.map((dirent) => dirent.name);
|
|
41
|
+
|
|
42
|
+
for (const folder of templateFolders) {
|
|
43
|
+
// Use folder name as display name
|
|
44
|
+
const name = folder;
|
|
45
|
+
|
|
46
|
+
// Try to read description from package.json
|
|
47
|
+
const packageJsonPath = resolve(templateDir, folder, 'package.json');
|
|
48
|
+
let description = `${name} template`;
|
|
49
|
+
|
|
50
|
+
if (existsSync(packageJsonPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const packageJson = JSON.parse(
|
|
53
|
+
readFileSync(packageJsonPath, 'utf-8')
|
|
54
|
+
);
|
|
55
|
+
if (packageJson.description) {
|
|
56
|
+
description = packageJson.description;
|
|
57
|
+
}
|
|
58
|
+
templates.push({
|
|
59
|
+
folder,
|
|
60
|
+
name,
|
|
61
|
+
description
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
// JSON parsing failed, skip this template
|
|
65
|
+
console.warn(
|
|
66
|
+
`Warning: Failed to parse package.json for template '${folder}', skipping.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Sort by name alphabetically
|
|
73
|
+
return templates.sort((a, b) => a.name.localeCompare(b.name));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if directory is empty (ignoring hidden files)
|
|
78
|
+
*/
|
|
79
|
+
export function isDirectoryEmpty(dirPath: string): boolean {
|
|
80
|
+
if (!existsSync(dirPath)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const files = readdirSync(dirPath);
|
|
85
|
+
// Only consider non-hidden files and directories
|
|
86
|
+
const nonHiddenFiles = files.filter((file) => !file.startsWith('.'));
|
|
87
|
+
return nonHiddenFiles.length === 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Copy template files to target directory with variable replacement
|
|
92
|
+
*/
|
|
93
|
+
export function copyTemplateFiles(
|
|
94
|
+
templatePath: string,
|
|
95
|
+
targetPath: string,
|
|
96
|
+
variables: TemplateVariables
|
|
97
|
+
): void {
|
|
98
|
+
const files = readdirSync(templatePath);
|
|
99
|
+
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const filePath = join(templatePath, file);
|
|
102
|
+
const targetFilePath = join(targetPath, file);
|
|
103
|
+
const stat = statSync(filePath);
|
|
104
|
+
|
|
105
|
+
if (stat.isDirectory()) {
|
|
106
|
+
mkdirSync(targetFilePath, { recursive: true });
|
|
107
|
+
copyTemplateFiles(filePath, targetFilePath, variables);
|
|
108
|
+
} else {
|
|
109
|
+
let content = readFileSync(filePath, 'utf-8');
|
|
110
|
+
|
|
111
|
+
// Replace all template variables using the utility function
|
|
112
|
+
content = replaceTemplateVariables(content, variables);
|
|
113
|
+
|
|
114
|
+
writeFileSync(targetFilePath, content);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for project creation
|
|
3
|
+
*/
|
|
4
|
+
export interface CliOptions {
|
|
5
|
+
argv?: string[]; // Command line arguments
|
|
6
|
+
cwd?: string; // Working directory
|
|
7
|
+
userAgent?: string; // Package manager user agent
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Template information structure
|
|
12
|
+
*/
|
|
13
|
+
export interface TemplateInfo {
|
|
14
|
+
folder: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Variables used in templates for replacement
|
|
21
|
+
*/
|
|
22
|
+
export interface TemplateVariables extends Record<string, string> {
|
|
23
|
+
projectName: string;
|
|
24
|
+
esmxVersion: string;
|
|
25
|
+
installCommand: string;
|
|
26
|
+
devCommand: string;
|
|
27
|
+
buildCommand: string;
|
|
28
|
+
startCommand: string;
|
|
29
|
+
buildTypeCommand: string;
|
|
30
|
+
lintTypeCommand: string;
|
|
31
|
+
}
|
package/template/vue2/README.md
CHANGED
|
@@ -60,7 +60,7 @@ Visit http://localhost:3000 to see the development environment.
|
|
|
60
60
|
│ │ └── hello-world.vue # Example component with counter functionality
|
|
61
61
|
│ ├── create-app.ts # Vue instance creation
|
|
62
62
|
│ ├── entry.client.ts # Client-side entry
|
|
63
|
-
│ ├── entry.node.ts #
|
|
63
|
+
│ ├── entry.node.ts # Node.js environment entry point
|
|
64
64
|
│ └── entry.server.ts # Server-side rendering functions
|
|
65
65
|
├── package.json
|
|
66
66
|
├── tsconfig.json
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"dev": "esmx dev",
|
|
9
9
|
"build": "esmx build",
|
|
10
10
|
"preview": "esmx preview",
|
|
11
|
-
"start": "
|
|
11
|
+
"start": "NODE_ENV=production node dist/index.mjs",
|
|
12
12
|
"lint:type": "vue-tsc --noEmit",
|
|
13
13
|
"build:type": "vue-tsc --declaration --emitDeclarationOnly --noEmit false --outDir dist/src && tsc-alias -p tsconfig.json --outDir dist/src"
|
|
14
14
|
},
|
|
@@ -28,11 +28,9 @@ const count = ref<number>(0);
|
|
|
28
28
|
<style scoped>
|
|
29
29
|
.card {
|
|
30
30
|
padding: 2em;
|
|
31
|
-
border: 1px solid var(--border-color);
|
|
32
31
|
border-radius: 12px;
|
|
33
32
|
margin: 2.5em 0;
|
|
34
33
|
background-color: var(--bg-card);
|
|
35
|
-
box-shadow: 0 4px 12px var(--shadow-color);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
button {
|
package/dist/index.test.mjs
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
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
|
-
});
|
package/src/index.test.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
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';
|
|
7
|
-
|
|
8
|
-
// Test utilities
|
|
9
|
-
async function createTempDir(prefix = 'esmx-unit-test-'): Promise<string> {
|
|
10
|
-
return mkdtemp(join(tmpdir(), prefix));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async function cleanupTempDir(tempDir: string): Promise<void> {
|
|
14
|
-
try {
|
|
15
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
16
|
-
} catch (error) {
|
|
17
|
-
console.warn(`Failed to cleanup temp directory: ${tempDir}`, error);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe('createProject unit tests', () => {
|
|
22
|
-
let tmpDir: string;
|
|
23
|
-
|
|
24
|
-
beforeEach(async () => {
|
|
25
|
-
tmpDir = await createTempDir();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(async () => {
|
|
29
|
-
await cleanupTempDir(tmpDir);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should handle isDirectoryEmpty edge cases', async () => {
|
|
33
|
-
// Test with directory containing only hidden files
|
|
34
|
-
const hiddenFilesDir = join(tmpDir, 'hidden-files-dir');
|
|
35
|
-
await mkdir(hiddenFilesDir, { recursive: true });
|
|
36
|
-
await writeFile(join(hiddenFilesDir, '.hidden-file'), 'hidden content');
|
|
37
|
-
await writeFile(join(hiddenFilesDir, '.gitignore'), 'node_modules/');
|
|
38
|
-
|
|
39
|
-
await createProject({
|
|
40
|
-
argv: ['hidden-files-dir', '--template', 'vue2'],
|
|
41
|
-
cwd: tmpDir,
|
|
42
|
-
userAgent: 'npm/test'
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Should succeed because hidden files are ignored
|
|
46
|
-
expect(existsSync(join(hiddenFilesDir, 'package.json'))).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should handle directory creation for nested paths', async () => {
|
|
50
|
-
const deepPath = join(
|
|
51
|
-
tmpDir,
|
|
52
|
-
'very',
|
|
53
|
-
'deep',
|
|
54
|
-
'nested',
|
|
55
|
-
'path',
|
|
56
|
-
'project'
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
await createProject({
|
|
60
|
-
argv: ['very/deep/nested/path/project', '--template', 'vue2'],
|
|
61
|
-
cwd: tmpDir,
|
|
62
|
-
userAgent: 'npm/test'
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
expect(existsSync(deepPath)).toBe(true);
|
|
66
|
-
expect(existsSync(join(deepPath, 'package.json'))).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should handle file copy with template variable replacement', async () => {
|
|
70
|
-
const projectPath = join(tmpDir, 'variable-test');
|
|
71
|
-
|
|
72
|
-
await createProject({
|
|
73
|
-
argv: ['variable-test', '--template', 'vue2'],
|
|
74
|
-
cwd: tmpDir,
|
|
75
|
-
userAgent: 'npm/test'
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Verify that package.json contains replaced variables
|
|
79
|
-
const packageJsonPath = join(projectPath, 'package.json');
|
|
80
|
-
expect(existsSync(packageJsonPath)).toBe(true);
|
|
81
|
-
|
|
82
|
-
const packageContent = require('node:fs').readFileSync(
|
|
83
|
-
packageJsonPath,
|
|
84
|
-
'utf-8'
|
|
85
|
-
);
|
|
86
|
-
const packageJson = JSON.parse(packageContent);
|
|
87
|
-
expect(packageJson.name).toBe('variable-test');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should handle empty directory detection correctly', async () => {
|
|
91
|
-
// Test completely empty directory
|
|
92
|
-
const emptyDir = join(tmpDir, 'empty-dir');
|
|
93
|
-
await mkdir(emptyDir, { recursive: true });
|
|
94
|
-
|
|
95
|
-
await createProject({
|
|
96
|
-
argv: ['empty-dir', '--template', 'vue2'],
|
|
97
|
-
cwd: tmpDir,
|
|
98
|
-
userAgent: 'npm/test'
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
expect(existsSync(join(emptyDir, 'package.json'))).toBe(true);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should handle mixed file types in directory', async () => {
|
|
105
|
-
// Test directory with mix of hidden and non-hidden files
|
|
106
|
-
const mixedDir = join(tmpDir, 'mixed-dir');
|
|
107
|
-
await mkdir(mixedDir, { recursive: true });
|
|
108
|
-
await writeFile(join(mixedDir, '.dotfile'), 'hidden');
|
|
109
|
-
await writeFile(join(mixedDir, 'regular-file.txt'), 'visible');
|
|
110
|
-
|
|
111
|
-
// This should require force flag since directory is not empty
|
|
112
|
-
await createProject({
|
|
113
|
-
argv: ['mixed-dir', '--template', 'vue2', '--force'],
|
|
114
|
-
cwd: tmpDir,
|
|
115
|
-
userAgent: 'npm/test'
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
expect(existsSync(join(mixedDir, 'package.json'))).toBe(true);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should handle various package manager user agents', async () => {
|
|
122
|
-
const testCases = ['npm', 'yarn', 'pnpm', 'bun'];
|
|
123
|
-
|
|
124
|
-
for (const userAgent of testCases) {
|
|
125
|
-
const projectName = `test-${userAgent}`;
|
|
126
|
-
const projectPath = join(tmpDir, projectName);
|
|
127
|
-
|
|
128
|
-
await createProject({
|
|
129
|
-
argv: [projectName, '--template', 'vue2'],
|
|
130
|
-
cwd: tmpDir,
|
|
131
|
-
userAgent: `${userAgent}/test-version`
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
expect(existsSync(projectPath)).toBe(true);
|
|
135
|
-
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('should handle special characters in project names', async () => {
|
|
140
|
-
const specialNames = [
|
|
141
|
-
'project-with-dashes',
|
|
142
|
-
'project_with_underscores',
|
|
143
|
-
'project.with.dots'
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
for (const projectName of specialNames) {
|
|
147
|
-
const projectPath = join(tmpDir, projectName);
|
|
148
|
-
|
|
149
|
-
await createProject({
|
|
150
|
-
argv: [projectName, '--template', 'vue2'],
|
|
151
|
-
cwd: tmpDir,
|
|
152
|
-
userAgent: 'npm/test'
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
expect(existsSync(projectPath)).toBe(true);
|
|
156
|
-
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
});
|
|
File without changes
|
|
File without changes
|