@getcoherent/cli 0.6.45 → 0.6.47

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,50 @@
1
+ // src/commands/fix-validation.ts
2
+ import { createRequire } from "module";
3
+ import { existsSync, readFileSync, writeFileSync } from "fs";
4
+ import { join } from "path";
5
+ var cachedTs = null;
6
+ var cachedProjectRoot = null;
7
+ function isValidTsx(code, projectRoot, ext = ".tsx") {
8
+ if (ext !== ".tsx" && ext !== ".ts") return true;
9
+ const pkgJson = join(projectRoot, "package.json");
10
+ if (!existsSync(pkgJson)) return true;
11
+ try {
12
+ if (!cachedTs || cachedProjectRoot !== projectRoot) {
13
+ const req = createRequire(pkgJson);
14
+ cachedTs = req("typescript");
15
+ cachedProjectRoot = projectRoot;
16
+ }
17
+ const sf = cachedTs.createSourceFile(
18
+ "check.tsx",
19
+ code,
20
+ cachedTs.ScriptTarget.Latest,
21
+ false,
22
+ cachedTs.ScriptKind.TSX
23
+ );
24
+ const diagnostics = sf.parseDiagnostics;
25
+ return !diagnostics || diagnostics.length === 0;
26
+ } catch {
27
+ return true;
28
+ }
29
+ }
30
+ function safeWrite(filePath, newContent, projectRoot, backups) {
31
+ if (!backups.has(filePath)) {
32
+ try {
33
+ backups.set(filePath, readFileSync(filePath, "utf-8"));
34
+ } catch {
35
+ }
36
+ }
37
+ const ext = filePath.slice(filePath.lastIndexOf("."));
38
+ writeFileSync(filePath, newContent, "utf-8");
39
+ if (!isValidTsx(newContent, projectRoot, ext)) {
40
+ const original = backups.get(filePath);
41
+ if (original) writeFileSync(filePath, original, "utf-8");
42
+ return { ok: false };
43
+ }
44
+ return { ok: true };
45
+ }
46
+
47
+ export {
48
+ isValidTsx,
49
+ safeWrite
50
+ };
@@ -0,0 +1,260 @@
1
+ import {
2
+ safeWrite
3
+ } from "./chunk-U6M76BKY.js";
4
+
5
+ // src/utils/tsc-autofix.ts
6
+ import { existsSync, readFileSync } from "fs";
7
+ import { execSync } from "child_process";
8
+ import { resolve } from "path";
9
+
10
+ // src/utils/tsc-error-parser.ts
11
+ var ERROR_RE = /^(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)$/;
12
+ var RELATED_LOCATION_RE = /^(.+?)\((\d+),(\d+)\):\s/;
13
+ function parseTscOutput(output) {
14
+ const lines = output.split("\n");
15
+ const errors = [];
16
+ const seen = /* @__PURE__ */ new Set();
17
+ let current = null;
18
+ for (const raw of lines) {
19
+ const trimmed = raw.trimStart();
20
+ const match = trimmed.match(ERROR_RE);
21
+ if (match) {
22
+ const [, file, lineStr, colStr, code, msg] = match;
23
+ const isRelated = raw.startsWith(" ");
24
+ if (isRelated && current) {
25
+ const cleanFile = file.trim();
26
+ if (!current.relatedFiles.includes(cleanFile)) {
27
+ current.relatedFiles.push(cleanFile);
28
+ }
29
+ } else {
30
+ flushCurrent();
31
+ current = {
32
+ file: file.trim(),
33
+ line: parseInt(lineStr, 10),
34
+ col: parseInt(colStr, 10),
35
+ code,
36
+ message: msg,
37
+ relatedFiles: []
38
+ };
39
+ }
40
+ } else if (current && raw.startsWith(" ") && raw.trim().length > 0) {
41
+ const locMatch = trimmed.match(RELATED_LOCATION_RE);
42
+ if (locMatch) {
43
+ const cleanFile = locMatch[1].trim();
44
+ if (!current.relatedFiles.includes(cleanFile)) {
45
+ current.relatedFiles.push(cleanFile);
46
+ }
47
+ } else {
48
+ current.message += "\n" + raw.trim();
49
+ }
50
+ }
51
+ }
52
+ flushCurrent();
53
+ return errors;
54
+ function flushCurrent() {
55
+ if (!current) return;
56
+ const key = `${current.file}:${current.line}:${current.code}`;
57
+ if (!seen.has(key)) {
58
+ seen.add(key);
59
+ errors.push(current);
60
+ }
61
+ current = null;
62
+ }
63
+ }
64
+
65
+ // src/utils/tsc-autofix.ts
66
+ function runTscCheck(projectRoot, timeout = 3e4) {
67
+ const tsconfigPath = resolve(projectRoot, "tsconfig.json");
68
+ if (!existsSync(tsconfigPath)) return [];
69
+ try {
70
+ execSync("npx tsc --noEmit 2>&1", {
71
+ cwd: projectRoot,
72
+ timeout,
73
+ encoding: "utf-8"
74
+ });
75
+ return [];
76
+ } catch (err) {
77
+ if (err && typeof err === "object" && "killed" in err && err.killed) {
78
+ console.log(" \u26A0 TypeScript check timed out \u2014 skipping");
79
+ return [];
80
+ }
81
+ const e = err;
82
+ const output = (e.stdout || "") + (e.stderr || "");
83
+ return parseTscOutput(output);
84
+ }
85
+ }
86
+ function levenshtein(a, b) {
87
+ const m = a.length, n = b.length;
88
+ const dp = Array.from(
89
+ { length: m + 1 },
90
+ (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
91
+ );
92
+ for (let i = 1; i <= m; i++)
93
+ for (let j = 1; j <= n; j++)
94
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
95
+ return dp[m][n];
96
+ }
97
+ function maxLevenshtein(fieldName) {
98
+ return Math.max(1, Math.floor(fieldName.length * 0.4));
99
+ }
100
+ function escapeRegExp(s) {
101
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
102
+ }
103
+ var MISSING_PROP_RE = /Property '(\w+)' is missing in type '\{([^}]*)\}'/;
104
+ var UNION_RE = /Type 'string' is not assignable to type '((?:"[^"]+"\s*\|\s*)*"[^"]+")'/;
105
+ var MISSING_REQUIRED_RE = /Property '(\w+)' is missing in type .* but required/;
106
+ function extractFieldsFromCode(code, line) {
107
+ const lines = code.split("\n");
108
+ const searchRange = lines.slice(Math.max(0, line - 3), line + 3).join(" ");
109
+ const fieldMatches = searchRange.match(/(\w+)\s*:/g);
110
+ if (!fieldMatches) return [];
111
+ return fieldMatches.map((m) => m.replace(/\s*:$/, ""));
112
+ }
113
+ function fixFieldRename(code, error, errorLine) {
114
+ const match = error.message.match(MISSING_PROP_RE);
115
+ const expectedField = match?.[1] ?? error.message.match(/Property '(\w+)' is missing/)?.[1];
116
+ if (!expectedField) return null;
117
+ let typeFields;
118
+ if (match?.[2]) {
119
+ typeFields = match[2].split(";").map((f) => f.trim().split(":")[0]?.trim()).filter(Boolean);
120
+ } else {
121
+ typeFields = extractFieldsFromCode(code, errorLine ?? error.line);
122
+ }
123
+ let bestMatch = null;
124
+ let bestDist = Infinity;
125
+ for (const field of typeFields) {
126
+ if (field === expectedField) continue;
127
+ if (field.includes(expectedField) || expectedField.includes(field)) {
128
+ bestMatch = field;
129
+ bestDist = 0;
130
+ break;
131
+ }
132
+ const dist = levenshtein(field.toLowerCase(), expectedField.toLowerCase());
133
+ if (dist <= maxLevenshtein(expectedField) && dist < bestDist) {
134
+ bestDist = dist;
135
+ bestMatch = field;
136
+ }
137
+ }
138
+ if (!bestMatch) return null;
139
+ const targetLine = errorLine ?? error.line;
140
+ const lines = code.split("\n");
141
+ const targetIdx = targetLine - 1;
142
+ const windowStart = Math.max(0, targetIdx - 5);
143
+ const windowEnd = Math.min(lines.length, targetIdx + 6);
144
+ const fieldRe = new RegExp(`(\\b)${escapeRegExp(bestMatch)}(\\s*:)`, "g");
145
+ for (let i = windowStart; i < windowEnd; i++) {
146
+ if (fieldRe.test(lines[i])) {
147
+ lines[i] = lines[i].replace(fieldRe, `$1${expectedField}$2`);
148
+ }
149
+ fieldRe.lastIndex = 0;
150
+ }
151
+ const newCode = lines.join("\n");
152
+ if (newCode === code) return null;
153
+ return { code: newCode, field: `${bestMatch} \u2192 ${expectedField}` };
154
+ }
155
+ function fixUnionType(code, error) {
156
+ const match = error.message.match(UNION_RE);
157
+ if (!match) return null;
158
+ const variants = match[1].match(/"([^"]+)"/g)?.map((v) => v.replace(/"/g, ""));
159
+ if (!variants || variants.length === 0) return null;
160
+ const lines = code.split("\n");
161
+ const errorLine = lines[error.line - 1];
162
+ if (!errorLine) return null;
163
+ for (const variant of variants) {
164
+ const caseInsensitiveRe = new RegExp(`['"]${escapeRegExp(variant)}['"]`, "i");
165
+ const exactRe = new RegExp(`['"]${escapeRegExp(variant)}['"]`);
166
+ if (caseInsensitiveRe.test(errorLine) && !exactRe.test(errorLine)) {
167
+ lines[error.line - 1] = errorLine.replace(caseInsensitiveRe, `'${variant}'`);
168
+ return { code: lines.join("\n"), fix: `union case: '${variant}'` };
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ function fixMissingEventHandler(code, error) {
174
+ const match = error.message.match(MISSING_REQUIRED_RE);
175
+ if (!match) return null;
176
+ const propName = match[1];
177
+ if (!propName.startsWith("on") || propName.length < 3) return null;
178
+ if (propName[2] !== propName[2].toUpperCase()) return null;
179
+ const lines = code.split("\n");
180
+ const errorLine = lines[error.line - 1];
181
+ if (!errorLine) return null;
182
+ const closingMatch = errorLine.match(/(\s*\/?>)/);
183
+ if (!closingMatch) return null;
184
+ const insertPos = errorLine.lastIndexOf(closingMatch[1]);
185
+ lines[error.line - 1] = errorLine.slice(0, insertPos) + ` ${propName}={() => {}}` + errorLine.slice(insertPos);
186
+ return { code: lines.join("\n"), prop: propName };
187
+ }
188
+ function deduplicateErrors(errors) {
189
+ const seen = /* @__PURE__ */ new Set();
190
+ return errors.filter((e) => {
191
+ const key = `${e.file}:${e.line}:${e.code}`;
192
+ if (seen.has(key)) return false;
193
+ seen.add(key);
194
+ return true;
195
+ });
196
+ }
197
+ async function applyDeterministicFixes(errors, projectRoot, backups) {
198
+ const deduped = deduplicateErrors(errors);
199
+ const fixed = [];
200
+ const remaining = [];
201
+ const fileErrors = /* @__PURE__ */ new Map();
202
+ for (const err of deduped) {
203
+ const list = fileErrors.get(err.file) || [];
204
+ list.push(err);
205
+ fileErrors.set(err.file, list);
206
+ }
207
+ for (const [file, errs] of fileErrors) {
208
+ const absPath = resolve(projectRoot, file);
209
+ let code;
210
+ try {
211
+ code = readFileSync(absPath, "utf-8");
212
+ } catch {
213
+ remaining.push(...errs);
214
+ continue;
215
+ }
216
+ let changed = false;
217
+ const fileRemaining = [];
218
+ for (const e of errs) {
219
+ const renameResult = fixFieldRename(code, e, e.line);
220
+ if (renameResult) {
221
+ code = renameResult.code;
222
+ changed = true;
223
+ continue;
224
+ }
225
+ const unionResult = fixUnionType(code, e);
226
+ if (unionResult) {
227
+ code = unionResult.code;
228
+ changed = true;
229
+ continue;
230
+ }
231
+ const handlerResult = fixMissingEventHandler(code, e);
232
+ if (handlerResult) {
233
+ code = handlerResult.code;
234
+ changed = true;
235
+ continue;
236
+ }
237
+ fileRemaining.push(e);
238
+ }
239
+ if (changed) {
240
+ const { ok } = safeWrite(absPath, code, projectRoot, backups);
241
+ if (ok) {
242
+ fixed.push(file);
243
+ remaining.push(...fileRemaining);
244
+ } else {
245
+ remaining.push(...errs);
246
+ }
247
+ } else {
248
+ remaining.push(...fileRemaining);
249
+ }
250
+ }
251
+ return { fixed, remaining };
252
+ }
253
+
254
+ export {
255
+ runTscCheck,
256
+ fixFieldRename,
257
+ fixUnionType,
258
+ fixMissingEventHandler,
259
+ applyDeterministicFixes
260
+ };