Skip to content

Commit

Permalink
feat(peers): add script and modules
Browse files Browse the repository at this point in the history
- parse package.json
- fetch peer dependencies online
- check if there are mismatches in peers
- keep things testable via functional
  • Loading branch information
mxdvl committed Jan 23, 2023
1 parent 47e22f8 commit 954a301
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 0 deletions.
13 changes: 13 additions & 0 deletions colours.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
blue,
cyan,
gray,
yellow,
} from "https://deno.land/[email protected]/fmt/colors.ts";

export const colour = {
dependency: blue,
file: cyan,
subdued: gray,
version: yellow,
};
7 changes: 7 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { literal, object, record, string } from "https://esm.sh/[email protected]";
export { coerce, satisfies, SemVer, Range } from "https://esm.sh/[email protected]";

export {
assertEquals,
assertThrows,
} from "https://deno.land/[email protected]/testing/asserts.ts";
70 changes: 70 additions & 0 deletions fetch_peer_dependencies.test.ts
Original file line number Diff line number Diff line change
@@ -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,
},
],
},
]);
});
35 changes: 35 additions & 0 deletions fetch_peer_dependencies.ts
Original file line number Diff line number Diff line change
@@ -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<Dependency[]> =>
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;
})
),
);
69 changes: 69 additions & 0 deletions find_mismatches.test.ts
Original file line number Diff line number Diff line change
@@ -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"),
},
],
},
]),
[]
);
});
46 changes: 46 additions & 0 deletions find_mismatches.ts
Original file line number Diff line number Diff line change
@@ -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;
};
10 changes: 10 additions & 0 deletions fixtures/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
9 changes: 9 additions & 0 deletions fixtures/package_duplicate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"dependencies": {
"@guardian/source-foundations": "8.0.0",
"@guardian/libs": "^12"
},
"devDependencies": {
"@guardian/libs": "^12"
}
}
10 changes: 10 additions & 0 deletions fixtures/package_typescript.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
15 changes: 15 additions & 0 deletions fixtures/package_valid.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
3 changes: 3 additions & 0 deletions main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Deno.test("Parses package.json", () => {
return;
});
54 changes: 54 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { literal, object, record, string } from "./deps.ts";
import { coerce, satisfies, SemVer } from "./deps.ts";
import {
blue,
cyan,
gray,
yellow,
} from "https://deno.land/[email protected]/fmt/colors.ts";
import { parse_declared_dependencies } from "./parse_dependencies.ts";
import { fetch_peer_dependencies } from "./fetch_peer_dependencies.ts";
import { colour } from "./colours.ts";
import { Dependency, PeerDependency } from "./types.ts";
import { find_mismatched_peer_dependencies } from "./find_mismatches.ts";

// https://unpkg.com/

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", cyan(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);
Loading

0 comments on commit 954a301

Please sign in to comment.