deno.land / std@0.224.0 / _tools / check_circular_package_dependencies.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307// deno-lint-ignore-file camelcase// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.import { createGraph, type ModuleGraphJson, type ModuleJson,} from "@deno/graph";
/** * Checks for circular dependencies in the std packages. * * Usage: deno run -A _tools/check_circular_package_dependencies.ts * * `--graph` option outputs graphviz diagram. You can convert the output to * a visual graph using tools like https://dreampuf.github.io/GraphvizOnline/ * * $ deno run -A _tools/check_circular_package_dependencies.ts --graph * * `--table` option outputs a table of packages and their status. * * $ deno run -A _tools/check_circular_package_dependencies.ts --table * * `--all-imports` option outputs script to import all packages. * * $ deno run -A _tools/check_circular_package_dependencies.ts --all-imports */
type DepState = "Stable" | "Unstable" | "Deprecated";type Dep = { name: string; set: Set<string>; state: DepState;};type Mod = | "archive" | "assert" | "async" | "bytes" | "cli" | "collections" | "console" | "crypto" | "csv" | "data_structures" | "datetime" | "dotenv" | "encoding" | "expect" | "flags" | "fmt" | "front_matter" | "fs" | "html" | "http" | "ini" | "io" | "json" | "jsonc" | "log" | "media_types" | "msgpack" | "net" | "path" | "permissions" | "regexp" | "semver" | "streams" | "testing" | "text" | "toml" | "ulid" | "url" | "uuid" | "webgpu" | "yaml";
const ENTRYPOINTS: Record<Mod, string[]> = { archive: ["mod.ts"], assert: ["mod.ts"], async: ["mod.ts"], bytes: ["mod.ts"], cli: ["mod.ts"], collections: ["mod.ts"], console: ["mod.ts"], crypto: ["mod.ts"], csv: ["mod.ts"], data_structures: ["mod.ts"], datetime: ["mod.ts"], dotenv: ["mod.ts"], encoding: [ "ascii85.ts", "base32.ts", "base58.ts", "base64.ts", "base64url.ts", "hex.ts", "varint.ts", ], expect: ["mod.ts"], flags: ["mod.ts"], fmt: ["bytes.ts", "colors.ts", "duration.ts", "printf.ts"], front_matter: ["mod.ts"], fs: ["mod.ts"], html: ["mod.ts"], http: ["mod.ts"], ini: ["mod.ts"], io: ["mod.ts"], json: ["mod.ts"], jsonc: ["mod.ts"], log: ["mod.ts"], media_types: ["mod.ts"], msgpack: ["mod.ts"], net: ["mod.ts"], path: ["mod.ts"], permissions: ["mod.ts"], regexp: ["mod.ts"], semver: ["mod.ts"], streams: ["mod.ts"], testing: ["bdd.ts", "mock.ts", "snapshot.ts", "time.ts", "types.ts"], text: ["mod.ts"], toml: ["mod.ts"], ulid: ["mod.ts"], url: ["mod.ts"], uuid: ["mod.ts"], webgpu: ["mod.ts"], yaml: ["mod.ts"],};
const STABILITY: Record<Mod, DepState> = { archive: "Unstable", assert: "Stable", async: "Stable", bytes: "Stable", cli: "Unstable", collections: "Stable", console: "Unstable", crypto: "Stable", csv: "Stable", data_structures: "Unstable", datetime: "Unstable", dotenv: "Unstable", encoding: "Stable", expect: "Unstable", flags: "Unstable", fmt: "Stable", front_matter: "Stable", fs: "Stable", html: "Unstable", http: "Unstable", ini: "Unstable", io: "Unstable", json: "Stable", jsonc: "Stable", log: "Unstable", media_types: "Stable", msgpack: "Unstable", net: "Unstable", path: "Stable", permissions: "Deprecated", regexp: "Unstable", semver: "Unstable", streams: "Stable", testing: "Stable", text: "Unstable", toml: "Stable", ulid: "Unstable", url: "Unstable", uuid: "Stable", webgpu: "Unstable", yaml: "Stable",};
const root = new URL("../", import.meta.url).href;const deps: Record<string, Dep> = {};
function getPackageNameFromUrl(url: string): string { return url.replace(root, "").split("/")[0]!;}
async function check( submod: string, state: DepState, paths: string[] = ["mod.ts"],): Promise<Dep> { const deps = new Set<string>(); for (const path of paths) { const entrypoint = new URL(`../${submod}/${path}`, import.meta.url).href; const graph = await createGraph(entrypoint);
for ( const dep of new Set(getPackageDepsFromSpecifier(graph, entrypoint)) ) { deps.add(dep); } } deps.delete(submod); deps.delete("version.ts"); return { name: submod, set: deps, state };}
/** Returns package dependencies */function getPackageDepsFromSpecifier( graph: ModuleGraphJson, specifier: string, seen: Set<string> = new Set(),): Set<string> { const { dependencies } = graph.modules.find((item: ModuleJson) => item.specifier === specifier )!; const deps = new Set([getPackageNameFromUrl(specifier)]); seen.add(specifier); if (dependencies) { for (const { code, type } of dependencies) { const specifier = code?.specifier ?? type?.specifier!; if (seen.has(specifier)) { continue; } const res = getPackageDepsFromSpecifier( graph, specifier, seen, )!; for (const dep of res) { deps.add(dep); } } } return deps;}
for (const [mod, entrypoints] of Object.entries(ENTRYPOINTS)) { deps[mod] = await check(mod, STABILITY[mod as Mod], entrypoints);}
/** Checks circular deps between sub modules */function checkCircularDeps( submod: string, ancestors: string[] = [], seen: Set<string> = new Set(),): string[] | undefined { const currentDeps = [...ancestors, submod]; if (ancestors.includes(submod)) { return currentDeps; } const d = deps[submod]; if (!d) { return; } for (const mod of d.set) { const res = checkCircularDeps(mod, currentDeps, seen); if (res) { return res; } }}
/** Formats label for diagram */function formatLabel(mod: string) { return '"' + mod.replace(/_/g, "_\\n") + '"';}
/** Returns node style (in DOT language) for each state */function stateToNodeStyle(state: DepState) { switch (state) { case "Stable": return "[shape=doublecircle fixedsize=1 height=1.1]"; case "Unstable": return "[shape=box style=filled, fillcolor=pink]"; case "Deprecated": return "[shape=box style=filled, fillcolor=gray]"; }}
if (Deno.args.includes("--graph")) { console.log("digraph std_deps {"); for (const mod of Object.keys(deps)) { const info = deps[mod]!; console.log(` ${formatLabel(mod)} ${stateToNodeStyle(info.state)};`); for (const dep of info.set) { console.log(` ${formatLabel(mod)} -> ${dep};`); } } console.log("}");} else if (Deno.args.includes("--table")) { console.log("| Package | Status |"); console.log("| --------------- | ---------- |"); for (const [mod, info] of Object.entries(deps)) { console.log(`| ${mod.padEnd(15)} | ${info.state.padEnd(10)} |`); }} else if (Deno.args.includes("--all-imports")) { for (const [mod, entrypoints] of Object.entries(ENTRYPOINTS)) { for (const path of entrypoints) { console.log(`import "std/${mod}/${path}";`); } }} else { console.log(`${Object.keys(deps).length} packages checked.`); for (const mod of Object.keys(deps)) { const res = checkCircularDeps(mod); if (res) { console.log(`Circular dependencies found: ${res.join(" -> ")}`); Deno.exit(1); } } console.log("No circular dependencies found.");}
Version Info