Skip to content

Commit 7831971

Browse files
refactor: remove unused imports from nuxt.config.ts
1 parent 91c93fc commit 7831971

File tree

8 files changed

+207
-259
lines changed

8 files changed

+207
-259
lines changed

packages/app/e2e.test.ts

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Response } from "@cloudflare/workers-types";
21
import ezSpawn from "@jsdevtools/ez-spawn";
32
import { simulation } from "@simulacrum/github-api-simulator";
43
import fs from "node:fs/promises";
@@ -151,7 +150,8 @@ describe.sequential.each([
151150

152151
it(`serves and installs playground-a for ${mode}`, async () => {
153152
const [owner, repo] = payload.repository.full_name.split("/");
154-
const sha = payload.workflow_run.head_sha.substring(0, 7);
153+
const fullSha = payload.workflow_run.head_sha;
154+
const sha = fullSha.substring(0, 7);
155155
const ref = pr?.payload.number ?? payload.workflow_run.head_branch;
156156

157157
// Test download with SHA
@@ -163,9 +163,15 @@ describe.sequential.each([
163163
expect(shaBlob.size).toBeGreaterThan(0);
164164

165165
// Test download with ref matches SHA content
166-
const refResponse = await fetchWithRedirect(
166+
const refResponse = await worker.fetch(
167167
`/${owner}/${repo}/playground-a@${ref}`,
168168
);
169+
expect(refResponse.status).toBe(200);
170+
expect(refResponse.headers.get("x-pkg-name-key")).toBe("playground-a");
171+
expect(refResponse.headers.get("x-commit-key")).toBe(
172+
`${owner}:${repo}:${fullSha}`,
173+
);
174+
169175
const refBlob = await refResponse.blob();
170176
const shaBlobSize = await shaBlob.arrayBuffer();
171177
const refBlobSize = await refBlob.arrayBuffer();
@@ -189,6 +195,28 @@ describe.sequential.each([
189195
);
190196
}, 10_000);
191197

198+
it(`returns metadata for HEAD requests (${mode})`, async () => {
199+
const [owner, repo] = payload.repository.full_name.split("/");
200+
const sha = payload.workflow_run.head_sha.substring(0, 7);
201+
202+
const headResponse = await worker.fetch(
203+
`/${owner}/${repo}/playground-a@${sha}`,
204+
{ method: "HEAD" },
205+
);
206+
207+
expect(headResponse.status).toBe(200);
208+
expect(headResponse.headers.get("x-pkg-name-key")).toBe("playground-a");
209+
expect(headResponse.headers.get("x-commit-key")).toBe(
210+
`${owner}:${repo}:${sha}`,
211+
);
212+
expect(headResponse.headers.get("content-type")).toBe(
213+
"application/tar+gzip",
214+
);
215+
expect(headResponse.headers.get("etag")).toBeDefined();
216+
const lastModified = headResponse.headers.get("last-modified");
217+
expect(new Date(lastModified!).toString()).not.toBe("Invalid Date");
218+
}, 10_000);
219+
192220
it(`serves and installs playground-b for ${mode}`, async () => {
193221
const [owner, repo] = payload.repository.full_name.split("/");
194222
const sha = payload.workflow_run.head_sha.substring(0, 7);
@@ -222,48 +250,57 @@ describe.sequential.each([
222250
}, 10_000);
223251
});
224252

225-
describe("URL redirects", () => {
253+
describe("URL resolution", () => {
226254
describe("standard packages", () => {
227-
it("redirects full URLs correctly", async () => {
228-
const response = await fetchWithRedirect("/tinylibs/tinybench@a832a55");
229-
expect(response.url).toContain("/tinylibs/tinybench/tinybench@a832a55");
255+
it.each([
256+
["full", "/tinylibs/tinybench/tinybench@a832a55"],
257+
["compact", "/tinybench@a832a55"],
258+
["with .tgz extension", "/tinybench@a832a55.tgz"],
259+
])("resolves %s URLs", async (_, url) => {
260+
const response = await worker.fetch(url);
261+
262+
expect(response.headers.get("x-commit-key")).toBe(
263+
"tinylibs:tinybench:a832a55",
264+
);
265+
expect(response.headers.get("x-pkg-name-key")).toBe("tinybench");
230266
});
231267

232-
it("redirects compact URLs correctly", async () => {
233-
const response = await fetchWithRedirect("/tinybench@a832a55");
234-
expect(response.url).toContain("/tinylibs/tinybench/tinybench@a832a55");
268+
it("resolves URL with full Git SHA", async () => {
269+
const response = await worker.fetch(
270+
"/tinylibs/tinybench/tinybench@a832a55e8f50c419ed8414024899e37e69b1f999",
271+
);
272+
273+
expect(response.headers.get("x-pkg-name-key")).toBe("tinybench");
274+
expect(response.headers.get("x-commit-key")).toBe(
275+
"tinylibs:tinybench:a832a55e8f50c419ed8414024899e37e69b1f999",
276+
);
235277
});
236278
});
237279

238280
describe("scoped packages", () => {
239-
const expectedPath = `/stackblitz/sdk/${encodeURIComponent("@stackblitz/sdk")}@a832a55`;
281+
it.each([
282+
["full", "/stackblitz/sdk/@stackblitz/sdk@a832a55"],
283+
["encoded", "/stackblitz/sdk/%40stackblitz%2Fsdk@a832a55"],
284+
["compact", "/@stackblitz/sdk@a832a55"],
285+
["compact encoded", "/%40stackblitz%2Fsdk@a832a55"],
286+
])("resolves %s URLs", async (_, url) => {
287+
const response = await worker.fetch(url);
240288

241-
it("redirects full scoped package URLs correctly", async () => {
242-
const response = await fetchWithRedirect(
243-
"/stackblitz/sdk/@stackblitz/sdk@a832a55",
289+
expect(response.headers.get("x-pkg-name-key")).toBe("@stackblitz:sdk");
290+
expect(response.headers.get("x-commit-key")).toBe(
291+
"stackblitz:sdk:a832a55",
244292
);
245-
expect(response.url).toContain(expectedPath);
246293
});
247294

248-
it("redirects compact scoped package URLs correctly", async () => {
249-
const response = await fetchWithRedirect("/@stackblitz/sdk@a832a55");
250-
expect(response.url).toContain(expectedPath);
295+
it("resolves URL with full Git SHA", async () => {
296+
const response = await worker.fetch(
297+
"/stackblitz/sdk/@stackblitz/sdk@a832a55e8f50c419ed8414024899e37e69b1f999",
298+
);
299+
300+
expect(response.headers.get("x-pkg-name-key")).toBe("@stackblitz:sdk");
301+
expect(response.headers.get("x-commit-key")).toBe(
302+
"stackblitz:sdk:a832a55e8f50c419ed8414024899e37e69b1f999",
303+
);
251304
});
252305
});
253306
});
254-
255-
async function fetchWithRedirect(
256-
url: string,
257-
maxRedirects = 999,
258-
): Promise<Response> {
259-
const response = await worker.fetch(url, { redirect: "manual" });
260-
261-
if (response.status >= 300 && response.status < 400 && maxRedirects > 0) {
262-
const location = response.headers.get("location");
263-
if (location) {
264-
return fetchWithRedirect(location, maxRedirects - 1);
265-
}
266-
}
267-
268-
return response as unknown as Response;
269-
}

packages/app/nuxt.config.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// import ncb from "nitro-cloudflare-dev";
2-
import { resolve } from "pathe";
3-
41
// https://nuxt.com/docs/api/configuration/nuxt-config
52
export default defineNuxtConfig({
63
sourcemap: true,
@@ -57,23 +54,6 @@ export default defineNuxtConfig({
5754
test: "",
5855
},
5956

60-
hooks: {
61-
"nitro:build:before": (nitro) => {
62-
// Override the server routes with the client routes so they are higher priority
63-
const clientRenderer = resolve(
64-
"node_modules/nuxt/dist/core/runtime/nitro/renderer",
65-
);
66-
nitro.options.handlers.unshift({
67-
route: "/",
68-
handler: clientRenderer,
69-
});
70-
nitro.options.handlers.unshift({
71-
route: "/~/**",
72-
handler: clientRenderer,
73-
});
74-
},
75-
},
76-
7757
icon: {
7858
clientBundle: {
7959
icons: ["mdi-github"],
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { extractOwnerAndRepo, extractRepository } from "@pkg-pr-new/utils";
2+
import { getPackageManifest } from "query-registry";
3+
import { normalizeKey } from "unstorage";
4+
5+
const RESERVED_ROUTES = new Set(["api", "~"]);
6+
const ALLOWED_METHODS = new Set(["GET", "HEAD"]);
7+
8+
export default eventHandler(async (event) => {
9+
let decodedPath: string;
10+
try {
11+
const path = event.path.split("?")[0];
12+
decodedPath = decodeURIComponent(path);
13+
} catch {
14+
throw createError({
15+
statusCode: 400,
16+
message: "Malformed URI",
17+
});
18+
}
19+
20+
const lastAtIndex = decodedPath.lastIndexOf("@");
21+
if (lastAtIndex === -1) return;
22+
23+
let refOrSha = decodedPath.slice(lastAtIndex + 1).replace(/\.tgz$/, "");
24+
if (!refOrSha) return;
25+
26+
const pathSegments = decodedPath
27+
.slice(0, lastAtIndex)
28+
.split("/")
29+
.filter(Boolean);
30+
if (pathSegments.length === 0) return;
31+
32+
const rootSegment = pathSegments[0];
33+
if (RESERVED_ROUTES.has(rootSegment) || !/^[a-z0-9@]/i.test(rootSegment)) {
34+
return;
35+
}
36+
if (!ALLOWED_METHODS.has(event.method)) return;
37+
38+
let packageName = pathSegments.pop()!;
39+
40+
if (pathSegments.at(-1)?.startsWith("@")) {
41+
packageName = `${pathSegments.pop()}/${packageName}`;
42+
}
43+
44+
let owner = pathSegments.shift();
45+
let repo = pathSegments.shift() ?? (owner ? packageName : undefined);
46+
47+
if (pathSegments.length > 0) return;
48+
49+
if (!repo) {
50+
try {
51+
const manifest = await getPackageManifest(packageName);
52+
53+
const repository = extractRepository(manifest);
54+
if (!repository) throw new Error();
55+
56+
const match = extractOwnerAndRepo(repository);
57+
if (!match) throw new Error();
58+
59+
[owner, repo] = match;
60+
} catch {
61+
throw createError({
62+
statusCode: 404,
63+
message: "Registry or repository not found",
64+
});
65+
}
66+
}
67+
68+
const isFullGitHash = isValidGitHash(refOrSha);
69+
if (!isFullGitHash) {
70+
const cursorBucket = useCursorsBucket(event);
71+
const cursorKey = `${owner}:${repo}:${refOrSha}`;
72+
const currentCursor = await cursorBucket.getItem(cursorKey);
73+
74+
if (currentCursor) {
75+
refOrSha = currentCursor.sha;
76+
}
77+
}
78+
79+
const repositoryCommitKey = `${owner}:${repo}:${refOrSha}`;
80+
setResponseHeader(event, "x-commit-key", repositoryCommitKey);
81+
82+
const normalizedPkgName = normalizeKey(packageName);
83+
setResponseHeader(event, "x-pkg-name-key", normalizedPkgName);
84+
85+
const prefix = `${usePackagesBucket.base}:${repositoryCommitKey}`;
86+
87+
const binding = useBinding(event);
88+
const { objects } = await binding.list({ prefix });
89+
90+
const packageMetadata = objects.find(({ key }) => {
91+
// bucket:package:stackblitz-labs:pkg.pr.new:ded05e838c418096e5dd77a29101c8af9e73daea:playground-b
92+
if (!key.endsWith(normalizedPkgName)) return false;
93+
94+
// ...:playground-b
95+
const remainder = key.slice(prefix.length);
96+
const colonIdx = remainder.indexOf(":");
97+
98+
return remainder.slice(colonIdx + 1) === normalizedPkgName;
99+
});
100+
101+
if (!packageMetadata) {
102+
throw createError({
103+
statusCode: 404,
104+
message: "Pkg not found",
105+
});
106+
}
107+
108+
setResponseHeader(event, "content-type", "application/tar+gzip");
109+
setResponseHeader(event, "etag", packageMetadata.etag);
110+
setResponseHeader(
111+
event,
112+
"last-modified",
113+
packageMetadata.uploaded.toUTCString(),
114+
);
115+
116+
if (event.method === "HEAD") {
117+
setResponseStatus(event, 200);
118+
return send(event, null);
119+
}
120+
121+
const downloadedAtBucket = useDownloadedAtBucket(event);
122+
event.waitUntil(downloadedAtBucket.setItem(packageMetadata.key, Date.now()));
123+
124+
const object = await binding.get(packageMetadata.key);
125+
const stream = object?.body;
126+
127+
// TODO: add HTTP caching
128+
return stream;
129+
});
130+
131+
const sha1Regex = /^[\da-f]{40}$/i;
132+
const sha256Regex = /^[\da-f]{64}$/i;
133+
134+
function isValidGitHash(hash: string): boolean {
135+
return sha1Regex.test(hash) || sha256Regex.test(hash);
136+
}

packages/app/server/plugins/config.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/app/server/routes/[owner]/[repo]/[npmOrg]/[packageAndRefOrSha].get.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)