diff --git a/package.json b/package.json index 17ea29d..11c8f78 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "watch": "tsbb watch --disable-babel -f src/cli.ts", "build": "tsbb build --disable-babel -f src/cli.ts", "prettier": "prettier --write \"**/*.{js,ts,less,md,json}\"", - "coverage": "tsbb test --coverage", - "test": "tsbb test" + "coverage": "tsbb test --coverage --detectOpenHandles", + "test": "tsbb test --detectOpenHandles" }, "files": [ "lib", @@ -48,6 +48,9 @@ "prettier --write" ] }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, "devDependencies": { "husky": "7.0.2", "lint-staged": "11.2.0", @@ -60,7 +63,7 @@ "@types/fs-extra": "9.0.13", "@types/minimist": "1.2.2", "@types/node": "16.7.1", - "ora": "6.0.1", + "ora": "5.4.1", "download": "8.0.0", "minimist": "1.2.5", "fs-extra": "10.0.0" diff --git a/src/create.ts b/src/create.ts index 7c13705..7aed6ed 100644 --- a/src/create.ts +++ b/src/create.ts @@ -14,26 +14,26 @@ export type CreateOptions = { output?: string; p?: string; path?: string; -} & ParsedArgs; +} & Omit; -export async function create(argv: CreateOptions, exampleHelp: () => void) { +export async function create(argv: CreateOptions, exampleHelp: string) { const spinner = ora('Downloading Example.'); try { if (!argv.appName || !/^[A-Za-z0-9_\-\.]{1,}$/.test(argv.appName)) { - console.log(`\n \x1b[31mPlease specify the project directory name\x1b[0m.`); - if (!/^[A-Za-z0-9_\-\.]{1,}$/.test(argv.appName)) { - console.log( - ` \x1b[31mThe name directory name\x1b[0m \x1b[33m${argv.appName}\x1b[0m \x1b[31mcontains special characters.\x1b[0m`, - ); - } - exampleHelp && exampleHelp(); - console.log(`\n`); + console.log(`\n \x1b[31mPlease specify the project directory name\x1b[0m. + ${ + !/^[A-Za-z0-9_\-\.]{1,}$/.test(argv.appName) + ? `\x1b[31mThe name directory name\x1b[0m \x1b[33m${argv.appName}\x1b[0m \x1b[31mcontains special characters.\x1b[0m` + : '' + } + ${exampleHelp || ''} + `); return; } if (!argv.path || typeof argv.path !== 'string') { - console.log(`\n Uh oh! \x1b[31mPlease specify download address\x1b[0m.`); - exampleHelp && exampleHelp(); - console.log(`\n`); + console.log( + `\n Uh oh! \x1b[31mPlease specify download address\x1b[0m. ${exampleHelp || ''}`, + ); return; } const projectPath = path.join(process.cwd(), argv.output, argv.appName); @@ -41,23 +41,17 @@ export async function create(argv: CreateOptions, exampleHelp: () => void) { await fs.remove(projectPath); await fs.ensureDir(projectPath); } else if (fs.existsSync(projectPath)) { - console.log( - `\n Uh oh! Looks like there's already a directory called \x1b[31m${argv.appName}\x1b[0m\n`, - `\x1b[33mPlease try a different name or delete that folder.\x1b[0m\n`, - `Path: \x1b[33m${projectPath}\x1b[0m\n`, - ); + console.log(` + Uh oh! Looks like there's already a directory called \x1b[31m${argv.appName}\x1b[0m\n + \x1b[33mPlease try a different name or delete that folder.\x1b[0m\n + Path: \x1b[33m${projectPath}\x1b[0m\n + `); process.exit(1); } await fs.ensureDir(projectPath); - const resultDirTree: string[] = []; - console.log(); - spinner.start(`Downloading \x1b[32m${argv.example}.zip\x1b[0m example.`); + spinner.start(`\nDownloading \x1b[32m${argv.example}.zip\x1b[0m example.`); await download(`${argv.path}${argv.example}.zip`, projectPath, { extract: true, - filter: (file) => { - resultDirTree.push(file.path); - return true; - }, }).on('downloadProgress', (progress) => { if (progress.percent !== 1) { spinner.text = `The example \x1b[32m${argv.example}.zip\x1b[0m has been downloaded ${( @@ -71,42 +65,32 @@ export async function create(argv: CreateOptions, exampleHelp: () => void) { const pkgPath = path.resolve(projectPath, 'package.json'); - console.log( - ` Success! Created \x1b[35m${argv.appName}\x1b[0m at \x1b[32m${projectPath}\x1b[0m`, - ); + let logstr = `Success! Created \x1b[35m${argv.appName}\x1b[0m at \x1b[32m${projectPath}\x1b[0m\n`; if (fs.existsSync(pkgPath)) { - console.log(' Inside that directory, you can run several commands:'); - console.log(''); const pkg = require(pkgPath); + if (pkg.version) { + await fs.writeJSON(pkgPath, { ...pkg, version: '1.0.0' }, { spaces: ' ' }); + } + logstr += `\nInside that directory, you can run several commands:\n`; if (pkg.scripts) { Object.keys(pkg.scripts).forEach((keyname) => { - console.log(` \x1b[36myarn run ${keyname}\x1b[0m`); - console.log(` └─> ${pkg.scripts[keyname]}\n`); + logstr += ` \x1b[36myarn run ${keyname}\x1b[0m\n`; + logstr += ` └─> ${pkg.scripts[keyname]}\n`; }); - } else { - console.log(` ---\n`); - } - if (pkg.version) { - await fs.writeJSON(pkgPath, { ...pkg, version: '1.0.0' }, { spaces: ' ' }); } - console.log(' We suggest that you begin by typing:'); - console.log(''); - console.log(` \x1b[36mcd ${argv.appName}\x1b[0m`); - console.log(` \x1b[36myarn install\x1b[0m`); - console.log(' \x1b[36myarn build\x1b[0m && \x1b[36myarn start\x1b[0m '); + logstr += '\nWe suggest that you begin by typing:\n'; + logstr += ` \x1b[36mcd ${argv.appName}\x1b[0m\n`; + logstr += ' \x1b[36myarn install\x1b[0m\n'; + logstr += ' \x1b[36myarn build\x1b[0m && \x1b[36myarn start\x1b[0m\n'; } - console.log(''); - console.log(' Happy hacking!\n'); + logstr += '\nHappy hacking!\n'; + console.log(logstr); } catch (error) { - spinner.fail(`\x1b[31m${error.message}\x1b[0m`); - if (error && error.statusCode === 404) { - console.log( - ` Error: \x1b[31m${error.statusCode}\x1b[0m, The example \x1b[31m${argv.example}.zip\x1b[0m does not exist.`, - ); - console.log(` Download link: \x1b[31m${argv.path}${argv.example}.zip\x1b[0m`); - } else { - console.log(error); - } + spinner.fail(`\x1b[31m${error.message}\x1b[0m\n Error: \x1b[31m${ + error.statusCode || '000' + }\x1b[0m, The example \x1b[31m${argv.example}.zip\x1b[0m does not exist. + \n Download link: \x1b[31m${argv.path}${argv.example}.zip\x1b[0m + `); process.exit(1); } } diff --git a/src/index.ts b/src/index.ts index 6a1858f..ea3d0e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,23 +20,7 @@ export async function run(): Promise { }, }); if (argvs.h || argvs.help) { - console.log('\n Usage: create-kkt [options] [--help|h]'); - console.log('\n Options:'); - console.log(' --version, -v', 'Show version number'); - console.log(' --help, -h', 'Displays help information.'); - console.log(' --output, -o', 'Output directory.'); - console.log( - ' --example, -e', - 'Example from: \x1b[34mhttps://kktjs.github.io/zip/ \x1b[0m , default: "basic"', - ); - console.log(' --force, -f', 'Overwrite target directory if it exists. default: false'); - console.log( - ' --path, -p', - 'Specify the download target git address. default: "\x1b[34mhttps://kktjs.github.io/zip/ \x1b[0m"', - ); - exampleHelp(); - console.log('\n Copyright 2021'); - console.log('\n'); + console.log(`${helpCli}${helpExample}${helpCopyright}`); return; } const { version } = require('../package.json'); @@ -46,7 +30,7 @@ export async function run(): Promise { } argvs.appName = argvs._[0]; argvs.example = argvs.e = String(argvs.example).toLocaleLowerCase(); - await create(argvs, exampleHelp); + await create(argvs, helpExample); } catch (error) { console.log(`\x1b[31m${error.message}\x1b[0m`); console.log(error); @@ -54,13 +38,21 @@ export async function run(): Promise { } } -export function exampleHelp() { - console.log('\n Example:'); - console.log(' \x1b[35myarn\x1b[0m create kkt \x1b[33mappName\x1b[0m'); - console.log(' \x1b[35mnpx\x1b[0m create-kkt \x1b[33mmy-app\x1b[0m'); - console.log(' \x1b[35mnpm\x1b[0m create kkt \x1b[33mmy-app\x1b[0m'); - console.log(' \x1b[35mnpm\x1b[0m create kkt \x1b[33mmy-app\x1b[0m -f'); - console.log( - ' \x1b[35mnpm\x1b[0m create kkt \x1b[33mmy-app\x1b[0m -p \x1b[34mhttps://kktjs.github.io/zip/\x1b[0m', - ); -} +export const helpCli = `\n Usage: create-kkt [options] [--help|h] + Options: + --version, -v Show version number + --help, -h Displays help information. + --output, -o Output directory. + --example, -e Example from: \x1b[34mhttps://kktjs.github.io/zip/ \x1b[0m , default: "basic" + --force, -f Overwrite target directory if it exists. default: false + --path, -p Specify the download target git address. default: "\x1b[34mhttps://kktjs.github.io/zip/ \x1b[0m" +`; +export const helpExample: string = `\n Example: + + \x1b[35myarn\x1b[0m create kkt \x1b[33mappName\x1b[0m + \x1b[35mnpx\x1b[0m create-kkt \x1b[33mmy-app\x1b[0m + \x1b[35mnpm\x1b[0m create kkt \x1b[33mmy-app\x1b[0m + \x1b[35mnpm\x1b[0m create kkt \x1b[33mmy-app\x1b[0m -f + \x1b[35mnpm\x1b[0m create kkt \x1b[33mmy-app\x1b[0m -p \x1b[34mhttps://kktjs.github.io/zip/\x1b[0m +`; +export const helpCopyright: string = `\n Copyright 2021`; diff --git a/test/cli.test.ts b/test/cli.test.ts index 1be25b6..44923f8 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -1,16 +1,20 @@ /** @jest-environment node */ -import { exampleHelp, run } from '../src/'; +import fs from 'fs-extra'; +import path from 'path'; -it('cliHelp test case', async () => { - expect(exampleHelp()).toBeUndefined(); - // process.argv.push('-v'); - // expect(await run()).toBeUndefined(); - process.argv.push('--version'); - expect(await run()).toBeUndefined(); -}); +console.log = jest.fn(); + +const argv = process.argv.slice(0, 2); -it('version test case', async () => { - expect(exampleHelp()).toBeUndefined(); - process.argv.push('-h'); - expect(await run()).toBeUndefined(); +it('create project. 1', async () => { + process.argv = argv; + process.argv.push('my-app2'); + process.argv.push('-f'); + process.argv.push('--output'); + process.argv.push('test'); + // console.log(process.argv) + await import('../src/cli'); + // console.log(path.resolve(__dirname, 'my-app2')) + expect(await fs.existsSync(path.resolve(__dirname, 'my-app2'))).toBeTruthy(); + await fs.remove('test/my-app2'); }); diff --git a/test/create.test.ts b/test/create.test.ts new file mode 100644 index 0000000..d86762f --- /dev/null +++ b/test/create.test.ts @@ -0,0 +1,87 @@ +/** @jest-environment node */ +import fs from 'fs-extra'; +import { create, CreateOptions } from '../src/create'; +import { helpExample } from '../src'; + +console.log = jest.fn(); + +it('create project. 1', async () => { + const opts: CreateOptions = {}; + await create(opts, ''); + // @ts-ignore + expect( + console.log.mock.calls[0][0].indexOf('Please specify the project directory name') > -1, + ).toBeTruthy(); +}); + +it('create project. 2', async () => { + const opts: CreateOptions = {}; + await create(opts, helpExample); + // @ts-ignore + expect(console.log.mock.calls[0][0].indexOf('Example:') > -1).toBeTruthy(); +}); + +it('create project. options => appName', async () => { + const opts: CreateOptions = { appName: 'my-app' }; + await create(opts, helpExample); + // @ts-ignore + expect(console.log.mock.calls[0][0].indexOf('my-app') > -1).toBeTruthy(); +}); + +it('create project. options => appName', async () => { + const opts: CreateOptions = { appName: [] as any }; + await create(opts, ''); + // @ts-ignore + expect(console.log.mock.calls[0][0].indexOf('The name directory nam') > -1).toBeTruthy(); +}); + +it('create project. options => path', async () => { + const opts: CreateOptions = { appName: 'my-app', path: [] as any }; + await create(opts, ''); + // @ts-ignore + expect(console.log.mock.calls[0][0].indexOf('Please specify download address') > -1).toBeTruthy(); +}); + +it('create project. options => force/example', async () => { + const mockExit = jest.spyOn(process, 'exit').mockImplementation(); + const opts: CreateOptions = { + force: true, + output: 'test', + appName: 'my-app', + path: 'https://kktjs.github.io/zip/', + }; + await create(opts, ''); + expect(mockExit).toHaveBeenCalledWith(1); + mockExit.mockRestore(); +}); + +it('create project. options => force', async () => { + await fs.ensureDir('test/my-app'); + const mockExit = jest.spyOn(process, 'exit').mockImplementation(); + const opts: CreateOptions = { + output: 'test', + appName: 'my-app', + path: 'https://kktjs.github.io/zip/', + }; + await create(opts, ''); + // @ts-ignore + expect( + console.log.mock.calls[0][0].indexOf("Looks like there's already a directory called") > -1, + ).toBeTruthy(); + expect(mockExit).toHaveBeenCalledWith(1); + mockExit.mockRestore(); +}); + +it('create project. options => example', async () => { + const opts: CreateOptions = { + force: true, + output: 'test', + example: 'basic', + appName: 'my-app', + path: 'https://kktjs.github.io/zip/', + }; + await create(opts, ''); + // @ts-ignore + expect(console.log.mock.calls[0][0].indexOf('Success! Created') > -1).toBeTruthy(); + await fs.remove('test/my-app'); +}); diff --git a/test/index.test.ts b/test/index.test.ts index afae16f..defda54 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,49 +1,38 @@ /** @jest-environment node */ +import fs from 'fs-extra'; import path from 'path'; -import FS from 'fs-extra'; -import { create, CreateOptions } from '../src/create'; +import { helpCli, helpExample, helpCopyright, run } from '../src'; +import pkg from '../package.json'; -it('create project.', async () => { - jest.spyOn(process, 'exit').mockImplementation(); - const opts: CreateOptions = { - _: ['my-app'], - f: true, - force: true, - e: 'basic', - example: 'basic', - path: 'https://kktjs.github.io/zip/', - p: 'https://kktjs.github.io/zip/', - output: 'test', - o: 'test', - appName: 'my-app', - }; - await create(opts, () => {}); - const output = path.join(process.cwd(), 'test', 'my-app'); - expect(FS.existsSync(output)).toBeTruthy(); - const dirs = await FS.readdir(output); - expect(dirs).toEqual(['.gitignore', 'README.md', 'package.json', 'public', 'src']); - const pkg = await FS.readJSON(path.join(output, 'package.json')); - expect(pkg.version).toEqual('1.0.0'); - expect(Object.keys(pkg)).toEqual(expect.arrayContaining(['name', 'version'])); +const argv = process.argv.slice(0, 2); +console.log = jest.fn(); + +it('appName test case', async () => { + // await fs.remove('test/my-app1') + process.argv = argv; + process.argv.push('my-app1'); + process.argv.push('-f'); + process.argv.push('--output'); + process.argv.push('test'); + await run(); + // expect(await run()).toBeUndefined(); + expect(await fs.existsSync(path.resolve(__dirname, 'my-app1'))).toBeTruthy(); + await fs.remove('test/my-app1'); }); -it('create project Option appName=undefined.', async () => { - jest.spyOn(process, 'exit').mockImplementation(); - const opts: CreateOptions = { - _: [], - f: true, - force: true, - }; - await create(opts, () => {}); +it('version test case', async () => { + process.argv = argv; + process.argv.push('--version'); + expect(await run()).toBeUndefined(); + // @ts-ignore + expect(console.log.mock.calls[0][0]).toBe(`\n create-kkt v${pkg.version}\n`); }); -it('create project Option path=undefined.', async () => { - jest.spyOn(process, 'exit').mockImplementation(); - const opts: CreateOptions = { - _: ['my-app'], - f: true, - force: true, - appName: 'my-app', - }; - await create(opts, () => {}); +it('help test case', async () => { + process.argv = argv; + expect(typeof helpCli).toEqual('string'); + expect(typeof helpExample).toEqual('string'); + expect(typeof helpCopyright).toEqual('string'); + process.argv.push('--help'); + expect(await run()).toBeUndefined(); });