@jatingargiitk/archmap 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +214 -2
- package/action.yml +21 -0
- package/dist/action.d.ts +2 -0
- package/dist/action.js +338 -0
- package/dist/action.js.map +1 -0
- package/dist/chunk-BCM7DLU6.js +904 -0
- package/dist/chunk-BCM7DLU6.js.map +1 -0
- package/dist/chunk-UVZVBM2N.js +911 -0
- package/dist/chunk-UVZVBM2N.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +350 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +871 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -4
- package/skills/archmap/SKILL.md +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jatin Garg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,215 @@
|
|
|
1
|
-
#
|
|
1
|
+
# archmap
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Snapshot tests for your architecture.** Deterministic module dependency graphs with lockfile diffing.
|
|
4
|
+
|
|
5
|
+
**✅ 100% Deterministic • 🔒 No API Keys • 🆓 Free**
|
|
6
|
+
|
|
7
|
+
**Your AI agent just made `ui/` import `db/`. archmap noticed.**
|
|
8
|
+
|
|
9
|
+
archmap commits a living map of your modules and flags every PR—human or AI—that adds a new dependency between them.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @jatingargiitk/archmap init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This creates:
|
|
20
|
+
- ✅ `.github/workflows/archmap.yml` - GitHub Action
|
|
21
|
+
- ✅ `ARCHITECTURE.md` - Living architecture map ([see ours](./ARCHITECTURE.md))
|
|
22
|
+
- ✅ `archmap.lock.json` - Dependency lockfile
|
|
23
|
+
|
|
24
|
+
Then:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
# .github/workflows/archmap.yml (auto-generated)
|
|
28
|
+
on: [pull_request, push]
|
|
29
|
+
permissions: { contents: write, pull-requests: write }
|
|
30
|
+
jobs:
|
|
31
|
+
archmap:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: jatingargiitk/archmap@v0
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## What Problem Does This Solve?
|
|
41
|
+
|
|
42
|
+
**The Problem:** AI agents (and humans) quietly add dependencies that violate your architecture. You discover this months later when the codebase is unmaintainable.
|
|
43
|
+
|
|
44
|
+
**The Solution:** archmap tracks your module-level dependency graph in a lockfile. New cross-module edges trigger a required approval step, making architecture changes visible and intentional.
|
|
45
|
+
|
|
46
|
+
Think of it as snapshot testing, but for your architecture.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Example: Before & After
|
|
51
|
+
|
|
52
|
+
### Before archmap
|
|
53
|
+
|
|
54
|
+
```diff
|
|
55
|
+
// src/ui/UserProfile.tsx
|
|
56
|
+
+ import { getUserFromDB } from '../db/users'; // Oops! UI directly importing from DB
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
✅ Tests pass
|
|
60
|
+
✅ Linter passes
|
|
61
|
+
✅ PR approved
|
|
62
|
+
❌ Architecture silently violated
|
|
63
|
+
|
|
64
|
+
### After archmap
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
The PR gets a sticky comment showing the new dependency and file location. Now the reviewer knows exactly what changed architecturally, and the decision is explicit.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Features
|
|
73
|
+
|
|
74
|
+
- ✅ **100% Deterministic** - Same input, same output. No LLM or API key required
|
|
75
|
+
- ✅ **Fast** - Parses with `web-tree-sitter` WASM (TS/JS + Python in v0.1)
|
|
76
|
+
- ✅ **Lockfile Diffing** - Git-friendly JSON lockfile tracks accepted edges
|
|
77
|
+
- ✅ **GitHub Action** - Sticky PR comments show only added/removed edges
|
|
78
|
+
- ✅ **Optional Gate Mode** - Fail CI if unapproved edges are added
|
|
79
|
+
- ✅ **Deny Rules** - `deny: { from: "ui/**", to: "db/**" }`
|
|
80
|
+
- ✅ **Living Docs** - `ARCHITECTURE.md` with Mermaid diagram, auto-regenerated
|
|
81
|
+
- ✅ **Agent Skill** - Works with Claude Code, Cursor, and Codex
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## CLI Commands
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx @jatingargiitk/archmap init # Initialize archmap
|
|
89
|
+
npx @jatingargiitk/archmap render # Generate ARCHITECTURE.md and lock
|
|
90
|
+
npx @jatingargiitk/archmap diff # Show diff against main
|
|
91
|
+
npx @jatingargiitk/archmap check # Check for unapproved changes
|
|
92
|
+
npx @jatingargiitk/archmap accept # Accept current changes
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
Optional `archmap.yml`:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
modules:
|
|
103
|
+
roots: [src, packages/*, apps/*]
|
|
104
|
+
depth: 1 # 0 = collapse entire root, 1+ = subfolders
|
|
105
|
+
|
|
106
|
+
ignore:
|
|
107
|
+
- "**/*.test.*"
|
|
108
|
+
- "**/fixtures/**"
|
|
109
|
+
|
|
110
|
+
rules:
|
|
111
|
+
- deny: { from: "ui/**", to: "db/**" }
|
|
112
|
+
- deny: { from: "api/**", to: "ui/**" }
|
|
113
|
+
|
|
114
|
+
mode: comment # or "gate" to fail CI on unapproved edges
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Agent Skill
|
|
120
|
+
|
|
121
|
+
archmap ships with a skill for Claude Code, Cursor, and Codex:
|
|
122
|
+
|
|
123
|
+
**Install:**
|
|
124
|
+
```bash
|
|
125
|
+
npx skills add jatingargiitk/archmap
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The agent will now run `archmap check` before finishing tasks, catching architectural drift automatically.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Comparison
|
|
133
|
+
|
|
134
|
+
| | **archmap** | graphify | CodeBoarding | pr-lens | dependency-cruiser |
|
|
135
|
+
|---|---|---|---|---|---|
|
|
136
|
+
| **Focus** | Architecture guarding | Agent memory | Platform + analysis | PR explainer | Linting |
|
|
137
|
+
| **Deterministic** | ✅ Yes | Hybrid | LLM-driven | LLM-driven | ✅ Yes |
|
|
138
|
+
| **No API Key** | ✅ Yes | ✅ Yes | ❌ Needs key | ❌ Needs key | ✅ Yes |
|
|
139
|
+
| **Lockfile Diffing** | ✅ Yes | ❌ No | ❌ No | ❌ No | ❌ No |
|
|
140
|
+
| **Living Docs** | ✅ Committed | Local HTML | Web platform | Per-PR only | Config file |
|
|
141
|
+
| **Languages** | TS/JS, Python | 40+ | 9 | General | JS/TS only |
|
|
142
|
+
| **Setup** | 1 workflow file | uv + skill | Hosted + telemetry | App or Action | Config wizard |
|
|
143
|
+
|
|
144
|
+
**Use archmap when:**
|
|
145
|
+
- You want snapshot testing for architecture
|
|
146
|
+
- You're reviewing AI-written code
|
|
147
|
+
- You need a deterministic, zero-config solution
|
|
148
|
+
|
|
149
|
+
**Use graphify when:**
|
|
150
|
+
- You need agent context and memory
|
|
151
|
+
- You want queryable knowledge graphs
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## FAQ
|
|
156
|
+
|
|
157
|
+
### Does archmap work with monorepos?
|
|
158
|
+
|
|
159
|
+
Yes! Configure `modules.roots` to match your structure:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
modules:
|
|
163
|
+
roots: [packages/*, apps/*]
|
|
164
|
+
depth: 0 # Collapse each package into one node
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### What about fork PRs?
|
|
168
|
+
|
|
169
|
+
archmap uses read-only tokens for forks. It posts a comment but cannot update files. The reviewer runs `archmap accept` locally.
|
|
170
|
+
|
|
171
|
+
### Does anything leave my runner?
|
|
172
|
+
|
|
173
|
+
No. archmap is 100% local. The optional `explain` mode (not yet implemented) will send structural diffs (JSON) to an LLM, but never your code.
|
|
174
|
+
|
|
175
|
+
### How is this different from Dependabot?
|
|
176
|
+
|
|
177
|
+
Dependabot tracks external package updates. archmap tracks your internal module architecture.
|
|
178
|
+
|
|
179
|
+
### Can I use this for Go/Rust/Java?
|
|
180
|
+
|
|
181
|
+
Not yet. v0.1 supports TypeScript/JavaScript and Python. Go is planned for v0.2.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Limitations
|
|
186
|
+
|
|
187
|
+
### Protected Default Branch
|
|
188
|
+
|
|
189
|
+
If your default branch (e.g., `main`) has branch protection rules enabled, the GitHub Action cannot push `ARCHITECTURE.md` and `archmap.lock.json` updates directly on `push` events.
|
|
190
|
+
|
|
191
|
+
**Workaround:** Either:
|
|
192
|
+
1. Disable branch protection for the `archmap[bot]` user or GitHub Actions
|
|
193
|
+
2. Only run archmap on `pull_request` events (recommended)
|
|
194
|
+
3. Manually run `archmap accept` locally and commit the changes
|
|
195
|
+
|
|
196
|
+
The `pull_request` workflow mode (default) is unaffected by branch protection.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Contributing
|
|
201
|
+
|
|
202
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, publishing instructions, and how to regenerate the demo GIF.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT © Jatin Garg
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
**Made for developers reviewing AI-written code.**
|
|
213
|
+
Track architecture. Make drift visible. Keep understanding your codebase.
|
|
214
|
+
|
|
215
|
+
Built by [@jatingargiitk](https://x.com/jatingargiitk)
|
package/action.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: 'archmap'
|
|
2
|
+
description: 'Snapshot tests for your architecture - track module dependencies'
|
|
3
|
+
author: 'Jatin Garg'
|
|
4
|
+
|
|
5
|
+
branding:
|
|
6
|
+
icon: 'map'
|
|
7
|
+
color: 'blue'
|
|
8
|
+
|
|
9
|
+
runs:
|
|
10
|
+
using: 'node20'
|
|
11
|
+
main: 'dist/action.js'
|
|
12
|
+
|
|
13
|
+
inputs:
|
|
14
|
+
mode:
|
|
15
|
+
description: 'Mode: comment (default) or gate'
|
|
16
|
+
required: false
|
|
17
|
+
default: 'comment'
|
|
18
|
+
github-token:
|
|
19
|
+
description: 'GitHub token for posting comments'
|
|
20
|
+
required: false
|
|
21
|
+
default: ${{ github.token }}
|
package/dist/action.d.ts
ADDED
package/dist/action.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildGraph,
|
|
4
|
+
checkRules,
|
|
5
|
+
diffLocks,
|
|
6
|
+
extractPythonImports,
|
|
7
|
+
extractTypeScriptImports,
|
|
8
|
+
findWorkspaces,
|
|
9
|
+
graphToLock,
|
|
10
|
+
loadTsConfig,
|
|
11
|
+
renderDeltaDiagram,
|
|
12
|
+
resolveImports,
|
|
13
|
+
shouldFailCheck,
|
|
14
|
+
writeArchitectureFile,
|
|
15
|
+
writeLockFile
|
|
16
|
+
} from "./chunk-UVZVBM2N.js";
|
|
17
|
+
|
|
18
|
+
// src/action.ts
|
|
19
|
+
import * as fs from "fs/promises";
|
|
20
|
+
import { readFileSync } from "fs";
|
|
21
|
+
import * as path from "path";
|
|
22
|
+
import { execSync } from "child_process";
|
|
23
|
+
async function runAction() {
|
|
24
|
+
const githubContext = getGitHubContext();
|
|
25
|
+
const rootDir = process.env.GITHUB_WORKSPACE || process.cwd();
|
|
26
|
+
console.log("\u{1F50D} Analyzing architecture...");
|
|
27
|
+
const config = await loadConfig(rootDir);
|
|
28
|
+
const currentGraph = await analyzeRepo(rootDir, config);
|
|
29
|
+
const currentLock = graphToLock(currentGraph);
|
|
30
|
+
await writeArchitectureFile(
|
|
31
|
+
path.join(rootDir, "ARCHITECTURE.md"),
|
|
32
|
+
currentGraph
|
|
33
|
+
);
|
|
34
|
+
await writeLockFile(path.join(rootDir, "archmap.lock.json"), currentLock);
|
|
35
|
+
if (githubContext.eventName === "pull_request") {
|
|
36
|
+
await handlePullRequest(rootDir, currentGraph, currentLock, config, githubContext);
|
|
37
|
+
} else if (githubContext.eventName === "push") {
|
|
38
|
+
await handlePush(rootDir, githubContext);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function handlePullRequest(rootDir, currentGraph, currentLock, config, context) {
|
|
42
|
+
console.log("\u{1F4CA} Computing diff...");
|
|
43
|
+
let baseLock = null;
|
|
44
|
+
try {
|
|
45
|
+
const baseLockContent = execSync(
|
|
46
|
+
`git show origin/${context.baseRef}:archmap.lock.json`,
|
|
47
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
48
|
+
);
|
|
49
|
+
baseLock = JSON.parse(baseLockContent);
|
|
50
|
+
} catch {
|
|
51
|
+
console.log("\u26A0\uFE0F No lock file found in base branch");
|
|
52
|
+
}
|
|
53
|
+
const diff = diffLocks(baseLock, currentLock, currentGraph);
|
|
54
|
+
const violations = checkRules(currentGraph.edges, config);
|
|
55
|
+
const commentBody = buildCommentBody(diff, violations, currentGraph);
|
|
56
|
+
if (process.env.GITHUB_TOKEN && context.prNumber) {
|
|
57
|
+
await postOrUpdateComment(context, commentBody);
|
|
58
|
+
} else {
|
|
59
|
+
console.log("Comment preview:");
|
|
60
|
+
console.log(commentBody);
|
|
61
|
+
}
|
|
62
|
+
const shouldFail = shouldFailCheck(diff, violations, config);
|
|
63
|
+
if (shouldFail) {
|
|
64
|
+
console.log("\u274C Check failed");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
} else {
|
|
67
|
+
console.log("\u2705 Check passed");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function handlePush(rootDir, context) {
|
|
71
|
+
if (context.ref === `refs/heads/${context.defaultBranch}`) {
|
|
72
|
+
console.log("\u{1F504} Committing updated architecture files...");
|
|
73
|
+
try {
|
|
74
|
+
execSync('git config user.name "archmap[bot]"', { cwd: rootDir });
|
|
75
|
+
execSync('git config user.email "archmap[bot]@users.noreply.github.com"', { cwd: rootDir });
|
|
76
|
+
execSync("git add ARCHITECTURE.md archmap.lock.json", { cwd: rootDir });
|
|
77
|
+
try {
|
|
78
|
+
execSync('git commit -m "chore: update architecture map [skip ci]"', { cwd: rootDir });
|
|
79
|
+
execSync(`git push origin ${context.defaultBranch}`, { cwd: rootDir });
|
|
80
|
+
console.log("\u2705 Architecture files updated");
|
|
81
|
+
} catch {
|
|
82
|
+
console.log("\u2139\uFE0F No changes to commit");
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.log("\u26A0\uFE0F Could not commit changes:", error.message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function buildCommentBody(diff, violations, _graph) {
|
|
90
|
+
const lines = [];
|
|
91
|
+
lines.push("## \u{1F5FA}\uFE0F archmap");
|
|
92
|
+
lines.push("");
|
|
93
|
+
const summary = `**${diff.added.length > 0 ? "+" : ""}${diff.added.length} new edge${diff.added.length !== 1 ? "s" : ""}, ${diff.removed.length > 0 ? "-" : ""}${diff.removed.length} removed**`;
|
|
94
|
+
lines.push(summary);
|
|
95
|
+
lines.push("");
|
|
96
|
+
if (violations.length > 0) {
|
|
97
|
+
lines.push("### \u26A0\uFE0F Rule Violations");
|
|
98
|
+
lines.push("");
|
|
99
|
+
for (const violation of violations) {
|
|
100
|
+
lines.push(`- ${violation.message}`);
|
|
101
|
+
lines.push(` - Rule: \`${violation.rule}\``);
|
|
102
|
+
}
|
|
103
|
+
lines.push("");
|
|
104
|
+
}
|
|
105
|
+
if (diff.added.length > 0 || diff.removed.length > 0) {
|
|
106
|
+
const touchedModules = /* @__PURE__ */ new Set();
|
|
107
|
+
for (const edge of diff.added) {
|
|
108
|
+
touchedModules.add(edge.from);
|
|
109
|
+
touchedModules.add(edge.to);
|
|
110
|
+
}
|
|
111
|
+
for (const edge of diff.removed) {
|
|
112
|
+
touchedModules.add(edge.from);
|
|
113
|
+
touchedModules.add(edge.to);
|
|
114
|
+
}
|
|
115
|
+
lines.push("### Changes");
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push(renderDeltaDiagram(diff.added, diff.removed, touchedModules));
|
|
118
|
+
lines.push("");
|
|
119
|
+
if (diff.added.length > 0) {
|
|
120
|
+
lines.push("#### Added Dependencies");
|
|
121
|
+
lines.push("");
|
|
122
|
+
for (const edge of diff.added) {
|
|
123
|
+
if (edge.locations && edge.locations.length > 0) {
|
|
124
|
+
const loc = edge.locations[0];
|
|
125
|
+
lines.push(`- \`${edge.from}\` \u2192 \`${edge.to}\` ([${loc.file}:${loc.line}](${loc.file}#L${loc.line}))`);
|
|
126
|
+
} else {
|
|
127
|
+
lines.push(`- \`${edge.from}\` \u2192 \`${edge.to}\``);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
lines.push("");
|
|
131
|
+
}
|
|
132
|
+
if (diff.removed.length > 0) {
|
|
133
|
+
lines.push("#### Removed Dependencies");
|
|
134
|
+
lines.push("");
|
|
135
|
+
for (const edge of diff.removed) {
|
|
136
|
+
lines.push(`- \`${edge.from}\` \u2192 \`${edge.to}\``);
|
|
137
|
+
}
|
|
138
|
+
lines.push("");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (diff.newCycles.length > 0) {
|
|
142
|
+
lines.push("### \u26A0\uFE0F New Cycles");
|
|
143
|
+
lines.push("");
|
|
144
|
+
for (const cycle of diff.newCycles) {
|
|
145
|
+
lines.push(`- ${cycle.map((m) => `\`${m}\``).join(" \u2192 ")}`);
|
|
146
|
+
}
|
|
147
|
+
lines.push("");
|
|
148
|
+
}
|
|
149
|
+
if (diff.added.length === 0 && diff.removed.length === 0 && violations.length === 0) {
|
|
150
|
+
lines.push("\u2705 No architectural changes detected");
|
|
151
|
+
} else {
|
|
152
|
+
lines.push("---");
|
|
153
|
+
lines.push("");
|
|
154
|
+
lines.push("To accept these changes, run:");
|
|
155
|
+
lines.push("```bash");
|
|
156
|
+
lines.push("npx @jatingargiitk/archmap accept");
|
|
157
|
+
lines.push("git add ARCHITECTURE.md archmap.lock.json");
|
|
158
|
+
lines.push('git commit -m "chore: update architecture map"');
|
|
159
|
+
lines.push("```");
|
|
160
|
+
}
|
|
161
|
+
lines.push("");
|
|
162
|
+
lines.push("<!-- archmap-comment -->");
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
|
165
|
+
async function postOrUpdateComment(context, body) {
|
|
166
|
+
const token = process.env.GITHUB_TOKEN;
|
|
167
|
+
if (!token) return;
|
|
168
|
+
const [owner, repo] = context.repository.split("/");
|
|
169
|
+
const issueNumber = context.prNumber;
|
|
170
|
+
try {
|
|
171
|
+
const existingComments = await fetch(
|
|
172
|
+
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
|
173
|
+
{
|
|
174
|
+
headers: {
|
|
175
|
+
Authorization: `Bearer ${token}`,
|
|
176
|
+
"User-Agent": "archmap"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
const comments = await existingComments.json();
|
|
181
|
+
const existingComment = comments.find(
|
|
182
|
+
(c) => c.body.includes("<!-- archmap-comment -->")
|
|
183
|
+
);
|
|
184
|
+
if (existingComment) {
|
|
185
|
+
await fetch(
|
|
186
|
+
`https://api.github.com/repos/${owner}/${repo}/issues/comments/${existingComment.id}`,
|
|
187
|
+
{
|
|
188
|
+
method: "PATCH",
|
|
189
|
+
headers: {
|
|
190
|
+
Authorization: `Bearer ${token}`,
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
"User-Agent": "archmap"
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify({ body })
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
console.log("\u2705 Updated existing comment");
|
|
198
|
+
} else {
|
|
199
|
+
await fetch(
|
|
200
|
+
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
|
201
|
+
{
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: {
|
|
204
|
+
Authorization: `Bearer ${token}`,
|
|
205
|
+
"Content-Type": "application/json",
|
|
206
|
+
"User-Agent": "archmap"
|
|
207
|
+
},
|
|
208
|
+
body: JSON.stringify({ body })
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
console.log("\u2705 Posted new comment");
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.log("\u26A0\uFE0F Could not post comment:", error.message);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function getGitHubContext() {
|
|
218
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
219
|
+
let event = {};
|
|
220
|
+
if (eventPath) {
|
|
221
|
+
try {
|
|
222
|
+
const eventData = readFileSync(eventPath, "utf-8");
|
|
223
|
+
event = JSON.parse(eventData);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
eventName: process.env.GITHUB_EVENT_NAME || "",
|
|
229
|
+
repository: process.env.GITHUB_REPOSITORY || "",
|
|
230
|
+
ref: process.env.GITHUB_REF || "",
|
|
231
|
+
sha: process.env.GITHUB_SHA || "",
|
|
232
|
+
actor: process.env.GITHUB_ACTOR || "",
|
|
233
|
+
workflow: process.env.GITHUB_WORKFLOW || "",
|
|
234
|
+
defaultBranch: event.repository?.default_branch || "main",
|
|
235
|
+
baseRef: event.pull_request?.base?.ref || "main",
|
|
236
|
+
prNumber: event.pull_request?.number
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
async function analyzeRepo(rootDir, config) {
|
|
240
|
+
const files = await findSourceFiles(rootDir, config.ignore || []);
|
|
241
|
+
const allImports = [];
|
|
242
|
+
const allFiles = [];
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const fullPath = path.join(rootDir, file);
|
|
245
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
246
|
+
let imports;
|
|
247
|
+
if (file.endsWith(".ts") || file.endsWith(".tsx") || file.endsWith(".js") || file.endsWith(".jsx")) {
|
|
248
|
+
imports = await extractTypeScriptImports(file, content);
|
|
249
|
+
} else if (file.endsWith(".py")) {
|
|
250
|
+
imports = await extractPythonImports(file, content);
|
|
251
|
+
} else {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
allImports.push(imports);
|
|
255
|
+
allFiles.push(file);
|
|
256
|
+
}
|
|
257
|
+
const tsconfig = await loadTsConfig(rootDir);
|
|
258
|
+
const workspaces = await findWorkspaces(rootDir);
|
|
259
|
+
const resolved = await resolveImports(allImports, {
|
|
260
|
+
rootDir,
|
|
261
|
+
tsconfig: tsconfig || void 0,
|
|
262
|
+
workspaces: workspaces.length > 0 ? workspaces : void 0
|
|
263
|
+
});
|
|
264
|
+
const moduleConfig = config.modules || {};
|
|
265
|
+
const graph = buildGraph(resolved, allFiles, moduleConfig);
|
|
266
|
+
return graph;
|
|
267
|
+
}
|
|
268
|
+
async function findSourceFiles(rootDir, ignore) {
|
|
269
|
+
const files = [];
|
|
270
|
+
const ignorePatterns = [
|
|
271
|
+
"**/node_modules/**",
|
|
272
|
+
"**/dist/**",
|
|
273
|
+
"**/build/**",
|
|
274
|
+
"**/.git/**",
|
|
275
|
+
"**/coverage/**",
|
|
276
|
+
"**/*.test.*",
|
|
277
|
+
"**/*.spec.*",
|
|
278
|
+
"**/__tests__/**",
|
|
279
|
+
...ignore
|
|
280
|
+
];
|
|
281
|
+
async function walk(dir) {
|
|
282
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
283
|
+
for (const entry of entries) {
|
|
284
|
+
const fullPath = path.join(dir, entry.name);
|
|
285
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
286
|
+
if (shouldIgnore(relativePath, ignorePatterns)) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (entry.isDirectory()) {
|
|
290
|
+
await walk(fullPath);
|
|
291
|
+
} else if (entry.isFile()) {
|
|
292
|
+
if (isSourceFile(entry.name)) {
|
|
293
|
+
files.push(relativePath);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
await walk(rootDir);
|
|
299
|
+
return files;
|
|
300
|
+
}
|
|
301
|
+
function shouldIgnore(filePath, patterns) {
|
|
302
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
303
|
+
for (const pattern of patterns) {
|
|
304
|
+
const regex = patternToRegex(pattern);
|
|
305
|
+
if (regex.test(normalized)) {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
function patternToRegex(pattern) {
|
|
312
|
+
const regexStr = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLESTAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLESTAR___/g, ".*");
|
|
313
|
+
return new RegExp(`^${regexStr}$`);
|
|
314
|
+
}
|
|
315
|
+
function isSourceFile(name) {
|
|
316
|
+
return /\.(ts|tsx|js|jsx|py)$/.test(name);
|
|
317
|
+
}
|
|
318
|
+
async function loadConfig(rootDir) {
|
|
319
|
+
const configPath = path.join(rootDir, "archmap.yml");
|
|
320
|
+
let config = {};
|
|
321
|
+
try {
|
|
322
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
323
|
+
const yaml = await import("js-yaml");
|
|
324
|
+
config = yaml.load(content);
|
|
325
|
+
} catch {
|
|
326
|
+
config = {};
|
|
327
|
+
}
|
|
328
|
+
const inputMode = process.env.INPUT_MODE;
|
|
329
|
+
if (inputMode === "comment" || inputMode === "gate") {
|
|
330
|
+
config.mode = inputMode;
|
|
331
|
+
}
|
|
332
|
+
return config;
|
|
333
|
+
}
|
|
334
|
+
runAction().catch((error) => {
|
|
335
|
+
console.error("Error:", error.message);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
});
|
|
338
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/action.ts"],"sourcesContent":["import * as fs from 'node:fs/promises';\nimport { readFileSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { extractTypeScriptImports, extractPythonImports } from './parser/index.js';\nimport { resolveImports, loadTsConfig, findWorkspaces } from './resolver/index.js';\nimport { buildGraph } from './graph/index.js';\nimport { writeArchitectureFile, renderDeltaDiagram } from './render/index.js';\nimport { graphToLock, writeLockFile, diffLocks } from './lock/index.js';\nimport { checkRules, shouldFailCheck } from './rules/index.js';\nimport type { Config } from './types.js';\n\nasync function runAction(): Promise<void> {\n const githubContext = getGitHubContext();\n const rootDir = process.env.GITHUB_WORKSPACE || process.cwd();\n\n console.log('🔍 Analyzing architecture...');\n\n const config = await loadConfig(rootDir);\n const currentGraph = await analyzeRepo(rootDir, config);\n const currentLock = graphToLock(currentGraph);\n\n await writeArchitectureFile(\n path.join(rootDir, 'ARCHITECTURE.md'),\n currentGraph\n );\n\n await writeLockFile(path.join(rootDir, 'archmap.lock.json'), currentLock);\n\n if (githubContext.eventName === 'pull_request') {\n await handlePullRequest(rootDir, currentGraph, currentLock, config, githubContext);\n } else if (githubContext.eventName === 'push') {\n await handlePush(rootDir, githubContext);\n }\n}\n\nasync function handlePullRequest(\n rootDir: string,\n currentGraph: any,\n currentLock: any,\n config: Config,\n context: any\n): Promise<void> {\n console.log('📊 Computing diff...');\n\n let baseLock = null;\n try {\n const baseLockContent = execSync(\n `git show origin/${context.baseRef}:archmap.lock.json`,\n { cwd: rootDir, encoding: 'utf-8' }\n );\n baseLock = JSON.parse(baseLockContent);\n } catch {\n console.log('⚠️ No lock file found in base branch');\n }\n\n const diff = diffLocks(baseLock, currentLock, currentGraph);\n const violations = checkRules(currentGraph.edges, config);\n\n const commentBody = buildCommentBody(diff, violations, currentGraph);\n\n if (process.env.GITHUB_TOKEN && context.prNumber) {\n await postOrUpdateComment(context, commentBody);\n } else {\n console.log('Comment preview:');\n console.log(commentBody);\n }\n\n const shouldFail = shouldFailCheck(diff, violations, config);\n\n if (shouldFail) {\n console.log('❌ Check failed');\n process.exit(1);\n } else {\n console.log('✅ Check passed');\n }\n}\n\nasync function handlePush(rootDir: string, context: any): Promise<void> {\n if (context.ref === `refs/heads/${context.defaultBranch}`) {\n console.log('🔄 Committing updated architecture files...');\n\n try {\n execSync('git config user.name \"archmap[bot]\"', { cwd: rootDir });\n execSync('git config user.email \"archmap[bot]@users.noreply.github.com\"', { cwd: rootDir });\n execSync('git add ARCHITECTURE.md archmap.lock.json', { cwd: rootDir });\n \n try {\n execSync('git commit -m \"chore: update architecture map [skip ci]\"', { cwd: rootDir });\n execSync(`git push origin ${context.defaultBranch}`, { cwd: rootDir });\n console.log('✅ Architecture files updated');\n } catch {\n console.log('ℹ️ No changes to commit');\n }\n } catch (error: any) {\n console.log('⚠️ Could not commit changes:', error.message);\n }\n }\n}\n\nfunction buildCommentBody(diff: any, violations: any[], _graph: any): string {\n const lines: string[] = [];\n\n lines.push('## 🗺️ archmap');\n lines.push('');\n\n const summary = `**${diff.added.length > 0 ? '+' : ''}${diff.added.length} new edge${diff.added.length !== 1 ? 's' : ''}, ${diff.removed.length > 0 ? '-' : ''}${diff.removed.length} removed**`;\n lines.push(summary);\n lines.push('');\n\n if (violations.length > 0) {\n lines.push('### ⚠️ Rule Violations');\n lines.push('');\n for (const violation of violations) {\n lines.push(`- ${violation.message}`);\n lines.push(` - Rule: \\`${violation.rule}\\``);\n }\n lines.push('');\n }\n\n if (diff.added.length > 0 || diff.removed.length > 0) {\n const touchedModules = new Set<string>();\n for (const edge of diff.added) {\n touchedModules.add(edge.from);\n touchedModules.add(edge.to);\n }\n for (const edge of diff.removed) {\n touchedModules.add(edge.from);\n touchedModules.add(edge.to);\n }\n\n lines.push('### Changes');\n lines.push('');\n lines.push(renderDeltaDiagram(diff.added, diff.removed, touchedModules));\n lines.push('');\n\n if (diff.added.length > 0) {\n lines.push('#### Added Dependencies');\n lines.push('');\n for (const edge of diff.added) {\n // Show locations (B6)\n if (edge.locations && edge.locations.length > 0) {\n const loc = edge.locations[0];\n lines.push(`- \\`${edge.from}\\` → \\`${edge.to}\\` ([${loc.file}:${loc.line}](${loc.file}#L${loc.line}))`);\n } else {\n lines.push(`- \\`${edge.from}\\` → \\`${edge.to}\\``);\n }\n }\n lines.push('');\n }\n\n if (diff.removed.length > 0) {\n lines.push('#### Removed Dependencies');\n lines.push('');\n for (const edge of diff.removed) {\n lines.push(`- \\`${edge.from}\\` → \\`${edge.to}\\``);\n }\n lines.push('');\n }\n }\n\n if (diff.newCycles.length > 0) {\n lines.push('### ⚠️ New Cycles');\n lines.push('');\n for (const cycle of diff.newCycles) {\n lines.push(`- ${cycle.map((m: string) => `\\`${m}\\``).join(' → ')}`);\n }\n lines.push('');\n }\n\n if (diff.added.length === 0 && diff.removed.length === 0 && violations.length === 0) {\n lines.push('✅ No architectural changes detected');\n } else {\n lines.push('---');\n lines.push('');\n lines.push('To accept these changes, run:');\n lines.push('```bash');\n lines.push('npx @jatingargiitk/archmap accept');\n lines.push('git add ARCHITECTURE.md archmap.lock.json');\n lines.push('git commit -m \"chore: update architecture map\"');\n lines.push('```');\n }\n\n lines.push('');\n lines.push('<!-- archmap-comment -->');\n\n return lines.join('\\n');\n}\n\nasync function postOrUpdateComment(context: any, body: string): Promise<void> {\n const token = process.env.GITHUB_TOKEN;\n if (!token) return;\n\n const [owner, repo] = context.repository.split('/');\n const issueNumber = context.prNumber;\n\n try {\n const existingComments = await fetch(\n `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,\n {\n headers: {\n Authorization: `Bearer ${token}`,\n 'User-Agent': 'archmap',\n },\n }\n );\n\n const comments = (await existingComments.json()) as any[];\n const existingComment = comments.find((c: any) =>\n c.body.includes('<!-- archmap-comment -->')\n );\n\n if (existingComment) {\n await fetch(\n `https://api.github.com/repos/${owner}/${repo}/issues/comments/${existingComment.id}`,\n {\n method: 'PATCH',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'archmap',\n },\n body: JSON.stringify({ body }),\n }\n );\n console.log('✅ Updated existing comment');\n } else {\n await fetch(\n `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'archmap',\n },\n body: JSON.stringify({ body }),\n }\n );\n console.log('✅ Posted new comment');\n }\n } catch (error: any) {\n console.log('⚠️ Could not post comment:', error.message);\n }\n}\n\nfunction getGitHubContext(): any {\n const eventPath = process.env.GITHUB_EVENT_PATH;\n let event = {};\n\n if (eventPath) {\n try {\n const eventData = readFileSync(eventPath, 'utf-8');\n event = JSON.parse(eventData);\n } catch {\n // Ignore\n }\n }\n\n return {\n eventName: process.env.GITHUB_EVENT_NAME || '',\n repository: process.env.GITHUB_REPOSITORY || '',\n ref: process.env.GITHUB_REF || '',\n sha: process.env.GITHUB_SHA || '',\n actor: process.env.GITHUB_ACTOR || '',\n workflow: process.env.GITHUB_WORKFLOW || '',\n defaultBranch: (event as any).repository?.default_branch || 'main',\n baseRef: (event as any).pull_request?.base?.ref || 'main',\n prNumber: (event as any).pull_request?.number,\n };\n}\n\nasync function analyzeRepo(rootDir: string, config: Config): Promise<any> {\n const files = await findSourceFiles(rootDir, config.ignore || []);\n\n const allImports = [];\n const allFiles: string[] = [];\n\n for (const file of files) {\n const fullPath = path.join(rootDir, file);\n const content = await fs.readFile(fullPath, 'utf-8');\n\n let imports;\n if (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js') || file.endsWith('.jsx')) {\n imports = await extractTypeScriptImports(file, content);\n } else if (file.endsWith('.py')) {\n imports = await extractPythonImports(file, content);\n } else {\n continue;\n }\n\n allImports.push(imports);\n allFiles.push(file);\n }\n\n const tsconfig = await loadTsConfig(rootDir);\n const workspaces = await findWorkspaces(rootDir);\n\n const resolved = await resolveImports(allImports, {\n rootDir,\n tsconfig: tsconfig || undefined,\n workspaces: workspaces.length > 0 ? workspaces : undefined,\n });\n\n const moduleConfig = config.modules || {};\n const graph = buildGraph(resolved, allFiles, moduleConfig);\n\n return graph;\n}\n\nasync function findSourceFiles(rootDir: string, ignore: string[]): Promise<string[]> {\n const files: string[] = [];\n const ignorePatterns = [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/.git/**',\n '**/coverage/**',\n '**/*.test.*',\n '**/*.spec.*',\n '**/__tests__/**',\n ...ignore,\n ];\n\n async function walk(dir: string): Promise<void> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (shouldIgnore(relativePath, ignorePatterns)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n await walk(fullPath);\n } else if (entry.isFile()) {\n if (isSourceFile(entry.name)) {\n files.push(relativePath);\n }\n }\n }\n }\n\n await walk(rootDir);\n return files;\n}\n\nfunction shouldIgnore(filePath: string, patterns: string[]): boolean {\n const normalized = filePath.replace(/\\\\/g, '/');\n\n for (const pattern of patterns) {\n const regex = patternToRegex(pattern);\n if (regex.test(normalized)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction patternToRegex(pattern: string): RegExp {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*\\*/g, '___DOUBLESTAR___')\n .replace(/\\*/g, '[^/]*')\n .replace(/___DOUBLESTAR___/g, '.*');\n\n return new RegExp(`^${regexStr}$`);\n}\n\nfunction isSourceFile(name: string): boolean {\n return /\\.(ts|tsx|js|jsx|py)$/.test(name);\n}\n\nasync function loadConfig(rootDir: string): Promise<Config> {\n const configPath = path.join(rootDir, 'archmap.yml');\n\n let config: Config = {};\n try {\n const content = await fs.readFile(configPath, 'utf-8');\n const yaml = await import('js-yaml');\n config = yaml.load(content) as Config;\n } catch {\n config = {};\n }\n\n // Override with INPUT_MODE if provided (B7)\n const inputMode = process.env.INPUT_MODE;\n if (inputMode === 'comment' || inputMode === 'gate') {\n config.mode = inputMode;\n }\n\n return config;\n}\n\nrunAction().catch((error) => {\n console.error('Error:', error.message);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,YAAY,QAAQ;AACpB,SAAS,oBAAoB;AAC7B,YAAY,UAAU;AACtB,SAAS,gBAAgB;AASzB,eAAe,YAA2B;AACxC,QAAM,gBAAgB,iBAAiB;AACvC,QAAM,UAAU,QAAQ,IAAI,oBAAoB,QAAQ,IAAI;AAE5D,UAAQ,IAAI,qCAA8B;AAE1C,QAAM,SAAS,MAAM,WAAW,OAAO;AACvC,QAAM,eAAe,MAAM,YAAY,SAAS,MAAM;AACtD,QAAM,cAAc,YAAY,YAAY;AAE5C,QAAM;AAAA,IACC,UAAK,SAAS,iBAAiB;AAAA,IACpC;AAAA,EACF;AAEA,QAAM,cAAmB,UAAK,SAAS,mBAAmB,GAAG,WAAW;AAExE,MAAI,cAAc,cAAc,gBAAgB;AAC9C,UAAM,kBAAkB,SAAS,cAAc,aAAa,QAAQ,aAAa;AAAA,EACnF,WAAW,cAAc,cAAc,QAAQ;AAC7C,UAAM,WAAW,SAAS,aAAa;AAAA,EACzC;AACF;AAEA,eAAe,kBACb,SACA,cACA,aACA,QACA,SACe;AACf,UAAQ,IAAI,6BAAsB;AAElC,MAAI,WAAW;AACf,MAAI;AACF,UAAM,kBAAkB;AAAA,MACtB,mBAAmB,QAAQ,OAAO;AAAA,MAClC,EAAE,KAAK,SAAS,UAAU,QAAQ;AAAA,IACpC;AACA,eAAW,KAAK,MAAM,eAAe;AAAA,EACvC,QAAQ;AACN,YAAQ,IAAI,iDAAuC;AAAA,EACrD;AAEA,QAAM,OAAO,UAAU,UAAU,aAAa,YAAY;AAC1D,QAAM,aAAa,WAAW,aAAa,OAAO,MAAM;AAExD,QAAM,cAAc,iBAAiB,MAAM,YAAY,YAAY;AAEnE,MAAI,QAAQ,IAAI,gBAAgB,QAAQ,UAAU;AAChD,UAAM,oBAAoB,SAAS,WAAW;AAAA,EAChD,OAAO;AACL,YAAQ,IAAI,kBAAkB;AAC9B,YAAQ,IAAI,WAAW;AAAA,EACzB;AAEA,QAAM,aAAa,gBAAgB,MAAM,YAAY,MAAM;AAE3D,MAAI,YAAY;AACd,YAAQ,IAAI,qBAAgB;AAC5B,YAAQ,KAAK,CAAC;AAAA,EAChB,OAAO;AACL,YAAQ,IAAI,qBAAgB;AAAA,EAC9B;AACF;AAEA,eAAe,WAAW,SAAiB,SAA6B;AACtE,MAAI,QAAQ,QAAQ,cAAc,QAAQ,aAAa,IAAI;AACzD,YAAQ,IAAI,oDAA6C;AAEzD,QAAI;AACF,eAAS,uCAAuC,EAAE,KAAK,QAAQ,CAAC;AAChE,eAAS,iEAAiE,EAAE,KAAK,QAAQ,CAAC;AAC1F,eAAS,6CAA6C,EAAE,KAAK,QAAQ,CAAC;AAEtE,UAAI;AACF,iBAAS,4DAA4D,EAAE,KAAK,QAAQ,CAAC;AACrF,iBAAS,mBAAmB,QAAQ,aAAa,IAAI,EAAE,KAAK,QAAQ,CAAC;AACrE,gBAAQ,IAAI,mCAA8B;AAAA,MAC5C,QAAQ;AACN,gBAAQ,IAAI,oCAA0B;AAAA,MACxC;AAAA,IACF,SAAS,OAAY;AACnB,cAAQ,IAAI,2CAAiC,MAAM,OAAO;AAAA,IAC5D;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,MAAW,YAAmB,QAAqB;AAC3E,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,4BAAgB;AAC3B,QAAM,KAAK,EAAE;AAEb,QAAM,UAAU,KAAK,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,GAAG,KAAK,MAAM,MAAM,YAAY,KAAK,MAAM,WAAW,IAAI,MAAM,EAAE,KAAK,KAAK,QAAQ,SAAS,IAAI,MAAM,EAAE,GAAG,KAAK,QAAQ,MAAM;AACpL,QAAM,KAAK,OAAO;AAClB,QAAM,KAAK,EAAE;AAEb,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,KAAK,kCAAwB;AACnC,UAAM,KAAK,EAAE;AACb,eAAW,aAAa,YAAY;AAClC,YAAM,KAAK,KAAK,UAAU,OAAO,EAAE;AACnC,YAAM,KAAK,eAAe,UAAU,IAAI,IAAI;AAAA,IAC9C;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,MAAI,KAAK,MAAM,SAAS,KAAK,KAAK,QAAQ,SAAS,GAAG;AACpD,UAAM,iBAAiB,oBAAI,IAAY;AACvC,eAAW,QAAQ,KAAK,OAAO;AAC7B,qBAAe,IAAI,KAAK,IAAI;AAC5B,qBAAe,IAAI,KAAK,EAAE;AAAA,IAC5B;AACA,eAAW,QAAQ,KAAK,SAAS;AAC/B,qBAAe,IAAI,KAAK,IAAI;AAC5B,qBAAe,IAAI,KAAK,EAAE;AAAA,IAC5B;AAEA,UAAM,KAAK,aAAa;AACxB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,mBAAmB,KAAK,OAAO,KAAK,SAAS,cAAc,CAAC;AACvE,UAAM,KAAK,EAAE;AAEb,QAAI,KAAK,MAAM,SAAS,GAAG;AACzB,YAAM,KAAK,yBAAyB;AACpC,YAAM,KAAK,EAAE;AACb,iBAAW,QAAQ,KAAK,OAAO;AAE7B,YAAI,KAAK,aAAa,KAAK,UAAU,SAAS,GAAG;AAC/C,gBAAM,MAAM,KAAK,UAAU,CAAC;AAC5B,gBAAM,KAAK,OAAO,KAAK,IAAI,eAAU,KAAK,EAAE,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;AAAA,QACxG,OAAO;AACL,gBAAM,KAAK,OAAO,KAAK,IAAI,eAAU,KAAK,EAAE,IAAI;AAAA,QAClD;AAAA,MACF;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAEA,QAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,YAAM,KAAK,2BAA2B;AACtC,YAAM,KAAK,EAAE;AACb,iBAAW,QAAQ,KAAK,SAAS;AAC/B,cAAM,KAAK,OAAO,KAAK,IAAI,eAAU,KAAK,EAAE,IAAI;AAAA,MAClD;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAAA,EACF;AAEA,MAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,UAAM,KAAK,6BAAmB;AAC9B,UAAM,KAAK,EAAE;AACb,eAAW,SAAS,KAAK,WAAW;AAClC,YAAM,KAAK,KAAK,MAAM,IAAI,CAAC,MAAc,KAAK,CAAC,IAAI,EAAE,KAAK,UAAK,CAAC,EAAE;AAAA,IACpE;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,MAAI,KAAK,MAAM,WAAW,KAAK,KAAK,QAAQ,WAAW,KAAK,WAAW,WAAW,GAAG;AACnF,UAAM,KAAK,0CAAqC;AAAA,EAClD,OAAO;AACL,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,+BAA+B;AAC1C,UAAM,KAAK,SAAS;AACpB,UAAM,KAAK,mCAAmC;AAC9C,UAAM,KAAK,2CAA2C;AACtD,UAAM,KAAK,gDAAgD;AAC3D,UAAM,KAAK,KAAK;AAAA,EAClB;AAEA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,0BAA0B;AAErC,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAe,oBAAoB,SAAc,MAA6B;AAC5E,QAAM,QAAQ,QAAQ,IAAI;AAC1B,MAAI,CAAC,MAAO;AAEZ,QAAM,CAAC,OAAO,IAAI,IAAI,QAAQ,WAAW,MAAM,GAAG;AAClD,QAAM,cAAc,QAAQ;AAE5B,MAAI;AACF,UAAM,mBAAmB,MAAM;AAAA,MAC7B,gCAAgC,KAAK,IAAI,IAAI,WAAW,WAAW;AAAA,MACnE;AAAA,QACE,SAAS;AAAA,UACP,eAAe,UAAU,KAAK;AAAA,UAC9B,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAY,MAAM,iBAAiB,KAAK;AAC9C,UAAM,kBAAkB,SAAS;AAAA,MAAK,CAAC,MACrC,EAAE,KAAK,SAAS,0BAA0B;AAAA,IAC5C;AAEA,QAAI,iBAAiB;AACnB,YAAM;AAAA,QACJ,gCAAgC,KAAK,IAAI,IAAI,oBAAoB,gBAAgB,EAAE;AAAA,QACnF;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,eAAe,UAAU,KAAK;AAAA,YAC9B,gBAAgB;AAAA,YAChB,cAAc;AAAA,UAChB;AAAA,UACA,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,QAC/B;AAAA,MACF;AACA,cAAQ,IAAI,iCAA4B;AAAA,IAC1C,OAAO;AACL,YAAM;AAAA,QACJ,gCAAgC,KAAK,IAAI,IAAI,WAAW,WAAW;AAAA,QACnE;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,eAAe,UAAU,KAAK;AAAA,YAC9B,gBAAgB;AAAA,YAChB,cAAc;AAAA,UAChB;AAAA,UACA,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,QAC/B;AAAA,MACF;AACA,cAAQ,IAAI,2BAAsB;AAAA,IACpC;AAAA,EACF,SAAS,OAAY;AACnB,YAAQ,IAAI,yCAA+B,MAAM,OAAO;AAAA,EAC1D;AACF;AAEA,SAAS,mBAAwB;AAC/B,QAAM,YAAY,QAAQ,IAAI;AAC9B,MAAI,QAAQ,CAAC;AAEb,MAAI,WAAW;AACb,QAAI;AACF,YAAM,YAAY,aAAa,WAAW,OAAO;AACjD,cAAQ,KAAK,MAAM,SAAS;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ,IAAI,qBAAqB;AAAA,IAC5C,YAAY,QAAQ,IAAI,qBAAqB;AAAA,IAC7C,KAAK,QAAQ,IAAI,cAAc;AAAA,IAC/B,KAAK,QAAQ,IAAI,cAAc;AAAA,IAC/B,OAAO,QAAQ,IAAI,gBAAgB;AAAA,IACnC,UAAU,QAAQ,IAAI,mBAAmB;AAAA,IACzC,eAAgB,MAAc,YAAY,kBAAkB;AAAA,IAC5D,SAAU,MAAc,cAAc,MAAM,OAAO;AAAA,IACnD,UAAW,MAAc,cAAc;AAAA,EACzC;AACF;AAEA,eAAe,YAAY,SAAiB,QAA8B;AACxE,QAAM,QAAQ,MAAM,gBAAgB,SAAS,OAAO,UAAU,CAAC,CAAC;AAEhE,QAAM,aAAa,CAAC;AACpB,QAAM,WAAqB,CAAC;AAE5B,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAgB,UAAK,SAAS,IAAI;AACxC,UAAM,UAAU,MAAS,YAAS,UAAU,OAAO;AAEnD,QAAI;AACJ,QAAI,KAAK,SAAS,KAAK,KAAK,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,SAAS,MAAM,GAAG;AAClG,gBAAU,MAAM,yBAAyB,MAAM,OAAO;AAAA,IACxD,WAAW,KAAK,SAAS,KAAK,GAAG;AAC/B,gBAAU,MAAM,qBAAqB,MAAM,OAAO;AAAA,IACpD,OAAO;AACL;AAAA,IACF;AAEA,eAAW,KAAK,OAAO;AACvB,aAAS,KAAK,IAAI;AAAA,EACpB;AAEA,QAAM,WAAW,MAAM,aAAa,OAAO;AAC3C,QAAM,aAAa,MAAM,eAAe,OAAO;AAE/C,QAAM,WAAW,MAAM,eAAe,YAAY;AAAA,IAChD;AAAA,IACA,UAAU,YAAY;AAAA,IACtB,YAAY,WAAW,SAAS,IAAI,aAAa;AAAA,EACnD,CAAC;AAED,QAAM,eAAe,OAAO,WAAW,CAAC;AACxC,QAAM,QAAQ,WAAW,UAAU,UAAU,YAAY;AAEzD,SAAO;AACT;AAEA,eAAe,gBAAgB,SAAiB,QAAqC;AACnF,QAAM,QAAkB,CAAC;AACzB,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL;AAEA,iBAAe,KAAK,KAA4B;AAC9C,UAAM,UAAU,MAAS,WAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAE7D,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAgB,UAAK,KAAK,MAAM,IAAI;AAC1C,YAAM,eAAoB,cAAS,SAAS,QAAQ;AAEpD,UAAI,aAAa,cAAc,cAAc,GAAG;AAC9C;AAAA,MACF;AAEA,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,KAAK,QAAQ;AAAA,MACrB,WAAW,MAAM,OAAO,GAAG;AACzB,YAAI,aAAa,MAAM,IAAI,GAAG;AAC5B,gBAAM,KAAK,YAAY;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,OAAO;AAClB,SAAO;AACT;AAEA,SAAS,aAAa,UAAkB,UAA6B;AACnE,QAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAE9C,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,eAAe,OAAO;AACpC,QAAI,MAAM,KAAK,UAAU,GAAG;AAC1B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,SAAyB;AAC/C,QAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,SAAS,kBAAkB,EACnC,QAAQ,OAAO,OAAO,EACtB,QAAQ,qBAAqB,IAAI;AAEpC,SAAO,IAAI,OAAO,IAAI,QAAQ,GAAG;AACnC;AAEA,SAAS,aAAa,MAAuB;AAC3C,SAAO,wBAAwB,KAAK,IAAI;AAC1C;AAEA,eAAe,WAAW,SAAkC;AAC1D,QAAM,aAAkB,UAAK,SAAS,aAAa;AAEnD,MAAI,SAAiB,CAAC;AACtB,MAAI;AACF,UAAM,UAAU,MAAS,YAAS,YAAY,OAAO;AACrD,UAAM,OAAO,MAAM,OAAO,SAAS;AACnC,aAAS,KAAK,KAAK,OAAO;AAAA,EAC5B,QAAQ;AACN,aAAS,CAAC;AAAA,EACZ;AAGA,QAAM,YAAY,QAAQ,IAAI;AAC9B,MAAI,cAAc,aAAa,cAAc,QAAQ;AACnD,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO;AACT;AAEA,UAAU,EAAE,MAAM,CAAC,UAAU;AAC3B,UAAQ,MAAM,UAAU,MAAM,OAAO;AACrC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|