From 72c254eac3feb8fcb9664693a9b28ef7559efb93 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Fri, 20 Jan 2023 16:56:36 +0000 Subject: [PATCH 01/28] feat(peers): add script and modules - parse package.json - fetch peer dependencies online - check if there are mismatches in peers - keep things testable via functional --- README.md | 8 ++++ colours.ts | 13 ++++++ deps.ts | 7 ++++ fetch_peer_dependencies.test.ts | 70 ++++++++++++++++++++++++++++++++ fetch_peer_dependencies.ts | 35 ++++++++++++++++ find_mismatches.test.ts | 69 +++++++++++++++++++++++++++++++ find_mismatches.ts | 46 +++++++++++++++++++++ fixtures/package.json | 10 +++++ fixtures/package_duplicate.json | 9 ++++ fixtures/package_typescript.json | 10 +++++ fixtures/package_valid.json | 15 +++++++ main.test.ts | 3 ++ main.ts | 43 ++++++++++++++++++++ parse_dependencies.test.ts | 56 +++++++++++++++++++++++++ parse_dependencies.ts | 52 ++++++++++++++++++++++++ types.ts | 13 ++++++ 16 files changed, 459 insertions(+) create mode 100644 colours.ts create mode 100644 deps.ts create mode 100644 fetch_peer_dependencies.test.ts create mode 100644 fetch_peer_dependencies.ts create mode 100644 find_mismatches.test.ts create mode 100644 find_mismatches.ts create mode 100644 fixtures/package.json create mode 100644 fixtures/package_duplicate.json create mode 100644 fixtures/package_typescript.json create mode 100644 fixtures/package_valid.json create mode 100644 main.test.ts create mode 100644 main.ts create mode 100644 parse_dependencies.test.ts create mode 100644 parse_dependencies.ts create mode 100644 types.ts diff --git a/README.md b/README.md index ee0cd4d..0255bb7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # actions-npm-dependencies + Validate your NPM dependencies without installing Node (WIP) + +## Todo + +- [X] Make it functional and composable +- [X] Handle peer dependencies +- [ ] Handle lock files? (probably not) +- [ ] Great error messaging diff --git a/colours.ts b/colours.ts new file mode 100644 index 0000000..a8af971 --- /dev/null +++ b/colours.ts @@ -0,0 +1,13 @@ +import { + blue, + cyan, + gray, + yellow, +} from "https://deno.land/std@0.171.0/fmt/colors.ts"; + +export const colour = { + dependency: blue, + file: cyan, + subdued: gray, + version: yellow, +}; diff --git a/deps.ts b/deps.ts new file mode 100644 index 0000000..437c955 --- /dev/null +++ b/deps.ts @@ -0,0 +1,7 @@ +export { literal, object, record, string } from "https://esm.sh/zod@3.20.2"; +export { coerce, satisfies, SemVer, Range } from "https://esm.sh/semver@7.3.8"; + +export { + assertEquals, + assertThrows, +} from "https://deno.land/std@0.156.0/testing/asserts.ts"; diff --git a/fetch_peer_dependencies.test.ts b/fetch_peer_dependencies.test.ts new file mode 100644 index 0000000..5785de8 --- /dev/null +++ b/fetch_peer_dependencies.test.ts @@ -0,0 +1,70 @@ +import { assertEquals } from "./deps.ts"; +import { Range, SemVer } from "./deps.ts"; +import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; + +Deno.test("Can get peer dependencies", async () => { + const with_peer_deps = await fetch_peer_dependencies([ + // { name: "@guardian/source-foundations", version: new SemVer("8.0.0") }, + { + name: "@guardian/core-web-vitals", + version: new SemVer("2.0.2"), + peers: [], + }, + // { name: "@guardian/libs", version: new SemVer("12.0.0") }, + // { name: "@guardian/ab-react", version: new SemVer("2.0.1") }, + ]); + + assertEquals(with_peer_deps, [ + { + name: "@guardian/core-web-vitals", + version: new SemVer("2.0.2"), + peers: [ + { + name: "@guardian/libs", + range: new Range("^12.0.0"), + optional: false, + }, + { + name: "tslib", + range: new Range("^2.4.1"), + optional: false, + }, + { + name: "typescript", + range: new Range("^4.3.2"), + optional: true, + }, + { + name: "web-vitals", + range: new Range("^2.0.0"), + optional: false, + }, + ], + }, + ]); +}); + +Deno.test("Can get optional peer dependencies", async () => { + const peer_deps = await fetch_peer_dependencies([ + { name: "@guardian/libs", version: new SemVer("12.0.0"), peers: [] }, + ]); + + assertEquals(peer_deps, [ + { + name: "@guardian/libs", + version: new SemVer("12.0.0"), + peers: [ + { + name: "tslib", + range: new Range("^2.4.1"), + optional: false, + }, + { + name: "typescript", + range: new Range("^4.3.2"), + optional: true, + }, + ], + }, + ]); +}); diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts new file mode 100644 index 0000000..b9bbb53 --- /dev/null +++ b/fetch_peer_dependencies.ts @@ -0,0 +1,35 @@ +import { literal, object, Range, record, string } from "./deps.ts"; +import { Dependency, PeerDependency } from "./types.ts"; + +const { parseAsync: parse_peers } = object({ + peerDependencies: record(string()).optional(), + peerDependenciesMeta: record(object({ optional: literal(true) })).optional(), +}); + +export const fetch_peer_dependencies = ( + dependencies: Dependency[], +): Promise => + Promise.all( + dependencies.map((dependency) => + fetch( + new URL( + `${dependency.name}@${dependency.version.version}/package.json`, + "https://esm.sh/", + ), + ) + .then((res) => res.json()) + .then(parse_peers) + .then(({ peerDependencies = {}, peerDependenciesMeta }) => ({ + ...dependency, + peers: Object.entries(peerDependencies).map(([name, range]) => ({ + name, + range: new Range(range), + optional: !!peerDependenciesMeta?.[name]?.optional, + })), + })) + .catch((error) => { + console.error("🚨 Failed to parse package.json for", dependency.name); + throw error; + }) + ), + ); diff --git a/find_mismatches.test.ts b/find_mismatches.test.ts new file mode 100644 index 0000000..2c4f013 --- /dev/null +++ b/find_mismatches.test.ts @@ -0,0 +1,69 @@ +import { assertEquals } from "./deps.ts"; +import { SemVer, Range } from "./deps.ts"; +import { find_mismatched_peer_dependencies } from "./find_mismatches.ts"; + +Deno.test("Works when all depencies are matches", () => { + assertEquals( + find_mismatched_peer_dependencies([ + { + name: "one", + version: new SemVer("1.0.0"), + peers: [ + { + name: "two", + optional: false, + range: new Range("^2.0.0"), + }, + ], + }, + { name: "two", version: new SemVer("2.0.2"), peers: [] }, + ]), + [] + ); +}); + +Deno.test("Fails on invalid range", () => { + assertEquals( + find_mismatched_peer_dependencies([ + { + name: "one", + version: new SemVer("1.0.0"), + peers: [ + { + name: "two", + optional: false, + range: new Range("^2.0.3"), + }, + ], + }, + { name: "two", version: new SemVer("2.0.2"), peers: [] }, + ]), + [ + { + name: "two", + optional: false, + range: new Range("^2.0.3"), + required_by: "one", + }, + ] + ); +}); + +Deno.test("Allows optional deps to fail if absent", () => { + assertEquals( + find_mismatched_peer_dependencies([ + { + name: "one", + version: new SemVer("1.0.0"), + peers: [ + { + name: "two", + optional: true, + range: new Range("^2.0.3"), + }, + ], + }, + ]), + [] + ); +}); diff --git a/find_mismatches.ts b/find_mismatches.ts new file mode 100644 index 0000000..323ea60 --- /dev/null +++ b/find_mismatches.ts @@ -0,0 +1,46 @@ +import { colour } from "./colours.ts"; +import { satisfies } from "./deps.ts"; +import { Dependency } from "./types.ts"; + +export const find_mismatched_peer_dependencies = ( + dependencies: Dependency[], +) => { + const peers = dependencies.flatMap(({ name, peers }) => + peers.map((peer) => ({ ...peer, required_by: name })) + ); + + const mismatched = peers.filter((peer_dependency) => { + const local_version = dependencies.find( + ({ name }) => name === peer_dependency.name, + ); + if (!local_version) { + return !peer_dependency.optional; + } + return !satisfies(local_version.version, peer_dependency.range); + }); + + if (mismatched.length === 0) { + console.info(`βœ… All ${colour.file("peerDependencies")} satisfied}`); + } else { + console.error( + `🚨 The following ${colour.file("peerDependencies")} are unsatisfied:`, + ); + + for (const { name, range, required_by } of mismatched) { + console.error( + ` - ${ + [ + colour.dependency(name), + colour.subdued("@"), + colour.version(range.raw), + " (from ", + colour.dependency(required_by), + " )", + ].join("") + }`, + ); + } + } + + return mismatched; +}; diff --git a/fixtures/package.json b/fixtures/package.json new file mode 100644 index 0000000..2850bb8 --- /dev/null +++ b/fixtures/package.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "@guardian/source-foundations": "8.0.0", + "@guardian/core-web-vitals": "2.0.2" + }, + "devDependencies": { + "@guardian/libs": "^12", + "@guardian/ab-react": "^2.0.1" + } +} diff --git a/fixtures/package_duplicate.json b/fixtures/package_duplicate.json new file mode 100644 index 0000000..9eded57 --- /dev/null +++ b/fixtures/package_duplicate.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "@guardian/source-foundations": "8.0.0", + "@guardian/libs": "^12" + }, + "devDependencies": { + "@guardian/libs": "^12" + } +} diff --git a/fixtures/package_typescript.json b/fixtures/package_typescript.json new file mode 100644 index 0000000..306a2d6 --- /dev/null +++ b/fixtures/package_typescript.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "@guardian/source-foundations": "8.0.0" + }, + "devDependencies": { + "typescript": "^4.2.4", + "@guardian/libs": "^12", + "@guardian/ab-react": "^2.0.1" + } +} diff --git a/fixtures/package_valid.json b/fixtures/package_valid.json new file mode 100644 index 0000000..4a194d9 --- /dev/null +++ b/fixtures/package_valid.json @@ -0,0 +1,15 @@ +{ + "dependencies": { + "@guardian/ab-core": "2.0.0", + "@guardian/core-web-vitals": "2.0.2", + "@guardian/source-foundations": "8.0.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "tslib": "^2.4.1", + "web-vitals": "^2.0.1" + }, + "devDependencies": { + "@guardian/libs": "^12", + "@guardian/ab-react": "^2.0.1" + } +} diff --git a/main.test.ts b/main.test.ts new file mode 100644 index 0000000..10da7df --- /dev/null +++ b/main.test.ts @@ -0,0 +1,3 @@ +Deno.test("Parses package.json", () => { + return; +}); diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..f0b57e6 --- /dev/null +++ b/main.ts @@ -0,0 +1,43 @@ +import { parse_declared_dependencies } from "./parse_dependencies.ts"; +import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; +import { colour } from "./colours.ts"; +import { find_mismatched_peer_dependencies } from "./find_mismatches.ts"; + +const [package_file] = Deno.args; + +if (!package_file) { + console.error("🚨 No package.json passed as argument"); + Deno.exit(1); +} + +const filename = Deno.cwd() + "/" + package_file; + +const package_content = await Deno.readTextFile(filename).catch(() => ""); + +if (package_content) { + console.info(`βœ… Found package.json file`); +} else { + console.error("🚨 No package.json found at", colour.file(filename)); + Deno.exit(1); +} + +const dependencies = parse_declared_dependencies(package_content); + +if (dependencies.length === 0) { + console.info("βœ… You have no dependencies and therefore no issues!"); + Deno.exit(); +} + +const dependencies_with_peers = await fetch_peer_dependencies(dependencies); + +const { length: number_of_mismatched_deps } = find_mismatched_peer_dependencies( + dependencies_with_peers, +); + +if (number_of_mismatched_deps === 0) { + console.info("βœ… Everything is fine with your dependencies"); +} else { + console.info("🚨 This shal"); +} + +Deno.exit(number_of_mismatched_deps); diff --git a/parse_dependencies.test.ts b/parse_dependencies.test.ts new file mode 100644 index 0000000..a3b9d38 --- /dev/null +++ b/parse_dependencies.test.ts @@ -0,0 +1,56 @@ +import { assertEquals, assertThrows, SemVer } from "./deps.ts"; +import { parse_declared_dependencies } from "./parse_dependencies.ts"; + +Deno.test("Handles valid package.json", () => { + const json = ` + { + "dependencies": { + "one": "1.0.0", + "two": "~1.1.0" + }, + "devDependencies": { + "three": "^1.1.1", + "four": "0.0.1" + } + } + `; + + assertEquals(parse_declared_dependencies(json), [ + { + name: "one", + version: new SemVer("1.0.0"), + peers: [], + }, + { + name: "two", + version: new SemVer("1.1.0"), + peers: [], + }, + { + name: "three", + version: new SemVer("1.1.1"), + peers: [], + }, + { + name: "four", + version: new SemVer("0.0.1"), + peers: [], + }, + ]); +}); + +Deno.test("Warns on duplicate dependencies", () => { + const json = ` + { + "dependencies": { + "one": "1.0.1", + "two": "2.0.1" + }, + "devDependencies": { + "two": "2.0.2" + } + } + `; + + assertThrows(() => parse_declared_dependencies(json)); +}); diff --git a/parse_dependencies.ts b/parse_dependencies.ts new file mode 100644 index 0000000..be6fc9a --- /dev/null +++ b/parse_dependencies.ts @@ -0,0 +1,52 @@ +import { object, record, string, coerce } from "./deps.ts"; +import { colour } from "./colours.ts"; +import { Dependency } from "./types.ts"; + +const { parse } = object({ + dependencies: record(string()).optional(), + devDependencies: record(string()).optional(), +}); + +const parse_dependencies = (o: Record): Dependency[] => + Object.entries(o).map(([name, version]) => { + const semver = coerce(version); + if (!semver) throw new Error(`Could not parse semver: ${version}`); + return { + name, + version: semver, + peers: [], + }; + }); + +const find_duplicates = (deps: Dependency[]): string[] => { + const seen = new Set(); + const duplicates = new Set(); + + for (const { name } of deps) { + if (seen.has(name)) duplicates.add(name); + seen.add(name); + } + + return [...duplicates]; +}; + +export const parse_declared_dependencies = (contents: string): Dependency[] => { + const { dependencies = {}, devDependencies = {} } = parse( + JSON.parse(contents) + ); + + const deps = [dependencies, devDependencies].map(parse_dependencies).flat(); + + const duplicates = find_duplicates(deps); + + if (duplicates.length > 0) { + console.warn("🚨 Duplicate dependencies found:"); + for (const duplicate of duplicates) { + console.warn(` - ${colour.dependency(duplicate)}`); + } + + throw new Error("Duplicates found"); + } + + return deps; +}; diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..e5bb9a5 --- /dev/null +++ b/types.ts @@ -0,0 +1,13 @@ +import { SemVer, Range } from "./deps.ts"; + +export interface Dependency { + name: string; + version: SemVer; + peers: PeerDependency[]; +} + +export interface PeerDependency { + name: string; + range: Range; + optional: boolean; +} From 36641e70038898da8f63e13747fa218658d078bc Mon Sep 17 00:00:00 2001 From: Max Duval Date: Mon, 23 Jan 2023 15:39:18 +0000 Subject: [PATCH 02/28] refactor: single pass analysis of satisfied Instead of looping twice over the dependencies, check whether they are satisfied via the registry call directly based on minimum range version or optional. --- deps.ts | 2 +- fetch_peer_dependencies.test.ts | 70 ++++++++++++++++++------ fetch_peer_dependencies.ts | 94 +++++++++++++++++++++++++++------ find_mismatches.test.ts | 67 +++++++++++------------ find_mismatches.ts | 63 +++++++++++----------- main.ts | 10 ++-- parse_dependencies.test.ts | 14 ++--- parse_dependencies.ts | 11 ++-- types.ts | 14 ++--- 9 files changed, 221 insertions(+), 124 deletions(-) diff --git a/deps.ts b/deps.ts index 437c955..245a4d1 100644 --- a/deps.ts +++ b/deps.ts @@ -1,5 +1,5 @@ export { literal, object, record, string } from "https://esm.sh/zod@3.20.2"; -export { coerce, satisfies, SemVer, Range } from "https://esm.sh/semver@7.3.8"; +export * from "https://esm.sh/semver@7.3.8"; export { assertEquals, diff --git a/fetch_peer_dependencies.test.ts b/fetch_peer_dependencies.test.ts index 5785de8..fa9aab9 100644 --- a/fetch_peer_dependencies.test.ts +++ b/fetch_peer_dependencies.test.ts @@ -1,43 +1,40 @@ -import { assertEquals } from "./deps.ts"; -import { Range, SemVer } from "./deps.ts"; +import { assertEquals, SemVer } from "./deps.ts"; +import { Range } from "./deps.ts"; import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; Deno.test("Can get peer dependencies", async () => { const with_peer_deps = await fetch_peer_dependencies([ - // { name: "@guardian/source-foundations", version: new SemVer("8.0.0") }, { name: "@guardian/core-web-vitals", - version: new SemVer("2.0.2"), - peers: [], + range: new Range("2.0.2"), }, - // { name: "@guardian/libs", version: new SemVer("12.0.0") }, - // { name: "@guardian/ab-react", version: new SemVer("2.0.1") }, ]); assertEquals(with_peer_deps, [ { name: "@guardian/core-web-vitals", - version: new SemVer("2.0.2"), + range: new Range("2.0.2"), + versions: [new SemVer("2.0.2")], peers: [ { name: "@guardian/libs", range: new Range("^12.0.0"), - optional: false, + satisfied: false, }, { name: "tslib", range: new Range("^2.4.1"), - optional: false, + satisfied: false, }, { name: "typescript", range: new Range("^4.3.2"), - optional: true, + satisfied: true, }, { name: "web-vitals", range: new Range("^2.0.0"), - optional: false, + satisfied: false, }, ], }, @@ -46,25 +43,66 @@ Deno.test("Can get peer dependencies", async () => { Deno.test("Can get optional peer dependencies", async () => { const peer_deps = await fetch_peer_dependencies([ - { name: "@guardian/libs", version: new SemVer("12.0.0"), peers: [] }, + { name: "@guardian/libs", range: new Range("12.0.0") }, ]); assertEquals(peer_deps, [ { name: "@guardian/libs", - version: new SemVer("12.0.0"), + range: new Range("12.0.0"), + versions: [new SemVer("12.0.0")], peers: [ { name: "tslib", range: new Range("^2.4.1"), - optional: false, + satisfied: false, }, { name: "typescript", range: new Range("^4.3.2"), - optional: true, + satisfied: true, }, ], }, ]); }); + +Deno.test("Will fail on optional dependencies that are defined locally", async () => { + const peer_deps = await fetch_peer_dependencies([ + { name: "@guardian/libs", range: new Range("12.0.0") }, + { name: "tslib", range: new Range("2.4.1") }, + { name: "typescript", range: new Range("4.2.2") }, + ]); + + assertEquals(peer_deps, [ + { + name: "@guardian/libs", + range: new Range("12.0.0"), + versions: [new SemVer("12.0.0")], + peers: [ + { + name: "tslib", + range: new Range("^2.4.1"), + satisfied: true, + }, + { + name: "typescript", + range: new Range("^4.3.2"), + satisfied: false, + }, + ], + }, + { + name: "tslib", + range: new Range("2.4.1"), + versions: [new SemVer("2.4.1")], + peers: [], + }, + { + name: "typescript", + range: new Range("4.2.2"), + versions: [new SemVer("4.2.2")], + peers: [], + }, + ]); +}); diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index b9bbb53..cbd6d5a 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -1,32 +1,96 @@ -import { literal, object, Range, record, string } from "./deps.ts"; -import { Dependency, PeerDependency } from "./types.ts"; +import { colour } from "./colours.ts"; +import { + literal, + minVersion, + object, + Range, + record, + satisfies, + SemVer, + string, +} from "./deps.ts"; +import type { Dependency, RegistryDependency } from "./types.ts"; const { parseAsync: parse_peers } = object({ - peerDependencies: record(string()).optional(), - peerDependenciesMeta: record(object({ optional: literal(true) })).optional(), + versions: record(object({ + version: string(), + dependencies: record(string()).optional(), + peerDependencies: record(string()).optional(), + peerDependenciesMeta: record(object({ optional: literal(true) })) + .optional(), + })), }); export const fetch_peer_dependencies = ( dependencies: Dependency[], -): Promise => +): Promise => Promise.all( dependencies.map((dependency) => fetch( new URL( - `${dependency.name}@${dependency.version.version}/package.json`, - "https://esm.sh/", + encodeURIComponent(dependency.name), + "https://registry.npmjs.org/", ), ) .then((res) => res.json()) .then(parse_peers) - .then(({ peerDependencies = {}, peerDependenciesMeta }) => ({ - ...dependency, - peers: Object.entries(peerDependencies).map(([name, range]) => ({ - name, - range: new Range(range), - optional: !!peerDependenciesMeta?.[name]?.optional, - })), - })) + .then((registry) => { + const [version, ...versions] = Object.values(registry.versions) + .filter(({ version }) => satisfies(version, dependency.range)); + + if (!version) { + throw new Error( + `Could not find ${dependency.name}@${dependency.range.range}`, + ); + } + + if (Object.keys(version.dependencies ?? {}).length > 0) { + console.warn( + "πŸ” Further dependencies not analysed for", + colour.dependency(dependency.name), + colour.subdued("@"), + colour.version(version.version), + ); + } + + const peers = version.peerDependencies + ? Object.entries(version.peerDependencies).map(( + [name, range], + ) => { + const local_version = dependencies.find((dependency) => + dependency.name === name + )?.range; + + const local_min_version = local_version + ? minVersion(local_version) + : null; + const local_version_matches = local_min_version + ? satisfies(local_min_version, range) + : false; + + const is_optional = !!version.peerDependenciesMeta?.[name] + ?.optional; + + const satisfied = local_version + ? local_version_matches + : is_optional; + + return ({ + name, + range: new Range(range), + satisfied, + }); + }) + : []; + + return ({ + ...dependency, + peers, + versions: [version, ...versions].map(({ version }) => + new SemVer(version) + ), + }); + }) .catch((error) => { console.error("🚨 Failed to parse package.json for", dependency.name); throw error; diff --git a/find_mismatches.test.ts b/find_mismatches.test.ts index 2c4f013..129f63a 100644 --- a/find_mismatches.test.ts +++ b/find_mismatches.test.ts @@ -1,69 +1,62 @@ -import { assertEquals } from "./deps.ts"; -import { SemVer, Range } from "./deps.ts"; -import { find_mismatched_peer_dependencies } from "./find_mismatches.ts"; +import { assertEquals, Range, SemVer } from "./deps.ts"; -Deno.test("Works when all depencies are matches", () => { +import { count_unsatisfied_peer_dependencies } from "./find_mismatches.ts"; + +Deno.test("Works when all dependencies are matched", () => { assertEquals( - find_mismatched_peer_dependencies([ + count_unsatisfied_peer_dependencies([ { name: "one", - version: new SemVer("1.0.0"), + range: new Range("1.0.0"), + versions: [new SemVer("1.0.0")], peers: [ { name: "two", - optional: false, + satisfied: true, range: new Range("^2.0.0"), }, - ], - }, - { name: "two", version: new SemVer("2.0.2"), peers: [] }, - ]), - [] - ); -}); - -Deno.test("Fails on invalid range", () => { - assertEquals( - find_mismatched_peer_dependencies([ - { - name: "one", - version: new SemVer("1.0.0"), - peers: [ { - name: "two", - optional: false, - range: new Range("^2.0.3"), + name: "three", + satisfied: true, + range: new Range("^3.0.0"), }, ], }, - { name: "two", version: new SemVer("2.0.2"), peers: [] }, - ]), - [ { name: "two", - optional: false, - range: new Range("^2.0.3"), - required_by: "one", + range: new Range("2.0.2"), + versions: [new SemVer("2.0.2")], + peers: [ + { name: "four", satisfied: true, range: new Range("^4") }, + ], }, - ] + ]), + 0, ); }); -Deno.test("Allows optional deps to fail if absent", () => { +Deno.test("Fails on invalid range", () => { assertEquals( - find_mismatched_peer_dependencies([ + count_unsatisfied_peer_dependencies([ { name: "one", - version: new SemVer("1.0.0"), + range: new Range("1.0.0"), + versions: [new SemVer("1.0.0")], peers: [ { name: "two", - optional: true, + satisfied: false, range: new Range("^2.0.3"), }, ], }, + { + name: "two", + range: new Range("2.0.2"), + versions: [new SemVer("2.0.2")], + peers: [], + }, ]), - [] + 1, ); }); diff --git a/find_mismatches.ts b/find_mismatches.ts index 323ea60..221fd41 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -1,46 +1,47 @@ import { colour } from "./colours.ts"; -import { satisfies } from "./deps.ts"; -import { Dependency } from "./types.ts"; +import { Dependency, RegistryDependency } from "./types.ts"; -export const find_mismatched_peer_dependencies = ( - dependencies: Dependency[], -) => { - const peers = dependencies.flatMap(({ name, peers }) => - peers.map((peer) => ({ ...peer, required_by: name })) - ); +interface Matching { + name: string; + peers?: never; +} - const mismatched = peers.filter((peer_dependency) => { - const local_version = dependencies.find( - ({ name }) => name === peer_dependency.name, - ); - if (!local_version) { - return !peer_dependency.optional; - } - return !satisfies(local_version.version, peer_dependency.range); - }); +interface Missing { + name: string; + peers: Dependency[]; +} + +export const count_unsatisfied_peer_dependencies = ( + dependencies: RegistryDependency[], +) => + dependencies.map(({ name, peers }) => { + const { length: unsatisfied } = peers.filter((peer) => !peer.satisfied); - if (mismatched.length === 0) { - console.info(`βœ… All ${colour.file("peerDependencies")} satisfied}`); - } else { - console.error( - `🚨 The following ${colour.file("peerDependencies")} are unsatisfied:`, - ); + if (unsatisfied === 0) { + console.info( + `βœ… All ${colour.file("peerDependencies")} satisfied for ${ + colour.dependency(name) + }`, + ); + } else { + console.error( + `🚨 Not all ${colour.file("peerDependencies")} are satisfied for ${ + colour.dependency(name) + }:`, + ); + } - for (const { name, range, required_by } of mismatched) { + for (const { name, range, satisfied } of peers) { console.error( - ` - ${ + ` - ${satisfied ? "βœ…" : "🚨"} ${ [ colour.dependency(name), colour.subdued("@"), colour.version(range.raw), - " (from ", - colour.dependency(required_by), - " )", ].join("") }`, ); } - } - return mismatched; -}; + return unsatisfied; + }).reduce((acc, curr) => acc + curr); diff --git a/main.ts b/main.ts index f0b57e6..88d6231 100644 --- a/main.ts +++ b/main.ts @@ -1,7 +1,7 @@ import { parse_declared_dependencies } from "./parse_dependencies.ts"; import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; import { colour } from "./colours.ts"; -import { find_mismatched_peer_dependencies } from "./find_mismatches.ts"; +import { count_unsatisfied_peer_dependencies } from "./find_mismatches.ts"; const [package_file] = Deno.args; @@ -30,14 +30,18 @@ if (dependencies.length === 0) { const dependencies_with_peers = await fetch_peer_dependencies(dependencies); -const { length: number_of_mismatched_deps } = find_mismatched_peer_dependencies( +const number_of_mismatched_deps = count_unsatisfied_peer_dependencies( dependencies_with_peers, ); if (number_of_mismatched_deps === 0) { console.info("βœ… Everything is fine with your dependencies"); } else { - console.info("🚨 This shal"); + console.info( + `🚨 There are ${ + colour.file(String(number_of_mismatched_deps)) + } unsatisfied peer dependencies`, + ); } Deno.exit(number_of_mismatched_deps); diff --git a/parse_dependencies.test.ts b/parse_dependencies.test.ts index a3b9d38..3b36d5b 100644 --- a/parse_dependencies.test.ts +++ b/parse_dependencies.test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertThrows, SemVer } from "./deps.ts"; +import { assertEquals, assertThrows, Range } from "./deps.ts"; import { parse_declared_dependencies } from "./parse_dependencies.ts"; Deno.test("Handles valid package.json", () => { @@ -18,23 +18,19 @@ Deno.test("Handles valid package.json", () => { assertEquals(parse_declared_dependencies(json), [ { name: "one", - version: new SemVer("1.0.0"), - peers: [], + range: new Range("1.0.0"), }, { name: "two", - version: new SemVer("1.1.0"), - peers: [], + range: new Range("~1.1.0"), }, { name: "three", - version: new SemVer("1.1.1"), - peers: [], + range: new Range("^1.1.1"), }, { name: "four", - version: new SemVer("0.0.1"), - peers: [], + range: new Range("0.0.1"), }, ]); }); diff --git a/parse_dependencies.ts b/parse_dependencies.ts index be6fc9a..3fdd091 100644 --- a/parse_dependencies.ts +++ b/parse_dependencies.ts @@ -1,4 +1,4 @@ -import { object, record, string, coerce } from "./deps.ts"; +import { object, Range, record, string } from "./deps.ts"; import { colour } from "./colours.ts"; import { Dependency } from "./types.ts"; @@ -9,12 +9,11 @@ const { parse } = object({ const parse_dependencies = (o: Record): Dependency[] => Object.entries(o).map(([name, version]) => { - const semver = coerce(version); - if (!semver) throw new Error(`Could not parse semver: ${version}`); + const range = new Range(version); + if (!range) throw new Error(`Could not parse semver: ${version}`); return { name, - version: semver, - peers: [], + range, }; }); @@ -32,7 +31,7 @@ const find_duplicates = (deps: Dependency[]): string[] => { export const parse_declared_dependencies = (contents: string): Dependency[] => { const { dependencies = {}, devDependencies = {} } = parse( - JSON.parse(contents) + JSON.parse(contents), ); const deps = [dependencies, devDependencies].map(parse_dependencies).flat(); diff --git a/types.ts b/types.ts index e5bb9a5..d632ff9 100644 --- a/types.ts +++ b/types.ts @@ -1,13 +1,15 @@ -import { SemVer, Range } from "./deps.ts"; +import { Range, SemVer } from "./deps.ts"; export interface Dependency { name: string; - version: SemVer; + range: Range; +} + +export interface RegistryDependency extends Dependency { + versions: SemVer[]; peers: PeerDependency[]; } -export interface PeerDependency { - name: string; - range: Range; - optional: boolean; +export interface PeerDependency extends Dependency { + satisfied: boolean; } From 5eaf930568e0c6b30b2c8ec18cd46a8850587691 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Mon, 23 Jan 2023 15:43:43 +0000 Subject: [PATCH 03/28] docs: update to-do list --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0255bb7..749738d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ Validate your NPM dependencies without installing Node (WIP) ## Todo -- [X] Make it functional and composable -- [X] Handle peer dependencies +- [x] Make it functional and composable +- [x] Handle peer dependencies - [ ] Handle lock files? (probably not) -- [ ] Great error messaging +- [x] Great error messaging +- [ ] Investigate direct dependencies of dependencies, which may have peers + themselves +- [ ] Give insight into possible resolution steps, including intersects From 808aa784dce55229bd11d807fc56e29e0d798e73 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Mon, 23 Jan 2023 17:30:34 +0000 Subject: [PATCH 04/28] feat: handle edge cases and quiet mode --- deps.ts | 2 +- fetch_peer_dependencies.ts | 7 ++--- find_mismatches.ts | 53 +++++++++++++++++++++++--------------- main.ts | 18 +++++++++---- parse_dependencies.ts | 27 ++++++++++++++++--- 5 files changed, 73 insertions(+), 34 deletions(-) diff --git a/deps.ts b/deps.ts index 245a4d1..3ce103d 100644 --- a/deps.ts +++ b/deps.ts @@ -1,4 +1,4 @@ -export { literal, object, record, string } from "https://esm.sh/zod@3.20.2"; +export { boolean, object, record, string } from "https://esm.sh/zod@3.20.2"; export * from "https://esm.sh/semver@7.3.8"; export { diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index cbd6d5a..5a4a4d2 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -1,6 +1,6 @@ import { colour } from "./colours.ts"; import { - literal, + boolean, minVersion, object, Range, @@ -16,13 +16,14 @@ const { parseAsync: parse_peers } = object({ version: string(), dependencies: record(string()).optional(), peerDependencies: record(string()).optional(), - peerDependenciesMeta: record(object({ optional: literal(true) })) + peerDependenciesMeta: record(object({ optional: boolean() })) .optional(), })), }); export const fetch_peer_dependencies = ( dependencies: Dependency[], + verbose = true, ): Promise => Promise.all( dependencies.map((dependency) => @@ -44,7 +45,7 @@ export const fetch_peer_dependencies = ( ); } - if (Object.keys(version.dependencies ?? {}).length > 0) { + if (verbose && Object.keys(version.dependencies ?? {}).length > 0) { console.warn( "πŸ” Further dependencies not analysed for", colour.dependency(dependency.name), diff --git a/find_mismatches.ts b/find_mismatches.ts index 221fd41..34ed793 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -13,35 +13,46 @@ interface Missing { export const count_unsatisfied_peer_dependencies = ( dependencies: RegistryDependency[], + verbose = true, ) => dependencies.map(({ name, peers }) => { + if (peers.length === 0) return 0; + const { length: unsatisfied } = peers.filter((peer) => !peer.satisfied); if (unsatisfied === 0) { - console.info( - `βœ… All ${colour.file("peerDependencies")} satisfied for ${ - colour.dependency(name) - }`, - ); - } else { - console.error( - `🚨 Not all ${colour.file("peerDependencies")} are satisfied for ${ - colour.dependency(name) - }:`, - ); + if (verbose) { + console.info( + `βœ… All ${colour.file("peerDependencies")} satisfied for ${ + colour.dependency(name) + }`, + ); + } + return 0; } + console.error( + `🚨 Not all ${colour.file("peerDependencies")} are satisfied for ${ + colour.dependency(name) + }:`, + ); + for (const { name, range, satisfied } of peers) { - console.error( - ` - ${satisfied ? "βœ…" : "🚨"} ${ - [ - colour.dependency(name), - colour.subdued("@"), - colour.version(range.raw), - ].join("") - }`, - ); - } + const dependency = [ + colour.dependency(name), + colour.subdued("@"), + colour.version(range.raw), + ].join(""); + if (satisfied) { + if (verbose) { + console.info(` - βœ… ${dependency}`); + } + } else { + console.error( + ` - 🚨 ${dependency}`, + ); + } + } return unsatisfied; }).reduce((acc, curr) => acc + curr); diff --git a/main.ts b/main.ts index 88d6231..ecdce97 100644 --- a/main.ts +++ b/main.ts @@ -3,7 +3,9 @@ import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; import { colour } from "./colours.ts"; import { count_unsatisfied_peer_dependencies } from "./find_mismatches.ts"; -const [package_file] = Deno.args; +const [package_file, quiet] = Deno.args; + +const verbose = quiet !== "--quiet"; if (!package_file) { console.error("🚨 No package.json passed as argument"); @@ -15,7 +17,7 @@ const filename = Deno.cwd() + "/" + package_file; const package_content = await Deno.readTextFile(filename).catch(() => ""); if (package_content) { - console.info(`βœ… Found package.json file`); + if (verbose) console.info(`βœ… Found package.json file`); } else { console.error("🚨 No package.json found at", colour.file(filename)); Deno.exit(1); @@ -24,18 +26,24 @@ if (package_content) { const dependencies = parse_declared_dependencies(package_content); if (dependencies.length === 0) { - console.info("βœ… You have no dependencies and therefore no issues!"); + if (verbose) { + console.info("βœ… You have no dependencies and therefore no issues!"); + } Deno.exit(); } -const dependencies_with_peers = await fetch_peer_dependencies(dependencies); +const dependencies_with_peers = await fetch_peer_dependencies( + dependencies, + verbose, +); const number_of_mismatched_deps = count_unsatisfied_peer_dependencies( dependencies_with_peers, + verbose, ); if (number_of_mismatched_deps === 0) { - console.info("βœ… Everything is fine with your dependencies"); + if (verbose) console.info("βœ… Everything is fine with your dependencies"); } else { console.info( `🚨 There are ${ diff --git a/parse_dependencies.ts b/parse_dependencies.ts index 3fdd091..16b2984 100644 --- a/parse_dependencies.ts +++ b/parse_dependencies.ts @@ -8,12 +8,10 @@ const { parse } = object({ }); const parse_dependencies = (o: Record): Dependency[] => - Object.entries(o).map(([name, version]) => { - const range = new Range(version); - if (!range) throw new Error(`Could not parse semver: ${version}`); + Object.entries(o).map(([name, range]) => { return { name, - range, + range: new Range(range), }; }); @@ -34,6 +32,27 @@ export const parse_declared_dependencies = (contents: string): Dependency[] => { JSON.parse(contents), ); + for ( + const [name, range] of Object.entries({ + ...dependencies, + ...devDependencies, + }) + ) { + try { + new Range(range); + } catch (error: unknown) { + if (error instanceof Error) console.error(error.message); + console.warn( + "🚨 Ignored peer dependency", + colour.dependency(name), + colour.subdued("@"), + colour.version(range), + ); + delete dependencies[name]; + delete devDependencies[name]; + } + } + const deps = [dependencies, devDependencies].map(parse_dependencies).flat(); const duplicates = find_duplicates(deps); From 203301c1bacc1b954de1d330b877126b16e01c4b Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 09:49:30 +0000 Subject: [PATCH 05/28] feat: add caching option --- cache.ts | 17 +++++++++++++++++ deps.ts | 2 +- fetch_peer_dependencies.ts | 32 +++++++++++++++++++++++++++----- find_mismatches.ts | 12 +----------- main.ts | 10 ++++++---- 5 files changed, 52 insertions(+), 21 deletions(-) create mode 100644 cache.ts diff --git a/cache.ts b/cache.ts new file mode 100644 index 0000000..2f14832 --- /dev/null +++ b/cache.ts @@ -0,0 +1,17 @@ +const name = "NPM registry"; + +const cache = await caches.open(name); + +export const get = async (url: URL, use_cache: boolean) => { + if (!use_cache) { + cache.delete(url); + return fetch(url); + } + + const found = await cache.match(url); + if (found) return found; + + const response = await fetch(url); + cache.put(url, response.clone()); + return response; +}; diff --git a/deps.ts b/deps.ts index 3ce103d..bda89cd 100644 --- a/deps.ts +++ b/deps.ts @@ -4,4 +4,4 @@ export * from "https://esm.sh/semver@7.3.8"; export { assertEquals, assertThrows, -} from "https://deno.land/std@0.156.0/testing/asserts.ts"; +} from "https://deno.land/std@0.168.0/testing/asserts.ts"; diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index 5a4a4d2..792b269 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -1,3 +1,4 @@ +import { get } from "./cache.ts"; import { colour } from "./colours.ts"; import { boolean, @@ -23,15 +24,16 @@ const { parseAsync: parse_peers } = object({ export const fetch_peer_dependencies = ( dependencies: Dependency[], - verbose = true, + { verbose = false, cache = false } = {}, ): Promise => Promise.all( dependencies.map((dependency) => - fetch( + get( new URL( encodeURIComponent(dependency.name), "https://registry.npmjs.org/", ), + cache, ) .then((res) => res.json()) .then(parse_peers) @@ -48,9 +50,11 @@ export const fetch_peer_dependencies = ( if (verbose && Object.keys(version.dependencies ?? {}).length > 0) { console.warn( "πŸ” Further dependencies not analysed for", - colour.dependency(dependency.name), - colour.subdued("@"), - colour.version(version.version), + [ + colour.dependency(dependency.name), + colour.subdued("@"), + colour.version(version.version), + ].join(""), ); } @@ -98,3 +102,21 @@ export const fetch_peer_dependencies = ( }) ), ); + +Deno.bench("Fetch with cache", async () => { + await fetch_peer_dependencies([ + { + name: "@guardian/core-web-vitals", + range: new Range("2.0.2"), + }, + ], { cache: true }); +}); + +Deno.bench("Fetch without cache", async () => { + await fetch_peer_dependencies([ + { + name: "@guardian/core-web-vitals", + range: new Range("2.0.2"), + }, + ], { cache: false }); +}); diff --git a/find_mismatches.ts b/find_mismatches.ts index 34ed793..d832ba1 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -1,15 +1,5 @@ import { colour } from "./colours.ts"; -import { Dependency, RegistryDependency } from "./types.ts"; - -interface Matching { - name: string; - peers?: never; -} - -interface Missing { - name: string; - peers: Dependency[]; -} +import { RegistryDependency } from "./types.ts"; export const count_unsatisfied_peer_dependencies = ( dependencies: RegistryDependency[], diff --git a/main.ts b/main.ts index ecdce97..030f4cd 100644 --- a/main.ts +++ b/main.ts @@ -2,10 +2,12 @@ import { parse_declared_dependencies } from "./parse_dependencies.ts"; import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; import { colour } from "./colours.ts"; import { count_unsatisfied_peer_dependencies } from "./find_mismatches.ts"; +import { parse } from "https://deno.land/std@0.168.0/flags/mod.ts"; -const [package_file, quiet] = Deno.args; - -const verbose = quiet !== "--quiet"; +const { _: [package_file], verbose, cache } = parse(Deno.args, { + boolean: ["verbose", "cache"], + default: { verbose: false, cache: true }, +}); if (!package_file) { console.error("🚨 No package.json passed as argument"); @@ -34,7 +36,7 @@ if (dependencies.length === 0) { const dependencies_with_peers = await fetch_peer_dependencies( dependencies, - verbose, + { verbose, cache }, ); const number_of_mismatched_deps = count_unsatisfied_peer_dependencies( From 2b84c6c2faa8de4ab701f3c17dee2e64d20bfdcd Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 10:26:36 +0000 Subject: [PATCH 06/28] refactor: extract formatting --- colours.ts | 9 +++++++++ fetch_peer_dependencies.ts | 11 ++++------- find_mismatches.ts | 26 ++++++++++---------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/colours.ts b/colours.ts index a8af971..7d40644 100644 --- a/colours.ts +++ b/colours.ts @@ -5,9 +5,18 @@ import { yellow, } from "https://deno.land/std@0.171.0/fmt/colors.ts"; +import { Range } from "./deps.ts"; + export const colour = { dependency: blue, file: cyan, subdued: gray, version: yellow, }; + +export const format = (name: string, range: Range) => + [ + colour.dependency(name), + colour.subdued("@"), + colour.version(range.raw), + ].join(""); diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index 792b269..f8e21ae 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -1,5 +1,5 @@ import { get } from "./cache.ts"; -import { colour } from "./colours.ts"; +import { format } from "./colours.ts"; import { boolean, minVersion, @@ -49,12 +49,9 @@ export const fetch_peer_dependencies = ( if (verbose && Object.keys(version.dependencies ?? {}).length > 0) { console.warn( - "πŸ” Further dependencies not analysed for", - [ - colour.dependency(dependency.name), - colour.subdued("@"), - colour.version(version.version), - ].join(""), + `πŸ” ${ + format(dependency.name, dependency.range) + } – futher deps not analysed`, ); } diff --git a/find_mismatches.ts b/find_mismatches.ts index d832ba1..62e255f 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -1,11 +1,11 @@ -import { colour } from "./colours.ts"; +import { colour, format } from "./colours.ts"; import { RegistryDependency } from "./types.ts"; export const count_unsatisfied_peer_dependencies = ( dependencies: RegistryDependency[], verbose = true, ) => - dependencies.map(({ name, peers }) => { + dependencies.map(({ name, range, peers }) => { if (peers.length === 0) return 0; const { length: unsatisfied } = peers.filter((peer) => !peer.satisfied); @@ -13,34 +13,28 @@ export const count_unsatisfied_peer_dependencies = ( if (unsatisfied === 0) { if (verbose) { console.info( - `βœ… All ${colour.file("peerDependencies")} satisfied for ${ - colour.dependency(name) - }`, + `βœ… ${format(name, range)} – all ${ + colour.file("peerDependencies") + } satisfied`, ); } return 0; } console.error( - `🚨 Not all ${colour.file("peerDependencies")} are satisfied for ${ - colour.dependency(name) - }:`, + `🚨 ${format(name, range)} – unsatisfied ${ + colour.file("peerDependencies") + }`, ); for (const { name, range, satisfied } of peers) { - const dependency = [ - colour.dependency(name), - colour.subdued("@"), - colour.version(range.raw), - ].join(""); - if (satisfied) { if (verbose) { - console.info(` - βœ… ${dependency}`); + console.info(` - βœ… ${format(name, range)}`); } } else { console.error( - ` - 🚨 ${dependency}`, + ` - 🚨 ${format(name, range)}`, ); } } From 2f2b82b31b9d07a75efe79c74779f86b39219abb Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 11:14:25 +0000 Subject: [PATCH 07/28] fix: clean up code --- main.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.ts b/main.ts index 030f4cd..c20bcee 100644 --- a/main.ts +++ b/main.ts @@ -6,10 +6,9 @@ import { parse } from "https://deno.land/std@0.168.0/flags/mod.ts"; const { _: [package_file], verbose, cache } = parse(Deno.args, { boolean: ["verbose", "cache"], - default: { verbose: false, cache: true }, }); -if (!package_file) { +if (typeof package_file !== "string") { console.error("🚨 No package.json passed as argument"); Deno.exit(1); } From ce37682beea184a17e33e1e84af83e9d7014fe4a Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 12:14:50 +0000 Subject: [PATCH 08/28] refactor: better tree shaping --- colours.ts | 4 ++ fetch_peer_dependencies.test.ts | 15 ++++-- fetch_peer_dependencies.ts | 85 ++++++++++++++++----------------- find_mismatches.test.ts | 12 +++-- find_mismatches.ts | 51 +++++++++++--------- fixtures/package.json | 2 + fixtures/package_valid.json | 2 + main.ts | 54 ++++++++++++++------- parse_dependencies.ts | 16 ++++++- types.ts | 3 +- 10 files changed, 149 insertions(+), 95 deletions(-) diff --git a/colours.ts b/colours.ts index 7d40644..6ac01ac 100644 --- a/colours.ts +++ b/colours.ts @@ -2,6 +2,8 @@ import { blue, cyan, gray, + green, + red, yellow, } from "https://deno.land/std@0.171.0/fmt/colors.ts"; @@ -12,6 +14,8 @@ export const colour = { file: cyan, subdued: gray, version: yellow, + valid: green, + invalid: red, }; export const format = (name: string, range: Range) => diff --git a/fetch_peer_dependencies.test.ts b/fetch_peer_dependencies.test.ts index fa9aab9..7e7bae7 100644 --- a/fetch_peer_dependencies.test.ts +++ b/fetch_peer_dependencies.test.ts @@ -14,7 +14,8 @@ Deno.test("Can get peer dependencies", async () => { { name: "@guardian/core-web-vitals", range: new Range("2.0.2"), - versions: [new SemVer("2.0.2")], + version: new SemVer("2.0.2"), + dependencies: [], peers: [ { name: "@guardian/libs", @@ -50,7 +51,8 @@ Deno.test("Can get optional peer dependencies", async () => { { name: "@guardian/libs", range: new Range("12.0.0"), - versions: [new SemVer("12.0.0")], + version: new SemVer("12.0.0"), + dependencies: [], peers: [ { name: "tslib", @@ -78,7 +80,8 @@ Deno.test("Will fail on optional dependencies that are defined locally", async ( { name: "@guardian/libs", range: new Range("12.0.0"), - versions: [new SemVer("12.0.0")], + version: new SemVer("12.0.0"), + dependencies: [], peers: [ { name: "tslib", @@ -95,13 +98,15 @@ Deno.test("Will fail on optional dependencies that are defined locally", async ( { name: "tslib", range: new Range("2.4.1"), - versions: [new SemVer("2.4.1")], + version: new SemVer("2.4.1"), + dependencies: [], peers: [], }, { name: "typescript", range: new Range("4.2.2"), - versions: [new SemVer("4.2.2")], + version: new SemVer("4.2.2"), + dependencies: [], peers: [], }, ]); diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index f8e21ae..a298e9b 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -13,18 +13,19 @@ import { import type { Dependency, RegistryDependency } from "./types.ts"; const { parseAsync: parse_peers } = object({ - versions: record(object({ - version: string(), - dependencies: record(string()).optional(), - peerDependencies: record(string()).optional(), - peerDependenciesMeta: record(object({ optional: boolean() })) - .optional(), - })), + versions: record( + object({ + version: string(), + dependencies: record(string()).optional(), + peerDependencies: record(string()).optional(), + peerDependenciesMeta: record(object({ optional: boolean() })).optional(), + }), + ), }); export const fetch_peer_dependencies = ( dependencies: Dependency[], - { verbose = false, cache = false } = {}, + { cache = false } = {}, ): Promise => Promise.all( dependencies.map((dependency) => @@ -38,29 +39,20 @@ export const fetch_peer_dependencies = ( .then((res) => res.json()) .then(parse_peers) .then((registry) => { - const [version, ...versions] = Object.values(registry.versions) - .filter(({ version }) => satisfies(version, dependency.range)); + const version = Object.values(registry.versions).find(({ version }) => + satisfies(version, dependency.range) + ); if (!version) { throw new Error( - `Could not find ${dependency.name}@${dependency.range.range}`, - ); - } - - if (verbose && Object.keys(version.dependencies ?? {}).length > 0) { - console.warn( - `πŸ” ${ - format(dependency.name, dependency.range) - } – futher deps not analysed`, + `Could not find ${format(dependency.name, dependency.range)}`, ); } const peers = version.peerDependencies - ? Object.entries(version.peerDependencies).map(( - [name, range], - ) => { - const local_version = dependencies.find((dependency) => - dependency.name === name + ? Object.entries(version.peerDependencies).map(([name, range]) => { + const local_version = dependencies.find( + (dependency) => dependency.name === name, )?.range; const local_min_version = local_version @@ -77,21 +69,22 @@ export const fetch_peer_dependencies = ( ? local_version_matches : is_optional; - return ({ + return { name, range: new Range(range), satisfied, - }); + }; }) : []; - return ({ + return { ...dependency, - peers, - versions: [version, ...versions].map(({ version }) => - new SemVer(version) + dependencies: Object.entries(version.dependencies ?? {}).map( + ([name, range]) => ({ name, range: new Range(range) }), ), - }); + peers, + version: new SemVer(version.version), + }; }) .catch((error) => { console.error("🚨 Failed to parse package.json for", dependency.name); @@ -101,19 +94,25 @@ export const fetch_peer_dependencies = ( ); Deno.bench("Fetch with cache", async () => { - await fetch_peer_dependencies([ - { - name: "@guardian/core-web-vitals", - range: new Range("2.0.2"), - }, - ], { cache: true }); + await fetch_peer_dependencies( + [ + { + name: "@guardian/core-web-vitals", + range: new Range("2.0.2"), + }, + ], + { cache: true }, + ); }); Deno.bench("Fetch without cache", async () => { - await fetch_peer_dependencies([ - { - name: "@guardian/core-web-vitals", - range: new Range("2.0.2"), - }, - ], { cache: false }); + await fetch_peer_dependencies( + [ + { + name: "@guardian/core-web-vitals", + range: new Range("2.0.2"), + }, + ], + { cache: false }, + ); }); diff --git a/find_mismatches.test.ts b/find_mismatches.test.ts index 129f63a..695f2f4 100644 --- a/find_mismatches.test.ts +++ b/find_mismatches.test.ts @@ -8,7 +8,8 @@ Deno.test("Works when all dependencies are matched", () => { { name: "one", range: new Range("1.0.0"), - versions: [new SemVer("1.0.0")], + version: new SemVer("1.0.0"), + dependencies: [], peers: [ { name: "two", @@ -25,7 +26,8 @@ Deno.test("Works when all dependencies are matched", () => { { name: "two", range: new Range("2.0.2"), - versions: [new SemVer("2.0.2")], + version: new SemVer("2.0.2"), + dependencies: [], peers: [ { name: "four", satisfied: true, range: new Range("^4") }, ], @@ -41,7 +43,8 @@ Deno.test("Fails on invalid range", () => { { name: "one", range: new Range("1.0.0"), - versions: [new SemVer("1.0.0")], + version: new SemVer("1.0.0"), + dependencies: [], peers: [ { name: "two", @@ -53,7 +56,8 @@ Deno.test("Fails on invalid range", () => { { name: "two", range: new Range("2.0.2"), - versions: [new SemVer("2.0.2")], + version: new SemVer("2.0.2"), + dependencies: [], peers: [], }, ]), diff --git a/find_mismatches.ts b/find_mismatches.ts index 62e255f..87e2296 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -3,40 +3,45 @@ import { RegistryDependency } from "./types.ts"; export const count_unsatisfied_peer_dependencies = ( dependencies: RegistryDependency[], - verbose = true, ) => - dependencies.map(({ name, range, peers }) => { - if (peers.length === 0) return 0; + dependencies.map(({ peers }) => + peers.filter((peer) => !peer.satisfied).length + ) + .reduce((acc, curr) => acc + curr); - const { length: unsatisfied } = peers.filter((peer) => !peer.satisfied); +export const format_dependencies = ( + dependencies: RegistryDependency[], + verbose = true, +): void => { + dependencies.map(({ name, range, dependencies, peers }) => { + console.info( + `β”œβ”€ ${format(name, range)}`, + ); - if (unsatisfied === 0) { - if (verbose) { - console.info( - `βœ… ${format(name, range)} – all ${ - colour.file("peerDependencies") - } satisfied`, - ); - } - return 0; + let count = dependencies.length; + for (const dependency of dependencies) { + const angle = peers.length === 0 && --count === 0 ? "β•°" : "β”œ"; + console.warn( + `β”‚ ${angle}─ ${colour.version("β–²")} ${ + format(dependency.name, dependency.range) + } – futher ${colour.file("dependencies")} not analysed`, + ); } - console.error( - `🚨 ${format(name, range)} – unsatisfied ${ - colour.file("peerDependencies") - }`, - ); - + count = peers.length; for (const { name, range, satisfied } of peers) { + const angle = --count === 0 ? "β•°" : "β”œ"; if (satisfied) { if (verbose) { - console.info(` - βœ… ${format(name, range)}`); + console.info( + `β”‚ ${angle}─ ${colour.valid("β—‹")} ${format(name, range)}`, + ); } } else { console.error( - ` - 🚨 ${format(name, range)}`, + `β”‚ ${angle}─ ${colour.invalid("βœ•")} ${format(name, range)}`, ); } } - return unsatisfied; - }).reduce((acc, curr) => acc + curr); + }); +}; diff --git a/fixtures/package.json b/fixtures/package.json index 2850bb8..5a0d1d7 100644 --- a/fixtures/package.json +++ b/fixtures/package.json @@ -1,4 +1,6 @@ { + "name": "npm-dependencies", + "version": "0.0.1", "dependencies": { "@guardian/source-foundations": "8.0.0", "@guardian/core-web-vitals": "2.0.2" diff --git a/fixtures/package_valid.json b/fixtures/package_valid.json index 4a194d9..69268b2 100644 --- a/fixtures/package_valid.json +++ b/fixtures/package_valid.json @@ -1,4 +1,6 @@ { + "name": "npm-dependencies", + "version": "0.0.1", "dependencies": { "@guardian/ab-core": "2.0.0", "@guardian/core-web-vitals": "2.0.2", diff --git a/main.ts b/main.ts index c20bcee..390d8fc 100644 --- a/main.ts +++ b/main.ts @@ -1,7 +1,13 @@ -import { parse_declared_dependencies } from "./parse_dependencies.ts"; +import { + parse_declared_dependencies, + parse_package_info, +} from "./parse_dependencies.ts"; import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; -import { colour } from "./colours.ts"; -import { count_unsatisfied_peer_dependencies } from "./find_mismatches.ts"; +import { colour, format } from "./colours.ts"; +import { + count_unsatisfied_peer_dependencies, + format_dependencies, +} from "./find_mismatches.ts"; import { parse } from "https://deno.land/std@0.168.0/flags/mod.ts"; const { _: [package_file], verbose, cache } = parse(Deno.args, { @@ -15,41 +21,55 @@ if (typeof package_file !== "string") { const filename = Deno.cwd() + "/" + package_file; -const package_content = await Deno.readTextFile(filename).catch(() => ""); +const package_content: unknown = await Deno.readTextFile(filename).catch(() => + "" +).then( + (contents) => JSON.parse(contents), +); -if (package_content) { - if (verbose) console.info(`βœ… Found package.json file`); -} else { +if (!package_content) { console.error("🚨 No package.json found at", colour.file(filename)); Deno.exit(1); } -const dependencies = parse_declared_dependencies(package_content); +const { name, range } = parse_package_info(package_content); + +console.info(`${format(name, range)}`); -if (dependencies.length === 0) { +const dependencies_from_package = parse_declared_dependencies(package_content); + +if (dependencies_from_package.length === 0) { if (verbose) { console.info("βœ… You have no dependencies and therefore no issues!"); } Deno.exit(); } -const dependencies_with_peers = await fetch_peer_dependencies( - dependencies, - { verbose, cache }, +const dependencies_from_registry = await fetch_peer_dependencies( + dependencies_from_package, + { cache }, ); -const number_of_mismatched_deps = count_unsatisfied_peer_dependencies( - dependencies_with_peers, +format_dependencies( + dependencies_from_registry, verbose, ); +const number_of_mismatched_deps = count_unsatisfied_peer_dependencies( + dependencies_from_registry, +); + if (number_of_mismatched_deps === 0) { - if (verbose) console.info("βœ… Everything is fine with your dependencies"); + if (verbose) console.info("βœ… Dependencies are in good shape"); +} else if (number_of_mismatched_deps === 1) { + console.error( + `🚨 There is ${colour.file("1")} dependencies problem`, + ); } else { - console.info( + console.error( `🚨 There are ${ colour.file(String(number_of_mismatched_deps)) - } unsatisfied peer dependencies`, + } dependencies problems`, ); } diff --git a/parse_dependencies.ts b/parse_dependencies.ts index 16b2984..e1fea1d 100644 --- a/parse_dependencies.ts +++ b/parse_dependencies.ts @@ -27,9 +27,21 @@ const find_duplicates = (deps: Dependency[]): string[] => { return [...duplicates]; }; -export const parse_declared_dependencies = (contents: string): Dependency[] => { +const package_parser = object({ + name: string(), + version: string(), +}); + +export const parse_package_info = (contents: unknown): Dependency => { + const { name, version } = package_parser.parse(contents); + return { name, range: new Range(version) }; +}; + +export const parse_declared_dependencies = ( + contents: unknown, +): Dependency[] => { const { dependencies = {}, devDependencies = {} } = parse( - JSON.parse(contents), + contents, ); for ( diff --git a/types.ts b/types.ts index d632ff9..891d82e 100644 --- a/types.ts +++ b/types.ts @@ -6,7 +6,8 @@ export interface Dependency { } export interface RegistryDependency extends Dependency { - versions: SemVer[]; + version: SemVer; + dependencies: Dependency[] peers: PeerDependency[]; } From 860fe75ad863833ab92cba981d5f5ee436843e5e Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 14:29:58 +0000 Subject: [PATCH 09/28] fix: handle invalid ranges --- fetch_peer_dependencies.ts | 12 +++++++++++- find_mismatches.ts | 39 ++++++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index a298e9b..ab89b8f 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -79,7 +79,17 @@ export const fetch_peer_dependencies = ( return { ...dependency, - dependencies: Object.entries(version.dependencies ?? {}).map( + dependencies: Object.entries(version.dependencies ?? {}).filter( + ([name, range]) => { + try { + new Range(range); + return true; + } catch { + console.warn("Invalid range:", `${name}@${range}`); + } + return false; + }, + ).map( ([name, range]) => ({ name, range: new Range(range) }), ), peers, diff --git a/find_mismatches.ts b/find_mismatches.ts index 87e2296..87d16c2 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -10,26 +10,31 @@ export const count_unsatisfied_peer_dependencies = ( .reduce((acc, curr) => acc + curr); export const format_dependencies = ( - dependencies: RegistryDependency[], + registry_dependencies: RegistryDependency[], verbose = true, ): void => { - dependencies.map(({ name, range, dependencies, peers }) => { - console.info( - `β”œβ”€ ${format(name, range)}`, - ); + for (const { name, range, dependencies, peers } of registry_dependencies) { + const unsatisfied = peers.filter(({ satisfied }) => !satisfied); + if (verbose || unsatisfied.length > 0) { + console.info( + `β”œβ”€ ${format(name, range)}`, + ); + } let count = dependencies.length; - for (const dependency of dependencies) { - const angle = peers.length === 0 && --count === 0 ? "β•°" : "β”œ"; - console.warn( - `β”‚ ${angle}─ ${colour.version("β–²")} ${ - format(dependency.name, dependency.range) - } – futher ${colour.file("dependencies")} not analysed`, - ); + if (verbose) { + for (const dependency of dependencies) { + const angle = peers.length === 0 && --count === 0 ? "β•°" : "β”œ"; + console.warn( + `β”‚ ${angle}─ ${colour.version("β–²")} ${ + format(dependency.name, dependency.range) + } – futher ${colour.file("dependencies")} not analysed`, + ); + } } - count = peers.length; - for (const { name, range, satisfied } of peers) { + count = verbose ? peers.length : unsatisfied.length; + for (const { name, range, satisfied } of verbose ? peers : unsatisfied) { const angle = --count === 0 ? "β•°" : "β”œ"; if (satisfied) { if (verbose) { @@ -39,9 +44,11 @@ export const format_dependencies = ( } } else { console.error( - `β”‚ ${angle}─ ${colour.invalid("βœ•")} ${format(name, range)}`, + `β”‚ ${angle}─ ${colour.invalid("βœ•")} ${ + format(name, range) + } – unsatisfied peer dependency`, ); } } - }); + } }; From 7490588f7b932068118dcf49c6421374aedd57fb Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 15:04:27 +0000 Subject: [PATCH 10/28] feat: breathe a little add more breathing room to CLI output --- find_mismatches.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/find_mismatches.ts b/find_mismatches.ts index 87d16c2..aea53f6 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -16,9 +16,8 @@ export const format_dependencies = ( for (const { name, range, dependencies, peers } of registry_dependencies) { const unsatisfied = peers.filter(({ satisfied }) => !satisfied); if (verbose || unsatisfied.length > 0) { - console.info( - `β”œβ”€ ${format(name, range)}`, - ); + console.info(`β”‚`); + console.info(`β”œβ”€ ${format(name, range)}`); } let count = dependencies.length; @@ -44,9 +43,7 @@ export const format_dependencies = ( } } else { console.error( - `β”‚ ${angle}─ ${colour.invalid("βœ•")} ${ - format(name, range) - } – unsatisfied peer dependency`, + `β”‚ ${angle}─ ${colour.invalid("βœ•")} ${format(name, range)}`, ); } } From e4083dd7e7e88811171808cb06077f241459acbd Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 15:15:38 +0000 Subject: [PATCH 11/28] feat: indicate the reason for mismatch --- fetch_peer_dependencies.ts | 1 + find_mismatches.ts | 12 ++++++++++-- fixtures/package_duplicate.json | 2 ++ fixtures/package_typescript.json | 2 ++ types.ts | 3 ++- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index ab89b8f..149c7a5 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -73,6 +73,7 @@ export const fetch_peer_dependencies = ( name, range: new Range(range), satisfied, + local: local_version, }; }) : []; diff --git a/find_mismatches.ts b/find_mismatches.ts index aea53f6..950d67d 100644 --- a/find_mismatches.ts +++ b/find_mismatches.ts @@ -33,7 +33,9 @@ export const format_dependencies = ( } count = verbose ? peers.length : unsatisfied.length; - for (const { name, range, satisfied } of verbose ? peers : unsatisfied) { + for ( + const { name, range, satisfied, local } of verbose ? peers : unsatisfied + ) { const angle = --count === 0 ? "β•°" : "β”œ"; if (satisfied) { if (verbose) { @@ -42,8 +44,14 @@ export const format_dependencies = ( ); } } else { + const reason = local + ? `locally specified to ${colour.version(local.raw)}` + : "locally missing"; + console.error( - `β”‚ ${angle}─ ${colour.invalid("βœ•")} ${format(name, range)}`, + `β”‚ ${angle}─ ${colour.invalid("βœ•")} ${ + format(name, range) + } – ${reason}`, ); } } diff --git a/fixtures/package_duplicate.json b/fixtures/package_duplicate.json index 9eded57..ff6e348 100644 --- a/fixtures/package_duplicate.json +++ b/fixtures/package_duplicate.json @@ -1,4 +1,6 @@ { + "name": "npm-dependencies-duplicate", + "version": "0.0.2", "dependencies": { "@guardian/source-foundations": "8.0.0", "@guardian/libs": "^12" diff --git a/fixtures/package_typescript.json b/fixtures/package_typescript.json index 306a2d6..dde15e4 100644 --- a/fixtures/package_typescript.json +++ b/fixtures/package_typescript.json @@ -1,4 +1,6 @@ { + "name": "npm-dependencies-typescript", + "version": "0.0.1", "dependencies": { "@guardian/source-foundations": "8.0.0" }, diff --git a/types.ts b/types.ts index 891d82e..9669db5 100644 --- a/types.ts +++ b/types.ts @@ -7,10 +7,11 @@ export interface Dependency { export interface RegistryDependency extends Dependency { version: SemVer; - dependencies: Dependency[] + dependencies: Dependency[]; peers: PeerDependency[]; } export interface PeerDependency extends Dependency { satisfied: boolean; + local?: Range; } From d5d7af389e47599c7197dfea13503ca3bb409f00 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 18:09:37 +0000 Subject: [PATCH 12/28] refactor: extract benchmark --- fetch_peer_dependencies.bench.ts | 26 ++++++++++++++++++++++++++ fetch_peer_dependencies.ts | 24 ------------------------ 2 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 fetch_peer_dependencies.bench.ts diff --git a/fetch_peer_dependencies.bench.ts b/fetch_peer_dependencies.bench.ts new file mode 100644 index 0000000..9ce0ea4 --- /dev/null +++ b/fetch_peer_dependencies.bench.ts @@ -0,0 +1,26 @@ +import { Range } from "./deps.ts"; +import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; + +Deno.bench("Fetch with cache", async () => { + await fetch_peer_dependencies( + [ + { + name: "@guardian/core-web-vitals", + range: new Range("2.0.2"), + }, + ], + { cache: true }, + ); +}); + +Deno.bench("Fetch without cache", async () => { + await fetch_peer_dependencies( + [ + { + name: "@guardian/core-web-vitals", + range: new Range("2.0.2"), + }, + ], + { cache: false }, + ); +}); diff --git a/fetch_peer_dependencies.ts b/fetch_peer_dependencies.ts index 149c7a5..8f3e750 100644 --- a/fetch_peer_dependencies.ts +++ b/fetch_peer_dependencies.ts @@ -103,27 +103,3 @@ export const fetch_peer_dependencies = ( }) ), ); - -Deno.bench("Fetch with cache", async () => { - await fetch_peer_dependencies( - [ - { - name: "@guardian/core-web-vitals", - range: new Range("2.0.2"), - }, - ], - { cache: true }, - ); -}); - -Deno.bench("Fetch without cache", async () => { - await fetch_peer_dependencies( - [ - { - name: "@guardian/core-web-vitals", - range: new Range("2.0.2"), - }, - ], - { cache: false }, - ); -}); From 9832ba9ce3d3c9a071a16bd300592fb7dba9f8ce Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 24 Jan 2023 18:09:47 +0000 Subject: [PATCH 13/28] feat: add lock file --- lock.json | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 lock.json diff --git a/lock.json b/lock.json new file mode 100644 index 0000000..feec490 --- /dev/null +++ b/lock.json @@ -0,0 +1,197 @@ +{ + "version": "2", + "remote": { + "https://deno.land/std@0.168.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", + "https://deno.land/std@0.168.0/flags/mod.ts": "4f50ec6383c02684db35de38b3ffb2cd5b9fcfcc0b1147055d1980c49e82521c", + "https://deno.land/std@0.168.0/fmt/colors.ts": "03ad95e543d2808bc43c17a3dd29d25b43d0f16287fe562a0be89bf632454a12", + "https://deno.land/std@0.168.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c", + "https://deno.land/std@0.168.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832", + "https://deno.land/std@0.168.0/testing/asserts.ts": "51353e79437361d4b02d8e32f3fc83b22231bc8f8d4c841d86fd32b0b0afe940", + "https://deno.land/std@0.171.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", + "https://deno.land/std@0.173.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.173.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.173.0/async/abortable.ts": "73acfb3ed7261ce0d930dbe89e43db8d34e017b063cf0eaa7d215477bf53442e", + "https://deno.land/std@0.173.0/async/deadline.ts": "b98e50d2c42399af03ad13bbb8cf59dadb9f0cd5d70648cc0c3b9202d75ab565", + "https://deno.land/std@0.173.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332", + "https://deno.land/std@0.173.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8", + "https://deno.land/std@0.173.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd", + "https://deno.land/std@0.173.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576", + "https://deno.land/std@0.173.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9", + "https://deno.land/std@0.173.0/async/pool.ts": "fd082bd4aaf26445909889435a5c74334c017847842ec035739b4ae637ae8260", + "https://deno.land/std@0.173.0/async/retry.ts": "5efa3ba450ac0c07a40a82e2df296287b5013755d232049efd7ea2244f15b20f", + "https://deno.land/std@0.173.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757", + "https://deno.land/std@0.173.0/bytes/index_of_needle.ts": "65c939607df609374c4415598fa4dad04a2f14c4d98cd15775216f0aaf597f24", + "https://deno.land/std@0.173.0/crypto/timing_safe_equal.ts": "8d69ab611c67fe51b6127d97fcfb4d8e7d0e1b6b4f3e0cc4ab86744c3691f965", + "https://deno.land/std@0.173.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1", + "https://deno.land/std@0.173.0/encoding/base64url.ts": "3f1178f6446834457b16bfde8b559c1cd3481727fe384d3385e4a9995dc2d851", + "https://deno.land/std@0.173.0/flags/mod.ts": "d1cdefa18472ef69858a17df5cf7c98445ed27ac10e1460183081303b0ebc270", + "https://deno.land/std@0.173.0/node/_core.ts": "8667bf9129cdcbaac6f5c14f4def45ca954928baaa697a0ffdb0ee1556d4c644", + "https://deno.land/std@0.173.0/node/_events.d.ts": "1347437fd6b084d7c9a4e16b9fe7435f00b030970086482edeeb3b179d0775af", + "https://deno.land/std@0.173.0/node/_events.mjs": "d4ba4e629abe3db9f1b14659fd5c282b7da8b2b95eaf13238eee4ebb142a2448", + "https://deno.land/std@0.173.0/node/_global.d.ts": "2d88342f38b4083b858998e27c706725fb03a74aa14ef8d985dc18438b5188e4", + "https://deno.land/std@0.173.0/node/_next_tick.ts": "9a3cf107d59b019a355d3cf32275b4c6157282e4b68ea85b46a799cb1d379305", + "https://deno.land/std@0.173.0/node/_process/exiting.ts": "6e336180aaabd1192bf99ffeb0d14b689116a3dec1dfb34a2afbacd6766e98ab", + "https://deno.land/std@0.173.0/node/_process/process.ts": "c96bb1f6253824c372f4866ee006dcefda02b7050d46759736e403f862d91051", + "https://deno.land/std@0.173.0/node/_process/stdio.mjs": "cf17727eac8da3a665851df700b5aca6a12bacc3ebbf33e63e4b919f80ba44a6", + "https://deno.land/std@0.173.0/node/_process/streams.mjs": "c1461c4dbf963a93a0ca8233467573a685bbde347562573761cc9435fd7080f6", + "https://deno.land/std@0.173.0/node/_stream.d.ts": "112e1a0677cd6db932c3ce0e6e5bbdc7a2ac1874572f449044ecc82afcf5ee2e", + "https://deno.land/std@0.173.0/node/_stream.mjs": "d6e2c86c1158ac65b4c2ca4fa019d7e84374ff12e21e2175345fe68c0823efe3", + "https://deno.land/std@0.173.0/node/_utils.ts": "7fd55872a0cf9275e3c080a60e2fa6d45b8de9e956ebcde9053e72a344185884", + "https://deno.land/std@0.173.0/node/buffer.ts": "85617be2063eccaf177dbb84c7580d1e32023724ed14bd9df4e453b152a26167", + "https://deno.land/std@0.173.0/node/events.ts": "d2de352d509de11a375e2cb397d6b98f5fed4e562fc1d41be33214903a38e6b0", + "https://deno.land/std@0.173.0/node/internal/buffer.d.ts": "bdfa991cd88cb02fd08bf8235d2618550e3e511c970b2a8f2e1a6885a2793cac", + "https://deno.land/std@0.173.0/node/internal/buffer.mjs": "e92303a3cc6d9aaabcd270a937ad9319825d9ba08cb332650944df4562029b27", + "https://deno.land/std@0.173.0/node/internal/crypto/_keys.ts": "8f3c3b5a141aa0331a53c205e9338655f1b3b307a08085fd6ff6dda6f7c4190b", + "https://deno.land/std@0.173.0/node/internal/crypto/constants.ts": "544d605703053218499b08214f2e25cf4310651d535b7ab995891c4b7a217693", + "https://deno.land/std@0.173.0/node/internal/error_codes.ts": "8495e33f448a484518d76fa3d41d34fc20fe03c14b30130ad8e936b0035d4b8b", + "https://deno.land/std@0.173.0/node/internal/errors.ts": "1c699b8a3cb93174f697a348c004b1c6d576b66688eac8a48ebb78e65c720aae", + "https://deno.land/std@0.173.0/node/internal/fixed_queue.ts": "62bb119afa5b5ae8fc0c7048b50502347bec82e2588017d0b250c4671d6eff8f", + "https://deno.land/std@0.173.0/node/internal/hide_stack_frames.ts": "9dd1bad0a6e62a1042ce3a51eb1b1ecee2f246907bff44835f86e8f021de679a", + "https://deno.land/std@0.173.0/node/internal/net.ts": "5538d31b595ac63d4b3e90393168bc65ace2f332c3317cffa2fd780070b2d86c", + "https://deno.land/std@0.173.0/node/internal/normalize_encoding.mjs": "fd1d9df61c44d7196432f6e8244621468715131d18cc79cd299fc78ac549f707", + "https://deno.land/std@0.173.0/node/internal/options.ts": "888f267c3fe8f18dc7b2f2fbdbe7e4a0fd3302ff3e99f5d6645601e924f3e3fb", + "https://deno.land/std@0.173.0/node/internal/primordials.mjs": "a72d86b5aa55d3d50b8e916b6a59b7cc0dc5a31da8937114b4a113ad5aa08c74", + "https://deno.land/std@0.173.0/node/internal/process/per_thread.mjs": "10142bbb13978c2f8f79778ad90f3a67a8ea6d8d2970f3dfc6bf2c6fff0162a2", + "https://deno.land/std@0.173.0/node/internal/readline/callbacks.mjs": "bdb129b140c3b21b5e08cdc3d8e43517ad818ac03f75197338d665cca1cbaed3", + "https://deno.land/std@0.173.0/node/internal/readline/utils.mjs": "c3dbf3a97c01ed14052cca3848f09e2fc24818c1822ceed57c33b9f0840f3b87", + "https://deno.land/std@0.173.0/node/internal/streams/destroy.mjs": "b665fc71178919a34ddeac8389d162a81b4bc693ff7dc2557fa41b3a91011967", + "https://deno.land/std@0.173.0/node/internal/streams/end-of-stream.mjs": "a4fb1c2e32d58dff440d4e716e2c4daaa403b3095304a028bb428575cfeed716", + "https://deno.land/std@0.173.0/node/internal/streams/utils.mjs": "f2fe2e6bdc506da24c758970890cc2a21642045b129dee618bd3827c60dd9e33", + "https://deno.land/std@0.173.0/node/internal/util.mjs": "f7fe2e1ca5e66f550ad0856b9f5ee4d666f0c071fe212ea7fc7f37cfa81f97a5", + "https://deno.land/std@0.173.0/node/internal/util/inspect.mjs": "11d7c9cab514b8e485acc3978c74b837263ff9c08ae4537fa18ad56bae633259", + "https://deno.land/std@0.173.0/node/internal/util/types.ts": "4f3625ea39111eaae1443c834e769b0c5ce9ea33b31d5a853b02af6a78105178", + "https://deno.land/std@0.173.0/node/internal/validators.mjs": "e02f2b02dd072a5d623970292588d541204dc82207b4c58985d933a5f4b382e6", + "https://deno.land/std@0.173.0/node/internal_binding/_libuv_winerror.ts": "30c9569603d4b97a1f1a034d88a3f74800d5ea1f12fcc3d225c9899d4e1a518b", + "https://deno.land/std@0.173.0/node/internal_binding/_listen.ts": "c6038be47116f7755c01fd98340a0d1e8e66ef874710ab59ed3f5607d50d7a25", + "https://deno.land/std@0.173.0/node/internal_binding/_node.ts": "cb2389b0eab121df99853eb6a5e3a684e4537e065fb8bf2cca0cbf219ce4e32e", + "https://deno.land/std@0.173.0/node/internal_binding/_timingSafeEqual.ts": "7d9732464d3c669ff07713868ce5d25bc974a06112edbfb5f017fc3c70c0853e", + "https://deno.land/std@0.173.0/node/internal_binding/_utils.ts": "7c58a2fbb031a204dee9583ba211cf9c67922112fe77e7f0b3226112469e9fe1", + "https://deno.land/std@0.173.0/node/internal_binding/_winerror.ts": "3e8cfdfe22e89f13d2b28529bab35155e6b1730c0221ec5a6fc7077dc037be13", + "https://deno.land/std@0.173.0/node/internal_binding/ares.ts": "bdd34c679265a6c115a8cfdde000656837a0a0dcdb0e4c258e622e136e9c31b8", + "https://deno.land/std@0.173.0/node/internal_binding/async_wrap.ts": "0dc5ae64eea2c9e57ab17887ef1573922245167ffe38e3685c28d636f487f1b7", + "https://deno.land/std@0.173.0/node/internal_binding/buffer.ts": "31729e0537921d6c730ad0afea44a7e8a0a1044d070ade8368226cb6f7390c8b", + "https://deno.land/std@0.173.0/node/internal_binding/cares_wrap.ts": "9b7247772167f8ed56acd0244a232d9d50e8d7c9cfc379f77f3d54cecc2f32ab", + "https://deno.land/std@0.173.0/node/internal_binding/config.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/connection_wrap.ts": "7dd089ea46de38e4992d0f43a09b586e4cf04878fb06863c1cb8cb2ece7da521", + "https://deno.land/std@0.173.0/node/internal_binding/constants.ts": "21ff9d1ee71d0a2086541083a7711842fc6ae25e264dbf45c73815aadce06f4c", + "https://deno.land/std@0.173.0/node/internal_binding/contextify.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/credentials.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/crypto.ts": "29e8f94f283a2e7d4229d3551369c6a40c2af9737fad948cb9be56bef6c468cd", + "https://deno.land/std@0.173.0/node/internal_binding/errors.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/fs.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/fs_dir.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/fs_event_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/handle_wrap.ts": "adf0b8063da2c54f26edd5e8ec50296a4d38e42716a70a229f14654b17a071d9", + "https://deno.land/std@0.173.0/node/internal_binding/heap_utils.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/http_parser.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/icu.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/inspector.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/js_stream.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/messaging.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/mod.ts": "9fc65f7af1d35e2d3557539a558ea9ad7a9954eefafe614ad82d94bddfe25845", + "https://deno.land/std@0.173.0/node/internal_binding/module_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/native_module.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/natives.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/node_file.ts": "21edbbc95653e45514aff252b6cae7bf127a4338cbc5f090557d258aa205d8a5", + "https://deno.land/std@0.173.0/node/internal_binding/node_options.ts": "0b5cb0bf4379a39278d7b7bb6bb2c2751baf428fe437abe5ed3e8441fae1f18b", + "https://deno.land/std@0.173.0/node/internal_binding/options.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/os.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/performance.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/pipe_wrap.ts": "e5429879551fb7195039986fe6da920a86971fad4342046cbf653643e6c85e21", + "https://deno.land/std@0.173.0/node/internal_binding/process_methods.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/report.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/serdes.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/signal_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/spawn_sync.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/stream_wrap.ts": "452bff74d1db280a0cd78c75a95bb6d163e849e06e9638c4af405d40296bd050", + "https://deno.land/std@0.173.0/node/internal_binding/string_decoder.ts": "54c3c1cbd5a9254881be58bf22637965dc69535483014dab60487e299cb95445", + "https://deno.land/std@0.173.0/node/internal_binding/symbols.ts": "4dee2f3a400d711fd57fa3430b8de1fdb011e08e260b81fef5b81cc06ed77129", + "https://deno.land/std@0.173.0/node/internal_binding/task_queue.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/tcp_wrap.ts": "cbede7224fcf0adc4b04e2e1222488a7a9c137807f143bd32cc8b1a121e0d4fa", + "https://deno.land/std@0.173.0/node/internal_binding/timers.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/tls_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/trace_events.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/tty_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/types.ts": "5a658bf08975af30d0fad6fa6247274379be26ba3f023425bec03e61c74083ef", + "https://deno.land/std@0.173.0/node/internal_binding/udp_wrap.ts": "cc86f7e51bf56fd619505cf9d4f77d7aae1526abdf295399dd277162d28ca6c1", + "https://deno.land/std@0.173.0/node/internal_binding/url.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/util.ts": "808ff3b92740284184ab824adfc420e75398c88c8bccf5111f0c24ac18c48f10", + "https://deno.land/std@0.173.0/node/internal_binding/uv.ts": "eb0048e30af4db407fb3f95563e30d70efd6187051c033713b0a5b768593a3a3", + "https://deno.land/std@0.173.0/node/internal_binding/v8.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/worker.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/zlib.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/process.ts": "6608012d6d51a17a7346f36079c574b9b9f81f1b5c35436489ad089f39757466", + "https://deno.land/std@0.173.0/node/stream.ts": "09e348302af40dcc7dc58aa5e40fdff868d11d8d6b0cfb85cbb9c75b9fe450c7", + "https://deno.land/std@0.173.0/node/string_decoder.ts": "1a17e3572037c512cc5fc4b29076613e90f225474362d18da908cb7e5ccb7e88", + "https://deno.land/std@0.173.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.173.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.173.0/path/_util.ts": "86c2375a996c1931b2f2ac71fefd5ddf0cf0e579fa4ab12d3e4c552d4223b8d8", + "https://deno.land/std@0.173.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.173.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.173.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", + "https://deno.land/std@0.173.0/path/posix.ts": "0874b341c2c6968ca38d323338b8b295ea1dae10fa872a768d812e2e7d634789", + "https://deno.land/std@0.173.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.173.0/path/win32.ts": "672942919dd66ce1b8c224e77d3447e2ad8254eaff13fe6946420a9f78fa141e", + "https://deno.land/std@0.173.0/streams/write_all.ts": "3b2e1ce44913f966348ce353d02fa5369e94115181037cd8b602510853ec3033", + "https://deno.land/std@0.173.0/types.d.ts": "220ed56662a0bd393ba5d124aa6ae2ad36a00d2fcbc0e8666a65f4606aaa9784", + "https://esm.sh/semver@7.3.8": "66cf5abf347faf6aacb50a0f1ee0ec9ac2c66e57fa1c5b975fd2cb56fe53c956", + "https://esm.sh/v104/@types/semver@7.3.13/classes/comparator.d.ts": "793bfebb9bb0a72aef35d56cf59fefc343c96c3c83f33e4b6133d9e520ea4969", + "https://esm.sh/v104/@types/semver@7.3.13/classes/range.d.ts": "70acf43190f9af19604401b079199cfc618082ddac3de48be2ee9694113f47e5", + "https://esm.sh/v104/@types/semver@7.3.13/classes/semver.d.ts": "d549806ef609c83125b75954e470a6f34335005fcbf81be87426b5a9e0d39608", + "https://esm.sh/v104/@types/semver@7.3.13/functions/clean.d.ts": "a5eb84f0ee2822d98b3d8516509352a33ce8688965eb9fac13cf0e21eda62845", + "https://esm.sh/v104/@types/semver@7.3.13/functions/cmp.d.ts": "d1e4d215126fe0db53e060245cf5e697e6002d7335a09cefbed4af9f3fb54a5e", + "https://esm.sh/v104/@types/semver@7.3.13/functions/coerce.d.ts": "19cddcbff1ce1d540aeaab143b0b9a71633ebe255ab08aaae0ad4f36bef49db0", + "https://esm.sh/v104/@types/semver@7.3.13/functions/compare-build.d.ts": "b5277ca0bdb12056c3687b8cee47734492f67bb0b58b4b7073e965ccbf708503", + "https://esm.sh/v104/@types/semver@7.3.13/functions/compare-loose.d.ts": "d19bbef11362960a463317e82f993dff44f656a87eb02bb7452ba0a862fe7e9e", + "https://esm.sh/v104/@types/semver@7.3.13/functions/compare.d.ts": "b6adb9d9ace09dba2b1cc582b551139e61e0e65d7da31f1cc9f30c3075d3e13d", + "https://esm.sh/v104/@types/semver@7.3.13/functions/diff.d.ts": "d39a11c79c625ca1ef1ce6f99fa08542c0b106bde116186cf45e97b6211aa9c7", + "https://esm.sh/v104/@types/semver@7.3.13/functions/eq.d.ts": "6f80724fa45cc52b273e3416b2484b98fc5c236fffa40d7218a4b6299b381b22", + "https://esm.sh/v104/@types/semver@7.3.13/functions/gt.d.ts": "54846f82c2156b18e41e6a1d62e074e76d7f01440bc1c641fc72e9f334d67c3b", + "https://esm.sh/v104/@types/semver@7.3.13/functions/gte.d.ts": "de60cee0c3cf3acf97636b73529129437b883e07190674389da683e1f48f8474", + "https://esm.sh/v104/@types/semver@7.3.13/functions/inc.d.ts": "bad52417b5275f8cd6dfe53c0d94b84bdf4735a676e0f7ce70cbe76cd29a2f02", + "https://esm.sh/v104/@types/semver@7.3.13/functions/lt.d.ts": "c9fb4feb61e5e39e482b1bd6438a0237e6b708f148b5827ce4332925576c5804", + "https://esm.sh/v104/@types/semver@7.3.13/functions/lte.d.ts": "0c57de12d88bf67cb888332a67b71d6dcf7e6a0f41e8c2768aea6ffcbbe0574a", + "https://esm.sh/v104/@types/semver@7.3.13/functions/major.d.ts": "d9f654955c3f42874a69c3993819f7eccd93fc191538252556b4b6b62250abdb", + "https://esm.sh/v104/@types/semver@7.3.13/functions/minor.d.ts": "0dadf77ef9211bd11deb83b28dc36210987fcf125118fcdceef2f250984f753e", + "https://esm.sh/v104/@types/semver@7.3.13/functions/neq.d.ts": "b57f145cb5c8cee6d2913186adab341ca1baa8addd81dc96e3b495b52ec60657", + "https://esm.sh/v104/@types/semver@7.3.13/functions/parse.d.ts": "7a47a862beeb12ebbb976e49d0cc4d95ad6aa07622c6953cb29b4d0623d456a6", + "https://esm.sh/v104/@types/semver@7.3.13/functions/patch.d.ts": "8bb0cc352dc68930a91100124413d20c4f088574c08619160a10a9af9e141291", + "https://esm.sh/v104/@types/semver@7.3.13/functions/prerelease.d.ts": "2144f4fcc33a321c10eed831c4748cda4fc0c9c5e648a71fa7899d36c8c00923", + "https://esm.sh/v104/@types/semver@7.3.13/functions/rcompare.d.ts": "cd9ddb89bed51980102fab0c3986f9ee7a64e27d117c1adbe3ca738fb0eea12a", + "https://esm.sh/v104/@types/semver@7.3.13/functions/rsort.d.ts": "b8e949f8075bfa126fae1c421da8c8e559a07c4c47840f355420e56b299c91bb", + "https://esm.sh/v104/@types/semver@7.3.13/functions/satisfies.d.ts": "eeddc303a4753825486dca6028f73b404419d49a0dac668495c8f71bce92d771", + "https://esm.sh/v104/@types/semver@7.3.13/functions/sort.d.ts": "e74717fa7ab44e65972cba3afe5db0fbb7c48a35346bd2072ca6c9f3749923d7", + "https://esm.sh/v104/@types/semver@7.3.13/functions/valid.d.ts": "30ed3b9abb6ae21e71bcdf23a44d1de33bdcd05445c6a748370cd21b756fc2f1", + "https://esm.sh/v104/@types/semver@7.3.13/index.d.ts": "bf2f2a19df95d88e17f96a3914a5024d9f61c9e874e0d744b3bc0e6223e9e328", + "https://esm.sh/v104/@types/semver@7.3.13/internals/identifiers.d.ts": "34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/gtr.d.ts": "fa58a5179a81fb88b32a0f0f5604e9cdc915f8734b6e394ff59b734e1a068a8d", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/intersects.d.ts": "aa6958786e1a4d90c0ca301edc86e04bc737e0bd489f5f38187bc6587474845b", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/ltr.d.ts": "1d7789ee92780ac62c4d331138e922d325286fb1d78c8447addfec23925ddb0f", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/max-satisfying.d.ts": "13524ac8f9a3c501aa03a9949ba5c5f01c6c30e33ff5c3505e1d2dc7a384b973", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/min-satisfying.d.ts": "192af061294d60671545e7341ad69494e6921b69805a15eb619afdf71f7a17c5", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/min-version.d.ts": "b6ccc5b599a1bd2677cd3a516984467f413f7c201bfe01c2f174f6afcbf98d50", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/outside.d.ts": "cf6a2f381910c3dd0e9e4c69719589a850cf843589759d9182eb6421a6864a65", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/simplify.d.ts": "568f0867b37eb79d1ca33c9712c7ada9ef41d722ad59cb0e5f37e89298861554", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/subset.d.ts": "4f46d5b9fdf398aac809a233fdc756e8bf4e2b743b159d24c9cb8b3a32c35f9f", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/to-comparators.d.ts": "ed87996328ada2100faff62f857b05fcedd53d1303a1f9b40cc3750ff0df10e4", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/valid.d.ts": "d717439745a1ab3fae112cdf7eaaf569304328039f8b6f1e1c508040c67e32e7", + "https://esm.sh/v104/lru-cache@6.0.0/deno/lru-cache.js": "fbacc9d1ad6bd5f2b957937d47d373f80707087ddc22e36148f0bee153f601df", + "https://esm.sh/v104/semver@7.3.8/deno/semver.js": "ebd99a9b9db10a5d2a18d53cb020825f316b25ffa131f2a287551c428e3aa52c", + "https://esm.sh/v104/yallist@4.0.0/deno/yallist.js": "55a926cdcbd6f8b54bb87a8b07589f02aa45ff136a51fc7f0c671a08e7d5c6f8", + "https://esm.sh/v104/zod@3.20.2/deno/zod.js": "4e1c7e6ae3b866c33bfebc0c6886cb22ce577dd9ebf53cebdbd8fa8a7ab4d739", + "https://esm.sh/v104/zod@3.20.2/index.d.ts": "b51c954b9557b1dcb03b2bf34cd136e710cb9aa276e686cac0e291249590a52a", + "https://esm.sh/v104/zod@3.20.2/lib/ZodError.d.ts": "c53bb6e3d82b7813bed0096be4185d253c8f7d32db9c8960bc7f06980f86261e", + "https://esm.sh/v104/zod@3.20.2/lib/errors.d.ts": "5ec2790f877581637c058eb5bbb72e855a078620d0781d2a6a8f308fe1d5436f", + "https://esm.sh/v104/zod@3.20.2/lib/external.d.ts": "91cec2fe1e1fb2574812a29a57ad2c39a1e69b0fa1179547382c92649fc5c66e", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/enumUtil.d.ts": "f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/errorUtil.d.ts": "98a9cc18f661d28e6bd31c436e1984f3980f35e0f0aa9cf795c54f8ccb667ffe", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/parseUtil.d.ts": "d3dbfc2f2fbf2c1bdea72db72f9f5c95d2ca5b4d32c9e229764ebaba0920203c", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/partialUtil.d.ts": "7da6f1e431746c5364ebe6703f5d3a844ab0495e2a8cedc0a29cced52d6284a1", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/typeAliases.d.ts": "5487b97cfa28b26b4a9ef0770f872bdbebd4c46124858de00f242c3eed7519f4", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/util.d.ts": "bb98c05ae5cb9bd7cb7ad76fe517251a661787a6f24337b842f47faf393f79c7", + "https://esm.sh/v104/zod@3.20.2/lib/index.d.ts": "0f86f053aecd06054359e146b6cd800fee9bf82905f8afb47f1fe774fd37311e", + "https://esm.sh/v104/zod@3.20.2/lib/locales/en.d.ts": "f8ec1611afb8d132c6c71822718805866890cf2071025153af97508eff24e4b6", + "https://esm.sh/v104/zod@3.20.2/lib/types.d.ts": "744e3c65de31c5f7f8e79f9a37e1e372c9043a5ca130ec1038180ba4c9941f1f", + "https://esm.sh/zod@3.20.2": "5da460955f749f7688ad10d15edfefd8eaa41d8a7ed54e65f6abd10d8d879128" + } +} From 4e728f7a8faf310198774c2b5178828d1c0cf705 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 11:29:44 +0000 Subject: [PATCH 14/28] refactor: check package in a single pass --- fetch_peer_dependencies.test.ts | 8 +++++ main.test.ts | 3 -- parse_dependencies.test.ts | 38 ++++++++--------------- parse_dependencies.ts | 54 +++++++++++---------------------- types.ts | 5 +++ 5 files changed, 44 insertions(+), 64 deletions(-) delete mode 100644 main.test.ts diff --git a/fetch_peer_dependencies.test.ts b/fetch_peer_dependencies.test.ts index 7e7bae7..ce4b567 100644 --- a/fetch_peer_dependencies.test.ts +++ b/fetch_peer_dependencies.test.ts @@ -21,21 +21,25 @@ Deno.test("Can get peer dependencies", async () => { name: "@guardian/libs", range: new Range("^12.0.0"), satisfied: false, + local: undefined, }, { name: "tslib", range: new Range("^2.4.1"), satisfied: false, + local: undefined, }, { name: "typescript", range: new Range("^4.3.2"), satisfied: true, + local: undefined, }, { name: "web-vitals", range: new Range("^2.0.0"), satisfied: false, + local: undefined, }, ], }, @@ -58,11 +62,13 @@ Deno.test("Can get optional peer dependencies", async () => { name: "tslib", range: new Range("^2.4.1"), satisfied: false, + local: undefined, }, { name: "typescript", range: new Range("^4.3.2"), satisfied: true, + local: undefined, }, ], }, @@ -87,11 +93,13 @@ Deno.test("Will fail on optional dependencies that are defined locally", async ( name: "tslib", range: new Range("^2.4.1"), satisfied: true, + local: new Range("2.4.1"), }, { name: "typescript", range: new Range("^4.3.2"), satisfied: false, + local: new Range("4.2.2"), }, ], }, diff --git a/main.test.ts b/main.test.ts deleted file mode 100644 index 10da7df..0000000 --- a/main.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -Deno.test("Parses package.json", () => { - return; -}); diff --git a/parse_dependencies.test.ts b/parse_dependencies.test.ts index 3b36d5b..f654f0c 100644 --- a/parse_dependencies.test.ts +++ b/parse_dependencies.test.ts @@ -2,20 +2,14 @@ import { assertEquals, assertThrows, Range } from "./deps.ts"; import { parse_declared_dependencies } from "./parse_dependencies.ts"; Deno.test("Handles valid package.json", () => { - const json = ` - { - "dependencies": { - "one": "1.0.0", - "two": "~1.1.0" - }, - "devDependencies": { - "three": "^1.1.1", - "four": "0.0.1" - } - } - `; + const tuples = [ + ["one", "1.0.0"], + ["two", "~1.1.0"], + ["three", "^1.1.1"], + ["four", "0.0.1"], + ] satisfies [string, string][]; - assertEquals(parse_declared_dependencies(json), [ + assertEquals(parse_declared_dependencies(tuples), [ { name: "one", range: new Range("1.0.0"), @@ -36,17 +30,11 @@ Deno.test("Handles valid package.json", () => { }); Deno.test("Warns on duplicate dependencies", () => { - const json = ` - { - "dependencies": { - "one": "1.0.1", - "two": "2.0.1" - }, - "devDependencies": { - "two": "2.0.2" - } - } - `; + const tuples = [ + ["one", "1.0.1"], + ["two", "2.0.1"], + ["two", "2.0.2"], + ] satisfies [string, string][]; - assertThrows(() => parse_declared_dependencies(json)); + assertThrows(() => parse_declared_dependencies(tuples)); }); diff --git a/parse_dependencies.ts b/parse_dependencies.ts index e1fea1d..e1b19b4 100644 --- a/parse_dependencies.ts +++ b/parse_dependencies.ts @@ -1,25 +1,12 @@ import { object, Range, record, string } from "./deps.ts"; import { colour } from "./colours.ts"; -import { Dependency } from "./types.ts"; +import { Dependency, UnrefinedDependency } from "./types.ts"; -const { parse } = object({ - dependencies: record(string()).optional(), - devDependencies: record(string()).optional(), -}); - -const parse_dependencies = (o: Record): Dependency[] => - Object.entries(o).map(([name, range]) => { - return { - name, - range: new Range(range), - }; - }); - -const find_duplicates = (deps: Dependency[]): string[] => { +const find_duplicates = (dependencies: Dependency[]): string[] => { const seen = new Set(); const duplicates = new Set(); - for (const { name } of deps) { + for (const { name } of dependencies) { if (seen.has(name)) duplicates.add(name); seen.add(name); } @@ -30,28 +17,26 @@ const find_duplicates = (deps: Dependency[]): string[] => { const package_parser = object({ name: string(), version: string(), + dependencies: record(string()).optional(), + devDependencies: record(string()).optional(), }); -export const parse_package_info = (contents: unknown): Dependency => { - const { name, version } = package_parser.parse(contents); - return { name, range: new Range(version) }; +export const parse_package_info = (contents: unknown): UnrefinedDependency => { + const { name, version, dependencies = {}, devDependencies = {} } = + package_parser.parse( + contents, + ); + return { name, range: new Range(version), dependencies, devDependencies }; }; +const isDefined = (_: T): _ is NonNullable => typeof _ !== "undefined"; + export const parse_declared_dependencies = ( - contents: unknown, + dependencies: [name: string, range: string][], ): Dependency[] => { - const { dependencies = {}, devDependencies = {} } = parse( - contents, - ); - - for ( - const [name, range] of Object.entries({ - ...dependencies, - ...devDependencies, - }) - ) { + const deps = dependencies.map(([name, range]) => { try { - new Range(range); + return { name, range: new Range(range) }; } catch (error: unknown) { if (error instanceof Error) console.error(error.message); console.warn( @@ -60,12 +45,9 @@ export const parse_declared_dependencies = ( colour.subdued("@"), colour.version(range), ); - delete dependencies[name]; - delete devDependencies[name]; } - } - - const deps = [dependencies, devDependencies].map(parse_dependencies).flat(); + return undefined; + }).filter(isDefined).flat(); const duplicates = find_duplicates(deps); diff --git a/types.ts b/types.ts index 9669db5..738b852 100644 --- a/types.ts +++ b/types.ts @@ -5,6 +5,11 @@ export interface Dependency { range: Range; } +export interface UnrefinedDependency extends Dependency { + dependencies: Record; + devDependencies: Record; +} + export interface RegistryDependency extends Dependency { version: SemVer; dependencies: Dependency[]; From 25097a9da45cfec1b51a319dc55dba4a50389cf8 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 11:30:01 +0000 Subject: [PATCH 15/28] feat: check @types/* packages --- check_types.test.ts | 55 +++++++++++++++++++++++++++++++++++++++++++ check_types.ts | 40 +++++++++++++++++++++++++++++++ main.ts | 49 ++++++++++++++++++++++++++++++-------- parse_dependencies.ts | 3 +-- utils.ts | 2 ++ 5 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 check_types.test.ts create mode 100644 check_types.ts create mode 100644 utils.ts diff --git a/check_types.test.ts b/check_types.test.ts new file mode 100644 index 0000000..9d0dbdd --- /dev/null +++ b/check_types.test.ts @@ -0,0 +1,55 @@ +import { assertEquals } from "./deps.ts"; +import { matched_types, mismatches } from "./check_types.ts"; + +Deno.test("will not complain on types without an associated pacakge", () => { + const mismatches = matched_types([ + ["@types/node", "~8.11"], + ["typescript", "^4.9"], + ]); + + assertEquals(mismatches, []); +}); + +Deno.test("will find potential mismatches on types with an associated pacakge", () => { + const matched = matched_types([ + ["@types/react", "~16.1"], + ["react", "^16.1"], + ]); + + assertEquals(matched, [ + { name: "react", range: "^16.1", type_range: "~16.1" }, + ]); +}); + +Deno.test("will error on caret ranges", () => { + const mismatched = mismatches(matched_types([ + ["@types/react", "^17"], + ["react", "^17"], + ])); + + assertEquals(mismatched, [ + ["react", "Invalid caret (^) notation. Use tilde (~) instead"], + ]); +}); + +Deno.test("will error on invalid major ranges", () => { + const mismatched = mismatches(matched_types([ + ["@types/react", "~17.1"], + ["react", "~18.1"], + ])); + + assertEquals(mismatched, [ + ["react", "Mismatching major versions"], + ]); +}); + +Deno.test("will error on invalid minor ranges", () => { + const mismatched = mismatches(matched_types([ + ["@types/react", "~17.1"], + ["react", "~17.0"], + ])); + + assertEquals(mismatched, [ + ["react", "Mismatching minor versions"], + ]); +}); diff --git a/check_types.ts b/check_types.ts new file mode 100644 index 0000000..e203b3a --- /dev/null +++ b/check_types.ts @@ -0,0 +1,40 @@ +import { major, minor, minVersion } from "./deps.ts"; +import { isDefined } from "./utils.ts"; + +export const filter_types = (dependencies: string[]) => + dependencies.filter((dependency) => dependency.startsWith("@types/")); + +export const matched_types = (dependencies: [string, string][]) => + dependencies.map(([name, range]) => { + const [, type_range] = dependencies.find(([other_name]) => + other_name === `@types/${name}` + ) ?? []; + + return type_range ? { name, range, type_range } : undefined; + }).filter(isDefined); + +export const mismatches = (dependencies: ReturnType) => + dependencies.map(({ name, range, type_range }) => { + if (range.startsWith("^") || type_range.startsWith("^")) { + return [ + name, + "Invalid caret (^) notation. Use tilde (~) instead", + ] as const; + } + + const main_min = minVersion(range); + const type_min = minVersion(type_range); + + if (!main_min || !type_min) { + return [name, "Invalid range or version types"] as const; + } + + if (major(main_min) !== major(type_min)) { + return [name, "Mismatching major versions"] as const; + } + if (minor(main_min) !== minor(type_min)) { + return [name, "Mismatching minor versions"] as const; + } + + return undefined; + }).filter(isDefined); diff --git a/main.ts b/main.ts index 390d8fc..3580894 100644 --- a/main.ts +++ b/main.ts @@ -9,6 +9,7 @@ import { format_dependencies, } from "./find_mismatches.ts"; import { parse } from "https://deno.land/std@0.168.0/flags/mod.ts"; +import { filter_types } from "./check_types.ts"; const { _: [package_file], verbose, cache } = parse(Deno.args, { boolean: ["verbose", "cache"], @@ -32,15 +33,34 @@ if (!package_content) { Deno.exit(1); } -const { name, range } = parse_package_info(package_content); +const { name, range, dependencies, devDependencies } = parse_package_info( + package_content, +); console.info(`${format(name, range)}`); -const dependencies_from_package = parse_declared_dependencies(package_content); +const types_in_direct_dependencies = filter_types(Object.keys(dependencies)); + +if (types_in_direct_dependencies.length > 0) { + console.error( + `β”œβ”€ ${colour.invalid("βœ•")} ${ + colour.dependency("@types/*") + } should only be present in devDependencies`, + ); +} + +const dependencies_tuple: [name: string, range: string][] = [ + dependencies, + devDependencies, +].map((_) => Object.entries(_)).flat(); + +const dependencies_from_package = parse_declared_dependencies( + dependencies_tuple, +); if (dependencies_from_package.length === 0) { if (verbose) { - console.info("βœ… You have no dependencies and therefore no issues!"); + console.info("β•° You have no dependencies and therefore no issues!"); } Deno.exit(); } @@ -59,18 +79,27 @@ const number_of_mismatched_deps = count_unsatisfied_peer_dependencies( dependencies_from_registry, ); -if (number_of_mismatched_deps === 0) { - if (verbose) console.info("βœ… Dependencies are in good shape"); -} else if (number_of_mismatched_deps === 1) { +const problems = number_of_mismatched_deps + + types_in_direct_dependencies.length; + +console.info("β”‚"); + +if (problems === 0) { + if (verbose) { + console.info(`╰─ ${colour.valid("β—‹")} Dependencies are in good shape`); + } +} else if (problems === 1) { console.error( - `🚨 There is ${colour.file("1")} dependencies problem`, + `╰─ ${colour.invalid("βœ•")} There is ${ + colour.invalid("1") + } dependencies problem`, ); } else { console.error( - `🚨 There are ${ - colour.file(String(number_of_mismatched_deps)) + `╰─ ${colour.invalid("βœ•")} There are ${ + colour.invalid(String(problems)) } dependencies problems`, ); } -Deno.exit(number_of_mismatched_deps); +Deno.exit(problems); diff --git a/parse_dependencies.ts b/parse_dependencies.ts index e1b19b4..764ca80 100644 --- a/parse_dependencies.ts +++ b/parse_dependencies.ts @@ -1,6 +1,7 @@ import { object, Range, record, string } from "./deps.ts"; import { colour } from "./colours.ts"; import { Dependency, UnrefinedDependency } from "./types.ts"; +import { isDefined } from "./utils.ts"; const find_duplicates = (dependencies: Dependency[]): string[] => { const seen = new Set(); @@ -29,8 +30,6 @@ export const parse_package_info = (contents: unknown): UnrefinedDependency => { return { name, range: new Range(version), dependencies, devDependencies }; }; -const isDefined = (_: T): _ is NonNullable => typeof _ !== "undefined"; - export const parse_declared_dependencies = ( dependencies: [name: string, range: string][], ): Dependency[] => { diff --git a/utils.ts b/utils.ts new file mode 100644 index 0000000..f7b0607 --- /dev/null +++ b/utils.ts @@ -0,0 +1,2 @@ +export const isDefined = (_: T): _ is NonNullable => + typeof _ !== "undefined"; From 79c779e6bb9045823ad79586481b8259dfee0640 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 11:30:17 +0000 Subject: [PATCH 16/28] chore: change colours and add deno config --- colours.ts | 6 +++--- deno.jsonc | 6 ++++++ fixtures/package.json | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 deno.jsonc diff --git a/colours.ts b/colours.ts index 6ac01ac..3980d36 100644 --- a/colours.ts +++ b/colours.ts @@ -1,17 +1,17 @@ import { - blue, cyan, gray, green, red, + underline, yellow, } from "https://deno.land/std@0.171.0/fmt/colors.ts"; import { Range } from "./deps.ts"; export const colour = { - dependency: blue, - file: cyan, + dependency: cyan, + file: underline, subdued: gray, version: yellow, valid: green, diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..d53c7e6 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noUncheckedIndexedAccess": true, + "lib": ["dom", "deno.ns"] + } +} diff --git a/fixtures/package.json b/fixtures/package.json index 5a0d1d7..51cb61a 100644 --- a/fixtures/package.json +++ b/fixtures/package.json @@ -2,6 +2,7 @@ "name": "npm-dependencies", "version": "0.0.1", "dependencies": { + "@types/node": "~18.11", "@guardian/source-foundations": "8.0.0", "@guardian/core-web-vitals": "2.0.2" }, From 24e15631d74c4d52602dfde4dfdb3d55bae14936 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 11:32:32 +0000 Subject: [PATCH 17/28] chore: vscode config --- .vscode/settings.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d2c12f0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "deno.enable": true, + "deno.unstable": true, + "editor.defaultFormatter": "denoland.vscode-deno", + "[typescript]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, + "deno.suggest.completeFunctionCalls": true +} From 7952a8e389433cc17d656f30d59c80ed9c95618c Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:07:54 +0000 Subject: [PATCH 18/28] feat: add lock file --- deno.lock | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 deno.lock diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..2205560 --- /dev/null +++ b/deno.lock @@ -0,0 +1,195 @@ +{ + "version": "2", + "remote": { + "https://deno.land/std@0.168.0/fmt/colors.ts": "03ad95e543d2808bc43c17a3dd29d25b43d0f16287fe562a0be89bf632454a12", + "https://deno.land/std@0.168.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c", + "https://deno.land/std@0.168.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832", + "https://deno.land/std@0.168.0/testing/asserts.ts": "51353e79437361d4b02d8e32f3fc83b22231bc8f8d4c841d86fd32b0b0afe940", + "https://deno.land/std@0.171.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", + "https://deno.land/std@0.173.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.173.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.173.0/async/abortable.ts": "73acfb3ed7261ce0d930dbe89e43db8d34e017b063cf0eaa7d215477bf53442e", + "https://deno.land/std@0.173.0/async/deadline.ts": "b98e50d2c42399af03ad13bbb8cf59dadb9f0cd5d70648cc0c3b9202d75ab565", + "https://deno.land/std@0.173.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332", + "https://deno.land/std@0.173.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8", + "https://deno.land/std@0.173.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd", + "https://deno.land/std@0.173.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576", + "https://deno.land/std@0.173.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9", + "https://deno.land/std@0.173.0/async/pool.ts": "fd082bd4aaf26445909889435a5c74334c017847842ec035739b4ae637ae8260", + "https://deno.land/std@0.173.0/async/retry.ts": "5efa3ba450ac0c07a40a82e2df296287b5013755d232049efd7ea2244f15b20f", + "https://deno.land/std@0.173.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757", + "https://deno.land/std@0.173.0/bytes/index_of_needle.ts": "65c939607df609374c4415598fa4dad04a2f14c4d98cd15775216f0aaf597f24", + "https://deno.land/std@0.173.0/crypto/timing_safe_equal.ts": "8d69ab611c67fe51b6127d97fcfb4d8e7d0e1b6b4f3e0cc4ab86744c3691f965", + "https://deno.land/std@0.173.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1", + "https://deno.land/std@0.173.0/encoding/base64url.ts": "3f1178f6446834457b16bfde8b559c1cd3481727fe384d3385e4a9995dc2d851", + "https://deno.land/std@0.173.0/flags/mod.ts": "d1cdefa18472ef69858a17df5cf7c98445ed27ac10e1460183081303b0ebc270", + "https://deno.land/std@0.173.0/node/_core.ts": "8667bf9129cdcbaac6f5c14f4def45ca954928baaa697a0ffdb0ee1556d4c644", + "https://deno.land/std@0.173.0/node/_events.d.ts": "1347437fd6b084d7c9a4e16b9fe7435f00b030970086482edeeb3b179d0775af", + "https://deno.land/std@0.173.0/node/_events.mjs": "d4ba4e629abe3db9f1b14659fd5c282b7da8b2b95eaf13238eee4ebb142a2448", + "https://deno.land/std@0.173.0/node/_global.d.ts": "2d88342f38b4083b858998e27c706725fb03a74aa14ef8d985dc18438b5188e4", + "https://deno.land/std@0.173.0/node/_next_tick.ts": "9a3cf107d59b019a355d3cf32275b4c6157282e4b68ea85b46a799cb1d379305", + "https://deno.land/std@0.173.0/node/_process/exiting.ts": "6e336180aaabd1192bf99ffeb0d14b689116a3dec1dfb34a2afbacd6766e98ab", + "https://deno.land/std@0.173.0/node/_process/process.ts": "c96bb1f6253824c372f4866ee006dcefda02b7050d46759736e403f862d91051", + "https://deno.land/std@0.173.0/node/_process/stdio.mjs": "cf17727eac8da3a665851df700b5aca6a12bacc3ebbf33e63e4b919f80ba44a6", + "https://deno.land/std@0.173.0/node/_process/streams.mjs": "c1461c4dbf963a93a0ca8233467573a685bbde347562573761cc9435fd7080f6", + "https://deno.land/std@0.173.0/node/_stream.d.ts": "112e1a0677cd6db932c3ce0e6e5bbdc7a2ac1874572f449044ecc82afcf5ee2e", + "https://deno.land/std@0.173.0/node/_stream.mjs": "d6e2c86c1158ac65b4c2ca4fa019d7e84374ff12e21e2175345fe68c0823efe3", + "https://deno.land/std@0.173.0/node/_utils.ts": "7fd55872a0cf9275e3c080a60e2fa6d45b8de9e956ebcde9053e72a344185884", + "https://deno.land/std@0.173.0/node/buffer.ts": "85617be2063eccaf177dbb84c7580d1e32023724ed14bd9df4e453b152a26167", + "https://deno.land/std@0.173.0/node/events.ts": "d2de352d509de11a375e2cb397d6b98f5fed4e562fc1d41be33214903a38e6b0", + "https://deno.land/std@0.173.0/node/internal/buffer.d.ts": "bdfa991cd88cb02fd08bf8235d2618550e3e511c970b2a8f2e1a6885a2793cac", + "https://deno.land/std@0.173.0/node/internal/buffer.mjs": "e92303a3cc6d9aaabcd270a937ad9319825d9ba08cb332650944df4562029b27", + "https://deno.land/std@0.173.0/node/internal/crypto/_keys.ts": "8f3c3b5a141aa0331a53c205e9338655f1b3b307a08085fd6ff6dda6f7c4190b", + "https://deno.land/std@0.173.0/node/internal/crypto/constants.ts": "544d605703053218499b08214f2e25cf4310651d535b7ab995891c4b7a217693", + "https://deno.land/std@0.173.0/node/internal/error_codes.ts": "8495e33f448a484518d76fa3d41d34fc20fe03c14b30130ad8e936b0035d4b8b", + "https://deno.land/std@0.173.0/node/internal/errors.ts": "1c699b8a3cb93174f697a348c004b1c6d576b66688eac8a48ebb78e65c720aae", + "https://deno.land/std@0.173.0/node/internal/fixed_queue.ts": "62bb119afa5b5ae8fc0c7048b50502347bec82e2588017d0b250c4671d6eff8f", + "https://deno.land/std@0.173.0/node/internal/hide_stack_frames.ts": "9dd1bad0a6e62a1042ce3a51eb1b1ecee2f246907bff44835f86e8f021de679a", + "https://deno.land/std@0.173.0/node/internal/net.ts": "5538d31b595ac63d4b3e90393168bc65ace2f332c3317cffa2fd780070b2d86c", + "https://deno.land/std@0.173.0/node/internal/normalize_encoding.mjs": "fd1d9df61c44d7196432f6e8244621468715131d18cc79cd299fc78ac549f707", + "https://deno.land/std@0.173.0/node/internal/options.ts": "888f267c3fe8f18dc7b2f2fbdbe7e4a0fd3302ff3e99f5d6645601e924f3e3fb", + "https://deno.land/std@0.173.0/node/internal/primordials.mjs": "a72d86b5aa55d3d50b8e916b6a59b7cc0dc5a31da8937114b4a113ad5aa08c74", + "https://deno.land/std@0.173.0/node/internal/process/per_thread.mjs": "10142bbb13978c2f8f79778ad90f3a67a8ea6d8d2970f3dfc6bf2c6fff0162a2", + "https://deno.land/std@0.173.0/node/internal/readline/callbacks.mjs": "bdb129b140c3b21b5e08cdc3d8e43517ad818ac03f75197338d665cca1cbaed3", + "https://deno.land/std@0.173.0/node/internal/readline/utils.mjs": "c3dbf3a97c01ed14052cca3848f09e2fc24818c1822ceed57c33b9f0840f3b87", + "https://deno.land/std@0.173.0/node/internal/streams/destroy.mjs": "b665fc71178919a34ddeac8389d162a81b4bc693ff7dc2557fa41b3a91011967", + "https://deno.land/std@0.173.0/node/internal/streams/end-of-stream.mjs": "a4fb1c2e32d58dff440d4e716e2c4daaa403b3095304a028bb428575cfeed716", + "https://deno.land/std@0.173.0/node/internal/streams/utils.mjs": "f2fe2e6bdc506da24c758970890cc2a21642045b129dee618bd3827c60dd9e33", + "https://deno.land/std@0.173.0/node/internal/util.mjs": "f7fe2e1ca5e66f550ad0856b9f5ee4d666f0c071fe212ea7fc7f37cfa81f97a5", + "https://deno.land/std@0.173.0/node/internal/util/inspect.mjs": "11d7c9cab514b8e485acc3978c74b837263ff9c08ae4537fa18ad56bae633259", + "https://deno.land/std@0.173.0/node/internal/util/types.ts": "4f3625ea39111eaae1443c834e769b0c5ce9ea33b31d5a853b02af6a78105178", + "https://deno.land/std@0.173.0/node/internal/validators.mjs": "e02f2b02dd072a5d623970292588d541204dc82207b4c58985d933a5f4b382e6", + "https://deno.land/std@0.173.0/node/internal_binding/_libuv_winerror.ts": "30c9569603d4b97a1f1a034d88a3f74800d5ea1f12fcc3d225c9899d4e1a518b", + "https://deno.land/std@0.173.0/node/internal_binding/_listen.ts": "c6038be47116f7755c01fd98340a0d1e8e66ef874710ab59ed3f5607d50d7a25", + "https://deno.land/std@0.173.0/node/internal_binding/_node.ts": "cb2389b0eab121df99853eb6a5e3a684e4537e065fb8bf2cca0cbf219ce4e32e", + "https://deno.land/std@0.173.0/node/internal_binding/_timingSafeEqual.ts": "7d9732464d3c669ff07713868ce5d25bc974a06112edbfb5f017fc3c70c0853e", + "https://deno.land/std@0.173.0/node/internal_binding/_utils.ts": "7c58a2fbb031a204dee9583ba211cf9c67922112fe77e7f0b3226112469e9fe1", + "https://deno.land/std@0.173.0/node/internal_binding/_winerror.ts": "3e8cfdfe22e89f13d2b28529bab35155e6b1730c0221ec5a6fc7077dc037be13", + "https://deno.land/std@0.173.0/node/internal_binding/ares.ts": "bdd34c679265a6c115a8cfdde000656837a0a0dcdb0e4c258e622e136e9c31b8", + "https://deno.land/std@0.173.0/node/internal_binding/async_wrap.ts": "0dc5ae64eea2c9e57ab17887ef1573922245167ffe38e3685c28d636f487f1b7", + "https://deno.land/std@0.173.0/node/internal_binding/buffer.ts": "31729e0537921d6c730ad0afea44a7e8a0a1044d070ade8368226cb6f7390c8b", + "https://deno.land/std@0.173.0/node/internal_binding/cares_wrap.ts": "9b7247772167f8ed56acd0244a232d9d50e8d7c9cfc379f77f3d54cecc2f32ab", + "https://deno.land/std@0.173.0/node/internal_binding/config.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/connection_wrap.ts": "7dd089ea46de38e4992d0f43a09b586e4cf04878fb06863c1cb8cb2ece7da521", + "https://deno.land/std@0.173.0/node/internal_binding/constants.ts": "21ff9d1ee71d0a2086541083a7711842fc6ae25e264dbf45c73815aadce06f4c", + "https://deno.land/std@0.173.0/node/internal_binding/contextify.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/credentials.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/crypto.ts": "29e8f94f283a2e7d4229d3551369c6a40c2af9737fad948cb9be56bef6c468cd", + "https://deno.land/std@0.173.0/node/internal_binding/errors.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/fs.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/fs_dir.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/fs_event_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/handle_wrap.ts": "adf0b8063da2c54f26edd5e8ec50296a4d38e42716a70a229f14654b17a071d9", + "https://deno.land/std@0.173.0/node/internal_binding/heap_utils.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/http_parser.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/icu.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/inspector.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/js_stream.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/messaging.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/mod.ts": "9fc65f7af1d35e2d3557539a558ea9ad7a9954eefafe614ad82d94bddfe25845", + "https://deno.land/std@0.173.0/node/internal_binding/module_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/native_module.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/natives.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/node_file.ts": "21edbbc95653e45514aff252b6cae7bf127a4338cbc5f090557d258aa205d8a5", + "https://deno.land/std@0.173.0/node/internal_binding/node_options.ts": "0b5cb0bf4379a39278d7b7bb6bb2c2751baf428fe437abe5ed3e8441fae1f18b", + "https://deno.land/std@0.173.0/node/internal_binding/options.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/os.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/performance.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/pipe_wrap.ts": "e5429879551fb7195039986fe6da920a86971fad4342046cbf653643e6c85e21", + "https://deno.land/std@0.173.0/node/internal_binding/process_methods.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/report.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/serdes.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/signal_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/spawn_sync.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/stream_wrap.ts": "452bff74d1db280a0cd78c75a95bb6d163e849e06e9638c4af405d40296bd050", + "https://deno.land/std@0.173.0/node/internal_binding/string_decoder.ts": "54c3c1cbd5a9254881be58bf22637965dc69535483014dab60487e299cb95445", + "https://deno.land/std@0.173.0/node/internal_binding/symbols.ts": "4dee2f3a400d711fd57fa3430b8de1fdb011e08e260b81fef5b81cc06ed77129", + "https://deno.land/std@0.173.0/node/internal_binding/task_queue.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/tcp_wrap.ts": "cbede7224fcf0adc4b04e2e1222488a7a9c137807f143bd32cc8b1a121e0d4fa", + "https://deno.land/std@0.173.0/node/internal_binding/timers.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/tls_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/trace_events.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/tty_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/types.ts": "5a658bf08975af30d0fad6fa6247274379be26ba3f023425bec03e61c74083ef", + "https://deno.land/std@0.173.0/node/internal_binding/udp_wrap.ts": "cc86f7e51bf56fd619505cf9d4f77d7aae1526abdf295399dd277162d28ca6c1", + "https://deno.land/std@0.173.0/node/internal_binding/url.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/util.ts": "808ff3b92740284184ab824adfc420e75398c88c8bccf5111f0c24ac18c48f10", + "https://deno.land/std@0.173.0/node/internal_binding/uv.ts": "eb0048e30af4db407fb3f95563e30d70efd6187051c033713b0a5b768593a3a3", + "https://deno.land/std@0.173.0/node/internal_binding/v8.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/worker.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/internal_binding/zlib.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", + "https://deno.land/std@0.173.0/node/process.ts": "6608012d6d51a17a7346f36079c574b9b9f81f1b5c35436489ad089f39757466", + "https://deno.land/std@0.173.0/node/stream.ts": "09e348302af40dcc7dc58aa5e40fdff868d11d8d6b0cfb85cbb9c75b9fe450c7", + "https://deno.land/std@0.173.0/node/string_decoder.ts": "1a17e3572037c512cc5fc4b29076613e90f225474362d18da908cb7e5ccb7e88", + "https://deno.land/std@0.173.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.173.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.173.0/path/_util.ts": "86c2375a996c1931b2f2ac71fefd5ddf0cf0e579fa4ab12d3e4c552d4223b8d8", + "https://deno.land/std@0.173.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.173.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.173.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", + "https://deno.land/std@0.173.0/path/posix.ts": "0874b341c2c6968ca38d323338b8b295ea1dae10fa872a768d812e2e7d634789", + "https://deno.land/std@0.173.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.173.0/path/win32.ts": "672942919dd66ce1b8c224e77d3447e2ad8254eaff13fe6946420a9f78fa141e", + "https://deno.land/std@0.173.0/streams/write_all.ts": "3b2e1ce44913f966348ce353d02fa5369e94115181037cd8b602510853ec3033", + "https://deno.land/std@0.173.0/types.d.ts": "220ed56662a0bd393ba5d124aa6ae2ad36a00d2fcbc0e8666a65f4606aaa9784", + "https://esm.sh/semver@7.3.8": "66cf5abf347faf6aacb50a0f1ee0ec9ac2c66e57fa1c5b975fd2cb56fe53c956", + "https://esm.sh/v104/@types/semver@7.3.13/classes/comparator.d.ts": "793bfebb9bb0a72aef35d56cf59fefc343c96c3c83f33e4b6133d9e520ea4969", + "https://esm.sh/v104/@types/semver@7.3.13/classes/range.d.ts": "70acf43190f9af19604401b079199cfc618082ddac3de48be2ee9694113f47e5", + "https://esm.sh/v104/@types/semver@7.3.13/classes/semver.d.ts": "d549806ef609c83125b75954e470a6f34335005fcbf81be87426b5a9e0d39608", + "https://esm.sh/v104/@types/semver@7.3.13/functions/clean.d.ts": "a5eb84f0ee2822d98b3d8516509352a33ce8688965eb9fac13cf0e21eda62845", + "https://esm.sh/v104/@types/semver@7.3.13/functions/cmp.d.ts": "d1e4d215126fe0db53e060245cf5e697e6002d7335a09cefbed4af9f3fb54a5e", + "https://esm.sh/v104/@types/semver@7.3.13/functions/coerce.d.ts": "19cddcbff1ce1d540aeaab143b0b9a71633ebe255ab08aaae0ad4f36bef49db0", + "https://esm.sh/v104/@types/semver@7.3.13/functions/compare-build.d.ts": "b5277ca0bdb12056c3687b8cee47734492f67bb0b58b4b7073e965ccbf708503", + "https://esm.sh/v104/@types/semver@7.3.13/functions/compare-loose.d.ts": "d19bbef11362960a463317e82f993dff44f656a87eb02bb7452ba0a862fe7e9e", + "https://esm.sh/v104/@types/semver@7.3.13/functions/compare.d.ts": "b6adb9d9ace09dba2b1cc582b551139e61e0e65d7da31f1cc9f30c3075d3e13d", + "https://esm.sh/v104/@types/semver@7.3.13/functions/diff.d.ts": "d39a11c79c625ca1ef1ce6f99fa08542c0b106bde116186cf45e97b6211aa9c7", + "https://esm.sh/v104/@types/semver@7.3.13/functions/eq.d.ts": "6f80724fa45cc52b273e3416b2484b98fc5c236fffa40d7218a4b6299b381b22", + "https://esm.sh/v104/@types/semver@7.3.13/functions/gt.d.ts": "54846f82c2156b18e41e6a1d62e074e76d7f01440bc1c641fc72e9f334d67c3b", + "https://esm.sh/v104/@types/semver@7.3.13/functions/gte.d.ts": "de60cee0c3cf3acf97636b73529129437b883e07190674389da683e1f48f8474", + "https://esm.sh/v104/@types/semver@7.3.13/functions/inc.d.ts": "bad52417b5275f8cd6dfe53c0d94b84bdf4735a676e0f7ce70cbe76cd29a2f02", + "https://esm.sh/v104/@types/semver@7.3.13/functions/lt.d.ts": "c9fb4feb61e5e39e482b1bd6438a0237e6b708f148b5827ce4332925576c5804", + "https://esm.sh/v104/@types/semver@7.3.13/functions/lte.d.ts": "0c57de12d88bf67cb888332a67b71d6dcf7e6a0f41e8c2768aea6ffcbbe0574a", + "https://esm.sh/v104/@types/semver@7.3.13/functions/major.d.ts": "d9f654955c3f42874a69c3993819f7eccd93fc191538252556b4b6b62250abdb", + "https://esm.sh/v104/@types/semver@7.3.13/functions/minor.d.ts": "0dadf77ef9211bd11deb83b28dc36210987fcf125118fcdceef2f250984f753e", + "https://esm.sh/v104/@types/semver@7.3.13/functions/neq.d.ts": "b57f145cb5c8cee6d2913186adab341ca1baa8addd81dc96e3b495b52ec60657", + "https://esm.sh/v104/@types/semver@7.3.13/functions/parse.d.ts": "7a47a862beeb12ebbb976e49d0cc4d95ad6aa07622c6953cb29b4d0623d456a6", + "https://esm.sh/v104/@types/semver@7.3.13/functions/patch.d.ts": "8bb0cc352dc68930a91100124413d20c4f088574c08619160a10a9af9e141291", + "https://esm.sh/v104/@types/semver@7.3.13/functions/prerelease.d.ts": "2144f4fcc33a321c10eed831c4748cda4fc0c9c5e648a71fa7899d36c8c00923", + "https://esm.sh/v104/@types/semver@7.3.13/functions/rcompare.d.ts": "cd9ddb89bed51980102fab0c3986f9ee7a64e27d117c1adbe3ca738fb0eea12a", + "https://esm.sh/v104/@types/semver@7.3.13/functions/rsort.d.ts": "b8e949f8075bfa126fae1c421da8c8e559a07c4c47840f355420e56b299c91bb", + "https://esm.sh/v104/@types/semver@7.3.13/functions/satisfies.d.ts": "eeddc303a4753825486dca6028f73b404419d49a0dac668495c8f71bce92d771", + "https://esm.sh/v104/@types/semver@7.3.13/functions/sort.d.ts": "e74717fa7ab44e65972cba3afe5db0fbb7c48a35346bd2072ca6c9f3749923d7", + "https://esm.sh/v104/@types/semver@7.3.13/functions/valid.d.ts": "30ed3b9abb6ae21e71bcdf23a44d1de33bdcd05445c6a748370cd21b756fc2f1", + "https://esm.sh/v104/@types/semver@7.3.13/index.d.ts": "bf2f2a19df95d88e17f96a3914a5024d9f61c9e874e0d744b3bc0e6223e9e328", + "https://esm.sh/v104/@types/semver@7.3.13/internals/identifiers.d.ts": "34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/gtr.d.ts": "fa58a5179a81fb88b32a0f0f5604e9cdc915f8734b6e394ff59b734e1a068a8d", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/intersects.d.ts": "aa6958786e1a4d90c0ca301edc86e04bc737e0bd489f5f38187bc6587474845b", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/ltr.d.ts": "1d7789ee92780ac62c4d331138e922d325286fb1d78c8447addfec23925ddb0f", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/max-satisfying.d.ts": "13524ac8f9a3c501aa03a9949ba5c5f01c6c30e33ff5c3505e1d2dc7a384b973", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/min-satisfying.d.ts": "192af061294d60671545e7341ad69494e6921b69805a15eb619afdf71f7a17c5", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/min-version.d.ts": "b6ccc5b599a1bd2677cd3a516984467f413f7c201bfe01c2f174f6afcbf98d50", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/outside.d.ts": "cf6a2f381910c3dd0e9e4c69719589a850cf843589759d9182eb6421a6864a65", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/simplify.d.ts": "568f0867b37eb79d1ca33c9712c7ada9ef41d722ad59cb0e5f37e89298861554", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/subset.d.ts": "4f46d5b9fdf398aac809a233fdc756e8bf4e2b743b159d24c9cb8b3a32c35f9f", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/to-comparators.d.ts": "ed87996328ada2100faff62f857b05fcedd53d1303a1f9b40cc3750ff0df10e4", + "https://esm.sh/v104/@types/semver@7.3.13/ranges/valid.d.ts": "d717439745a1ab3fae112cdf7eaaf569304328039f8b6f1e1c508040c67e32e7", + "https://esm.sh/v104/lru-cache@6.0.0/deno/lru-cache.js": "fbacc9d1ad6bd5f2b957937d47d373f80707087ddc22e36148f0bee153f601df", + "https://esm.sh/v104/semver@7.3.8/deno/semver.js": "ebd99a9b9db10a5d2a18d53cb020825f316b25ffa131f2a287551c428e3aa52c", + "https://esm.sh/v104/yallist@4.0.0/deno/yallist.js": "55a926cdcbd6f8b54bb87a8b07589f02aa45ff136a51fc7f0c671a08e7d5c6f8", + "https://esm.sh/v104/zod@3.20.2/deno/zod.js": "4e1c7e6ae3b866c33bfebc0c6886cb22ce577dd9ebf53cebdbd8fa8a7ab4d739", + "https://esm.sh/v104/zod@3.20.2/index.d.ts": "b51c954b9557b1dcb03b2bf34cd136e710cb9aa276e686cac0e291249590a52a", + "https://esm.sh/v104/zod@3.20.2/lib/ZodError.d.ts": "c53bb6e3d82b7813bed0096be4185d253c8f7d32db9c8960bc7f06980f86261e", + "https://esm.sh/v104/zod@3.20.2/lib/errors.d.ts": "5ec2790f877581637c058eb5bbb72e855a078620d0781d2a6a8f308fe1d5436f", + "https://esm.sh/v104/zod@3.20.2/lib/external.d.ts": "91cec2fe1e1fb2574812a29a57ad2c39a1e69b0fa1179547382c92649fc5c66e", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/enumUtil.d.ts": "f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/errorUtil.d.ts": "98a9cc18f661d28e6bd31c436e1984f3980f35e0f0aa9cf795c54f8ccb667ffe", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/parseUtil.d.ts": "d3dbfc2f2fbf2c1bdea72db72f9f5c95d2ca5b4d32c9e229764ebaba0920203c", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/partialUtil.d.ts": "7da6f1e431746c5364ebe6703f5d3a844ab0495e2a8cedc0a29cced52d6284a1", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/typeAliases.d.ts": "5487b97cfa28b26b4a9ef0770f872bdbebd4c46124858de00f242c3eed7519f4", + "https://esm.sh/v104/zod@3.20.2/lib/helpers/util.d.ts": "bb98c05ae5cb9bd7cb7ad76fe517251a661787a6f24337b842f47faf393f79c7", + "https://esm.sh/v104/zod@3.20.2/lib/index.d.ts": "0f86f053aecd06054359e146b6cd800fee9bf82905f8afb47f1fe774fd37311e", + "https://esm.sh/v104/zod@3.20.2/lib/locales/en.d.ts": "f8ec1611afb8d132c6c71822718805866890cf2071025153af97508eff24e4b6", + "https://esm.sh/v104/zod@3.20.2/lib/types.d.ts": "744e3c65de31c5f7f8e79f9a37e1e372c9043a5ca130ec1038180ba4c9941f1f", + "https://esm.sh/zod@3.20.2": "5da460955f749f7688ad10d15edfefd8eaa41d8a7ed54e65f6abd10d8d879128" + } +} From c79fa648943e0c3294dc6ea0b7cfe2b4fddee700 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:08:11 +0000 Subject: [PATCH 19/28] feat: add CI workflow --- .github/workflows/ci.yml | 94 ++++++++++++++++++++++++++++++++++++++++ deno.lock | 2 + 2 files changed, 96 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5b56bf4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI +on: + pull_request: + workflow_dispatch: + + +env: + DENO_DIR: my_cache_directory + +jobs: + health: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Cache Deno dependencies + uses: actions/cache@v2 + with: + path: ${{ env.DENO_DIR }} + key: ${{ hashFiles('lock.json') }} + + - name: Format + run: deno fmt --check + + - name: Lint + run: deno lint + + - name: Test + run: deno test --allow-net=registry.npmjs.org + + working_package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Cache Deno dependencies + uses: actions/cache@v2 + with: + path: ${{ env.DENO_DIR }} + key: ${{ hashFiles('lock.json') }} + + - run: | + deno run \ + --allow-net=registry.npmjs.org \ + --allow-read=. \ + ./main.ts \ + fixtures/package_valid.json \ + --verbose --cache + + failing_peer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Cache Deno dependencies + uses: actions/cache@v2 + with: + path: ${{ env.DENO_DIR }} + key: ${{ hashFiles('lock.json') }} + + - run: | + deno run \ + --allow-net=registry.npmjs.org \ + --allow-read=. \ + ./main.ts \ + fixtures/package.json \ + --verbose --cache + + - name: The job has failed, which is expected + if: ${{ failure() }} + run: exit 0 + + - name: The job has succeeded, which is wrong + if: ${{ success() }} + run: exit 1 + + final: + needs: [health, working_package, failing_peer] + runs-on: ubuntu-latest + steps: + - name: "All good" + run: exit 0 \ No newline at end of file diff --git a/deno.lock b/deno.lock index 2205560..feec490 100644 --- a/deno.lock +++ b/deno.lock @@ -1,6 +1,8 @@ { "version": "2", "remote": { + "https://deno.land/std@0.168.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", + "https://deno.land/std@0.168.0/flags/mod.ts": "4f50ec6383c02684db35de38b3ffb2cd5b9fcfcc0b1147055d1980c49e82521c", "https://deno.land/std@0.168.0/fmt/colors.ts": "03ad95e543d2808bc43c17a3dd29d25b43d0f16287fe562a0be89bf632454a12", "https://deno.land/std@0.168.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c", "https://deno.land/std@0.168.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832", From e3d3bfcf1a3eb5e4dde4bb942b71959ae0ddbfef Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:15:33 +0000 Subject: [PATCH 20/28] fix: use cache @ v3 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b56bf4..e1d97c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: deno-version: v1.x - name: Cache Deno dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ env.DENO_DIR }} key: ${{ hashFiles('lock.json') }} @@ -42,7 +42,7 @@ jobs: deno-version: v1.x - name: Cache Deno dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ env.DENO_DIR }} key: ${{ hashFiles('lock.json') }} @@ -65,7 +65,7 @@ jobs: deno-version: v1.x - name: Cache Deno dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ env.DENO_DIR }} key: ${{ hashFiles('lock.json') }} From b2d9ebe0255bb2cfb0f6b6d8d4bb61ea82ac2c04 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:20:28 +0000 Subject: [PATCH 21/28] refactor: move files to src --- .env | 1 + .github/workflows/ci.yml | 12 ++++++------ .gitignore | 1 + cache.ts => src/cache.ts | 0 check_types.test.ts => src/check_types.test.ts | 0 check_types.ts => src/check_types.ts | 0 colours.ts => src/colours.ts | 0 deps.ts => src/deps.ts | 0 .../fetch_peer_dependencies.bench.ts | 0 .../fetch_peer_dependencies.test.ts | 0 .../fetch_peer_dependencies.ts | 0 .../find_mismatches.test.ts | 0 find_mismatches.ts => src/find_mismatches.ts | 0 main.ts => src/main.ts | 0 .../parse_dependencies.test.ts | 0 parse_dependencies.ts => src/parse_dependencies.ts | 0 types.ts => src/types.ts | 0 utils.ts => src/utils.ts | 0 18 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 .env create mode 100644 .gitignore rename cache.ts => src/cache.ts (100%) rename check_types.test.ts => src/check_types.test.ts (100%) rename check_types.ts => src/check_types.ts (100%) rename colours.ts => src/colours.ts (100%) rename deps.ts => src/deps.ts (100%) rename fetch_peer_dependencies.bench.ts => src/fetch_peer_dependencies.bench.ts (100%) rename fetch_peer_dependencies.test.ts => src/fetch_peer_dependencies.test.ts (100%) rename fetch_peer_dependencies.ts => src/fetch_peer_dependencies.ts (100%) rename find_mismatches.test.ts => src/find_mismatches.test.ts (100%) rename find_mismatches.ts => src/find_mismatches.ts (100%) rename main.ts => src/main.ts (100%) rename parse_dependencies.test.ts => src/parse_dependencies.test.ts (100%) rename parse_dependencies.ts => src/parse_dependencies.ts (100%) rename types.ts => src/types.ts (100%) rename utils.ts => src/utils.ts (100%) diff --git a/.env b/.env new file mode 100644 index 0000000..a5a446e --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DENO_DIR=.cache \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1d97c5..5eb1447 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: env: - DENO_DIR: my_cache_directory + DENO_DIR: ./cache jobs: health: @@ -24,13 +24,13 @@ jobs: key: ${{ hashFiles('lock.json') }} - name: Format - run: deno fmt --check + run: deno fmt src --check - name: Lint - run: deno lint + run: deno lint src - name: Test - run: deno test --allow-net=registry.npmjs.org + run: deno test src --allow-net=registry.npmjs.org working_package: runs-on: ubuntu-latest @@ -51,7 +51,7 @@ jobs: deno run \ --allow-net=registry.npmjs.org \ --allow-read=. \ - ./main.ts \ + src/main.ts \ fixtures/package_valid.json \ --verbose --cache @@ -74,7 +74,7 @@ jobs: deno run \ --allow-net=registry.npmjs.org \ --allow-read=. \ - ./main.ts \ + src/main.ts \ fixtures/package.json \ --verbose --cache diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1998c29 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.cache \ No newline at end of file diff --git a/cache.ts b/src/cache.ts similarity index 100% rename from cache.ts rename to src/cache.ts diff --git a/check_types.test.ts b/src/check_types.test.ts similarity index 100% rename from check_types.test.ts rename to src/check_types.test.ts diff --git a/check_types.ts b/src/check_types.ts similarity index 100% rename from check_types.ts rename to src/check_types.ts diff --git a/colours.ts b/src/colours.ts similarity index 100% rename from colours.ts rename to src/colours.ts diff --git a/deps.ts b/src/deps.ts similarity index 100% rename from deps.ts rename to src/deps.ts diff --git a/fetch_peer_dependencies.bench.ts b/src/fetch_peer_dependencies.bench.ts similarity index 100% rename from fetch_peer_dependencies.bench.ts rename to src/fetch_peer_dependencies.bench.ts diff --git a/fetch_peer_dependencies.test.ts b/src/fetch_peer_dependencies.test.ts similarity index 100% rename from fetch_peer_dependencies.test.ts rename to src/fetch_peer_dependencies.test.ts diff --git a/fetch_peer_dependencies.ts b/src/fetch_peer_dependencies.ts similarity index 100% rename from fetch_peer_dependencies.ts rename to src/fetch_peer_dependencies.ts diff --git a/find_mismatches.test.ts b/src/find_mismatches.test.ts similarity index 100% rename from find_mismatches.test.ts rename to src/find_mismatches.test.ts diff --git a/find_mismatches.ts b/src/find_mismatches.ts similarity index 100% rename from find_mismatches.ts rename to src/find_mismatches.ts diff --git a/main.ts b/src/main.ts similarity index 100% rename from main.ts rename to src/main.ts diff --git a/parse_dependencies.test.ts b/src/parse_dependencies.test.ts similarity index 100% rename from parse_dependencies.test.ts rename to src/parse_dependencies.test.ts diff --git a/parse_dependencies.ts b/src/parse_dependencies.ts similarity index 100% rename from parse_dependencies.ts rename to src/parse_dependencies.ts diff --git a/types.ts b/src/types.ts similarity index 100% rename from types.ts rename to src/types.ts diff --git a/utils.ts b/src/utils.ts similarity index 100% rename from utils.ts rename to src/utils.ts From 4c773af9a7fccfd2d53c45a4a79c1f91c1c8768d Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:30:07 +0000 Subject: [PATCH 22/28] feat: allow success if known number of errors --- .github/workflows/ci.yml | 10 +--------- src/main.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5eb1447..a0f8433 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,15 +76,7 @@ jobs: --allow-read=. \ src/main.ts \ fixtures/package.json \ - --verbose --cache - - - name: The job has failed, which is expected - if: ${{ failure() }} - run: exit 0 - - - name: The job has succeeded, which is wrong - if: ${{ success() }} - run: exit 1 + --verbose --cache --errors=7 final: needs: [health, working_package, failing_peer] diff --git a/src/main.ts b/src/main.ts index 3580894..7cbe454 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,8 +11,10 @@ import { import { parse } from "https://deno.land/std@0.168.0/flags/mod.ts"; import { filter_types } from "./check_types.ts"; -const { _: [package_file], verbose, cache } = parse(Deno.args, { +const { _: [package_file], verbose, cache, errors } = parse(Deno.args, { boolean: ["verbose", "cache"], + string: ["errors"], + default: { errors: "0" }, }); if (typeof package_file !== "string") { @@ -101,5 +103,10 @@ if (problems === 0) { } dependencies problems`, ); } +if (errors === problems.toString()) { + // Pass if the number of problems matched the --errors flag + console.info(`\nAll ${errors} errors found – this is a success!`); + Deno.exit(); +} Deno.exit(problems); From 3c00012014e431bd5790181c095a7d7a098ea46c Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:31:33 +0000 Subject: [PATCH 23/28] chore: remove unused final step --- .github/workflows/ci.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0f8433..6b6f67c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,11 +76,4 @@ jobs: --allow-read=. \ src/main.ts \ fixtures/package.json \ - --verbose --cache --errors=7 - - final: - needs: [health, working_package, failing_peer] - runs-on: ubuntu-latest - steps: - - name: "All good" - run: exit 0 \ No newline at end of file + --verbose --cache --errors=7 \ No newline at end of file From dbef96cd2ce742c33d78db0377918d289d1bd123 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:39:47 +0000 Subject: [PATCH 24/28] feat: better error on duplicate --- .github/workflows/ci.yml | 25 ++++++++++++++++++++++++- src/main.ts | 12 +++++++++++- src/parse_dependencies.ts | 20 +++----------------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b6f67c..8233dfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,4 +76,27 @@ jobs: --allow-read=. \ src/main.ts \ fixtures/package.json \ - --verbose --cache --errors=7 \ No newline at end of file + --verbose --cache --errors=7 + + failing_duplicates: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Cache Deno dependencies + uses: actions/cache@v3 + with: + path: ${{ env.DENO_DIR }} + key: ${{ hashFiles('lock.json') }} + + - run: | + deno run \ + --allow-net=registry.npmjs.org \ + --allow-read=. \ + src/main.ts \ + fixtures/package_duplicate.json \ + --verbose --cache --errors=3 \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 7cbe454..c96869c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,5 @@ import { + find_duplicates, parse_declared_dependencies, parse_package_info, } from "./parse_dependencies.ts"; @@ -67,6 +68,15 @@ if (dependencies_from_package.length === 0) { Deno.exit(); } +const duplicates = find_duplicates(dependencies_from_package); + +if (duplicates.length > 0) { + console.error(`β”‚ Duplicate dependencies found!`); + for (const name of duplicates) { + console.error(`β”‚ ╰─ ${colour.invalid("βœ•")} ${colour.dependency(name)}`); + } +} + const dependencies_from_registry = await fetch_peer_dependencies( dependencies_from_package, { cache }, @@ -82,7 +92,7 @@ const number_of_mismatched_deps = count_unsatisfied_peer_dependencies( ); const problems = number_of_mismatched_deps + - types_in_direct_dependencies.length; + types_in_direct_dependencies.length + duplicates.length; console.info("β”‚"); diff --git a/src/parse_dependencies.ts b/src/parse_dependencies.ts index 764ca80..ac33093 100644 --- a/src/parse_dependencies.ts +++ b/src/parse_dependencies.ts @@ -3,7 +3,7 @@ import { colour } from "./colours.ts"; import { Dependency, UnrefinedDependency } from "./types.ts"; import { isDefined } from "./utils.ts"; -const find_duplicates = (dependencies: Dependency[]): string[] => { +export const find_duplicates = (dependencies: Dependency[]): string[] => { const seen = new Set(); const duplicates = new Set(); @@ -32,8 +32,8 @@ export const parse_package_info = (contents: unknown): UnrefinedDependency => { export const parse_declared_dependencies = ( dependencies: [name: string, range: string][], -): Dependency[] => { - const deps = dependencies.map(([name, range]) => { +): Dependency[] => + dependencies.map(([name, range]) => { try { return { name, range: new Range(range) }; } catch (error: unknown) { @@ -47,17 +47,3 @@ export const parse_declared_dependencies = ( } return undefined; }).filter(isDefined).flat(); - - const duplicates = find_duplicates(deps); - - if (duplicates.length > 0) { - console.warn("🚨 Duplicate dependencies found:"); - for (const duplicate of duplicates) { - console.warn(` - ${colour.dependency(duplicate)}`); - } - - throw new Error("Duplicates found"); - } - - return deps; -}; From d646727588a547a1c2f1c69931068683069d16a8 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:58:30 +0000 Subject: [PATCH 25/28] refactor(cache): passe it around --- src/cache.ts | 9 ++------- src/fetch_peer_dependencies.bench.ts | 18 ++++++++++++++++-- src/fetch_peer_dependencies.test.ts | 6 +++--- src/fetch_peer_dependencies.ts | 2 +- src/main.ts | 2 +- src/parse_dependencies.test.ts | 12 ++++++++---- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 2f14832..9653107 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,10 +1,5 @@ -const name = "NPM registry"; - -const cache = await caches.open(name); - -export const get = async (url: URL, use_cache: boolean) => { - if (!use_cache) { - cache.delete(url); +export const get = async (url: URL, cache?: Cache) => { + if (!cache) { return fetch(url); } diff --git a/src/fetch_peer_dependencies.bench.ts b/src/fetch_peer_dependencies.bench.ts index 9ce0ea4..21e3cad 100644 --- a/src/fetch_peer_dependencies.bench.ts +++ b/src/fetch_peer_dependencies.bench.ts @@ -1,6 +1,8 @@ import { Range } from "./deps.ts"; import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; +const cache = await caches.open("bench"); + Deno.bench("Fetch with cache", async () => { await fetch_peer_dependencies( [ @@ -9,7 +11,7 @@ Deno.bench("Fetch with cache", async () => { range: new Range("2.0.2"), }, ], - { cache: true }, + undefined, ); }); @@ -21,6 +23,18 @@ Deno.bench("Fetch without cache", async () => { range: new Range("2.0.2"), }, ], - { cache: false }, + cache, + ); +}); + +Deno.bench("Fetch large dependency with cache", async () => { + await fetch_peer_dependencies( + [ + { + name: "typescript", + range: new Range("4.9.3"), + }, + ], + cache, ); }); diff --git a/src/fetch_peer_dependencies.test.ts b/src/fetch_peer_dependencies.test.ts index ce4b567..7f820e3 100644 --- a/src/fetch_peer_dependencies.test.ts +++ b/src/fetch_peer_dependencies.test.ts @@ -8,7 +8,7 @@ Deno.test("Can get peer dependencies", async () => { name: "@guardian/core-web-vitals", range: new Range("2.0.2"), }, - ]); + ], await caches.open("test")); assertEquals(with_peer_deps, [ { @@ -49,7 +49,7 @@ Deno.test("Can get peer dependencies", async () => { Deno.test("Can get optional peer dependencies", async () => { const peer_deps = await fetch_peer_dependencies([ { name: "@guardian/libs", range: new Range("12.0.0") }, - ]); + ], await caches.open("test")); assertEquals(peer_deps, [ { @@ -80,7 +80,7 @@ Deno.test("Will fail on optional dependencies that are defined locally", async ( { name: "@guardian/libs", range: new Range("12.0.0") }, { name: "tslib", range: new Range("2.4.1") }, { name: "typescript", range: new Range("4.2.2") }, - ]); + ], await caches.open("test")); assertEquals(peer_deps, [ { diff --git a/src/fetch_peer_dependencies.ts b/src/fetch_peer_dependencies.ts index 8f3e750..ab188a3 100644 --- a/src/fetch_peer_dependencies.ts +++ b/src/fetch_peer_dependencies.ts @@ -25,7 +25,7 @@ const { parseAsync: parse_peers } = object({ export const fetch_peer_dependencies = ( dependencies: Dependency[], - { cache = false } = {}, + cache?: Cache, ): Promise => Promise.all( dependencies.map((dependency) => diff --git a/src/main.ts b/src/main.ts index c96869c..fb6470d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -79,7 +79,7 @@ if (duplicates.length > 0) { const dependencies_from_registry = await fetch_peer_dependencies( dependencies_from_package, - { cache }, + cache ? await caches.open("npm-registry-cache") : undefined, ); format_dependencies( diff --git a/src/parse_dependencies.test.ts b/src/parse_dependencies.test.ts index f654f0c..f2e3733 100644 --- a/src/parse_dependencies.test.ts +++ b/src/parse_dependencies.test.ts @@ -1,5 +1,8 @@ import { assertEquals, assertThrows, Range } from "./deps.ts"; -import { parse_declared_dependencies } from "./parse_dependencies.ts"; +import { + find_duplicates, + parse_declared_dependencies, +} from "./parse_dependencies.ts"; Deno.test("Handles valid package.json", () => { const tuples = [ @@ -30,11 +33,12 @@ Deno.test("Handles valid package.json", () => { }); Deno.test("Warns on duplicate dependencies", () => { - const tuples = [ + const dependencies = parse_declared_dependencies([ ["one", "1.0.1"], ["two", "2.0.1"], ["two", "2.0.2"], - ] satisfies [string, string][]; + ]); + const duplicates = find_duplicates(dependencies); - assertThrows(() => parse_declared_dependencies(tuples)); + assertEquals(duplicates, ["two"]); }); From 628d47565a2e75dad4527a5da2d438655d552905 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 12:58:42 +0000 Subject: [PATCH 26/28] chore: remove unused import --- src/parse_dependencies.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse_dependencies.test.ts b/src/parse_dependencies.test.ts index f2e3733..5c91877 100644 --- a/src/parse_dependencies.test.ts +++ b/src/parse_dependencies.test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertThrows, Range } from "./deps.ts"; +import { assertEquals, Range } from "./deps.ts"; import { find_duplicates, parse_declared_dependencies, From a26585a53416af4b83b97da89a53458bc3013d44 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 13:17:51 +0000 Subject: [PATCH 27/28] feat: use unpkg.com for speed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran a benchmark to compare parsing `typescript` and the results are incredible: - registry.npmjs.org ~ 75ms - unpkg.com ~ 150 Β΅s (0.15ms) Instead of getting all possible versions of a package, it’s best to target a specific version directly. --- .github/workflows/ci.yml | 8 ++--- src/fetch_peer_dependencies.bench.ts | 4 +-- src/fetch_peer_dependencies.ts | 47 ++++++++++------------------ 3 files changed, 22 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8233dfb..c5dd420 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: deno lint src - name: Test - run: deno test src --allow-net=registry.npmjs.org + run: deno test src --allow-net=unpkg.com working_package: runs-on: ubuntu-latest @@ -49,7 +49,7 @@ jobs: - run: | deno run \ - --allow-net=registry.npmjs.org \ + --allow-net=unpkg.com \ --allow-read=. \ src/main.ts \ fixtures/package_valid.json \ @@ -72,7 +72,7 @@ jobs: - run: | deno run \ - --allow-net=registry.npmjs.org \ + --allow-net=unpkg.com \ --allow-read=. \ src/main.ts \ fixtures/package.json \ @@ -95,7 +95,7 @@ jobs: - run: | deno run \ - --allow-net=registry.npmjs.org \ + --allow-net=unpkg.com \ --allow-read=. \ src/main.ts \ fixtures/package_duplicate.json \ diff --git a/src/fetch_peer_dependencies.bench.ts b/src/fetch_peer_dependencies.bench.ts index 21e3cad..02ffcd6 100644 --- a/src/fetch_peer_dependencies.bench.ts +++ b/src/fetch_peer_dependencies.bench.ts @@ -3,7 +3,7 @@ import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts"; const cache = await caches.open("bench"); -Deno.bench("Fetch with cache", async () => { +Deno.bench("Fetch without cache", async () => { await fetch_peer_dependencies( [ { @@ -15,7 +15,7 @@ Deno.bench("Fetch with cache", async () => { ); }); -Deno.bench("Fetch without cache", async () => { +Deno.bench("Fetch with cache", async () => { await fetch_peer_dependencies( [ { diff --git a/src/fetch_peer_dependencies.ts b/src/fetch_peer_dependencies.ts index ab188a3..05660be 100644 --- a/src/fetch_peer_dependencies.ts +++ b/src/fetch_peer_dependencies.ts @@ -1,5 +1,4 @@ import { get } from "./cache.ts"; -import { format } from "./colours.ts"; import { boolean, minVersion, @@ -12,15 +11,11 @@ import { } from "./deps.ts"; import type { Dependency, RegistryDependency } from "./types.ts"; -const { parseAsync: parse_peers } = object({ - versions: record( - object({ - version: string(), - dependencies: record(string()).optional(), - peerDependencies: record(string()).optional(), - peerDependenciesMeta: record(object({ optional: boolean() })).optional(), - }), - ), +const { parse } = object({ + version: string(), + dependencies: record(string()).optional(), + peerDependencies: record(string()).optional(), + peerDependenciesMeta: record(object({ optional: boolean() })).optional(), }); export const fetch_peer_dependencies = ( @@ -31,26 +26,16 @@ export const fetch_peer_dependencies = ( dependencies.map((dependency) => get( new URL( - encodeURIComponent(dependency.name), - "https://registry.npmjs.org/", + `${dependency.name}@${minVersion(dependency.range)}/package.json`, + "https://unpkg.com/", ), cache, ) - .then((res) => res.json()) - .then(parse_peers) + .then((res) => res.json() as unknown) + .then((json) => parse(json)) .then((registry) => { - const version = Object.values(registry.versions).find(({ version }) => - satisfies(version, dependency.range) - ); - - if (!version) { - throw new Error( - `Could not find ${format(dependency.name, dependency.range)}`, - ); - } - - const peers = version.peerDependencies - ? Object.entries(version.peerDependencies).map(([name, range]) => { + const peers = Object.entries(registry.peerDependencies ?? {}).map( + ([name, range]) => { const local_version = dependencies.find( (dependency) => dependency.name === name, )?.range; @@ -62,7 +47,7 @@ export const fetch_peer_dependencies = ( ? satisfies(local_min_version, range) : false; - const is_optional = !!version.peerDependenciesMeta?.[name] + const is_optional = !!registry.peerDependenciesMeta?.[name] ?.optional; const satisfied = local_version @@ -75,12 +60,12 @@ export const fetch_peer_dependencies = ( satisfied, local: local_version, }; - }) - : []; + }, + ); return { ...dependency, - dependencies: Object.entries(version.dependencies ?? {}).filter( + dependencies: Object.entries(registry.dependencies ?? {}).filter( ([name, range]) => { try { new Range(range); @@ -94,7 +79,7 @@ export const fetch_peer_dependencies = ( ([name, range]) => ({ name, range: new Range(range) }), ), peers, - version: new SemVer(version.version), + version: new SemVer(registry.version), }; }) .catch((error) => { From 56db854f6732026b967dff7cfad3e83c3eaee286 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Wed, 25 Jan 2023 13:31:50 +0000 Subject: [PATCH 28/28] fix: remove caches from tests --- src/fetch_peer_dependencies.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fetch_peer_dependencies.test.ts b/src/fetch_peer_dependencies.test.ts index 7f820e3..ce4b567 100644 --- a/src/fetch_peer_dependencies.test.ts +++ b/src/fetch_peer_dependencies.test.ts @@ -8,7 +8,7 @@ Deno.test("Can get peer dependencies", async () => { name: "@guardian/core-web-vitals", range: new Range("2.0.2"), }, - ], await caches.open("test")); + ]); assertEquals(with_peer_deps, [ { @@ -49,7 +49,7 @@ Deno.test("Can get peer dependencies", async () => { Deno.test("Can get optional peer dependencies", async () => { const peer_deps = await fetch_peer_dependencies([ { name: "@guardian/libs", range: new Range("12.0.0") }, - ], await caches.open("test")); + ]); assertEquals(peer_deps, [ { @@ -80,7 +80,7 @@ Deno.test("Will fail on optional dependencies that are defined locally", async ( { name: "@guardian/libs", range: new Range("12.0.0") }, { name: "tslib", range: new Range("2.4.1") }, { name: "typescript", range: new Range("4.2.2") }, - ], await caches.open("test")); + ]); assertEquals(peer_deps, [ {