summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Nate Moore <natemoo-re@users.noreply.github.com> 2023-08-16 14:37:43 -0500
committerGravatar GitHub <noreply@github.com> 2023-08-16 14:37:43 -0500
commite6e1de4f08ddba3a7703136a81f275de1976dc9e (patch)
treecf053566e682cb866e028d0dbbc11614cca7660f
parent42ed85b3e263bb5e28725395924d0b595e1e0041 (diff)
downloadastro-e6e1de4f08ddba3a7703136a81f275de1976dc9e.tar.gz
astro-e6e1de4f08ddba3a7703136a81f275de1976dc9e.tar.zst
astro-e6e1de4f08ddba3a7703136a81f275de1976dc9e.zip
[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
Diffstat (limited to '')
-rw-r--r--.changeset/silent-baboons-juggle.md5
-rw-r--r--packages/create-astro/src/actions/template.ts2
-rw-r--r--packages/create-astro/src/actions/verify.ts89
-rw-r--r--packages/create-astro/src/index.ts3
-rw-r--r--packages/create-astro/src/messages.ts9
-rw-r--r--packages/create-astro/test/verify.test.js41
6 files changed, 146 insertions, 3 deletions
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<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..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')
+ });
+});