Skip to content

Commit

Permalink
refactor: better tree shaping
Browse files Browse the repository at this point in the history
  • Loading branch information
mxdvl committed Jan 24, 2023
1 parent 2f2b82b commit eb65f0e
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 67 deletions.
4 changes: 4 additions & 0 deletions colours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
blue,
cyan,
gray,
green,
red,
yellow,
} from "https://deno.land/[email protected]/fmt/colors.ts";

Expand All @@ -12,6 +14,8 @@ export const colour = {
file: cyan,
subdued: gray,
version: yellow,
valid: green,
invalid: red,
};

export const format = (name: string, range: Range) =>
Expand Down
15 changes: 10 additions & 5 deletions fetch_peer_dependencies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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: [],
},
]);
Expand Down
25 changes: 10 additions & 15 deletions fetch_peer_dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const { parseAsync: parse_peers } = object({

export const fetch_peer_dependencies = (
dependencies: Dependency[],
{ verbose = false, cache = false } = {},
{ cache = false } = {},
): Promise<RegistryDependency[]> =>
Promise.all(
dependencies.map((dependency) =>
Expand All @@ -38,20 +38,12 @@ 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)}`,
);
}

Expand Down Expand Up @@ -87,10 +79,13 @@ export const fetch_peer_dependencies = (

return ({
...dependency,
dependencies: Object.entries(
version.dependencies ?? {},
).map((
[name, range],
) => ({ name, range: new Range(range) })),
peers,
versions: [version, ...versions].map(({ version }) =>
new SemVer(version)
),
version: new SemVer(version.version),
});
})
.catch((error) => {
Expand Down
12 changes: 8 additions & 4 deletions find_mismatches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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") },
],
Expand All @@ -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",
Expand All @@ -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: [],
},
]),
Expand Down
51 changes: 28 additions & 23 deletions find_mismatches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
};
2 changes: 2 additions & 0 deletions fixtures/package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 2 additions & 0 deletions fixtures/package_valid.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
54 changes: 37 additions & 17 deletions main.ts
Original file line number Diff line number Diff line change
@@ -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/[email protected]/flags/mod.ts";

const { _: [package_file], verbose, cache } = parse(Deno.args, {
Expand All @@ -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`,
);
}

Expand Down
16 changes: 14 additions & 2 deletions parse_dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
3 changes: 2 additions & 1 deletion types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export interface Dependency {
}

export interface RegistryDependency extends Dependency {
versions: SemVer[];
version: SemVer;
dependencies: Dependency[]
peers: PeerDependency[];
}

Expand Down

0 comments on commit eb65f0e

Please sign in to comment.