From d6c9d7be68b944b0f6bbeb50c28102ab85cf6ac3 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Thu, 29 Feb 2024 15:30:13 +0100 Subject: [PATCH] feat: add migrate package meta tool (#61) --- tools/migrate_package_meta.ts | 139 ++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tools/migrate_package_meta.ts diff --git a/tools/migrate_package_meta.ts b/tools/migrate_package_meta.ts new file mode 100644 index 00000000..09b85324 --- /dev/null +++ b/tools/migrate_package_meta.ts @@ -0,0 +1,139 @@ +// Copyright 2024 the JSR authors. All rights reserved. MIT license. +import { Client } from "https://deno.land/x/postgres/mod.ts"; +import type { + DocNode, + JsDocTagDoc, +} from "https://deno.land/x/deno_doc@0.115.0/types.d.ts"; + +const client = new Client({ + user: "user", + password: "password", + database: "registry", + hostname: "localhost", + port: 5432, +}); +await client.connect(); + +const packages = await client.queryObject< + { + version: string; + scope: string; + name: string; + exports: Record; + } +>( + "SELECT version, scope, name, exports FROM package_versions", +); + +await Promise.all(packages.rows.map((row) => generateMeta(row))); + +async function generateMeta( + { version, scope, name, exports }: { + version: string; + scope: string; + name: string; + exports: Record; + }, +) { + const mainEntrypoint: string | undefined = exports["."] + ? (new URL(exports["."], "file:///")).href + : undefined; + const readme = await getReadme(version, scope, name); + const docNodes = await getDocNodes(version, scope, name); + const mainEntrypointDoc = mainEntrypoint + ? docNodes[mainEntrypoint].find((node) => node.kind === "moduleDoc")?.jsDoc + : undefined; + + const meta = { + hasReadme: !!readme || + (!!mainEntrypointDoc?.doc && mainEntrypointDoc.doc.length != 0), + hasReadmeExamples: readme + ? readme.includes("```") + : !!((mainEntrypointDoc?.tags?.find((tag) => tag.kind === "example") as + | JsDocTagDoc + | undefined)?.doc?.includes?.("```")), + percentageDocumentedSymbols: percentageOfSymbolsWithDocs(docNodes), + allEntrypointsDocs: allEntrypointsHaveModuleDoc(docNodes), + allFastCheck: true, + }; + + await client.queryObject( + `UPDATE package_versions SET meta = '${ + JSON.stringify(meta) + }' WHERE scope = '${scope}' AND name = '${name}' AND version = '${version}'`, + ); +} + +async function getReadme( + version: string, + scope: string, + name: string, +): Promise { + const readme = await client.queryObject<{ path: string }>( + `SELECT path FROM package_files WHERE scope = '${scope}' AND name = '${name}' AND version = '${version}' AND path ILIKE '/README%'`, + ); + + if (readme.rows.length === 0) { + return null; + } + + const res = await fetch( + `http://jsr.test/@${scope}/${name}/${version}${readme.rows[0].path}`, + ); + + return res.text(); +} + +async function getDocNodes( + version: string, + scope: string, + name: string, +): Promise> { + const res = await Deno.readTextFile( + `./.gcs/docs/@${scope}/${name}/${version}/raw.json`, + ); + return JSON.parse(res); +} + +function allEntrypointsHaveModuleDoc(docNodes: Record) { + modules: for (const [_specifier, nodes] of Object.entries(docNodes)) { + for (const node of nodes) { + if (node.kind == "moduleDoc") { + continue modules; + } + } + + return false; + } + + return true; +} + +function percentageOfSymbolsWithDocs(docNodes: Record) { + let totalSymbols = 0; + let documentedSymbols = 0; + + for (const [_specifier, nodes] of Object.entries(docNodes)) { + for (const node of nodes) { + if ( + node.kind == "moduleDoc" || + node.kind == "import" || + node.declarationKind == "private" + ) { + continue; + } + + totalSymbols += 1; + + if (node.jsDoc) { + documentedSymbols += 1; + } + } + } + + if (totalSymbols === 0) { + return 1; + } + + return documentedSymbols / totalSymbols; +}