universal-ast-mapper 1.28.0 → 2.0.1

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.
@@ -0,0 +1,122 @@
1
+ import { execSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import crypto from "node:crypto";
5
+ // ─── State persistence ────────────────────────────────────────────────────────
6
+ const STATE_FILE = ".ast-map/incremental.json";
7
+ export function statePath(root) {
8
+ return path.join(root, STATE_FILE);
9
+ }
10
+ export function loadState(root) {
11
+ try {
12
+ const raw = fs.readFileSync(statePath(root), "utf8");
13
+ return JSON.parse(raw);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ export function saveState(root, hashes) {
20
+ const state = { hashes, lastRun: new Date().toISOString(), root };
21
+ const p = statePath(root);
22
+ fs.mkdirSync(path.dirname(p), { recursive: true });
23
+ fs.writeFileSync(p, JSON.stringify(state, null, 2), "utf8");
24
+ return state;
25
+ }
26
+ // ─── Hashing ──────────────────────────────────────────────────────────────────
27
+ export function hashFile(filePath) {
28
+ try {
29
+ return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex").slice(0, 16);
30
+ }
31
+ catch {
32
+ return "";
33
+ }
34
+ }
35
+ export function hashFiles(files, root) {
36
+ const hashes = {};
37
+ for (const abs of files) {
38
+ const rel = path.relative(root, abs).split(path.sep).join("/");
39
+ hashes[rel] = hashFile(abs);
40
+ }
41
+ return hashes;
42
+ }
43
+ // ─── Change detection ─────────────────────────────────────────────────────────
44
+ /** Compare current file set against saved state to find changed/deleted/unchanged files. */
45
+ export function detectChanges(files, root, state) {
46
+ if (!state) {
47
+ // No prior state → everything is "changed"
48
+ return { changed: files, deleted: [], unchanged: [] };
49
+ }
50
+ const currentHashes = hashFiles(files, root);
51
+ const changed = [];
52
+ const unchanged = [];
53
+ for (const [rel, hash] of Object.entries(currentHashes)) {
54
+ const abs = path.resolve(root, rel);
55
+ if (state.hashes[rel] === hash) {
56
+ unchanged.push(abs);
57
+ }
58
+ else {
59
+ changed.push(abs);
60
+ }
61
+ }
62
+ const currentRels = new Set(Object.keys(currentHashes));
63
+ const deleted = Object.keys(state.hashes)
64
+ .filter((rel) => !currentRels.has(rel));
65
+ return { changed, deleted, unchanged };
66
+ }
67
+ // ─── Git-based change detection ───────────────────────────────────────────────
68
+ /**
69
+ * Get files changed relative to a git ref (e.g. "HEAD", "main", "origin/main").
70
+ * Returns absolute paths of changed/added/modified files (excludes deleted).
71
+ */
72
+ export function gitChangedFiles(root, base = "HEAD") {
73
+ try {
74
+ const raw = execSync(`git diff --name-only --diff-filter=ACM ${base}`, {
75
+ cwd: root,
76
+ encoding: "utf8",
77
+ timeout: 10_000,
78
+ });
79
+ return raw
80
+ .trim()
81
+ .split("\n")
82
+ .filter(Boolean)
83
+ .map((rel) => path.resolve(root, rel))
84
+ .filter((abs) => fs.existsSync(abs));
85
+ }
86
+ catch {
87
+ return [];
88
+ }
89
+ }
90
+ /**
91
+ * Get files staged for commit (git index).
92
+ */
93
+ export function gitStagedFiles(root) {
94
+ try {
95
+ const raw = execSync("git diff --cached --name-only --diff-filter=ACM", {
96
+ cwd: root,
97
+ encoding: "utf8",
98
+ timeout: 10_000,
99
+ });
100
+ return raw
101
+ .trim()
102
+ .split("\n")
103
+ .filter(Boolean)
104
+ .map((rel) => path.resolve(root, rel))
105
+ .filter((abs) => fs.existsSync(abs));
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ }
111
+ /**
112
+ * Filter a file list to only those changed since `base` according to git.
113
+ * Falls back to returning all files if git is unavailable.
114
+ */
115
+ export function filterToGitChanged(allFiles, root, base = "HEAD") {
116
+ const changed = gitChangedFiles(root, base);
117
+ if (changed.length === 0)
118
+ return { files: allFiles, fromGit: false };
119
+ const changedSet = new Set(changed);
120
+ const filtered = allFiles.filter((f) => changedSet.has(f));
121
+ return { files: filtered.length > 0 ? filtered : allFiles, fromGit: filtered.length > 0 };
122
+ }