Skip to content
This repository has been archived by the owner on Jun 29, 2023. It is now read-only.

Commit

Permalink
feat: add support for v2 registry suggestions (#1980)
Browse files Browse the repository at this point in the history
Closes: #1974
  • Loading branch information
kitsonk authored Jan 4, 2022
1 parent e54e42b commit 7b84fdb
Show file tree
Hide file tree
Showing 9 changed files with 997 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This is the code for https://deno.land/

This website consists of two parts

1. A Cloudflare Worker
1. A Deploy worker
2. A Next.js app hosted on Vercel

We want to provide pretty and semantic URLs for modules that will be used within
Expand Down
2 changes: 1 addition & 1 deletion components/Registry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function Registry(): React.ReactElement {
path,
]);
const documentationURL = useMemo(() => {
const doc = `https://doc.deno.land/https/deno.land${canonicalPath}`;
const doc = `https://doc.deno.land/https://deno.land${canonicalPath}`;
return denoDocAvailableForURL(canonicalPath) ? doc : null;
}, [canonicalPath]);

Expand Down
45 changes: 32 additions & 13 deletions worker/handler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/* Copyright 2020 the Deno authors. All rights reserved. MIT license. */

import { reportAnalytics } from "./analytics.ts";
import { handleRegistryRequest } from "./registry.ts";
import { handleConfigRequest } from "./registry_config.ts";
import { handleApiRequest } from "./suggestions.ts";
import { handleVSCRequest } from "./vscode.ts";
import { reportAnalytics } from "./analytics.ts";
import { ConnInfo } from "https://deno.land/[email protected]/http/server.ts";

import type { ConnInfo } from "https://deno.land/[email protected]/http/server.ts";
import { accepts } from "https://deno.land/x/[email protected]/negotiation.ts";

const REMOTE_URL = "https://deno-website2.now.sh";

Expand Down Expand Up @@ -36,37 +40,50 @@ export function withLog(
};
}

