|
| 1 | +import { Bun, getPackageJson } from "@jsdocs-io/extractor"; |
| 2 | +import { goTry } from "go-go-try"; |
| 3 | +import { join } from "pathe"; |
| 4 | +import { serverEnv } from "../../server-env"; |
| 5 | +import { checkLicense } from "../../utils/check-license"; |
| 6 | +import { packageId } from "../../utils/package-id"; |
| 7 | +import { resolvePackage } from "../../utils/resolve-package"; |
| 8 | +import { tempDir } from "../../utils/temp-dir"; |
| 9 | +import { getLogger } from "../get-logger"; |
| 10 | +import { redirect } from "../redirect"; |
| 11 | +import { parsePackageSlug } from "./parse-package-slug"; |
| 12 | + |
| 13 | +const bun = new Bun(serverEnv.BUN_PATH); |
| 14 | + |
| 15 | +export interface HandlePackageOutput {} |
| 16 | + |
| 17 | +export async function handlePackage(slug: string) { |
| 18 | + const log = getLogger("handlePackage"); |
| 19 | + log.info({ path: `/package/${slug}` }); |
| 20 | + |
| 21 | + // Start the performance timer. |
| 22 | + const start = performance.now(); |
| 23 | + |
| 24 | + // Parse the package page slug. |
| 25 | + const [slugErr, slugOut] = goTry(() => parsePackageSlug(slug)); |
| 26 | + if (slugErr !== undefined) { |
| 27 | + log.error({ err: slugErr }); |
| 28 | + return redirect("/404"); |
| 29 | + } |
| 30 | + const { pkg, pkgName, subpath } = slugOut; |
| 31 | + |
| 32 | + // Get a temporary work directory. |
| 33 | + await using dir = await tempDir(); |
| 34 | + const cwd = dir.path; |
| 35 | + |
| 36 | + // Install the package to let bun resolve the correct version. |
| 37 | + // Assume that installation errors are only caused by non existing packages. |
| 38 | + const [bunErr, packages] = await goTry(bun.add(pkg, cwd)); |
| 39 | + if (bunErr !== undefined) { |
| 40 | + log.error({ err: bunErr }); |
| 41 | + return redirect("/404"); |
| 42 | + } |
| 43 | + |
| 44 | + // Redirect to the canonical package page if necessary. |
| 45 | + const resolvedPkg = resolvePackage(pkgName, packages); |
| 46 | + const pkgId = packageId(resolvedPkg, subpath); |
| 47 | + if (pkg !== resolvedPkg) { |
| 48 | + log.info({ redirect: `${pkg} -> ${resolvedPkg}` }); |
| 49 | + return redirect(`/package/${pkgId}`); |
| 50 | + } |
| 51 | + |
| 52 | + // Read the package's own `package.json`. |
| 53 | + const pkgDir = join(cwd, "node_modules", pkgName); |
| 54 | + const [pkgJsonErr, pkgJson] = await goTry(getPackageJson(pkgDir)); |
| 55 | + if (pkgJsonErr !== undefined) { |
| 56 | + log.error({ err: pkgJsonErr }); |
| 57 | + return redirect("/500"); |
| 58 | + } |
| 59 | + |
| 60 | + // Check if the package has an SPDX license. |
| 61 | + const [licenseErr] = goTry(() => checkLicense(pkgJson.license)); |
| 62 | + if (licenseErr !== undefined) { |
| 63 | + log.warn({ warn: licenseErr }); |
| 64 | + return { |
| 65 | + status: "invalid-license" as const, |
| 66 | + pkgId, |
| 67 | + subpath, |
| 68 | + pkgJson, |
| 69 | + generatedAt: generatedAt(), |
| 70 | + generatedIn: generatedIn(start), |
| 71 | + }; |
| 72 | + } |
| 73 | + |
| 74 | + // TODO: |
| 75 | + // // Check if the package provides type definitions and if not |
| 76 | + // // check if there is an associated DefinitelyTyped (DT) package. |
| 77 | + // const typesRes = yield * Effect.either(packageTypes(pkgJson, subpath)); |
| 78 | + // if (Either.isLeft(typesRes)) { |
| 79 | + // const dtPkgName = yield * findDefinitelyTypedPackage({ pkgName, cwd }); |
| 80 | + // if (!dtPkgName) { |
| 81 | + // yield * Effect.logWarning(`no types: ${pkgId}`); |
| 82 | + // return { |
| 83 | + // status: "no-types" as const, |
| 84 | + // pkgId, |
| 85 | + // subpath, |
| 86 | + // pkgJson, |
| 87 | + // generatedAt: generatedAt(), |
| 88 | + // generatedIn: generatedIn(start), |
| 89 | + // }; |
| 90 | + // } |
| 91 | + // return { |
| 92 | + // status: "definitely-typed" as const, |
| 93 | + // pkgId, |
| 94 | + // subpath, |
| 95 | + // pkgJson, |
| 96 | + // dtPkgName, |
| 97 | + // generatedAt: generatedAt(), |
| 98 | + // generatedIn: generatedIn(start), |
| 99 | + // }; |
| 100 | + // } |
| 101 | + |
| 102 | + // // Check if the DB already has the package API. |
| 103 | + // const db = yield * Db; |
| 104 | + // yield * Effect.logInfo(`using db: ${db.name}`); |
| 105 | + // const getPkgApiRes = yield * Effect.either(db.getPackageApi({ pkg, subpath })); |
| 106 | + // if (Either.isLeft(getPkgApiRes)) { |
| 107 | + // yield * Effect.logWarning(getPkgApiRes.left); |
| 108 | + // } else { |
| 109 | + // yield * Effect.logInfo(`db has package api for: ${pkgId}`); |
| 110 | + // const pkgApi = getPkgApiRes.right; |
| 111 | + // return { |
| 112 | + // status: "with-api" as const, |
| 113 | + // pkgId, |
| 114 | + // subpath, |
| 115 | + // pkgJson, |
| 116 | + // pkgApi, |
| 117 | + // generatedAt: generatedAt(), |
| 118 | + // generatedIn: generatedIn(start), |
| 119 | + // }; |
| 120 | + // } |
| 121 | + |
| 122 | + // // Extract the package API. |
| 123 | + // const pkgApiRes = yield * Effect.either(extractPackageApi({ pkg, subpath })); |
| 124 | + // if (Either.isLeft(pkgApiRes)) { |
| 125 | + // yield * Effect.logError(pkgApiRes.left); |
| 126 | + // return { |
| 127 | + // status: "no-api" as const, |
| 128 | + // pkgId, |
| 129 | + // subpath, |
| 130 | + // pkgJson, |
| 131 | + // generatedAt: generatedAt(), |
| 132 | + // generatedIn: generatedIn(start), |
| 133 | + // }; |
| 134 | + // } |
| 135 | + // const pkgApi = pkgApiRes.right; |
| 136 | + |
| 137 | + // // Store the package API in the DB. |
| 138 | + // const setPkgApiRes = yield * Effect.either(db.setPackageApi({ pkg, subpath, pkgApi })); |
| 139 | + // if (Either.isLeft(setPkgApiRes)) { |
| 140 | + // yield * Effect.logError(setPkgApiRes.left); |
| 141 | + // } else { |
| 142 | + // yield * Effect.logInfo(`db set package api for: ${pkgId}`); |
| 143 | + // } |
| 144 | + |
| 145 | + // // Return data for rendering. |
| 146 | + // return { |
| 147 | + // status: "with-api" as const, |
| 148 | + // pkgId, |
| 149 | + // subpath, |
| 150 | + // pkgJson, |
| 151 | + // pkgApi, |
| 152 | + // generatedAt: generatedAt(), |
| 153 | + // generatedIn: generatedIn(start), |
| 154 | + // }; |
| 155 | +} |
| 156 | + |
| 157 | +function generatedAt(): string { |
| 158 | + return new Date().toISOString(); |
| 159 | +} |
| 160 | + |
| 161 | +function generatedIn(start: number): number { |
| 162 | + return Math.round(performance.now() - start); |
| 163 | +} |
0 commit comments