rsnext/packages/create-next-app/helpers/examples.ts
Marco Moretti b3e45fab5e
feat(cli): use default template when GH is offline (#12194)
fix #12136 

I add a prompt if there is an error when trying to download example files.
Maybe could be better to add an error class and in create-app.ts on every "console.error" trow a new Exception and manage it in catch. What you think ? 👯
2020-05-26 16:39:18 +00:00

118 lines
3.3 KiB
TypeScript

import got from 'got'
import tar from 'tar'
import { Stream } from 'stream'
import { promisify } from 'util'
const pipeline = promisify(Stream.pipeline)
export type RepoInfo = {
username: string
name: string
branch: string
filePath: string
}
export async function isUrlOk(url: string): Promise<boolean> {
const res = await got(url).catch((e) => e)
return res.statusCode === 200
}
export async function getRepoInfo(
url: URL,
examplePath?: string
): Promise<RepoInfo | undefined> {
const [, username, name, t, _branch, ...file] = url.pathname.split('/')
const filePath = examplePath ? examplePath.replace(/^\//, '') : file.join('/')
// Support repos whose entire purpose is to be a NextJS example, e.g.
// https://github.com/:username/:my-cool-nextjs-example-repo-name.
if (t === undefined) {
const infoResponse = await got(
`https://api.github.com/repos/${username}/${name}`
).catch((e) => e)
if (infoResponse.statusCode !== 200) {
return
}
const info = JSON.parse(infoResponse.body)
return { username, name, branch: info['default_branch'], filePath }
}
// If examplePath is available, the branch name takes the entire path
const branch = examplePath
? `${_branch}/${file.join('/')}`.replace(new RegExp(`/${filePath}|/$`), '')
: _branch
if (username && name && branch && t === 'tree') {
return { username, name, branch, filePath }
}
}
export function hasRepo({
username,
name,
branch,
filePath,
}: RepoInfo): Promise<boolean> {
const contentsUrl = `https://api.github.com/repos/${username}/${name}/contents`
const packagePath = `${filePath ? `/${filePath}` : ''}/package.json`
return isUrlOk(contentsUrl + packagePath + `?ref=${branch}`)
}
export function hasExample(name: string): Promise<boolean> {
return isUrlOk(
`https://api.github.com/repos/zeit/next.js/contents/examples/${encodeURIComponent(
name
)}/package.json`
)
}
export function downloadAndExtractRepo(
root: string,
{ username, name, branch, filePath }: RepoInfo
): Promise<void> {
return pipeline(
got.stream(
`https://codeload.github.com/${username}/${name}/tar.gz/${branch}`
),
tar.extract(
{ cwd: root, strip: filePath ? filePath.split('/').length + 1 : 1 },
[`${name}-${branch}${filePath ? `/${filePath}` : ''}`]
)
)
}
export async function downloadAndExtractExample(
root: string,
name: string
): Promise<void> {
if (name === '__internal-testing-retry') {
throw new Error('This is an internal example for testing the CLI.')
}
try {
return await pipeline(
got.stream('https://codeload.github.com/zeit/next.js/tar.gz/canary'),
tar.extract({ cwd: root, strip: 3 }, [`next.js-canary/examples/${name}`])
)
} catch (err) {
// TODO: remove after this change has been landed
if (err?.response?.statusCode === 404) {
return pipeline(
got.stream('https://codeload.github.com/vercel/next.js/tar.gz/canary'),
tar.extract({ cwd: root, strip: 3 }, [
`next.js-canary/examples/${name}`,
])
)
} else {
throw err
}
}
}
export async function listExamples(): Promise<any> {
const res = await got(
'https://api.github.com/repositories/70107786/contents/examples'
)
return JSON.parse(res.body)
}