Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Create Astro] Improved prompts, template handling, Houston #5088

Merged
merged 12 commits into from
Oct 26, 2022
15 changes: 15 additions & 0 deletions .changeset/smooth-panthers-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'create-astro': minor
---

Introducing your new automated assistant: Houston! ๐ŸŽ‰

```
โ•ญโ”€โ”€โ”€โ”€โ”€โ•ฎ Houston:
โ”‚ โ—  โ—ก โ—  Initiating launch sequence... right... now!
โ•ฐโ”€โ”€โ”€โ”€โ”€โ•ฏ
```

Updates template and TypeScript prompts for clarity and friendliness.

Migrates template copying from [`degit`](https://github.com/Rich-Harris/degit) (unmaintained) to [`giget`](https://github.com/unjs/giget) for stability.
3 changes: 2 additions & 1 deletion packages/create-astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
"tsconfigs"
],
"dependencies": {
"@astrojs/cli-kit": "^0.1.0",
"chalk": "^5.0.1",
"comment-json": "^4.2.3",
"degit": "^2.8.4",
"execa": "^6.1.0",
"giget": "^0.1.7",
"kleur": "^4.1.4",
"ora": "^6.1.0",
"prompts": "^2.4.2",
Expand Down
191 changes: 37 additions & 154 deletions packages/create-astro/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
/* eslint no-console: 'off' */
import { assign, parse, stringify } from 'comment-json';
import degit from 'degit';
import { downloadTemplate } from 'giget';
import { execa, execaCommand } from 'execa';
import fs from 'fs';
import { bgCyan, black, bold, cyan, dim, gray, green, red, reset, yellow } from 'kleur/colors';
import { say, label, color, generateProjectName } from '@astrojs/cli-kit';
import { random } from '@astrojs/cli-kit/utils';
import { bold, dim, green, red, reset, yellow } from 'kleur/colors';
import ora from 'ora';
import os from 'os';
import path from 'path';
import prompts from 'prompts';
import detectPackageManager from 'which-pm-runs';
import yargs from 'yargs-parser';
import { loadWithRocketGradient, rocketAscii } from './gradient.js';
import { defaultLogLevel, logger } from './logger.js';
import { TEMPLATES } from './templates.js';
import { getName, getVersion, welcome, banner, typescriptByDefault, nextSteps, info } from './messages.js';

function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

function logAndWait(message: string, ms = 100) {
console.log(message);
return wait(ms);
}
// NOTE: In the v7.x version of npm, the default behavior of `npm init` was changed
// to no longer require `--` to pass args and instead pass `--` directly to us. This
// broke our arg parser, since `--` is a special kind of flag. Filtering for `--` here
Expand Down Expand Up @@ -83,19 +77,22 @@ function isValidProjectDirectory(dirPath: string) {
return conflicts.length === 0;
}

const { version } = JSON.parse(
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')
);

const FILES_TO_REMOVE = ['.stackblitzrc', 'sandbox.config.json', 'CHANGELOG.md']; // some files are only needed for online editors when using astro.new. Remove for create-astro installs.

// Please also update the installation instructions in the docs at https://github.com/withastro/docs/blob/main/src/pages/en/install/auto.md if you make any changes to the flow or wording here.
export async function main() {
const pkgManager = detectPackageManager()?.name || 'npm';
const [username, version] = await Promise.all([getName(), getVersion()]);
const skipHouston = Object.entries(args).filter(([k]) => k !== '_').length > 0;
natemoo-re marked this conversation as resolved.
Show resolved Hide resolved

logger.debug('Verbose logging turned on');
console.log(`\n${bold('Welcome to Astro!')} ${gray(`(create-astro v${version})`)}`);
console.log(`Lets walk through setting up your new Astro project.\n`);
if (!skipHouston) {
await say([
['Welcome', 'to', label('astro', color.bgGreen, color.black), color.green(`v${version}`) + ',', `${username}!`],
random(welcome),
]);
await banner(version);
}

let cwd = args['_'][2] as string;

Expand All @@ -119,7 +116,7 @@ export async function main() {
type: 'text',
name: 'directory',
message: 'Where would you like to create your new project?',
initial: './my-astro-site',
initial: `./${generateProjectName()}`,
validate(value) {
if (!isValidProjectDirectory(value)) {
return notEmptyMsg(value);
Expand All @@ -141,7 +138,7 @@ export async function main() {
{
type: 'select',
name: 'template',
message: 'Which template would you like to use?',
message: 'How would you like to start your new project?',
choices: TEMPLATES,
},
],
Expand All @@ -161,92 +158,22 @@ export async function main() {
? options.template
: `withastro/astro/examples/${options.template}#latest`;

const emitter = degit(`${templateTarget}${hash}`, {
cache: false,
force: true,
verbose: defaultLogLevel === 'debug' ? true : false,
});

logger.debug('Initialized degit with following config:', `${templateTarget}${hash}`, {
cache: false,
force: true,
verbose: defaultLogLevel === 'debug' ? true : false,
});

// Copy
if (!args.dryRun) {
try {
emitter.on('info', (info) => {
logger.debug(info.message);
await downloadTemplate(`${templateTarget}${hash}`, {
force: true,
provider: 'github',
cwd,
dir: '.',
});
await emitter.clone(cwd);

// degit does not return an error when an invalid template is provided, as such we need to handle this manually
// It's not very pretty, but to the user eye, we just return a nice message and nothing weird happened
if (isValidProjectDirectory(cwd)) {
if (isEmpty(cwd)) {
fs.rmdirSync(cwd);
}
throw new Error(`Error: The provided template (${cyan(options.template)}) does not exist`);
}
} catch (err: any) {
templateSpinner.fail();

// degit is compiled, so the stacktrace is pretty noisy. Only report the stacktrace when using verbose mode.
logger.debug(err);
console.error(red(err.message));

// Warning for issue #655 and other corrupted cache issue
if (
err.message === 'zlib: unexpected end of file' ||
err.message === 'TAR_BAD_ARCHIVE: Unrecognized archive format'
) {
console.log(
yellow(
'Local degit cache seems to be corrupted. For more information check out this issue: https://github.com/withastro/astro/issues/655. '
)
);
const cacheIssueResponse = await prompts({
type: 'confirm',
name: 'cache',
message: 'Would you like us to clear the cache and try again?',
initial: true,
});

if (cacheIssueResponse.cache) {
const homeDirectory = os.homedir();
const cacheDir = path.join(homeDirectory, '.degit', 'github', 'withastro');

fs.rmSync(cacheDir, { recursive: true, force: true, maxRetries: 3 });

templateSpinner = await loadWithRocketGradient('Copying project files...');
try {
await emitter.clone(cwd);
} catch (e: any) {
logger.debug(e);
console.error(red(e.message));
}
} else {
console.log(
"Okay, no worries! To fix this manually, remove the folder '~/.degit/github/withastro' and rerun the command."
);
}
}

// Helpful message when encountering the "could not find commit hash for ..." error
if (err.code === 'MISSING_REF') {
console.log(
yellow(
"This seems to be an issue with degit. Please check if you have 'git' installed on your system, and install it if you don't have (https://git-scm.com)."
)
);
console.log(
yellow(
"If you do have 'git' installed, please run this command with the --verbose flag and file a new issue with the command output here: https://github.com/withastro/astro/issues"
)
);
fs.rmdirSync(cwd);
if (err.message.includes('404')) {
console.error(`Template ${color.underline(options.template)} does not exist!`);
} else {
console.error(err.message);
}

process.exit(1);
}

Expand Down Expand Up @@ -303,7 +230,7 @@ export async function main() {
installSpinner.text = green('Packages installed!');
installSpinner.succeed();
} else {
ora().info(dim(`No problem! Remember to install dependencies after setup.`));
await info('No problem!', 'Remember to install dependencies after setup.')
}

const gitResponse = await prompts(
Expand All @@ -329,7 +256,7 @@ export async function main() {
await execaCommand('git init', { cwd });
ora().succeed('Git repository created!');
} else {
ora().info(dim(`Sounds good! You can come back and run ${cyan(`git init`)} later.`));
await info('Sounds good!', `You can come back and run ${color.reset(`git init`)}${color.dim(' later.')}`)
}

const tsResponse = await prompts(
Expand All @@ -338,25 +265,10 @@ export async function main() {
name: 'typescript',
message: 'How would you like to setup TypeScript?',
choices: [
{
title: 'Relaxed',
value: 'base',
},
{
title: 'Strict (recommended)',
description: 'Enable `strict` typechecking rules',
value: 'strict',
},
{
title: 'Strictest',
description: 'Enable all typechecking rules',
value: 'strictest',
},
{
title: 'I prefer not to use TypeScript',
description: `That's cool too!`,
value: 'optout',
},
{ value: 'strict', title: 'Strict', description: '(recommended)' },
{ value: 'strictest', title: 'Strictest' },
{ value: 'base', title: 'Relaxed' },
{ value: 'unsure', title: `Hmm... I'm not sure` },
natemoo-re marked this conversation as resolved.
Show resolved Hide resolved
],
},
{
Expand All @@ -371,16 +283,9 @@ export async function main() {
}
);

if (tsResponse.typescript === 'optout') {
console.log(``);
ora().warn(yellow(bold(`Astro โค๏ธ TypeScript!`)));
console.log(` Astro supports TypeScript inside of ".astro" component scripts, so`);
console.log(` we still need to create some TypeScript-related files in your project.`);
console.log(` You can safely ignore these files, but don't delete them!`);
console.log(dim(' (ex: tsconfig.json, src/env.d.ts)'));
console.log(``);
if (tsResponse.typescript === 'unsure') {
await typescriptByDefault();
tsResponse.typescript = 'base';
await wait(300);
}
if (args.dryRun) {
ora().info(dim(`--dry-run enabled, skipping.`));
Expand Down Expand Up @@ -415,34 +320,12 @@ export async function main() {
});
ora().succeed('TypeScript settings applied!');
}

ora().succeed('Setup complete.');
ora({ text: green('Ready for liftoff!') }).succeed();
await wait(300);

console.log(`\n${bgCyan(black(' Next steps '))}\n`);


let projectDir = path.relative(process.cwd(), cwd);
const devCmd = pkgManager === 'npm' ? 'npm run dev' : `${pkgManager} dev`;
await nextSteps({ projectDir, devCmd });

// If the project dir is the current dir, no need to tell users to cd in the folder
if (projectDir !== '/') {
await logAndWait(
`You can now ${bold(cyan('cd'))} into the ${bold(cyan(projectDir))} project directory.`
);
}
await logAndWait(
`Run ${bold(cyan(devCmd))} to start the Astro dev server. ${bold(cyan('CTRL-C'))} to close.`
);
await logAndWait(
`Add frameworks like ${bold(cyan('react'))} and ${bold(
cyan('tailwind')
)} to your project using ${bold(cyan('astro add'))}`
);
await logAndWait('');
await logAndWait(`Stuck? Come join us at ${bold(cyan('https://astro.build/chat'))}`, 750);
await logAndWait(dim('Good luck out there, astronaut.'));
await logAndWait('', 300);
await say(['Good luck out there, astronaut!']);
}

function emojiWithFallback(char: string, fallback: string) {
Expand Down
Loading