diff options
Diffstat (limited to 'packages/create-astro')
-rw-r--r-- | packages/create-astro/CHANGELOG.md | 12 | ||||
-rw-r--r-- | packages/create-astro/src/actions/next-steps.ts | 7 | ||||
-rw-r--r-- | packages/create-astro/src/actions/template.ts | 2 | ||||
-rw-r--r-- | packages/create-astro/src/actions/verify.ts | 93 | ||||
-rw-r--r-- | packages/create-astro/src/index.ts | 3 | ||||
-rw-r--r-- | packages/create-astro/src/messages.ts | 7 | ||||
-rw-r--r-- | packages/create-astro/src/shell.ts | 36 | ||||
-rw-r--r-- | packages/create-astro/test/verify.test.js | 41 |
8 files changed, 180 insertions, 21 deletions
diff --git a/packages/create-astro/CHANGELOG.md b/packages/create-astro/CHANGELOG.md index 519133c71..be4257b58 100644 --- a/packages/create-astro/CHANGELOG.md +++ b/packages/create-astro/CHANGELOG.md @@ -6,6 +6,18 @@ - [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023. +## 3.2.1 + +### Patch Changes + +- [#8089](https://github.com/withastro/astro/pull/8089) [`04755e846`](https://github.com/withastro/astro/commit/04755e84658ea10914a09f3d07f302267326d610) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Fix install step to avoid uncaught errors + +## 3.2.0 + +### Minor Changes + +- [#8077](https://github.com/withastro/astro/pull/8077) [`44cf30a25`](https://github.com/withastro/astro/commit/44cf30a25209b331e6e8a95a4b40a768ede3604a) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Reduce dependency installation size, swap `execa` for light `node:child_process` wrapper + ## 3.1.13 ### Patch Changes diff --git a/packages/create-astro/src/actions/next-steps.ts b/packages/create-astro/src/actions/next-steps.ts index 1b3a0db28..01c1963d9 100644 --- a/packages/create-astro/src/actions/next-steps.ts +++ b/packages/create-astro/src/actions/next-steps.ts @@ -5,7 +5,12 @@ import { nextSteps, say } from '../messages.js'; export async function next(ctx: Pick<Context, 'cwd' | 'pkgManager' | 'skipHouston'>) { let projectDir = path.relative(process.cwd(), ctx.cwd); - const devCmd = ctx.pkgManager === 'npm' ? 'npm run dev' : `${ctx.pkgManager} dev`; + const devCmd = + ctx.pkgManager === 'npm' + ? 'npm run dev' + : ctx.pkgManager === 'bun' + ? 'bun run dev' + : `${ctx.pkgManager} dev`; await nextSteps({ projectDir, devCmd }); if (!ctx.skipHouston) { diff --git a/packages/create-astro/src/actions/template.ts b/packages/create-astro/src/actions/template.ts index ca041642b..887ba69f5 100644 --- a/packages/create-astro/src/actions/template.ts +++ b/packages/create-astro/src/actions/template.ts @@ -66,7 +66,7 @@ const FILES_TO_UPDATE = { }), }; -function getTemplateTarget(tmpl: string, ref = 'latest') { +export function getTemplateTarget(tmpl: string, ref = 'latest') { if (tmpl.startsWith('starlight')) { const [, starter = 'basics'] = tmpl.split('/'); return `withastro/starlight/examples/${starter}`; diff --git a/packages/create-astro/src/actions/verify.ts b/packages/create-astro/src/actions/verify.ts new file mode 100644 index 000000000..73359142b --- /dev/null +++ b/packages/create-astro/src/actions/verify.ts @@ -0,0 +1,93 @@ +import type { Context } from './context'; + +import { color } from '@astrojs/cli-kit'; +import fetch from 'node-fetch-native'; +import dns from 'node:dns/promises'; +import { bannerAbort, error, info, log } from '../messages.js'; +import { getTemplateTarget } from './template.js'; + +export async function verify( + ctx: Pick<Context, 'version' | 'dryRun' | 'template' | 'ref' | 'exit'> +) { + if (!ctx.dryRun) { + const online = await isOnline(); + if (!online) { + bannerAbort(); + log(''); + error('error', `Unable to connect to the internet.`); + ctx.exit(1); + } + } + + if (ctx.template) { + const ok = await verifyTemplate(ctx.template, ctx.ref); + if (!ok) { + bannerAbort(); + log(''); + error('error', `Template ${color.reset(ctx.template)} ${color.dim('could not be found!')}`); + await info('check', 'https://astro.build/examples'); + ctx.exit(1); + } + } +} + +function isOnline(): Promise<boolean> { + return dns.lookup('github.com').then( + () => true, + () => false + ); +} + +async function verifyTemplate(tmpl: string, ref?: string) { + const target = getTemplateTarget(tmpl, ref); + const { repo, subdir, ref: branch } = parseGitURI(target.replace('github:', '')); + const url = new URL(`/repos/${repo}/contents${subdir}?ref=${branch}`, 'https://api.github.com/'); + + let res = await fetch(url.toString(), { + headers: { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }); + + // If users hit a ratelimit, fallback to the GitHub website + if (res.status === 403) { + res = await fetch(`https://github.com/${repo}/tree/${branch}${subdir}`); + } + + return res.status === 200; +} + +// Adapted from https://github.com/unjs/giget/blob/main/src/_utils.ts +// MIT License + +// Copyright (c) Pooya Parsa <pooya@pi0.io> + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +const GIT_RE = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w.-]+)?/; + +function parseGitURI(input: string) { + const m = input.match(GIT_RE)?.groups; + if (!m) throw new Error(`Unable to parse "${input}"`); + return { + repo: m.repo, + subdir: m.subdir || '/', + ref: m.ref ? m.ref.slice(1) : 'main', + }; +} diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index b99aa1cc3..a33e11bff 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -8,6 +8,7 @@ import { next } from './actions/next-steps.js'; import { projectName } from './actions/project-name.js'; import { template } from './actions/template.js'; import { setupTypeScript, typescript } from './actions/typescript.js'; +import { verify } from './actions/verify.js'; import { setStdout } from './messages.js'; const exit = () => process.exit(0); @@ -30,6 +31,7 @@ export async function main() { } const steps = [ + verify, intro, projectName, template, @@ -58,4 +60,5 @@ export { setupTypeScript, template, typescript, + verify, }; diff --git a/packages/create-astro/src/messages.ts b/packages/create-astro/src/messages.ts index 89ccddcdb..fbf276794 100644 --- a/packages/create-astro/src/messages.ts +++ b/packages/create-astro/src/messages.ts @@ -93,11 +93,14 @@ export const getVersion = () => export const log = (message: string) => stdout.write(message + '\n'); export const banner = async (version: string) => log( - `\n${label('astro', color.bgGreen, color.black)} ${ - version ? color.green(color.bold(`v${version}`)) : '' + `\n${label('astro', color.bgGreen, color.black)}${ + version ? ' ' + color.green(color.bold(`v${version}`)) : '' } ${color.bold('Launch sequence initiated.')}` ); +export const bannerAbort = () => + log(`\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}`); + export const info = async (prefix: string, text: string) => { await sleep(100); if (stdout.columns < 80) { diff --git a/packages/create-astro/src/shell.ts b/packages/create-astro/src/shell.ts index d2d7ef033..e6f1295ea 100644 --- a/packages/create-astro/src/shell.ts +++ b/packages/create-astro/src/shell.ts @@ -1,11 +1,10 @@ // This is an extremely simplified version of [`execa`](https://github.com/sindresorhus/execa) // intended to keep our dependency size down -import type { StdioOptions } from 'node:child_process'; +import type { ChildProcess, StdioOptions } from 'node:child_process'; import type { Readable } from 'node:stream'; import { spawn } from 'node:child_process'; import { text as textFromStream } from 'node:stream/consumers'; -import { setTimeout as sleep } from 'node:timers/promises'; export interface ExecaOptions { cwd?: string | URL; @@ -25,25 +24,28 @@ export async function shell( flags: string[], opts: ExecaOptions = {} ): Promise<Output> { - const controller = opts.timeout ? new AbortController() : undefined; - const child = spawn(command, flags, { - cwd: opts.cwd, - shell: true, - stdio: opts.stdio, - signal: controller?.signal, - }); - const stdout = await text(child.stdout); - const stderr = await text(child.stderr); - if (opts.timeout) { - sleep(opts.timeout).then(() => { - controller!.abort(); - throw { stdout, stderr, exitCode: 1 }; + let child: ChildProcess; + let stdout = ''; + let stderr = ''; + try { + child = spawn(command, flags, { + cwd: opts.cwd, + shell: true, + stdio: opts.stdio, + timeout: opts.timeout, }); + const done = new Promise((resolve) => child.on('close', resolve)); + [stdout, stderr] = await Promise.all([text(child.stdout), text(child.stderr)]); + await done; + } catch (e) { + throw { stdout, stderr, exitCode: 1 }; } - await new Promise((resolve) => child.on('exit', resolve)); const { exitCode } = child; + if (exitCode === null) { + throw new Error('Timeout'); + } if (exitCode !== 0) { - throw { stdout, stderr, exitCode }; + throw new Error(stderr); } return { stdout, stderr, exitCode }; } diff --git a/packages/create-astro/test/verify.test.js b/packages/create-astro/test/verify.test.js new file mode 100644 index 000000000..ecfaba727 --- /dev/null +++ b/packages/create-astro/test/verify.test.js @@ -0,0 +1,41 @@ +import { expect } from 'chai'; + +import { verify } from '../dist/index.js'; +import { setup } from './utils.js'; + +describe('verify', () => { + const fixture = setup(); + const exit = (code) => { + throw code; + }; + + it('basics', async () => { + const context = { template: 'basics', exit }; + await verify(context); + expect(fixture.messages().length).to.equal(0, 'Did not expect `verify` to log any messages'); + }); + + it('missing', async () => { + const context = { template: 'missing', exit }; + let err = null; + try { + await verify(context); + } catch (e) { + err = e; + } + expect(err).to.eq(1); + expect(fixture.hasMessage('Template missing does not exist!')); + }); + + it('starlight', async () => { + const context = { template: 'starlight', exit }; + await verify(context); + expect(fixture.messages().length).to.equal(0, 'Did not expect `verify` to log any messages'); + }); + + it('starlight/tailwind', async () => { + const context = { template: 'starlight/tailwind', exit }; + await verify(context); + expect(fixture.messages().length).to.equal(0, 'Did not expect `verify` to log any messages'); + }); +}); |