From 7c49194ca2161a09cc304ba8327533f8176ae0da Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Thu, 21 Apr 2022 16:36:48 -0400 Subject: [PATCH] Feat: [create astro] add directory prompt (#3168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip: add prompt for directory with validation * feat: wire up dir response to cwd * feat: improve error handling on non-empty dirs * fix: update test helpers to execaSync * chore: add .skipped to old tests for clarity * deps: add mocha and chai to create-astro * feat: add directory step test with fixture * feat: update turbo to run create-astro tests again 🥳 * chore: changeset --- .changeset/soft-berries-admire.md | 5 + package.json | 2 +- packages/create-astro/package.json | 6 +- packages/create-astro/src/index.ts | 53 ++++++--- packages/create-astro/test/.gitignore | 1 - ...o.test.js => create-astro.test.js.skipped} | 0 .../create-astro/test/directory-step.test.js | 102 ++++++++++++++++++ ...ernal.test.js => external.test.js.skipped} | 0 .../nonempty-dir/astro-origin-story.php | 0 packages/create-astro/test/helpers.js | 4 +- pnpm-lock.yaml | 8 ++ 11 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 .changeset/soft-berries-admire.md delete mode 100644 packages/create-astro/test/.gitignore rename packages/create-astro/test/{create-astro.test.js => create-astro.test.js.skipped} (100%) create mode 100644 packages/create-astro/test/directory-step.test.js rename packages/create-astro/test/{external.test.js => external.test.js.skipped} (100%) create mode 100644 packages/create-astro/test/fixtures/select-directory/nonempty-dir/astro-origin-story.php diff --git a/.changeset/soft-berries-admire.md b/.changeset/soft-berries-admire.md new file mode 100644 index 000000000..a0d2957ad --- /dev/null +++ b/.changeset/soft-berries-admire.md @@ -0,0 +1,5 @@ +--- +'create-astro': minor +--- + +Add prompt to choose a directory, now defaulting to a separate "./my-astro-site" instead of "." (current directory) diff --git a/package.json b/package.json index badc19ad0..5a9b6e267 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build:ci": "turbo run build:ci --no-deps --scope=astro --scope=create-astro --scope=\"@astrojs/*\"", "build:examples": "turbo run build --scope=\"@example/*\"", "dev": "turbo run dev --no-deps --no-cache --parallel --scope=astro --scope=create-astro --scope=\"@astrojs/*\"", - "test": "turbo run test --filter=!create-astro --concurrency=1", + "test": "turbo run test --concurrency=1", "test:match": "cd packages/astro && pnpm run test:match", "test:templates": "turbo run test --filter=create-astro --concurrency=1", "test:smoke": "node scripts/smoke/index.js", diff --git a/packages/create-astro/package.json b/packages/create-astro/package.json index 3714ddbdd..b28997d3a 100644 --- a/packages/create-astro/package.json +++ b/packages/create-astro/package.json @@ -21,7 +21,7 @@ "build": "astro-scripts build \"src/**/*.ts\" && tsc", "build:ci": "astro-scripts build \"src/**/*.ts\"", "dev": "astro-scripts dev \"src/**/*.ts\"", - "test": "rm -rf test/fixtures && mkdir test/fixtures && node --unhandled-rejections=strict test/create-astro.test.js" + "test": "mocha --exit --timeout 20000" }, "files": [ "dist", @@ -38,8 +38,12 @@ "yargs-parser": "^21.0.1" }, "devDependencies": { + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.0", "@types/yargs-parser": "^21.0.0", "astro-scripts": "workspace:*", + "chai": "^4.3.6", + "mocha": "^9.2.2", "uvu": "^0.5.3" }, "engines": { diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index ec54d250c..2286c13f5 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -28,6 +28,10 @@ export function mkdirp(dir: string) { } } +function isEmpty(dirPath: string) { + return !fs.existsSync(dirPath) || fs.readdirSync(dirPath).length === 0; +} + const { version } = JSON.parse( fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8') ); @@ -47,22 +51,41 @@ export async function main() { spinner.succeed(); - const cwd = (args['_'][2] as string) || '.'; - if (fs.existsSync(cwd)) { - if (fs.readdirSync(cwd).length > 0) { - const response = await prompts({ - type: 'confirm', - name: 'forceOverwrite', - message: 'Directory not empty. Continue [force overwrite]?', - initial: false, - }); - if (!response.forceOverwrite) { - process.exit(1); - } - mkdirp(cwd); + let cwd = args['_'][2] as string; + + if (cwd && isEmpty(cwd)) { + let acknowledgeProjectDir = ora({ + color: 'green', + text: `Using ${bold(cwd)} as project directory.`, + }); + acknowledgeProjectDir.succeed(); + } + + if (!cwd || !isEmpty(cwd)) { + const notEmptyMsg = (dirPath: string) => + `"${bold(dirPath)}" is not empty. Please clear contents or choose a different path.`; + + if (!isEmpty(cwd)) { + let rejectProjectDir = ora({ color: 'red', text: notEmptyMsg(cwd) }); + rejectProjectDir.fail(); } - } else { - mkdirp(cwd); + const dirResponse = await prompts({ + type: 'text', + name: 'directory', + message: 'Where would you like to create your app?', + initial: './my-astro-site', + validate(value) { + if (!isEmpty(value)) { + return notEmptyMsg(value); + } + return true; + }, + }); + cwd = dirResponse.directory; + } + + if (!cwd) { + process.exit(1); } const options = await prompts([ diff --git a/packages/create-astro/test/.gitignore b/packages/create-astro/test/.gitignore deleted file mode 100644 index 116caa127..000000000 --- a/packages/create-astro/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -fixtures diff --git a/packages/create-astro/test/create-astro.test.js b/packages/create-astro/test/create-astro.test.js.skipped similarity index 100% rename from packages/create-astro/test/create-astro.test.js rename to packages/create-astro/test/create-astro.test.js.skipped diff --git a/packages/create-astro/test/directory-step.test.js b/packages/create-astro/test/directory-step.test.js new file mode 100644 index 000000000..843b8cf92 --- /dev/null +++ b/packages/create-astro/test/directory-step.test.js @@ -0,0 +1,102 @@ +import { execa} from 'execa'; +import { fileURLToPath } from 'url'; +import { dirname, resolve } from 'path'; +import {promises, existsSync} from 'fs' + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const createAstroError = new Error('Timed out waiting for create-astro to respond with expected output.') +const timeout = 5000; + +const instructions = { + directory: 'Where would you like to create your app?', + template: 'Which app template would you like to use?', +}; +const inputs = { + nonEmptyDir: './fixtures/select-directory/nonempty-dir', + emptyDir: './fixtures/select-directory/empty-dir', + nonexistentDir: './fixtures/select-directory/banana-dir', +}; + +function promiseWithTimeout(testFn) { + return new Promise((resolve, reject) => { + const timeoutEvent = setTimeout(() => { + reject(createAstroError); + }, timeout); + function resolver() { + clearTimeout(timeoutEvent); + resolve(); + } + testFn(resolver); + }) +} + +function setup(args = []) { + const {stdout, stdin} = execa('../create-astro.mjs', args, { cwd: __dirname }) + return { + stdin, + stdout, + } +} + +describe('[create-astro] select directory', function() { + this.timeout(timeout); + it ('should prompt for directory when none is provided', function () { + return promiseWithTimeout(resolve => { + const {stdout} = setup() + stdout.on('data', chunk => { + if (chunk.includes(instructions.directory)) { + resolve() + } + }) + }) + }) + it ('should NOT proceed on a non-empty directory', function () { + return promiseWithTimeout(resolve => { + const {stdout} = setup([inputs.nonEmptyDir]) + stdout.on('data', chunk => { + if (chunk.includes(instructions.directory)) { + resolve() + } + }) + }) + }) + it ('should proceed on an empty directory', async function () { + const resolvedEmptyDirPath = resolve(__dirname, inputs.emptyDir) + if (!existsSync(resolvedEmptyDirPath)) { + await promises.mkdir(resolvedEmptyDirPath) + } + return promiseWithTimeout(resolve => { + const {stdout} = setup([inputs.emptyDir]) + stdout.on('data', chunk => { + if (chunk.includes(instructions.template)) { + resolve() + } + }) + }) + }) + it ('should proceed when directory does not exist', function () { + return promiseWithTimeout(resolve => { + const {stdout} = setup([inputs.nonexistentDir]) + stdout.on('data', chunk => { + if (chunk.includes(instructions.template)) { + resolve() + } + }) + }) + }) + it ('should error on bad directory selection in prompt', function () { + return promiseWithTimeout(resolve => { + const {stdout, stdin} = setup() + stdout.on('data', chunk => { + if (chunk.includes('Please clear contents or choose a different path.')) { + resolve() + } + if (chunk.includes(instructions.directory)) { + stdin.write(`${inputs.nonEmptyDir}\x0D`) + } + }) + }) + }) +}) diff --git a/packages/create-astro/test/external.test.js b/packages/create-astro/test/external.test.js.skipped similarity index 100% rename from packages/create-astro/test/external.test.js rename to packages/create-astro/test/external.test.js.skipped diff --git a/packages/create-astro/test/fixtures/select-directory/nonempty-dir/astro-origin-story.php b/packages/create-astro/test/fixtures/select-directory/nonempty-dir/astro-origin-story.php new file mode 100644 index 000000000..e69de29bb diff --git a/packages/create-astro/test/helpers.js b/packages/create-astro/test/helpers.js index 6e4445045..4f0b6ec3e 100644 --- a/packages/create-astro/test/helpers.js +++ b/packages/create-astro/test/helpers.js @@ -1,8 +1,8 @@ -import { execa } from 'execa'; +import { execaSync } from 'execa'; import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; -const GITHUB_SHA = process.env.GITHUB_SHA || execa.sync('git', ['rev-parse', 'HEAD']).stdout; // process.env.GITHUB_SHA will be set in CI; if testing locally execa() will gather this +const GITHUB_SHA = process.env.GITHUB_SHA || execaSync('git', ['rev-parse', 'HEAD']).stdout; // process.env.GITHUB_SHA will be set in CI; if testing locally execa() will gather this const FIXTURES_DIR = path.join(fileURLToPath(path.dirname(import.meta.url)), 'fixtures'); const FIXTURES_URL = pathToFileURL(FIXTURES_DIR + '/'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6c860568..ca2cdee4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1202,12 +1202,16 @@ importers: packages/create-astro: specifiers: + '@types/chai': ^4.3.1 '@types/degit': ^2.8.3 + '@types/mocha': ^9.1.0 '@types/prompts': ^2.0.14 '@types/yargs-parser': ^21.0.0 astro-scripts: workspace:* + chai: ^4.3.6 degit: ^2.8.4 kleur: ^4.1.4 + mocha: ^9.2.2 node-fetch: ^3.2.3 ora: ^6.1.0 prompts: ^2.4.2 @@ -1223,8 +1227,12 @@ importers: prompts: 2.4.2 yargs-parser: 21.0.1 devDependencies: + '@types/chai': 4.3.1 + '@types/mocha': 9.1.0 '@types/yargs-parser': 21.0.0 astro-scripts: link:../../scripts + chai: 4.3.6 + mocha: 9.2.2 uvu: 0.5.3 packages/integrations/deno: