From af1a9c43e0fbf9ac7048c413dd58b3dfb9e2b527 Mon Sep 17 00:00:00 2001 From: Luca Matei Pintilie Date: Sun, 5 Sep 2021 14:28:48 +0200 Subject: [PATCH 1/3] Update code to use native HTTP server --- .vscode/settings.json | 5 + README.md | 14 +-- deps.ts | 10 +- docuraptor.ts | 211 ++++++++++++++++++++++-------------------- renderer.ts | 7 +- 5 files changed, 130 insertions(+), 117 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..99f2846 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": false +} diff --git a/README.md b/README.md index 14533b9..e66d1fb 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ Docuraptor Logo -Docuraptor is an offline alternative to the [doc.deno.land](https://doc.deno.land) service. +Docuraptor is an offline alternative to the +[doc.deno.land](https://doc.deno.land) service. -It generates and serves HTML documentation for JS/TS modules with the help of [Deno's](https://deno.land) documentation parser. +It generates and serves HTML documentation for JS/TS modules with the help of +[Deno's](https://deno.land) documentation parser. ## Features @@ -16,8 +18,8 @@ It generates and serves HTML documentation for JS/TS modules with the help of [D `$ deno install -A https://deno.land/x/docuraptor@20200930.0/docuraptor.ts` -The permissions can be restricted. -Read the `--help` documentation for more details. +The permissions can be restricted. Read the `--help` documentation for more +details. ## Usage @@ -25,7 +27,7 @@ Read the `--help` documentation for more details. ## Examples -![Docuraptor in w3m screencast](./assets/demo.svg) -_Docuraptor with `BROWSER=w3m`_ +![Docuraptor in w3m screencast](./assets/demo.svg) _Docuraptor with +`BROWSER=w3m`_ ![Docuraptor documentation screenshot](./assets/screenshot.png) diff --git a/deps.ts b/deps.ts index 747cf65..8da6224 100644 --- a/deps.ts +++ b/deps.ts @@ -2,10 +2,6 @@ export { assert, unreachable, } from "https://deno.land/std@0.69.0/testing/asserts.ts"; -export { parse as argsParse } from "https://deno.land/std@0.69.0/flags/mod.ts"; -export { - serve, - ServerRequest, -} from "https://deno.land/std@0.69.0/http/server.ts"; -export { join as pathJoin } from "https://deno.land/std@0.69.0/path/mod.ts"; -export { pooledMap } from "https://deno.land/std@0.69.0/async/pool.ts"; +export { parse as argsParse } from "https://deno.land/std@0.106.0/flags/mod.ts"; +export { join as pathJoin } from "https://deno.land/std@0.106.0/path/mod.ts"; +export { pooledMap } from "https://deno.land/std@0.106.0/async/pool.ts"; diff --git a/docuraptor.ts b/docuraptor.ts index 32b7f72..07250c0 100644 --- a/docuraptor.ts +++ b/docuraptor.ts @@ -1,12 +1,6 @@ +// deno-lint-ignore-file camelcase import assets from "./assets.ts"; -import { - assert, - argsParse, - pathJoin, - serve, - ServerRequest, - unreachable, -} from "./deps.ts"; +import { argsParse, assert, unreachable } from "./deps.ts"; import { generateStatic } from "./generator.ts"; import { DocRenderer } from "./renderer.ts"; import { htmlEscape } from "./utility.ts"; @@ -18,10 +12,11 @@ const decoder = new TextDecoder(); */ const doc_prefix = "/doc/"; -async function handleDoc(req: ServerRequest): Promise { - assert(req.url.startsWith(doc_prefix)); +async function handleDoc(req: Deno.RequestEvent): Promise { + const path = new URL(req.request.url).pathname; + assert(path.startsWith(doc_prefix)); - const args = req.url.substr(doc_prefix.length); + const args = path.substr(doc_prefix.length); const search_index = args.indexOf("?"); const doc_url = decodeURIComponent( @@ -40,6 +35,7 @@ async function handleDoc(req: ServerRequest): Promise { doc_url.length > 0 ? doc_url : undefined, ); } catch (err) { + console.log(err); if (err.stderr !== undefined) { handleFail(req, 500, htmlEscape(err.stderr)); } else { @@ -48,46 +44,51 @@ async function handleDoc(req: ServerRequest): Promise { return; } - await req.respond({ - status: 200, - headers: new Headers({ - "Content-Type": "text/html", + await req.respondWith( + new Response(doc, { + status: 200, + headers: new Headers({ + "Content-Type": "text/html", + }), }), - body: doc, - }); + ); } async function handleFail( - req: ServerRequest, + req: Deno.RequestEvent, status: number, message: string, ): Promise { const rend = new DocRenderer(); - await req.respond({ - status, - headers: new Headers({ - "Content-Type": "text/html", - }), - body: ` - - - ${rend.renderHead("Docuraptor Error")} - - - ${rend.renderHeader("An error occured")} -
-
-            ${htmlEscape(message)}
-          
-
- - `, - }); + await req.respondWith( + new Response( + ` + + + ${rend.renderHead("Docuraptor Error")} + + + ${rend.renderHeader("An error occured")} +
+
+          ${htmlEscape(message)}
+        
+
+ + `, + { + status, + headers: new Headers({ + "Content-Type": "text/html", + }), + }, + ), + ); } const file_url = new URL("file:/"); let deps_url: URL | undefined = undefined; -async function handleIndex(req: ServerRequest): Promise { +async function handleIndex(req: Deno.RequestEvent): Promise { const known_documentation = []; if (deps_url !== undefined) { @@ -122,40 +123,45 @@ async function handleIndex(req: ServerRequest): Promise { } const rend = new DocRenderer(); - await req.respond({ - status: 200, - headers: new Headers({ - "Content-Type": "text/html", - }), - body: ` - - ${rend.renderHead("Docuraptor Index")} - - - ${rend.renderHeader("Docuraptor Index – Locally available modules")} -
- -
- - `, - }); + await req.respondWith( + new Response( + ` + + ${rend.renderHead("Docuraptor Index")} + + + ${rend.renderHeader("Docuraptor Index – Locally available modules")} +
+ +
+ +`, + { + status: 200, + headers: new Headers({ + "Content-Type": "text/html", + }), + }, + ), + ); } const form_prefix = "/form/"; -async function handleForm(req: ServerRequest): Promise { - assert(req.url.startsWith(form_prefix)); +async function handleForm(req: Deno.RequestEvent): Promise { + const path = new URL(req.request.url).pathname; + assert(path.startsWith(form_prefix)); - const args = req.url.substr(form_prefix.length); + const args = path.substr(form_prefix.length); const search_index = args.indexOf("?"); const form_action = args.slice(0, search_index); const search = new URLSearchParams( @@ -169,12 +175,14 @@ async function handleForm(req: ServerRequest): Promise { return; } - await req.respond({ - status: 301, - headers: new Headers({ - "Location": `/doc/${search.get("url")!}`, + await req.respondWith( + new Response("", { + status: 301, + headers: new Headers({ + "Location": `/doc/${search.get("url")!}`, + }), }), - }); + ); break; } default: @@ -187,44 +195,45 @@ async function handleForm(req: ServerRequest): Promise { } const static_prefix = "/static/"; -async function handleStatic(req: ServerRequest): Promise { - assert(req.url.startsWith(static_prefix)); - const resource = req.url.substr(static_prefix.length); +async function handleStatic(req: Deno.RequestEvent): Promise { + const path = new URL(req.request.url).pathname; + assert(path.startsWith(static_prefix)); + const resource = path.substr(static_prefix.length); const asset = assets[resource]; if (asset === undefined) { handleFail(req, 404, "Resource not found"); } else { - await req.respond({ - status: 200, - headers: new Headers({ - "Content-Type": asset.mimetype ?? "application/octet-stream", + await req.respondWith( + new Response(asset.content, { + status: 200, + headers: new Headers({ + "Content-Type": asset.mimetype ?? "application/octet-stream", + }), }), - body: asset.content, - }); + ); } } -async function handler(req: ServerRequest): Promise { +async function handler(req: Deno.RequestEvent): Promise { try { - if (!["HEAD", "GET"].includes(req.method)) { + const path = new URL(req.request.url).pathname; + if (!["HEAD", "GET"].includes(req.request.method)) { handleFail(req, 404, "Invalid method"); } - - if (req.url.startsWith(static_prefix)) { + if (path.startsWith(static_prefix)) { await handleStatic(req); - } else if (req.url.startsWith(doc_prefix)) { + } else if (path.startsWith(doc_prefix)) { await handleDoc(req); - } else if (req.url.startsWith(form_prefix)) { + } else if (path.startsWith(form_prefix)) { await handleForm(req); - } else if (req.url === "/") { + } else if (path === "/") { await handleIndex(req); } else { await handleFail(req, 404, "Malformed path"); } } finally { - req.finalize(); - req.conn.close(); + // pass } } @@ -245,7 +254,7 @@ function argCheck( } function open(s: string): void { - let run = Deno.run({ + const run = Deno.run({ cmd: Deno.build.os === "windows" ? ["start", "", s] : Deno.build.os === "darwin" @@ -287,7 +296,6 @@ async function mainGenerate() { const { builtin, dependencies, - generate, index, out, private: priv, @@ -365,18 +373,23 @@ async function mainServer() { throw null; } - let run = Deno.run({ + const run = Deno.run({ cmd: [browser, url], }); run.status().finally(() => run.close()); } catch { - open(url); + // open(url); } } - - for await (const req of serve({ hostname, port })) { - await handler(req); + const listener = Deno.listen({ port, hostname }); + for await (const conn of listener) { + (async () => { + const httpConn = Deno.serveHttp(conn); + for await (const req of httpConn) { + handler(req); + } + })(); } } diff --git a/renderer.ts b/renderer.ts index 4771cfb..838f804 100644 --- a/renderer.ts +++ b/renderer.ts @@ -2,11 +2,8 @@ import assets from "./assets.ts"; import { getDenoData } from "./deno_api.ts"; import type * as ddoc from "./deno_doc_json.ts"; import type * as info from "./deno_info_json.ts"; -import { - assert, - unreachable, -} from "./deps.ts"; -import { htmlEscape, identifierId, humanSize } from "./utility.ts"; +import { assert, unreachable } from "./deps.ts"; +import { htmlEscape, humanSize, identifierId } from "./utility.ts"; const sort_order: ddoc.DocNode["kind"][] = [ "import", From 3364ad45dee12d8733355d4ff923850e139d4641 Mon Sep 17 00:00:00 2001 From: Luca Matei Pintilie Date: Sun, 5 Sep 2021 16:03:12 +0200 Subject: [PATCH 2/3] Update code to work on latest deno version --- deno_info_json.ts | 24 ++++++++----- deps.ts | 7 +++- docuraptor.ts | 91 +++++++++++++++++++---------------------------- generator.ts | 4 +-- renderer.ts | 32 ++++++++++------- 5 files changed, 79 insertions(+), 79 deletions(-) diff --git a/deno_info_json.ts b/deno_info_json.ts index c1a3b14..6cdd74b 100644 --- a/deno_info_json.ts +++ b/deno_info_json.ts @@ -6,17 +6,23 @@ export interface DenoInfo { typescriptCache: string; } -export interface FileDependency { +export interface Dependency { + specifier: string; + code: string; +} + +export interface Module { + specifier: string; + dependencies: Dependency[]; size: number; - deps: string[]; + mediaType: string; + local: string; + checksum: string; + emit: string; } export interface FileInfo { - local: string; - fileType: "TypeScript" | "JavaScript"; - compiled: string | null; - map: string | null; - depCount: number; - totalSize: number; - files: Record; + root: string; + modules: Module[]; + size: number; } diff --git a/deps.ts b/deps.ts index 8da6224..fbcbf8f 100644 --- a/deps.ts +++ b/deps.ts @@ -1,7 +1,12 @@ export { assert, unreachable, -} from "https://deno.land/std@0.69.0/testing/asserts.ts"; +} from "https://deno.land/std@0.106.0/testing/asserts.ts"; export { parse as argsParse } from "https://deno.land/std@0.106.0/flags/mod.ts"; export { join as pathJoin } from "https://deno.land/std@0.106.0/path/mod.ts"; export { pooledMap } from "https://deno.land/std@0.106.0/async/pool.ts"; +export { + bold, + italic, + underline, +} from "https://deno.land/std@0.106.0/fmt/colors.ts"; diff --git a/docuraptor.ts b/docuraptor.ts index 07250c0..59d7a9f 100644 --- a/docuraptor.ts +++ b/docuraptor.ts @@ -1,6 +1,13 @@ // deno-lint-ignore-file camelcase import assets from "./assets.ts"; -import { argsParse, assert, unreachable } from "./deps.ts"; +import { + argsParse, + assert, + bold, + italic, + underline, + unreachable, +} from "./deps.ts"; import { generateStatic } from "./generator.ts"; import { DocRenderer } from "./renderer.ts"; import { htmlEscape } from "./utility.ts"; @@ -47,9 +54,9 @@ async function handleDoc(req: Deno.RequestEvent): Promise { await req.respondWith( new Response(doc, { status: 200, - headers: new Headers({ + headers: { "Content-Type": "text/html", - }), + }, }), ); } @@ -78,9 +85,9 @@ async function handleFail( `, { status, - headers: new Headers({ + headers: { "Content-Type": "text/html", - }), + }, }, ), ); @@ -148,9 +155,9 @@ async function handleIndex(req: Deno.RequestEvent): Promise { `, { status: 200, - headers: new Headers({ + headers: { "Content-Type": "text/html", - }), + }, }, ), ); @@ -158,29 +165,24 @@ async function handleIndex(req: Deno.RequestEvent): Promise { const form_prefix = "/form/"; async function handleForm(req: Deno.RequestEvent): Promise { - const path = new URL(req.request.url).pathname; - assert(path.startsWith(form_prefix)); + const url = new URL(req.request.url); + assert(url.pathname.startsWith(form_prefix)); - const args = path.substr(form_prefix.length); - const search_index = args.indexOf("?"); - const form_action = args.slice(0, search_index); - const search = new URLSearchParams( - search_index === -1 ? "" : args.slice(search_index), - ); + const args = url.pathname.substr(form_prefix.length); - switch (form_action) { + switch (args) { case "open": { - if (!search.has("url")) { + if (!url.searchParams.has("url")) { await handleFail(req, 400, "Received invalid request"); return; } await req.respondWith( - new Response("", { - status: 301, - headers: new Headers({ - "Location": `/doc/${search.get("url")!}`, - }), + new Response(null, { + status: 302, + headers: { + Location: `/doc/${url.searchParams.get("url")!}`, + }, }), ); break; @@ -189,7 +191,7 @@ async function handleForm(req: Deno.RequestEvent): Promise { await handleFail( req, 400, - `Invalid form action ${htmlEscape(form_action)}`, + `Invalid form action ${htmlEscape(args)}`, ); } } @@ -207,9 +209,9 @@ async function handleStatic(req: Deno.RequestEvent): Promise { await req.respondWith( new Response(asset.content, { status: 200, - headers: new Headers({ + headers: { "Content-Type": asset.mimetype ?? "application/octet-stream", - }), + }, }), ); } @@ -379,7 +381,7 @@ async function mainServer() { run.status().finally(() => run.close()); } catch { - // open(url); + open(url); } } const listener = Deno.listen({ port, hostname }); @@ -394,9 +396,9 @@ async function mainServer() { } if (import.meta.main) { - const usage_string = `%cDocuraptor%c (${import.meta.url}) + const usage_string = `${bold("Docuraptor")} (${import.meta.url}) -%cStart documentation server:%c +${underline("Start documentation server:")} $ docuraptor [--port=] [--hostname=] [--skip-browser] [--private] [--builtin | ] @@ -405,10 +407,10 @@ if the module specifier is omitted, the documentation index, in the system browser. Listens on 127.0.0.1:8709 by default. -%cAdditionally requires network access for hostname:port.%c +${italic("Additionally requires network access for hostname:port.")} -%cGenerate HTML documentation:%c +${underline("Generate HTML documentation:")} $ docuraptor --generate [--out=] [--index=] [--dependencies] [--private] ... @@ -418,42 +420,23 @@ current working directory. With the dependencies flag set documentation is also generated for all modules dependet upon. Writes an index of all generated documentation -to the index file, defaulting to %cindex.html%c. +to the index file, defaulting to ${italic("index.html")}. -%cAdditionally requires write access to the output directory.%c +${italic("Additionally requires write access to the output directory.")} -%cAll functions require allow-run and read access to the Deno cache.%c +${italic("All functions require allow-run and read access to the Deno cache.")} The system browser can be overwritten with the DOCURAPTOR_BROWSER and BROWSER environment variables. -%cRequires allow-env.%c`; - - const usage_css = [ - "font-weight: bold", - "", - "text-decoration: underline;", - "", - "font-style: italic;", - "", - "text-decoration: underline;", - "", - "font-style: italic;", - "", - "font-style: italic;", - "", - "font-style: italic;", - "", - "font-style: italic;", - "", - ]; +${italic("Requires allow-env.")}`; const { help, generate } = argsParse(Deno.args, { boolean: ["help", "generate"], }); if (help) { - console.log(usage_string, ...usage_css); + console.log(usage_string); Deno.exit(0); } diff --git a/generator.ts b/generator.ts index e56e939..f75da17 100644 --- a/generator.ts +++ b/generator.ts @@ -32,14 +32,14 @@ export async function generateStatic( assert(info, `Deno failed to generate metadata for module ${mod}`); if (options?.recursive) { - for (const mod of Object.keys(info.files)) { + for (const mod of info.modules.map((mod) => mod.specifier)) { full_modules.add(mod); } } else { try { new URL(mod); } catch { - mod = new URL(info.local, "file:///").toString(); + mod = new URL(info.root, "file:///").toString(); } full_modules.add(mod); } diff --git a/renderer.ts b/renderer.ts index 838f804..b8dfff6 100644 --- a/renderer.ts +++ b/renderer.ts @@ -2,6 +2,7 @@ import assets from "./assets.ts"; import { getDenoData } from "./deno_api.ts"; import type * as ddoc from "./deno_doc_json.ts"; import type * as info from "./deno_info_json.ts"; +import type { Dependency, Module } from "./deno_info_json.ts"; import { assert, unreachable } from "./deps.ts"; import { htmlEscape, humanSize, identifierId } from "./utility.ts"; @@ -60,7 +61,7 @@ export class DocRenderer { try { new URL(specifier!); } catch { - specifier = new URL(info_j.local, "file:///").toString(); + specifier = new URL(info_j.root, "file:///").toString(); } } @@ -407,7 +408,7 @@ export class DocRenderer { renderInfo(specifier: string, info: info.FileInfo): string { const link = ( spec: string, - dep: info.FileDependency, + dep: info.Module, icon: string, icon_type: string, ) => { @@ -421,19 +422,23 @@ export class DocRenderer { }`; }; - const unique_deps = Object.entries(info.files); - const direct_deps = new Set(info.files[specifier].deps); + const unique_deps = info.modules; + const direct_deps = new Set( + info.modules.find((module) => module.specifier === specifier) + ?.dependencies as info.Module[] | undefined, + ); function compare_deps( - a_name: string, - b_name: string, + a_name: Module, + b_name: Module, ): number { let a_dir = direct_deps.has(a_name); let b_dir = direct_deps.has(b_name); - return -(a_name === specifier) || +(b_name === specifier) || + return -(a_name.specifier === specifier) || + +(b_name.specifier === specifier) || (a_dir === b_dir - ? a_name.localeCompare(b_name) + ? a_name.specifier.localeCompare(b_name.specifier) : Number(b_dir) - Number(a_dir)); } @@ -444,18 +449,19 @@ export class DocRenderer {
Unique dependencies: ${direct_deps.size} direct; ${transitive} transitive. ${ - humanSize(info.totalSize ?? 0) + humanSize(info.size ?? 0) }
    ${ - unique_deps.sort(([sp_a], [sp_b]) => compare_deps(sp_a, sp_b)).map(( - [sp, dep], + unique_deps.sort((sp_a, sp_b) => compare_deps(sp_a, sp_b)).map(( + sp, + dep, ) => link( + sp.specifier, sp, - dep, - ...<[string, string]> (specifier === sp + ...<[string, string]> (specifier === sp.specifier ? ["S", "white"] : direct_deps.has(sp) ? ["D", "green"] From b1d2478b4114390a0b55da41eb4b24ddedb8981d Mon Sep 17 00:00:00 2001 From: Luca Matei Pintilie Date: Sun, 5 Sep 2021 17:28:30 +0200 Subject: [PATCH 3/3] Use `deno_graph` instead of `deno info` --- deno_api.ts | 32 +++++--------------------------- deno_info_json.ts | 28 ---------------------------- deps.ts | 5 +++++ generator.ts | 1 + renderer.ts | 22 +++++++++++----------- utility.ts | 3 ++- 6 files changed, 24 insertions(+), 67 deletions(-) delete mode 100644 deno_info_json.ts diff --git a/deno_api.ts b/deno_api.ts index b2a2470..8542f28 100644 --- a/deno_api.ts +++ b/deno_api.ts @@ -1,14 +1,14 @@ +// deno-lint-ignore-file camelcase import type * as ddoc from "./deno_doc_json.ts"; -import type * as info from "./deno_info_json.ts"; +import { createGraph, ModuleGraph } from "./deps.ts"; const decoder = new TextDecoder(); export async function getDenoData( specifier?: string, { private: priv }: { private?: boolean } = {}, -): Promise<{ doc: ddoc.DocNode[]; info: info.FileInfo | null }> { +): Promise<{ doc: ddoc.DocNode[]; info: ModuleGraph | null }> { let proc_d; - let proc_i; try { proc_d = Deno.run({ cmd: [ @@ -32,36 +32,14 @@ export async function getDenoData( } const doc_j: ddoc.DocNode[] = JSON.parse(stdout); - let info_j: info.FileInfo | null = null; + let info_j: ModuleGraph | null = null; if (specifier !== undefined) { - proc_i = Deno.run({ - cmd: [ - "deno", - "info", - "--json", - "--unstable", - specifier, - ], - stdin: "null", - stdout: "piped", - stderr: "piped", - }); - - const stdout = decoder.decode(await proc_i.output()); - const stderr = decoder.decode(await proc_i.stderrOutput()); - const { success } = await proc_i.status(); - - if (!success) { - throw { stderr }; - } - - info_j = JSON.parse(stdout); + info_j = await createGraph(specifier); } return { doc: doc_j, info: info_j }; } finally { proc_d?.close(); - proc_i?.close(); } } diff --git a/deno_info_json.ts b/deno_info_json.ts deleted file mode 100644 index 6cdd74b..0000000 --- a/deno_info_json.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type Info = DenoInfo | FileInfo; - -export interface DenoInfo { - denoDir: string; - modulesCache: string; - typescriptCache: string; -} - -export interface Dependency { - specifier: string; - code: string; -} - -export interface Module { - specifier: string; - dependencies: Dependency[]; - size: number; - mediaType: string; - local: string; - checksum: string; - emit: string; -} - -export interface FileInfo { - root: string; - modules: Module[]; - size: number; -} diff --git a/deps.ts b/deps.ts index fbcbf8f..35dcc83 100644 --- a/deps.ts +++ b/deps.ts @@ -10,3 +10,8 @@ export { italic, underline, } from "https://deno.land/std@0.106.0/fmt/colors.ts"; +export { createGraph } from "https://deno.land/x/deno_graph@0.2.1/mod.ts"; +export type { + Module, + ModuleGraph, +} from "https://deno.land/x/deno_graph@0.2.1/mod.ts"; diff --git a/generator.ts b/generator.ts index f75da17..66d1400 100644 --- a/generator.ts +++ b/generator.ts @@ -1,3 +1,4 @@ +// deno-lint-ignore-file camelcase import { getDenoData } from "./deno_api.ts"; import { assert, pathJoin, pooledMap } from "./deps.ts"; import { DocRenderer } from "./renderer.ts"; diff --git a/renderer.ts b/renderer.ts index b8dfff6..bf32e23 100644 --- a/renderer.ts +++ b/renderer.ts @@ -1,9 +1,8 @@ +// deno-lint-ignore-file camelcase import assets from "./assets.ts"; import { getDenoData } from "./deno_api.ts"; import type * as ddoc from "./deno_doc_json.ts"; -import type * as info from "./deno_info_json.ts"; -import type { Dependency, Module } from "./deno_info_json.ts"; -import { assert, unreachable } from "./deps.ts"; +import { assert, Module, ModuleGraph, unreachable } from "./deps.ts"; import { htmlEscape, humanSize, identifierId } from "./utility.ts"; const sort_order: ddoc.DocNode["kind"][] = [ @@ -405,10 +404,10 @@ export class DocRenderer { return res; } - renderInfo(specifier: string, info: info.FileInfo): string { + renderInfo(specifier: string, info: ModuleGraph): string { const link = ( spec: string, - dep: info.Module, + dep: Module, icon: string, icon_type: string, ) => { @@ -424,16 +423,15 @@ export class DocRenderer { const unique_deps = info.modules; const direct_deps = new Set( - info.modules.find((module) => module.specifier === specifier) - ?.dependencies as info.Module[] | undefined, + info.modules, ); function compare_deps( a_name: Module, b_name: Module, ): number { - let a_dir = direct_deps.has(a_name); - let b_dir = direct_deps.has(b_name); + const a_dir = direct_deps.has(a_name); + const b_dir = direct_deps.has(b_name); return -(a_name.specifier === specifier) || +(b_name.specifier === specifier) || @@ -449,14 +447,16 @@ export class DocRenderer {
    Unique dependencies: ${direct_deps.size} direct; ${transitive} transitive. ${ - humanSize(info.size ?? 0) + humanSize( + info.modules.find((module) => module.specifier === info.root)?.size ?? + 0, + ) }
      ${ unique_deps.sort((sp_a, sp_b) => compare_deps(sp_a, sp_b)).map(( sp, - dep, ) => link( sp.specifier, diff --git a/utility.ts b/utility.ts index 0d764f0..023b781 100644 --- a/utility.ts +++ b/utility.ts @@ -1,3 +1,4 @@ +// deno-lint-ignore-file camelcase export function htmlEscape(s: string): string { return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll( ">", @@ -13,7 +14,7 @@ export function humanSize(bytes: number): string { unit++; } - let visual = Math.round(bytes * 100) / 100; + const visual = Math.round(bytes * 100) / 100; return `${visual !== bytes ? "~" : ""}${visual}${size_units[unit]}`; }