Skip to content

Commit

Permalink
Feat: [create astro] add directory prompt (#3168)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bholmesdev committed Apr 21, 2022
1 parent 908fffb commit 7c49194
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/soft-berries-admire.md
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion packages/create-astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
53 changes: 38 additions & 15 deletions packages/create-astro/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
);
Expand All @@ -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([
Expand Down
1 change: 0 additions & 1 deletion packages/create-astro/test/.gitignore

This file was deleted.

102 changes: 102 additions & 0 deletions packages/create-astro/test/directory-step.test.js
Original file line number Diff line number Diff line change
@@ -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`)
}
})
})
})
})
File renamed without changes.
Empty file.
4 changes: 2 additions & 2 deletions packages/create-astro/test/helpers.js
Original file line number Diff line number Diff line change
@@ -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 + '/');

Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7c49194

Please sign in to comment.