From 04755e84658ea10914a09f3d07f302267326d610 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 15 Aug 2023 12:25:24 -0500 Subject: fix(create-astro): update install step (#8089) --- packages/create-astro/src/shell.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) (limited to 'packages/create-astro/src') diff --git a/packages/create-astro/src/shell.ts b/packages/create-astro/src/shell.ts index d2d7ef033..02dd941f1 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 { - 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 }; } -- cgit v1.2.3 From 76a2ba270d04fa178e2c671c9bfef4ff787fdb7b Mon Sep 17 00:00:00 2001 From: natemoo-re Date: Tue, 15 Aug 2023 17:27:28 +0000 Subject: [ci] format --- packages/create-astro/src/shell.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/create-astro/src') diff --git a/packages/create-astro/src/shell.ts b/packages/create-astro/src/shell.ts index 02dd941f1..e6f1295ea 100644 --- a/packages/create-astro/src/shell.ts +++ b/packages/create-astro/src/shell.ts @@ -35,7 +35,7 @@ export async function shell( timeout: opts.timeout, }); const done = new Promise((resolve) => child.on('close', resolve)); - ([stdout, stderr] = await Promise.all([text(child.stdout), text(child.stderr)])); + [stdout, stderr] = await Promise.all([text(child.stdout), text(child.stderr)]); await done; } catch (e) { throw { stdout, stderr, exitCode: 1 }; -- cgit v1.2.3 From e6e1de4f08ddba3a7703136a81f275de1976dc9e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 16 Aug 2023 14:37:43 -0500 Subject: [create-astro] verify connectivity and --template (#8102) * feat(create-astro): verify that --template exists * feat: verify internet connectivity * chore: skip connectivity check on --dry-run * chore: fix lint --- .changeset/silent-baboons-juggle.md | 5 ++ packages/create-astro/src/actions/template.ts | 2 +- packages/create-astro/src/actions/verify.ts | 89 +++++++++++++++++++++++++++ packages/create-astro/src/index.ts | 3 + packages/create-astro/src/messages.ts | 9 ++- packages/create-astro/test/verify.test.js | 41 ++++++++++++ 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 .changeset/silent-baboons-juggle.md create mode 100644 packages/create-astro/src/actions/verify.ts create mode 100644 packages/create-astro/test/verify.test.js (limited to 'packages/create-astro/src') diff --git a/.changeset/silent-baboons-juggle.md b/.changeset/silent-baboons-juggle.md new file mode 100644 index 000000000..bd57c6a8a --- /dev/null +++ b/.changeset/silent-baboons-juggle.md @@ -0,0 +1,5 @@ +--- +'create-astro': patch +--- + +Verify internet connection and that `--template` exists before continuing 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..220c794d6 --- /dev/null +++ b/packages/create-astro/src/actions/verify.ts @@ -0,0 +1,89 @@ +import type { Context } from './context'; + +import dns from 'node:dns/promises'; +import { color } from '@astrojs/cli-kit'; +import { getTemplateTarget } from "./template.js"; +import { error, log, info, bannerAbort } from '../messages.js'; +import fetch from 'node-fetch-native'; + +export async function verify(ctx: Pick) { + 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 { + 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 + +// 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 = + /^(?[\w.-]+\/[\w.-]+)(?[^#]+)?(?#[\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..d23163c7e 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -3,6 +3,7 @@ import { getContext } from './actions/context.js'; import { dependencies } from './actions/dependencies.js'; import { git } from './actions/git.js'; import { help } from './actions/help.js'; +import { verify } from './actions/verify.js'; import { intro } from './actions/intro.js'; import { next } from './actions/next-steps.js'; import { projectName } from './actions/project-name.js'; @@ -30,6 +31,7 @@ export async function main() { } const steps = [ + verify, intro, projectName, template, @@ -51,6 +53,7 @@ export { dependencies, getContext, git, + verify, intro, next, projectName, diff --git a/packages/create-astro/src/messages.ts b/packages/create-astro/src/messages.ts index 89ccddcdb..7a4ec5885 100644 --- a/packages/create-astro/src/messages.ts +++ b/packages/create-astro/src/messages.ts @@ -93,11 +93,16 @@ 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/test/verify.test.js b/packages/create-astro/test/verify.test.js new file mode 100644 index 000000000..6b1ab1344 --- /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') + }); +}); -- cgit v1.2.3 From e0d1439f2aef729cd398fda823d17bed70db1f9a Mon Sep 17 00:00:00 2001 From: natemoo-re Date: Wed, 16 Aug 2023 19:39:52 +0000 Subject: [ci] format --- packages/create-astro/src/actions/verify.ts | 98 +++++++++++++++-------------- packages/create-astro/src/index.ts | 4 +- packages/create-astro/src/messages.ts | 4 +- packages/create-astro/test/verify.test.js | 8 +-- 4 files changed, 58 insertions(+), 56 deletions(-) (limited to 'packages/create-astro/src') diff --git a/packages/create-astro/src/actions/verify.ts b/packages/create-astro/src/actions/verify.ts index 220c794d6..73359142b 100644 --- a/packages/create-astro/src/actions/verify.ts +++ b/packages/create-astro/src/actions/verify.ts @@ -1,56 +1,61 @@ import type { Context } from './context'; -import dns from 'node:dns/promises'; import { color } from '@astrojs/cli-kit'; -import { getTemplateTarget } from "./template.js"; -import { error, log, info, bannerAbort } from '../messages.js'; 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) { - if (!ctx.dryRun) { - const online = await isOnline(); - if (!online) { - bannerAbort(); - log(''); - error('error', `Unable to connect to the internet.`); - ctx.exit(1); - } - } +export async function verify( + ctx: Pick +) { + 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); - } - } + 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 { - return dns.lookup('github.com').then(() => true, () => false); + 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/') + 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" - } - }) + 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}`) - } + // 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; + return res.status === 200; } // Adapted from https://github.com/unjs/giget/blob/main/src/_utils.ts @@ -75,15 +80,14 @@ async function verifyTemplate(tmpl: string, ref?: string) { // 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 = - /^(?[\w.-]+\/[\w.-]+)(?[^#]+)?(?#[\w.-]+)?/; +const GIT_RE = /^(?[\w.-]+\/[\w.-]+)(?[^#]+)?(?#[\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", - }; + 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 d23163c7e..a33e11bff 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -3,12 +3,12 @@ import { getContext } from './actions/context.js'; import { dependencies } from './actions/dependencies.js'; import { git } from './actions/git.js'; import { help } from './actions/help.js'; -import { verify } from './actions/verify.js'; import { intro } from './actions/intro.js'; 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); @@ -53,7 +53,6 @@ export { dependencies, getContext, git, - verify, intro, next, projectName, @@ -61,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 7a4ec5885..fbf276794 100644 --- a/packages/create-astro/src/messages.ts +++ b/packages/create-astro/src/messages.ts @@ -99,9 +99,7 @@ export const banner = async (version: string) => ); export const bannerAbort = () => - log( - `\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}` - ); + log(`\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}`); export const info = async (prefix: string, text: string) => { await sleep(100); diff --git a/packages/create-astro/test/verify.test.js b/packages/create-astro/test/verify.test.js index 6b1ab1344..ecfaba727 100644 --- a/packages/create-astro/test/verify.test.js +++ b/packages/create-astro/test/verify.test.js @@ -7,12 +7,12 @@ 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') + expect(fixture.messages().length).to.equal(0, 'Did not expect `verify` to log any messages'); }); it('missing', async () => { @@ -30,12 +30,12 @@ describe('verify', () => { 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') + 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') + expect(fixture.messages().length).to.equal(0, 'Did not expect `verify` to log any messages'); }); }); -- cgit v1.2.3 From dff0f0f8ddd531c5d92a90ac00fdb86d71f77509 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Thu, 17 Aug 2023 01:35:19 -0700 Subject: Detect Bun package manager in create-astro (#7944) Co-authored-by: Nate Moore --- .changeset/brown-wolves-tan.md | 5 +++++ packages/create-astro/src/actions/next-steps.ts | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/brown-wolves-tan.md (limited to 'packages/create-astro/src') diff --git a/.changeset/brown-wolves-tan.md b/.changeset/brown-wolves-tan.md new file mode 100644 index 000000000..4f11f4534 --- /dev/null +++ b/.changeset/brown-wolves-tan.md @@ -0,0 +1,5 @@ +--- +'create-astro': patch +--- + +Update 'dev' command for Bun users 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) { 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) { -- cgit v1.2.3