Skip to content

Commit

Permalink
[create-astro] verify connectivity and --template (#8102)
Browse files Browse the repository at this point in the history
* feat(create-astro): verify that --template exists

* feat: verify internet connectivity

* chore: skip connectivity check on --dry-run

* chore: fix lint
  • Loading branch information
natemoo-re authored Aug 16, 2023
1 parent 42ed85b commit e6e1de4
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-baboons-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'create-astro': patch
---

Verify internet connection and that `--template` exists before continuing
2 changes: 1 addition & 1 deletion packages/create-astro/src/actions/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand Down
89 changes: 89 additions & 0 deletions packages/create-astro/src/actions/verify.ts
Original file line number Diff line number Diff line change
@@ -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",
};
}
3 changes: 3 additions & 0 deletions packages/create-astro/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -30,6 +31,7 @@ export async function main() {
}

const steps = [
verify,
intro,
projectName,
template,
Expand All @@ -51,6 +53,7 @@ export {
dependencies,
getContext,
git,
verify,
intro,
next,
projectName,
Expand Down
9 changes: 7 additions & 2 deletions packages/create-astro/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
41 changes: 41 additions & 0 deletions packages/create-astro/test/verify.test.js
Original file line number Diff line number Diff line change
@@ -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')
});
});

0 comments on commit e6e1de4

Please sign in to comment.