UNPKG

create-esmx

Version:

A scaffold tool for creating Esmx projects

284 lines (237 loc) 9.15 kB
import { existsSync, 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'; import { getAvailableTemplates } from './template'; async function createTempDir(prefix = 'esmx-test-'): Promise<string> { return mkdtemp(join(tmpdir(), prefix)); } async function cleanupTempDir(tempDir: string): Promise<void> { try { await rm(tempDir, { recursive: true, force: true }); } catch (error) { console.warn(`Failed to cleanup temp directory: ${tempDir}`, error); } } async function verifyProjectStructure( projectPath: string, projectName: string ): Promise<void> { 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: string; 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: string[] = []; console.log = (...args: any[]) => { 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: string[] = []; console.log = (...args: any[]) => { 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); } }); });