UNPKG

create-esmx

Version:

A scaffold tool for creating Esmx projects

236 lines (235 loc) 8.04 kB
import { existsSync } from "node:fs"; import { readFileSync } from "node:fs"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { cli } from "./cli.mjs"; import { getAvailableTemplates } from "./template.mjs"; async function createTempDir(prefix = "esmx-test-") { return mkdtemp(join(tmpdir(), prefix)); } async function cleanupTempDir(tempDir) { try { await rm(tempDir, { recursive: true, force: true }); } catch (error) { console.warn(`Failed to cleanup temp directory: ${tempDir}`, error); } } async function verifyProjectStructure(projectPath, projectName) { expect(existsSync(projectPath)).toBe(true); expect(existsSync(join(projectPath, "src"))).toBe(true); const requiredFiles = [ "package.json", "tsconfig.json", "README.md", "src/entry.client.ts", "src/entry.node.ts" ]; for (const file of requiredFiles) { expect(existsSync(join(projectPath, file))).toBe(true); if (!existsSync(join(projectPath, file))) { throw new Error(`Missing required file: ${file}`); } } const packageJson = JSON.parse( readFileSync(join(projectPath, "package.json"), "utf-8") ); const typeCheckCommands = ["vue-tsc --noEmit", "tsc --noEmit"]; const typeGenCommands = [ "vue-tsc --declaration --emitDeclarationOnly --noEmit false --outDir dist/src && tsc-alias -p tsconfig.json --outDir dist/src", "tsc --declaration --emitDeclarationOnly --outDir dist/src && tsc-alias -p tsconfig.json --outDir dist/src" ]; expect(packageJson).toMatchObject({ name: projectName, type: "module", private: true, scripts: { dev: "esmx dev", build: "esmx build", preview: "esmx preview", start: "esmx start" }, dependencies: { "@esmx/core": expect.any(String) }, devDependencies: { typescript: expect.any(String), "@types/node": expect.any(String), "tsc-alias": expect.any(String) } }); expect(packageJson.scripts["lint:type"]).toBeOneOf(typeCheckCommands); expect(packageJson.scripts["build:type"]).toBeOneOf(typeGenCommands); const tsconfig = JSON.parse( readFileSync(join(projectPath, "tsconfig.json"), "utf-8") ); expect(tsconfig).toMatchObject({ compilerOptions: { module: "ESNext", moduleResolution: "node", target: "ESNext", strict: true, baseUrl: ".", paths: { [`${projectName}/src/*`]: ["./src/*"], [`${projectName}/*`]: ["./*"] } }, include: ["src"], exclude: ["dist", "node_modules"] }); const readmeContent = readFileSync(join(projectPath, "README.md"), "utf-8"); expect(readmeContent.length).toBeGreaterThan(0); expect(readmeContent).toContain(projectName); } describe("create-esmx CLI integration tests", () => { let tmpDir; beforeEach(async () => { tmpDir = await createTempDir(); }); afterEach(async () => { await cleanupTempDir(tmpDir); }); it("should create project with all available templates", async () => { const templates = getAvailableTemplates(); expect(templates.length).toBeGreaterThan(0); for (const template of templates) { const projectName = `test-${template.folder}`; const projectPath = join(tmpDir, projectName); await cli({ argv: [projectName, "--template", template.folder], cwd: tmpDir, userAgent: "npm/test" }); await verifyProjectStructure(projectPath, projectName); } }); it("should handle --force parameter correctly", async () => { const projectPath = join(tmpDir, "test-project"); await cli({ argv: ["test-project", "--template", "vue2-csr"], cwd: tmpDir, userAgent: "npm/test" }); expect(existsSync(join(projectPath, "package.json"))).toBe(true); await cli({ argv: ["test-project", "--template", "vue2-csr", "--force"], cwd: tmpDir, userAgent: "npm/test" }); expect(existsSync(join(projectPath, "package.json"))).toBe(true); expect(existsSync(join(projectPath, "src"))).toBe(true); expect(existsSync(join(projectPath, "src/entry.client.ts"))).toBe(true); expect(existsSync(join(projectPath, "src/entry.node.ts"))).toBe(true); expect(existsSync(join(projectPath, "src/entry.server.ts"))).toBe(true); expect(existsSync(join(projectPath, "src/create-app.ts"))).toBe(true); }); it("should show help information", async () => { const originalLog = console.log; const logOutput = []; console.log = (...args) => { logOutput.push(args.join(" ")); }; try { await cli({ argv: ["--help"], cwd: tmpDir, userAgent: "npm/test" }); const output = logOutput.join("\n"); expect(output).toContain("Usage"); expect(output).toContain("Options"); expect(output).toContain("Examples"); } finally { console.log = originalLog; } }); it("should show version information", async () => { const originalLog = console.log; const logOutput = []; console.log = (...args) => { logOutput.push(args.join(" ")); }; try { await cli({ argv: ["--version"], cwd: tmpDir, userAgent: "npm/test" }); const output = logOutput.join("\n"); expect(output).toMatch(/^\d+\.\d+\.\d+/); } finally { console.log = originalLog; } }); it("should handle creating directory when target directory does not exist", async () => { const projectPath = join(tmpDir, "non-existent-parent", "test-project"); await cli({ argv: [ "non-existent-parent/test-project", "--template", "vue2-csr" ], cwd: tmpDir, userAgent: "npm/test" }); expect(existsSync(projectPath)).toBe(true); expect(existsSync(join(projectPath, "package.json"))).toBe(true); expect(existsSync(join(projectPath, "src"))).toBe(true); }); it("should handle force overwrite for non-empty directory", async () => { const projectPath = join(tmpDir, "test-project"); await mkdir(projectPath, { recursive: true }); await writeFile( join(projectPath, "existing-file.txt"), "existing content" ); await cli({ argv: ["test-project", "--template", "vue2-csr", "--force"], cwd: tmpDir, userAgent: "npm/test" }); expect(existsSync(join(projectPath, "package.json"))).toBe(true); expect(existsSync(join(projectPath, "src"))).toBe(true); }); it("should handle force overwrite in current directory", async () => { const testFile = join(tmpDir, "existing-file.txt"); await writeFile(testFile, "existing content"); await cli({ argv: [".", "--template", "vue2-csr", "--force"], cwd: tmpDir, userAgent: "npm/test" }); expect(existsSync(join(tmpDir, "package.json"))).toBe(true); expect(existsSync(join(tmpDir, "src"))).toBe(true); expect(existsSync(join(tmpDir, "src/entry.client.ts"))).toBe(true); }); it('should create project in current directory when target is "."', async () => { await cli({ argv: [".", "--template", "vue2-csr"], cwd: tmpDir, userAgent: "npm/test" }); expect(existsSync(join(tmpDir, "package.json"))).toBe(true); expect(existsSync(join(tmpDir, "src"))).toBe(true); expect(existsSync(join(tmpDir, "src/entry.client.ts"))).toBe(true); }); it("should handle various project name formats", async () => { const testCases = [ "simple-name", "nested/project-name", "deep/nested/project-name" ]; for (const projectName of testCases) { const projectPath = join(tmpDir, projectName); await cli({ argv: [projectName, "--template", "vue2-csr"], cwd: tmpDir, userAgent: "npm/test" }); expect(existsSync(projectPath)).toBe(true); expect(existsSync(join(projectPath, "package.json"))).toBe(true); } }); });