mirror of
https://github.com/withastro/astro.git
synced 2024-12-30 22:03:56 -05:00
Feat: [create astro] add directory prompt (#3168)
* 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
This commit is contained in:
parent
908fffb5ec
commit
7c49194ca2
11 changed files with 161 additions and 20 deletions
5
.changeset/soft-berries-admire.md
Normal file
5
.changeset/soft-berries-admire.md
Normal file
|
@ -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)
|
|
@ -13,7 +13,7 @@
|
||||||
"build:ci": "turbo run build:ci --no-deps --scope=astro --scope=create-astro --scope=\"@astrojs/*\"",
|
"build:ci": "turbo run build:ci --no-deps --scope=astro --scope=create-astro --scope=\"@astrojs/*\"",
|
||||||
"build:examples": "turbo run build --scope=\"@example/*\"",
|
"build:examples": "turbo run build --scope=\"@example/*\"",
|
||||||
"dev": "turbo run dev --no-deps --no-cache --parallel --scope=astro --scope=create-astro --scope=\"@astrojs/*\"",
|
"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:match": "cd packages/astro && pnpm run test:match",
|
||||||
"test:templates": "turbo run test --filter=create-astro --concurrency=1",
|
"test:templates": "turbo run test --filter=create-astro --concurrency=1",
|
||||||
"test:smoke": "node scripts/smoke/index.js",
|
"test:smoke": "node scripts/smoke/index.js",
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
||||||
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
||||||
"dev": "astro-scripts dev \"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": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
@ -38,8 +38,12 @@
|
||||||
"yargs-parser": "^21.0.1"
|
"yargs-parser": "^21.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.3.1",
|
||||||
|
"@types/mocha": "^9.1.0",
|
||||||
"@types/yargs-parser": "^21.0.0",
|
"@types/yargs-parser": "^21.0.0",
|
||||||
"astro-scripts": "workspace:*",
|
"astro-scripts": "workspace:*",
|
||||||
|
"chai": "^4.3.6",
|
||||||
|
"mocha": "^9.2.2",
|
||||||
"uvu": "^0.5.3"
|
"uvu": "^0.5.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
@ -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(
|
const { version } = JSON.parse(
|
||||||
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')
|
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')
|
||||||
);
|
);
|
||||||
|
@ -47,22 +51,41 @@ export async function main() {
|
||||||
|
|
||||||
spinner.succeed();
|
spinner.succeed();
|
||||||
|
|
||||||
const cwd = (args['_'][2] as string) || '.';
|
let cwd = args['_'][2] as string;
|
||||||
if (fs.existsSync(cwd)) {
|
|
||||||
if (fs.readdirSync(cwd).length > 0) {
|
if (cwd && isEmpty(cwd)) {
|
||||||
const response = await prompts({
|
let acknowledgeProjectDir = ora({
|
||||||
type: 'confirm',
|
color: 'green',
|
||||||
name: 'forceOverwrite',
|
text: `Using ${bold(cwd)} as project directory.`,
|
||||||
message: 'Directory not empty. Continue [force overwrite]?',
|
});
|
||||||
initial: false,
|
acknowledgeProjectDir.succeed();
|
||||||
});
|
}
|
||||||
if (!response.forceOverwrite) {
|
|
||||||
process.exit(1);
|
if (!cwd || !isEmpty(cwd)) {
|
||||||
}
|
const notEmptyMsg = (dirPath: string) =>
|
||||||
mkdirp(cwd);
|
`"${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 {
|
const dirResponse = await prompts({
|
||||||
mkdirp(cwd);
|
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([
|
const options = await prompts([
|
||||||
|
|
1
packages/create-astro/test/.gitignore
vendored
1
packages/create-astro/test/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
fixtures
|
|
102
packages/create-astro/test/directory-step.test.js
Normal file
102
packages/create-astro/test/directory-step.test.js
Normal file
|
@ -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`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
0
packages/create-astro/test/fixtures/select-directory/nonempty-dir/astro-origin-story.php
vendored
Normal file
0
packages/create-astro/test/fixtures/select-directory/nonempty-dir/astro-origin-story.php
vendored
Normal file
|
@ -1,8 +1,8 @@
|
||||||
import { execa } from 'execa';
|
import { execaSync } from 'execa';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath, pathToFileURL } from 'url';
|
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_DIR = path.join(fileURLToPath(path.dirname(import.meta.url)), 'fixtures');
|
||||||
const FIXTURES_URL = pathToFileURL(FIXTURES_DIR + '/');
|
const FIXTURES_URL = pathToFileURL(FIXTURES_DIR + '/');
|
||||||
|
|
||||||
|
|
|
@ -1202,12 +1202,16 @@ importers:
|
||||||
|
|
||||||
packages/create-astro:
|
packages/create-astro:
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@types/chai': ^4.3.1
|
||||||
'@types/degit': ^2.8.3
|
'@types/degit': ^2.8.3
|
||||||
|
'@types/mocha': ^9.1.0
|
||||||
'@types/prompts': ^2.0.14
|
'@types/prompts': ^2.0.14
|
||||||
'@types/yargs-parser': ^21.0.0
|
'@types/yargs-parser': ^21.0.0
|
||||||
astro-scripts: workspace:*
|
astro-scripts: workspace:*
|
||||||
|
chai: ^4.3.6
|
||||||
degit: ^2.8.4
|
degit: ^2.8.4
|
||||||
kleur: ^4.1.4
|
kleur: ^4.1.4
|
||||||
|
mocha: ^9.2.2
|
||||||
node-fetch: ^3.2.3
|
node-fetch: ^3.2.3
|
||||||
ora: ^6.1.0
|
ora: ^6.1.0
|
||||||
prompts: ^2.4.2
|
prompts: ^2.4.2
|
||||||
|
@ -1223,8 +1227,12 @@ importers:
|
||||||
prompts: 2.4.2
|
prompts: 2.4.2
|
||||||
yargs-parser: 21.0.1
|
yargs-parser: 21.0.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/chai': 4.3.1
|
||||||
|
'@types/mocha': 9.1.0
|
||||||
'@types/yargs-parser': 21.0.0
|
'@types/yargs-parser': 21.0.0
|
||||||
astro-scripts: link:../../scripts
|
astro-scripts: link:../../scripts
|
||||||
|
chai: 4.3.6
|
||||||
|
mocha: 9.2.2
|
||||||
uvu: 0.5.3
|
uvu: 0.5.3
|
||||||
|
|
||||||
packages/integrations/deno:
|
packages/integrations/deno:
|
||||||
|
|
Loading…
Reference in a new issue