@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 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
@@ -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
+ }