export async function handleRequest(request: Request): Promise<Response> {
const accept = request.headers.get("accept");
const isHtml = accept && accept.indexOf("html") >= 0;
export function handleRequest(request: Request): Promise<Response> {
const isHtml = request.headers.has("accept") && accepts(request, "text/html");

const url = new URL(request.url);

if (url.pathname === "/v1") {
return Response.redirect("https://deno.land/posts/v1", 301);
return Promise.resolve(
Response.redirect("https://deno.land/posts/v1", 301),
);
}

if (url.pathname === "/posts") {
return Response.redirect("https://deno.com/blog", 307);
return Promise.resolve(Response.redirect("https://deno.com/blog", 307));
}

if (url.pathname.startsWith("/posts/")) {
return Response.redirect(
return Promise.resolve(Response.redirect(
`https://deno.com/blog/${url.pathname.substring("/posts/".length)}`,
307,
);
));
}

if (url.pathname.startsWith("/typedoc")) {
return Response.redirect("https://doc.deno.land/builtin/stable", 301);
return Promise.resolve(
Response.redirect("https://doc.deno.land/deno/stable", 301),
);
}

if (url.pathname.startsWith("/_vsc")) {
return handleVSCRequest(url);
}

if (url.pathname.startsWith("/_api/")) {
return handleApiRequest(url);
}

if (["/install.sh", "/install.ps1"].includes(url.pathname)) {
return Response.redirect(`https://deno.land/x/install${url.pathname}`, 307);
return Promise.resolve(
Response.redirect(`https://deno.land/x/install${url.pathname}`, 307),
);
}

if (url.pathname === "/.well-known/deno-import-intellisense.json") {
return handleConfigRequest(request);
}

const isRegistryRequest = url.pathname.startsWith("/std") ||
Expand All @@ -76,15 +93,17 @@ export async function handleRequest(request: Request): Promise<Response> {
if (isHtml) {
const ln = extractAltLineNumberReference(url.toString());
if (ln) {
return Response.redirect(ln.rest + "#L" + ln.line, 302);
return Promise.resolve(
Response.redirect(`${ln.rest}#L${ln.line}`, 302),
);
}
} else {
return handleRegistryRequest(url);
}
}

if (!["HEAD", "GET"].includes(request.method)) {
return new Response(null, { status: 405 }); // Method not allowed.
return Promise.resolve(new Response(null, { status: 405 })); // Method not allowed.
}

return proxyFile(url, REMOTE_URL, request);
Expand Down
5 changes: 3 additions & 2 deletions worker/main.ts
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env -S deno run --allow-read=. --allow-net --allow-env
/* Copyright 2020 the Deno authors. All rights reserved. MIT license. */

import { handleRequest, withLog } from "./handler.ts";
import { listenAndServe } from "https://deno.land/[email protected]/http/server.ts";

const handler = withLog(handleRequest);

console.log("The server is available at http://localhost:8080");
listenAndServe(":8080", handler);
console.log("The server is available at http://localhost:8081");
listenAndServe(":8081", handler);
2 changes: 2 additions & 0 deletions worker/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { parseNameVersion } from "../util/registry_utils.ts";
export const S3_BUCKET =
"http://deno-registry2-prod-storagebucket-b3a31d16.s3-website-us-east-1.amazonaws.com/";

/** Handle _legacy_ v1 registry requests. v2 is handled by `./suggestions.ts`.
*/
export async function handleRegistryRequest(url: URL): Promise<Response> {
const entry = parsePathname(url.pathname);
if (!entry) {
Expand Down
175 changes: 175 additions & 0 deletions worker/registry_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/* Copyright 2021 the Deno authors. All rights reserved. MIT license. */

import { accepts } from "https://deno.land/x/[email protected]/negotiation.ts";

interface RegistryDefVariable {
key: string;
documentation?: string;
url: string;
}

interface RegistryDef {
schema: string;
variables: RegistryDefVariable[];
}

interface RegistryConfig {
version: 1 | 2;
registries: RegistryDef[];
}

const MAX_AGE_1_DAY = "max-age=86400";

/** The _legacy_ v1 configuration file. This will be provided to any client
* which does not indicate it is capable of understanding the v2 registry
* (earlier than Deno 1.17.1) */
const configV1: RegistryConfig = {
version: 1,
registries: [
{
schema: "/x/:module([a-z0-9_]*)@:version?/:path*",
variables: [
{
key: "module",
url: "https://api.deno.land/modules?simple=1",
},
{
key: "version",
url: "https://deno.land/_vsc1/modules/${module}",
},
{
key: "path",
url: "https://deno.land/_vsc1/modules/${module}/v/${{version}}",
},
],
},
{
schema: "/x/:module([a-z0-9_]*)/:path*",
variables: [
{
key: "module",
url: "https://api.deno.land/modules?simple=1",
},
{
key: "path",
url: "https://deno.land/_vsc1/modules/${module}/v_latest",
},
],
},
{
schema: "/std@:version?/:path*",
variables: [
{
key: "version",
url: "https://deno.land/_vsc1/modules/std",
},
{
key: "path",
url: "https://deno.land/_vsc1/modules/std/v/${{version}}",
},
],
},
{
schema: "/std/:path*",
variables: [
{
key: "path",
url: "https://deno.land/_vsc1/modules/std/v_latest",
},
],
},
],
};

/** This is the v2 registry configuration which provides documentation
* endpoints and allows incremental completion/search of variables. */
const configV2: RegistryConfig = {
version: 2,
registries: [
{
schema: "/x/:module([a-z0-9_]+)@:version?/:path*",
variables: [
{
key: "module",
documentation: "/_api/details/x/${module}",
url: "/_api/x/${module}",
},
{
key: "version",
documentation: "/_api/details/x/${module}/${{version}}",
url: "/_api/x/${module}/${{version}}",
},
{
key: "path",
documentation: "/_api/details/x/${module}/${{version}}/${path}",
url: "/_api/x/${module}/${{version}}/${path}",
},
],
},
{
schema: "/x/:module([a-z0-9_]*)/:path*",
variables: [
{
key: "module",
documentation: "/_api/details/x/${module}",
url: "/_api/x/${module}",
},
{
key: "path",
documentation: "/_api/details/x/${module}/_latest/${path}",
url: "/_api/x/${module}/_latest/${path}",
},
],
},
{
schema: "/std@:version?/:path*",
variables: [
{
key: "version",
documentation: "/_api/details/std/${{version}}",
url: "/_api/x/std/${{version}}",
},
{
key: "path",
documentation: "/_api/details/std/${{version}}/${path}",
url: "/_api/x/std/${{version}}/${path}",
},
],
},
{
schema: "/std/:path*",
variables: [
{
key: "path",
documentation: "/_api/details/std/_latest/${path}",
url: "/_api/x/std/_latest/${path}",
},
],
},
],
};

/** Provide the v1 or v2 registry configuration based on the accepts header
* provided by the client. Deno 1.17.1 and later indicates it accepts a
* configuration of v2. */
export function handleConfigRequest(request: Request): Promise<Response> {
let body: unknown;
let contentType = "application/json";
if (
request.headers.has("accept") &&
accepts(request, "application/vnd.deno.reg.v2+json")
) {
contentType = "application/vnd.deno.reg.v2+json";
body = configV2;
} else {
body = configV1;
}
return Promise.resolve(
new Response(JSON.stringify(body), {
headers: {
"cache-control": MAX_AGE_1_DAY,
"content-type": contentType,
},
}),
);
}
57 changes: 57 additions & 0 deletions worker/registry_config_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* Copyright 2021-2022 the Deno authors. All rights reserved. MIT license. */

import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";

import { handleConfigRequest } from "./registry_config.ts";

Deno.test({
name: "handleConfigRequest - v1 version of manifest",
async fn() {
const req = new Request(
"https://deno.land//.well-known/deno-import-intellisense.json",
);
const res = await handleConfigRequest(req);
assertEquals(res.status, 200);
const json = await res.json();
assertEquals(json.version, 1);
},
});

Deno.test({
name: "handleConfigRequest - browser",
async fn() {
const req = new Request(
"https://deno.land//.well-known/deno-import-intellisense.json",
{
headers: {
"accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
},
},
);
const res = await handleConfigRequest(req);
assertEquals(res.status, 200);
const json = await res.json();
assertEquals(json.version, 2);
},
});

Deno.test({
name: "handleConfigRequest - v2 version of manifest",
async fn() {
const req = new Request(
"https://deno.land//.well-known/deno-import-intellisense.json",
{
headers: {
// this is the accept header Deno v 1.17.1 and later sends in the request
accept:
"application/vnd.deno.reg.v2+json, application/vnd.deno.reg.v1+json;q=0.9, application/json;q=0.8",
},
},
);
const res = await handleConfigRequest(req);
assertEquals(res.status, 200);
const json = await res.json();
assertEquals(json.version, 2);
},
});
Loading

0 comments on commit 7b84fdb

Please sign in to comment.