@route-intelligence/github-action 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/action.yml +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +103 -0
- package/package.json +37 -0
package/action.yml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Route Intelligence
|
|
2
|
+
description: Analyze routing changes in pull requests
|
|
3
|
+
branding:
|
|
4
|
+
icon: map
|
|
5
|
+
color: green
|
|
6
|
+
inputs:
|
|
7
|
+
root:
|
|
8
|
+
description: Project root directory
|
|
9
|
+
required: false
|
|
10
|
+
default: "."
|
|
11
|
+
fail-on-error:
|
|
12
|
+
description: Fail the action if routing errors are found
|
|
13
|
+
required: false
|
|
14
|
+
default: "true"
|
|
15
|
+
runs:
|
|
16
|
+
using: node20
|
|
17
|
+
main: dist/index.js
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SerializedGraph } from '@route-intelligence/shared';
|
|
2
|
+
|
|
3
|
+
interface GraphDiff {
|
|
4
|
+
addedRoutes: string[];
|
|
5
|
+
removedRoutes: string[];
|
|
6
|
+
modifiedConditions: string[];
|
|
7
|
+
newBrokenLinks: number;
|
|
8
|
+
newDeadRoutes: number;
|
|
9
|
+
newRedirectCycles: number;
|
|
10
|
+
riskScoreDelta: number;
|
|
11
|
+
}
|
|
12
|
+
declare function diffGraphs(before: SerializedGraph, after: SerializedGraph): GraphDiff;
|
|
13
|
+
declare function formatPrComment(diff: GraphDiff): string;
|
|
14
|
+
declare function analyze(root: string): Promise<SerializedGraph>;
|
|
15
|
+
declare function run(): Promise<void>;
|
|
16
|
+
|
|
17
|
+
export { type GraphDiff, analyze, diffGraphs, formatPrComment, run };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { writeFileSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { createAnalyzer, exportJson } from "@route-intelligence/core";
|
|
5
|
+
import { NextPlugin } from "@route-intelligence/next";
|
|
6
|
+
function diffGraphs(before, after) {
|
|
7
|
+
const beforeRoutes = new Map(
|
|
8
|
+
before.nodes.filter((n) => n.attributes.type === "route").map((n) => [n.attributes.path, n])
|
|
9
|
+
);
|
|
10
|
+
const afterRoutes = new Map(
|
|
11
|
+
after.nodes.filter((n) => n.attributes.type === "route").map((n) => [n.attributes.path, n])
|
|
12
|
+
);
|
|
13
|
+
const addedRoutes = [...afterRoutes.keys()].filter((p) => !beforeRoutes.has(p));
|
|
14
|
+
const removedRoutes = [...beforeRoutes.keys()].filter((p) => !afterRoutes.has(p));
|
|
15
|
+
const modifiedConditions = [];
|
|
16
|
+
for (const [path, afterNode] of afterRoutes) {
|
|
17
|
+
const beforeNode = beforeRoutes.get(path);
|
|
18
|
+
if (!beforeNode) continue;
|
|
19
|
+
if (JSON.stringify(beforeNode.attributes.conditions) !== JSON.stringify(afterNode.attributes.conditions)) {
|
|
20
|
+
modifiedConditions.push(path);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const beforeBroken = before.edges.filter(
|
|
24
|
+
(e) => e.attributes.diagnostics?.some((d) => d.ruleId === "broken-link")
|
|
25
|
+
).length;
|
|
26
|
+
const afterBroken = after.edges.filter(
|
|
27
|
+
(e) => e.attributes.diagnostics?.some((d) => d.ruleId === "broken-link")
|
|
28
|
+
).length;
|
|
29
|
+
const beforeDead = before.nodes.filter((n) => n.attributes.isDead).length;
|
|
30
|
+
const afterDead = after.nodes.filter((n) => n.attributes.isDead).length;
|
|
31
|
+
return {
|
|
32
|
+
addedRoutes,
|
|
33
|
+
removedRoutes,
|
|
34
|
+
modifiedConditions,
|
|
35
|
+
newBrokenLinks: afterBroken - beforeBroken,
|
|
36
|
+
newDeadRoutes: afterDead - beforeDead,
|
|
37
|
+
newRedirectCycles: (after.metadata?.cycleCount ?? 0) - (before.metadata?.cycleCount ?? 0),
|
|
38
|
+
riskScoreDelta: (after.metadata?.riskScore ?? 0) - (before.metadata?.riskScore ?? 0)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function formatPrComment(diff) {
|
|
42
|
+
const lines = ["## Route Intelligence Report", ""];
|
|
43
|
+
if (diff.addedRoutes.length > 0) {
|
|
44
|
+
lines.push("### Added Routes", ...diff.addedRoutes.map((r) => `- \`${r}\``), "");
|
|
45
|
+
}
|
|
46
|
+
if (diff.removedRoutes.length > 0) {
|
|
47
|
+
lines.push("### Removed Routes", ...diff.removedRoutes.map((r) => `- \`${r}\``), "");
|
|
48
|
+
}
|
|
49
|
+
if (diff.modifiedConditions.length > 0) {
|
|
50
|
+
lines.push("### Modified Conditions", ...diff.modifiedConditions.map((r) => `- \`${r}\``), "");
|
|
51
|
+
}
|
|
52
|
+
lines.push(
|
|
53
|
+
"### Analysis Summary",
|
|
54
|
+
`- New broken links: ${diff.newBrokenLinks}`,
|
|
55
|
+
`- New dead routes: ${diff.newDeadRoutes}`,
|
|
56
|
+
`- New redirect cycles: ${diff.newRedirectCycles}`,
|
|
57
|
+
`- Risk score delta: ${diff.riskScoreDelta > 0 ? "+" : ""}${diff.riskScoreDelta.toFixed(1)}`
|
|
58
|
+
);
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
async function analyze(root) {
|
|
62
|
+
const analyzer = createAnalyzer({
|
|
63
|
+
root,
|
|
64
|
+
plugins: [NextPlugin()],
|
|
65
|
+
include: ["app/**", "pages/**", "src/**", "middleware.ts"],
|
|
66
|
+
exclude: ["**/node_modules/**", "**/.next/**"]
|
|
67
|
+
});
|
|
68
|
+
const result = await analyzer.analyze();
|
|
69
|
+
return JSON.parse(exportJson(result.graph, root));
|
|
70
|
+
}
|
|
71
|
+
async function run() {
|
|
72
|
+
const root = resolve(process.env.INPUT_ROOT ?? process.env.GITHUB_WORKSPACE ?? ".");
|
|
73
|
+
const failOnError = process.env.INPUT_FAIL_ON_ERROR !== "false";
|
|
74
|
+
const currentGraph = await analyze(root);
|
|
75
|
+
writeFileSync(resolve(root, "ri-output", "graph.json"), JSON.stringify(currentGraph, null, 2));
|
|
76
|
+
const diff = {
|
|
77
|
+
addedRoutes: currentGraph.nodes.filter((n) => n.attributes.type === "route").map((n) => n.attributes.path),
|
|
78
|
+
removedRoutes: [],
|
|
79
|
+
modifiedConditions: [],
|
|
80
|
+
newBrokenLinks: 0,
|
|
81
|
+
newDeadRoutes: currentGraph.metadata?.deadRouteCount ?? 0,
|
|
82
|
+
newRedirectCycles: currentGraph.metadata?.cycleCount ?? 0,
|
|
83
|
+
riskScoreDelta: 0
|
|
84
|
+
};
|
|
85
|
+
const comment = formatPrComment(diff);
|
|
86
|
+
writeFileSync(resolve(root, "ri-output", "pr-comment.md"), comment);
|
|
87
|
+
console.log(comment);
|
|
88
|
+
if (failOnError && (diff.newBrokenLinks > 0 || diff.newRedirectCycles > 0)) {
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (process.env.GITHUB_ACTIONS) {
|
|
93
|
+
run().catch((err) => {
|
|
94
|
+
console.error(err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
analyze,
|
|
100
|
+
diffGraphs,
|
|
101
|
+
formatPrComment,
|
|
102
|
+
run
|
|
103
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@route-intelligence/github-action",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "GitHub Action for Route Intelligence PR analysis",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"development": "./src/index.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"action.yml"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@route-intelligence/core": "*",
|
|
24
|
+
"@route-intelligence/next": "*",
|
|
25
|
+
"@route-intelligence/shared": "*"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@route-intelligence/tsconfig": "*",
|
|
29
|
+
"@types/node": "^22.15.32",
|
|
30
|
+
"tsup": "^8.4.0",
|
|
31
|
+
"typescript": "^5.8.3"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|