diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index d1b98dac..1115980b 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -47,7 +47,7 @@ jobs: - name: Download Staticalize run: | - wget https://github.com/thefrontside/staticalize/releases/download/v0.2.6/staticalize-linux.tar.gz \ + wget https://github.com/thefrontside/staticalize/releases/download/v0.2.7/staticalize-linux.tar.gz \ -O /tmp/staticalize-linux.tar.gz tar -xzf /tmp/staticalize-linux.tar.gz -C /usr/local/bin chmod +x /usr/local/bin/staticalize-linux @@ -57,7 +57,9 @@ jobs: staticalize-linux \ --site=http://127.0.0.1:8005 \ --output=built \ - --base=https://frontside.com + --base=https://frontside.com \ + --concurrency=25 \ + --retries=5 - name: Deploy Preview to Netlify id: netlify @@ -130,7 +132,7 @@ jobs: - name: Download Staticalize run: | - wget https://github.com/thefrontside/staticalize/releases/download/v0.2.6/staticalize-linux.tar.gz \ + wget https://github.com/thefrontside/staticalize/releases/download/v0.2.7/staticalize-linux.tar.gz \ -O /tmp/staticalize-linux.tar.gz tar -xzf /tmp/staticalize-linux.tar.gz -C /usr/local/bin chmod +x /usr/local/bin/staticalize-linux @@ -140,7 +142,9 @@ jobs: staticalize-linux \ --site=http://127.0.0.1:8005 \ --output=built \ - --base=https://frontside.com + --base=https://frontside.com \ + --concurrency=25 \ + --retries=5 - name: Deploy to Production run: | diff --git a/components/project-select.tsx b/components/project-select.tsx index 6490a14c..551fa3fa 100644 --- a/components/project-select.tsx +++ b/components/project-select.tsx @@ -78,7 +78,7 @@ const projects = [ { title: "Interactors", description: "Page Objects for components libraries", - url: "https://frontside.com/interactors", + url: "/interactors", version: "v1", img: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNCIgaGVpZ2h0PSIzNCIgdmlld0JveD0iMCAwIDM0IDM0Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmEgewogICAgICAgIGZpbGw6ICNmZmY7CiAgICAgIH0KCiAgICAgIC5hLCAuYiwgLmMgewogICAgICAgIHN0cm9rZTogIzE0MzA1YzsKICAgICAgICBzdHJva2UtbWl0ZXJsaW1pdDogMTA7CiAgICAgICAgc3Ryb2tlLXdpZHRoOiAycHg7CiAgICAgIH0KCiAgICAgIC5iIHsKICAgICAgICBmaWxsOiAjMjZhYmU4OwogICAgICB9CgogICAgICAuYyB7CiAgICAgICAgZmlsbDogI2Y3NGQ3YjsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CiAgPGc+CiAgICA8ZWxsaXBzZSBjbGFzcz0iYSIgY3g9IjI4LjQ1IiBjeT0iNi45MSIgcng9IjQiIHJ5PSIzLjg5Ii8+CiAgICA8cGF0aCBjbGFzcz0iYiIgZD0iTTI4LjQ1LDEwLjhhMy45NCwzLjk0LDAsMCwxLTQtMy44OSw0LDQsMCwwLDEsLjI1LTEuMzQsNi40NSw2LjQ1LDAsMCwwLTEuMzMtLjE0QTYuMyw2LjMsMCwwLDAsMTcsMTEuNjRhNi4zOCw2LjM4LDAsMCwwLDEyLjc1LDAsNi45MSw2LjkxLDAsMCwwLS4xLTFBNC4zLDQuMywwLDAsMSwyOC40NSwxMC44WiIvPgogICAgPHBhdGggY2xhc3M9ImMiIGQ9Ik0xNywxMS42NGE2LjI2LDYuMjYsMCwwLDEsLjEtMSwxMS4xMSwxMS4xMSwwLDAsMC00LjY5LTEsMTAuODIsMTAuODIsMCwwLDAtMTEsMTAuNjhBMTAuODIsMTAuODIsMCwwLDAsMTIuNDEsMzFhMTAuODMsMTAuODMsMCwwLDAsMTEtMTAuNjgsMTAuNjYsMTAuNjYsMCwwLDAtLjMxLTIuNDhBNi4yNyw2LjI3LDAsMCwxLDE3LDExLjY0WiIvPgogIDwvZz4KPC9zdmc+Cg==", @@ -86,25 +86,26 @@ const projects = [ { title: "Effection", description: "Structured Concurrency for JavaScript", - url: "https://frontside.com/effection", - version: "v3", + url: "/effection", + version: "v4", img: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNCIgaGVpZ2h0PSIzNCIgdmlld0JveD0iMCAwIDM0IDM0Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmEgewogICAgICAgIGZpbGw6ICMxYzMwNWE7CiAgICAgIH0KCiAgICAgIC5iIHsKICAgICAgICBmaWxsOiAjMjZhYmU4OwogICAgICB9CgogICAgICAuYyB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmQgewogICAgICAgIGZpbGw6ICNmNzRkN2I7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxnPgogICAgPHJlY3QgY2xhc3M9ImEiIHg9IjEzLjU4IiB5PSI2Ljg5IiB3aWR0aD0iMTcuMTgiIGhlaWdodD0iMTcuMTgiIHJ4PSIxLjk2IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC40NiAyMC4yMSkgcm90YXRlKC00NSkiLz4KICAgIDxyZWN0IGNsYXNzPSJhIiB4PSI4LjM5IiB5PSIxMi4wOCIgd2lkdGg9IjE3LjE4IiBoZWlnaHQ9IjE3LjE4IiByeD0iMS45NiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTkuNjQgMTguMDYpIHJvdGF0ZSgtNDUpIi8+CiAgICA8cmVjdCBjbGFzcz0iYSIgeD0iMy4yNCIgeT0iNi45NyIgd2lkdGg9IjE3LjE4IiBoZWlnaHQ9IjE3LjE4IiByeD0iMS45NiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTcuNTMgMTIuOTIpIHJvdGF0ZSgtNDUpIi8+CiAgICA8cmVjdCBjbGFzcz0iYiIgeD0iMTYuMjgiIHk9IjkuNjEiIHdpZHRoPSIxMS43OCIgaGVpZ2h0PSIxMS43OCIgcng9IjIuMjYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00LjQ3IDIwLjIxKSByb3RhdGUoLTQ1KSIvPgogICAgPHJlY3QgY2xhc3M9ImMiIHg9IjExLjA3IiB5PSIxNC44MSIgd2lkdGg9IjExLjc4IiBoZWlnaHQ9IjExLjc4IiByeD0iMi4yNiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTkuNjcgMTguMDYpIHJvdGF0ZSgtNDUpIi8+CiAgICA8cmVjdCBjbGFzcz0iZCIgeD0iNS45NyIgeT0iOS43MSIgd2lkdGg9IjExLjc4IiBoZWlnaHQ9IjExLjc4IiByeD0iMi4yNiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTcuNTYgMTIuOTUpIHJvdGF0ZSgtNDUpIi8+CiAgPC9nPgo8L3N2Zz4K", }, { - title: "Auth0 Simulator", - description: "Enabling testing and local development", - url: "https://github.com/thefrontside/simulacrum/tree/v0/packages/auth0", + title: "Simulacrum", + description: "Simulation enabling testing and local development", + url: "https://github.com/thefrontside/simulacrum", version: "v0", img: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNCIgaGVpZ2h0PSIzNCIgdmlld0JveD0iMCAwIDM0IDM0Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmEgewogICAgICAgIGZpbGw6ICMxNDMxNWQ7CiAgICAgIH0KCiAgICAgIC5iIHsKICAgICAgICBmaWxsOiAjMjZhYmU4OwogICAgICB9CgogICAgICAuYyB7CiAgICAgICAgZmlsbDogI2Y3NGQ3YjsKICAgICAgICBmaWxsLXJ1bGU6IGV2ZW5vZGQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxnPgogICAgPHBhdGggY2xhc3M9ImEiIGQ9Ik0xNyw1LjE2bDEwLjMzLDUuOTVWMjIuODlMMTcsMjguODQsNi42NywyMi44OVYxMS4xMUwxNyw1LjE2TTE3LC4yOCwyLjQ1LDguNjZWMjUuMzRMMTcsMzMuNzJsMTQuNTUtOC4zOFY4LjY2TDE3LC4yOFoiLz4KICAgIDxwYXRoIGNsYXNzPSJiIiBkPSJNMTcsNy42OWw4LjEyLDQuNjd2OS4yOEwxNywyNi4zMSw4Ljg4LDIxLjY0VjEyLjM2TDE3LDcuNjltMC0zLjI1TDYuMDYsMTAuNzRWMjMuMjZMMTcsMjkuNTZsMTAuOTQtNi4zVjEwLjc0TDE3LDQuNDRaIi8+CiAgICA8cG9seWdvbiBjbGFzcz0iYyIgcG9pbnRzPSIxNyAxMy44OCAxNC4yOCAxNS40NCAxNC4yOCAxOC41NiAxNyAyMC4xMiAxOS43MiAxOC41NiAxOS43MiAxNS40NCAxNyAxMy44OCIvPgogIDwvZz4KPC9zdmc+Cg==", - }, - { - title: "Bigtest", - description: "Universal test runner, GraphQL driven", - url: "https://frontside.com/bigtest", - version: "v0", - img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNCIgaGVpZ2h0PSIzNCIgdmlld0JveD0iMCAwIDM0IDM0Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmEgewogICAgICAgIGZpbGw6ICMxNDMxNWQ7CiAgICAgIH0KCiAgICAgIC5iIHsKICAgICAgICBmaWxsOiAjZmZmOwogICAgICB9CgogICAgICAuYyB7CiAgICAgICAgZmlsbDogI2Y3NGQ3YjsKICAgICAgfQoKICAgICAgLmQgewogICAgICAgIGZpbGw6ICMyNmFiZTg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxnPgogICAgPHBvbHlnb24gY2xhc3M9ImEiIHBvaW50cz0iMTYuOTYgMC4yNyAyLjU0IDUuMyAzLjU0IDI1LjIyIDE2Ljk2IDMzLjU4IDMwLjM5IDI1LjIyIDMxLjM5IDUuMyAxNi45NiAwLjI3Ii8+CiAgICA8cG9seWdvbiBjbGFzcz0iYiIgcG9pbnRzPSIxNi45NiAzLjUyIDEyLjUzIDUuMTMgMTEuNjggMTYuOTQgMTYuOTYgMTkuNTcgMTUuOTUgMjkuNzIgMTYuOTUgMzAuMzYgMTguNDMgMjkuNDIgMjMuNiAxNi45NCAxOC4zOSAxNC4xNCAyMC44OCA0Ljk0IDE2Ljk2IDMuNTIiLz4KICAgIDxwb2x5Z29uIGNsYXNzPSJjIiBwb2ludHM9IjUuMzMgNy43NCAxMi41MyA1LjEzIDExLjY4IDE2Ljk0IDE2Ljk2IDE5LjU3IDE1Ljk1IDI5LjcyIDYuNDEgMjMuNjMgNS4zMyA3Ljc0Ii8+CiAgICA8cG9seWdvbiBjbGFzcz0iZCIgcG9pbnRzPSIyNy41MiAyMy42MyAxOC40MyAyOS40MiAyMy42IDE2Ljk0IDE4LjM5IDE0LjE0IDIwLjg4IDQuOTQgMjguNiA3Ljc0IDI3LjUyIDIzLjYzIi8+CiAgPC9nPgo8L3N2Zz4K", - }, + } + // we aren't actively maintaining, remove for now + // { + // title: "Bigtest", + // description: "Universal test runner, GraphQL driven", + // url: "/bigtest", + // version: "v0", + // img: + // "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNCIgaGVpZ2h0PSIzNCIgdmlld0JveD0iMCAwIDM0IDM0Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmEgewogICAgICAgIGZpbGw6ICMxNDMxNWQ7CiAgICAgIH0KCiAgICAgIC5iIHsKICAgICAgICBmaWxsOiAjZmZmOwogICAgICB9CgogICAgICAuYyB7CiAgICAgICAgZmlsbDogI2Y3NGQ3YjsKICAgICAgfQoKICAgICAgLmQgewogICAgICAgIGZpbGw6ICMyNmFiZTg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxnPgogICAgPHBvbHlnb24gY2xhc3M9ImEiIHBvaW50cz0iMTYuOTYgMC4yNyAyLjU0IDUuMyAzLjU0IDI1LjIyIDE2Ljk2IDMzLjU4IDMwLjM5IDI1LjIyIDMxLjM5IDUuMyAxNi45NiAwLjI3Ii8+CiAgICA8cG9seWdvbiBjbGFzcz0iYiIgcG9pbnRzPSIxNi45NiAzLjUyIDEyLjUzIDUuMTMgMTEuNjggMTYuOTQgMTYuOTYgMTkuNTcgMTUuOTUgMjkuNzIgMTYuOTUgMzAuMzYgMTguNDMgMjkuNDIgMjMuNiAxNi45NCAxOC4zOSAxNC4xNCAyMC44OCA0Ljk0IDE2Ljk2IDMuNTIiLz4KICAgIDxwb2x5Z29uIGNsYXNzPSJjIiBwb2ludHM9IjUuMzMgNy43NCAxMi41MyA1LjEzIDExLjY4IDE2Ljk0IDE2Ljk2IDE5LjU3IDE1Ljk1IDI5LjcyIDYuNDEgMjMuNjMgNS4zMyA3Ljc0Ii8+CiAgICA8cG9seWdvbiBjbGFzcz0iZCIgcG9pbnRzPSIyNy41MiAyMy42MyAxOC40MyAyOS40MiAyMy42IDE2Ljk0IDE4LjM5IDE0LjE0IDIwLjg4IDQuOTQgMjguNiA3Ljc0IDI3LjUyIDIzLjYzIi8+CiAgPC9nPgo8L3N2Zz4K", + // }, ]; diff --git a/main.tsx b/main.tsx index d18f307c..285883de 100644 --- a/main.tsx +++ b/main.tsx @@ -94,10 +94,10 @@ await main(function* (args) { route("/redirects", redirectsRoute()), route("/platformscript", platformscriptRoute()), $route("/platformscript/(.*)", redirect("/platformscript")), - proxyRoute({ ...proxies.effection, pattern: "/effection(.*)" }), - proxyRoute({ ...proxies.graphgen, pattern: "/graphgen(.*)" }), + proxyRoute(proxies.effection), + proxyRoute(proxies.graphgen), $route("/assets(.*)", assetsRoute("assets")), - proxyRoute({ ...proxies.interactors, pattern: "/interactors(.*)" }), + proxyRoute(proxies.interactors), ], plugins: [ @@ -127,16 +127,16 @@ function proxySites() { effection: { prefix: "effection", website: Deno.env.get("EFFECTION_URL") ?? - "https://effection.deno.dev", + "https://effection.netlify.app", }, interactors: { prefix: "interactors", website: Deno.env.get("INTERACTORS_URL") ?? - "https://interactors.deno.dev", + "https://interactors.netlify.app", }, graphgen: { prefix: "graphgen", - website: Deno.env.get("GRAPHGEN_URL") ?? "https://graphgen.deno.dev", + website: Deno.env.get("GRAPHGEN_URL") ?? "https://graphgen-data.netlify.app", }, } as const; } diff --git a/routes/proxy-route.ts b/routes/proxy-route.ts index aa95a45b..7132ac9f 100644 --- a/routes/proxy-route.ts +++ b/routes/proxy-route.ts @@ -42,80 +42,97 @@ export function proxyRoute(options: ProxyRouteOptions): HTTPMiddleware { if ([301, 302, 307, 308].includes(response.status)) { let location = response.headers.get("location"); - if (location?.startsWith(String(website))) { - let headers = copyHeaders(response); - - let url = new URL(request.url); + if (location) { + // Resolve relative Location headers against the upstream website + // - Netlify returns relative redirects like "/search/"" + // - Deno Deploy returns absolute ones like "https://host/search/" + let loc = location.startsWith("http") + ? new URL(location) + : new URL(location, website); + + if (loc.origin === website.origin) { + let headers = copyHeaders(response); + let url = new URL(request.url); + + if (!options.root) { + loc.pathname = `${options.prefix}${loc.pathname}`; + } + headers.location = loc.toString().replace(target.origin, url.origin); - let loc = new URL(location); - if (!options.root) { - loc.pathname = `${options.prefix}${loc.pathname}`; + response = new Response(null, { + status: response.status, + statusText: response.statusText, + headers, + }); } - headers.location = loc.toString().replace(target.origin, url.origin); - - response = new Response(null, { - status: response.status, - statusText: response.statusText, - headers, - }); } } else if ( response.headers.get("Content-Type")?.match(/html/) && !options.root ) { - let body = yield* call(() => response.text()); - let tree = fromHtml(body); + try { + let body = yield* call(() => response.text()); + let tree = fromHtml(body); - yield* injectPlausible(tree); - yield* injectUmami(tree); - yield* injectMatomo(tree); + yield* injectPlausible(tree); + yield* injectUmami(tree); + yield* injectMatomo(tree); - let elements = selectAll( - '[href^="/"],[src^="/"],form[action],meta[content]', - tree, - ); + let elements = selectAll( + '[href^="/"],[src^="/"],form[action],meta[content]', + tree, + ); - for (let element of elements) { - let properties = element.properties!; + for (let element of elements) { + let properties = element.properties!; - if (properties.href) { - properties.href = posixNormalize( - `${base.pathname}${properties.href}`, - ); - } - if (properties.src) { - properties.src = posixNormalize(`${base.pathname}${properties.src}`); - } - if (properties.action) { - properties.action = posixNormalize( - `${base.pathname}${properties.action}`, - ); - } - if (properties.content) { - if (typeof properties.content === "string") { - const parts = properties.content.match(/\d;\s*url=(.*)/); - if (parts) { - const [, url] = parts; - properties.content = properties.content.replace( - url, - posixNormalize(`${base.pathname}${url}`), - ); - } else if (properties.content.startsWith("http")) { - properties.content = properties.content.replace( - target.origin, - base.href.replace(/\/?$/, ""), - ); + if (properties.href) { + properties.href = posixNormalize( + `${base.pathname}${properties.href}`, + ); + } + if (properties.src) { + properties.src = posixNormalize( + `${base.pathname}${properties.src}`, + ); + } + if (properties.action) { + properties.action = posixNormalize( + `${base.pathname}${properties.action}`, + ); + } + if (properties.content) { + if (typeof properties.content === "string") { + const parts = properties.content.match(/\d;\s*url=(.*)/); + if (parts) { + const [, url] = parts; + properties.content = properties.content.replace( + url, + posixNormalize(`${base.pathname}${url}`), + ); + } else if (properties.content.startsWith("http")) { + properties.content = properties.content.replace( + target.origin, + base.href.replace(/\/?$/, ""), + ); + } } } } + response = new Response(toHtml(tree), { + status: response.status, + statusText: response.statusText, + headers: copyHeaders(response), + }); + } catch (error) { + console.error(`Proxy HTML rewrite failed for ${request.url}:`, error); } - response = new Response(toHtml(tree), { - status: response.status, - statusText: response.statusText, - headers: copyHeaders(response), - }); } - return response; + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: copyHeaders(response), + }); }; if (options.prefix) { @@ -135,17 +152,15 @@ export function proxyRoute(options: ProxyRouteOptions): HTTPMiddleware { }; } - if (options.pattern) { - let handler = revolutionRoute(options.pattern, middleware); - if (middleware.sitemapExtension) { - Object.defineProperty(handler, "sitemapExtension", { - value: middleware.sitemapExtension, - }); - } - return handler; - } + let pattern = options.pattern ?? `/${options.prefix}(.*)`; - return middleware; + let handler = revolutionRoute(pattern, middleware); + if (middleware.sitemapExtension) { + Object.defineProperty(handler, "sitemapExtension", { + value: middleware.sitemapExtension, + }); + } + return handler; } // Copy an upstream response's headers, dropping the ones that describe how the