From a9e46899218b25ec2da9e56c204debcdce980c5c Mon Sep 17 00:00:00 2001 From: John Berger Date: Wed, 5 Oct 2022 13:03:24 -0400 Subject: [PATCH 1/6] allow private github repos to use the latest release url --- packages/remix-dev/cli/create.ts | 65 +++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/packages/remix-dev/cli/create.ts b/packages/remix-dev/cli/create.ts index 8f978a0be00..a26f7058ea3 100644 --- a/packages/remix-dev/cli/create.ts +++ b/packages/remix-dev/cli/create.ts @@ -300,10 +300,14 @@ async function downloadAndExtractTarball( // asset id let info = getGithubReleaseAssetInfo(url); headers.Accept = "application/vnd.github.v3+json"; - let response = await fetch( - `https://api.github.com/repos/${info.owner}/${info.name}/releases/tags/${info.tag}`, - { headers } - ); + + let releaseUrl = + info.tag === "latest" + ? `https://api.github.com/repos/${info.owner}/${info.name}/releases/latest` + : `https://api.github.com/repos/${info.owner}/${info.name}/releases/tags/${info.tag}`; + + let response = await fetch(releaseUrl, { headers }); + if (response.status !== 200) { throw Error( "🚨 There was a problem fetching the file from GitHub. The request " + @@ -311,9 +315,13 @@ async function downloadAndExtractTarball( ); } let body = await response.json(); - let assetId: number | undefined = body?.assets?.find( - (a: any) => a?.browser_download_url === url - )?.id; + // If the release is "latest", the url won't match the download url, so we grab the id from the response + let assetId: number | undefined = + info.tag === "latest" + ? body?.assets?.find((a: any) => + a?.browser_download_url?.includes(info.asset) + )?.id + : body?.assets?.find((a: any) => a?.browser_download_url === url)?.id; if (!assetId) { throw Error( "🚨 There was a problem fetching the file from GitHub. No asset was " + @@ -424,8 +432,16 @@ function getGithubUrl(info: Omit) { } function isGithubReleaseAssetUrl(url: string) { + /** + * Accounts for the following formats: + * https://github.com/owner/repository/releases/download/v0.0.1/stack.tar.gz + * ~or~ + * https://github.com/owner/repository/releases/latest/download/stack.tar.gz + */ return ( - url.startsWith("https://github.com") && url.includes("/releases/download/") + url.startsWith("https://github.com") && + (url.includes("/releases/download/") || + url.includes("/releases/latest/download/")) ); } interface ReleaseAssetInfo { @@ -436,18 +452,30 @@ interface ReleaseAssetInfo { tag: string; } function getGithubReleaseAssetInfo(browserUrl: string): ReleaseAssetInfo { - // for example, https://github.com/owner/repository/releases/download/v0.0.1/stack.tar.gz + /** + * https://github.com/owner/repository/releases/download/v0.0.1/stack.tar.gz + * ~or~ + * https://github.com/owner/repository/releases/latest/download/stack.tar.gz + */ + let url = new URL(browserUrl); - let [, owner, name, , , tag, asset] = url.pathname.split("/") as [ + let [, owner, name, , downloadOrLatest, tag, asset] = url.pathname.split( + "/" + ) as [ _: string, Owner: string, Name: string, Releases: string, - Download: string, + DownloadOrLatest: string, Tag: string, AssetFilename: string ]; + if (downloadOrLatest === "latest" && tag === "download") { + // handle the Github URL quirk for latest releases + tag = "latest"; + } + return { browserUrl, owner, @@ -573,7 +601,10 @@ export async function validateTemplate( let headers: Record = {}; if (isGithubReleaseAssetUrl(input)) { let info = getGithubReleaseAssetInfo(input); - apiUrl = `https://api.github.com/repos/${info.owner}/${info.name}/releases/tags/${info.tag}`; + apiUrl = + info.tag === "latest" + ? `https://api.github.com/repos/${info.owner}/${info.name}/releases/latest` + : `https://api.github.com/repos/${info.owner}/${info.name}/releases/tags/${info.tag}`; headers = { Authorization: `token ${options?.githubToken}`, Accept: "application/vnd.github.v3+json", @@ -595,9 +626,17 @@ export async function validateTemplate( switch (response.status) { case 200: if (isGithubReleaseAssetUrl(input)) { + let info = getGithubReleaseAssetInfo(input); let body = await response.json(); if ( - !body?.assets?.some((a: any) => a?.browser_download_url === input) + // if a tag is specified, make sure it exists. + !body?.assets?.some( + (a: any) => a?.browser_download_url === input + ) && + // if the latest is specified, make sure there is an asset + !body?.assets?.some((a: any) => + a?.browser_download_url?.includes(info.asset) + ) ) { throw Error( "🚨 The template file could not be verified. Please double check " + From 9db631cbb77e67962448b9150bb66391c1070c21 Mon Sep 17 00:00:00 2001 From: John Berger Date: Wed, 5 Oct 2022 13:06:55 -0400 Subject: [PATCH 2/6] add test --- packages/remix-dev/__tests__/create-test.ts | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/remix-dev/__tests__/create-test.ts b/packages/remix-dev/__tests__/create-test.ts index 5c9e5102daf..21abc9fe427 100644 --- a/packages/remix-dev/__tests__/create-test.ts +++ b/packages/remix-dev/__tests__/create-test.ts @@ -260,6 +260,31 @@ describe("the create command", () => { ); }); + it("succeeds for links to latest private github release tarballs when including token", async () => { + let projectDir = await getProjectDir( + "latest-private-release-tarball-with-token" + ); + await run([ + "create", + projectDir, + "--template", + "https://github.com/private-org/private-repo/releases/latest/download/stack.tar.gz", + "--no-install", + "--typescript", + "--token", + "valid-token", + ]); + expect(output.trim()).toBe( + getOptOutOfInstallMessage() + + "\n\n" + + getSuccessMessage( + path.join("", "latest-private-release-tarball-with-token") + ) + ); + expect(fse.existsSync(path.join(projectDir, "package.json"))).toBeTruthy(); + expect(fse.existsSync(path.join(projectDir, "app/root.tsx"))).toBeTruthy(); + }); + it("succeeds for private github release tarballs when including token", async () => { let projectDir = await getProjectDir("private-release-tarball-with-token"); await run([ From 6a086cfd71afbb511507161ad63121a5a1f68d13 Mon Sep 17 00:00:00 2001 From: John Berger Date: Wed, 5 Oct 2022 13:10:09 -0400 Subject: [PATCH 3/6] add line in documentation referencing this --- docs/other-api/dev.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/other-api/dev.md b/docs/other-api/dev.md index 5abcfd5b35f..66c58da4d79 100644 --- a/docs/other-api/dev.md +++ b/docs/other-api/dev.md @@ -40,6 +40,7 @@ remix create ./my-app --template :username/:repo remix create ./my-app --template https://github.com/:username/:repo remix create ./my-app --template https://github.com/:username/:repo/tree/:branch remix create ./my-app --template https://github.com/:username/:repo/archive/refs/tags/:tag.tar.gz +remix create ./my-app --template https://github.com/:username/:repo/releases/latest/download/:tag.tar.gz remix create ./my-app --template https://example.com/remix-template.tar.gz ``` From d3d74f7d57795f297e25e30cc1e9fa25068e5622 Mon Sep 17 00:00:00 2001 From: John Berger Date: Wed, 5 Oct 2022 14:16:58 -0400 Subject: [PATCH 4/6] clean up private repository logic and tests --- packages/remix-dev/__tests__/create-test.ts | 27 +-------------------- packages/remix-dev/cli/create.ts | 2 +- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/packages/remix-dev/__tests__/create-test.ts b/packages/remix-dev/__tests__/create-test.ts index 21abc9fe427..749fc518e60 100644 --- a/packages/remix-dev/__tests__/create-test.ts +++ b/packages/remix-dev/__tests__/create-test.ts @@ -260,38 +260,13 @@ describe("the create command", () => { ); }); - it("succeeds for links to latest private github release tarballs when including token", async () => { - let projectDir = await getProjectDir( - "latest-private-release-tarball-with-token" - ); - await run([ - "create", - projectDir, - "--template", - "https://github.com/private-org/private-repo/releases/latest/download/stack.tar.gz", - "--no-install", - "--typescript", - "--token", - "valid-token", - ]); - expect(output.trim()).toBe( - getOptOutOfInstallMessage() + - "\n\n" + - getSuccessMessage( - path.join("", "latest-private-release-tarball-with-token") - ) - ); - expect(fse.existsSync(path.join(projectDir, "package.json"))).toBeTruthy(); - expect(fse.existsSync(path.join(projectDir, "app/root.tsx"))).toBeTruthy(); - }); - it("succeeds for private github release tarballs when including token", async () => { let projectDir = await getProjectDir("private-release-tarball-with-token"); await run([ "create", projectDir, "--template", - "https://example.com/remix-stack.tar.gz", + "https://github.com/private-org/private-repo/releases/download/v0.0.1/stack.tar.gz", "--no-install", "--typescript", "--token", diff --git a/packages/remix-dev/cli/create.ts b/packages/remix-dev/cli/create.ts index a26f7058ea3..b56899038a6 100644 --- a/packages/remix-dev/cli/create.ts +++ b/packages/remix-dev/cli/create.ts @@ -635,7 +635,7 @@ export async function validateTemplate( ) && // if the latest is specified, make sure there is an asset !body?.assets?.some((a: any) => - a?.browser_download_url?.includes(info.asset) + a?.browser_download_url.includes(info.asset) ) ) { throw Error( From 6ccac77e14d240cd8eb5b213021f08eb9cbb32a0 Mon Sep 17 00:00:00 2001 From: John Berger Date: Wed, 5 Oct 2022 14:21:46 -0400 Subject: [PATCH 5/6] Sign CLA --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index b98b531d622..2e3b6af6f7d 100644 --- a/contributors.yml +++ b/contributors.yml @@ -195,6 +195,7 @@ - jodygeraldo - joelazar - johannesbraeunig +- johnmberger - johnpolacek - johnson444 - joms From 95d0507ef95246cc5d8ebdf977dbc9b1cdc7287b Mon Sep 17 00:00:00 2001 From: Logan McAnsh Date: Wed, 12 Oct 2022 14:46:03 -0400 Subject: [PATCH 6/6] Create tame-hotels-attack.md --- .changeset/tame-hotels-attack.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tame-hotels-attack.md diff --git a/.changeset/tame-hotels-attack.md b/.changeset/tame-hotels-attack.md new file mode 100644 index 00000000000..b05f4e98efa --- /dev/null +++ b/.changeset/tame-hotels-attack.md @@ -0,0 +1,6 @@ +--- +"remix": patch +"@remix-run/dev": patch +--- + +Allow private GitHub repos to use the latest release url for tarballs when using `create-remix`