@illuminis/comprism 0.1.5 → 0.1.7
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/out/agent/command.js +4 -4
- package/out/agent/render.d.ts +24 -0
- package/out/agent/render.js +35 -3
- package/out/agent/session.d.ts +6 -1
- package/out/agent/session.js +17 -8
- package/out/commands/agents.js +3 -3
- package/out/commands/ask.js +8 -8
- package/out/commands/codemap.d.ts +1 -1
- package/out/commands/codemap.js +3 -3
- package/out/commands/commands-thin.js +4 -4
- package/out/commands/config.js +1 -1
- package/out/commands/cost.js +4 -4
- package/out/commands/decisionEngine.d.ts +1 -0
- package/out/commands/decisionEngine.js +66 -0
- package/out/commands/hooks.js +1 -1
- package/out/commands/install.d.ts +1 -1
- package/out/commands/install.js +3 -2
- package/out/commands/instructions.js +1 -1
- package/out/commands/integrations.js +4 -4
- package/out/commands/keys.js +6 -6
- package/out/commands/login.js +37 -21
- package/out/commands/permissions.js +2 -2
- package/out/commands/plugins.js +3 -3
- package/out/commands/privacy.js +2 -2
- package/out/commands/repl.js +107 -83
- package/out/commands/review.js +6 -6
- package/out/commands/settings.js +30 -123
- package/out/commands/skills.js +2 -2
- package/out/commands/unattended.js +6 -6
- package/out/commands/update.js +1 -1
- package/out/commands/welcome.js +20 -29
- package/out/commands/worktrees.d.ts +1 -1
- package/out/graph/sync.d.ts +1 -1
- package/out/graph/sync.js +6 -6
- package/out/lib/attach.js +4 -3
- package/out/lib/commandlist.d.ts +1 -1
- package/out/lib/commandlist.js +2 -2
- package/out/lib/config.d.ts +3 -2
- package/out/lib/config.js +7 -8
- package/out/lib/connection.js +1 -1
- package/out/lib/gateway.d.ts +5 -5
- package/out/lib/machine.js +1 -1
- package/out/lib/project-ops.d.ts +1 -1
- package/out/lib/project-ops.js +4 -4
- package/out/lib/queue.js +1 -1
- package/out/lib/readiness.d.ts +3 -30
- package/out/lib/readiness.js +13 -82
- package/out/lib/servicecommand.d.ts +14 -0
- package/out/lib/servicecommand.js +74 -0
- package/out/lib/sessions.js +3 -3
- package/out/lib/ui.d.ts +9 -6
- package/out/lib/ui.js +20 -26
- package/out/lib/voice.d.ts +2 -39
- package/out/lib/voice.js +7 -148
- package/out/lib/words.d.ts +20 -0
- package/out/lib/words.js +27 -0
- package/out/machine/codemap/build.d.ts +45 -0
- package/out/machine/codemap/build.js +91 -0
- package/out/machine/codemap/facts.d.ts +47 -0
- package/out/machine/codemap/facts.js +12 -0
- package/out/machine/codemap/files.d.ts +45 -0
- package/out/machine/codemap/files.js +207 -0
- package/out/machine/codemap/read-locales.d.ts +29 -0
- package/out/machine/codemap/read-locales.js +246 -0
- package/out/machine/codemap/read-python.d.ts +11 -0
- package/out/machine/codemap/read-python.js +116 -0
- package/out/machine/codemap/read-typescript.d.ts +16 -0
- package/out/machine/codemap/read-typescript.js +292 -0
- package/out/machine/executor/browser.d.ts +14 -0
- package/out/machine/executor/browser.js +270 -0
- package/out/machine/executor/diagnostics.d.ts +2 -0
- package/out/machine/executor/diagnostics.js +181 -0
- package/out/machine/executor/documents.d.ts +40 -0
- package/out/machine/executor/documents.js +170 -0
- package/out/machine/executor/files.d.ts +2 -0
- package/out/machine/executor/files.js +590 -0
- package/out/machine/executor/git.d.ts +48 -0
- package/out/machine/executor/git.js +145 -0
- package/out/machine/executor/hooks.d.ts +51 -0
- package/out/machine/executor/hooks.js +154 -0
- package/out/machine/executor/index.d.ts +45 -0
- package/out/machine/executor/index.js +367 -0
- package/out/machine/executor/notebook.d.ts +2 -0
- package/out/machine/executor/notebook.js +147 -0
- package/out/machine/executor/paths.d.ts +20 -0
- package/out/machine/executor/paths.js +154 -0
- package/out/machine/executor/sandbox.d.ts +40 -0
- package/out/machine/executor/sandbox.js +299 -0
- package/out/machine/executor/shell.d.ts +86 -0
- package/out/machine/executor/shell.js +582 -0
- package/out/machine/executor/toolservers.d.ts +20 -0
- package/out/machine/executor/toolservers.js +189 -0
- package/out/machine/executor/worktree.d.ts +9 -0
- package/out/machine/executor/worktree.js +119 -0
- package/out/machine/folder/project.d.ts +28 -0
- package/out/machine/folder/project.js +114 -0
- package/out/machine/runtime/home.d.ts +2 -0
- package/out/machine/runtime/home.js +48 -0
- package/out/machine/runtime/needs.d.ts +30 -0
- package/out/machine/runtime/needs.js +89 -0
- package/out/machine/runtime/self.d.ts +23 -0
- package/out/machine/runtime/self.js +124 -0
- package/out/machine/voice/record.d.ts +38 -0
- package/out/machine/voice/record.js +208 -0
- package/out/providers/index.js +2 -1
- package/out/thin.js +18 -10
- package/package.json +4 -4
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.looksLikeTranslations = looksLikeTranslations;
|
|
37
|
+
exports.languageOf = languageOf;
|
|
38
|
+
exports.scanKeys = scanKeys;
|
|
39
|
+
exports.readLocales = readLocales;
|
|
40
|
+
/**
|
|
41
|
+
* Read translation files into code map facts.
|
|
42
|
+
*
|
|
43
|
+
* ## Why these are in the map at all
|
|
44
|
+
*
|
|
45
|
+
* A label a person sees on screen is not written in the code. It is a key in
|
|
46
|
+
* the code and a sentence in ten language files, and the question people
|
|
47
|
+
* actually ask about it is one no code parser can answer: *where is this label,
|
|
48
|
+
* and which languages are missing it.* An agent asked to add a field to a form
|
|
49
|
+
* has to put the same line in ten files, and a map that indexes only Python and
|
|
50
|
+
* TypeScript sends it to search for them one at a time.
|
|
51
|
+
*
|
|
52
|
+
* ## One object per key, not one per key per file
|
|
53
|
+
*
|
|
54
|
+
* Ten language files times three thousand keys is thirty one thousand objects,
|
|
55
|
+
* and almost all of that is the same key repeated. Each file reports its own
|
|
56
|
+
* keys here, because facts are read per file and a file has to be re-readable
|
|
57
|
+
* on its own. The service then collapses them into ONE object per key carrying
|
|
58
|
+
* which languages have it and which do not, which is both far smaller and the
|
|
59
|
+
* answer somebody wanted.
|
|
60
|
+
*
|
|
61
|
+
* ## Line numbers are tracked by hand, and that is deliberate
|
|
62
|
+
*
|
|
63
|
+
* `JSON.parse` gives no positions, and "the key is in this file somewhere" is
|
|
64
|
+
* not a useful answer when the file is three thousand lines. So the text is
|
|
65
|
+
* scanned once, tracking the line of every key as it goes. It is a scanner, not
|
|
66
|
+
* a guess: the same file always produces the same lines.
|
|
67
|
+
*/
|
|
68
|
+
const fs = __importStar(require("fs"));
|
|
69
|
+
const path = __importStar(require("path"));
|
|
70
|
+
/** Folders whose JSON files are translations. */
|
|
71
|
+
const LOCALE_DIRS = new Set(["locales", "locale", "i18n", "lang", "translations"]);
|
|
72
|
+
/** A file named like a language: `en`, `en-US`, `pt_BR`. */
|
|
73
|
+
const LANGUAGE_NAME = /^[a-z]{2}([-_][A-Za-z]{2,4})?$/;
|
|
74
|
+
/** Is this JSON file a translation file?
|
|
75
|
+
*
|
|
76
|
+
* Both conditions, not either: a `data/en.json` of sample records is not a
|
|
77
|
+
* translation file, and a `locales/schema.json` is not one either. Indexing
|
|
78
|
+
* the wrong JSON would fill the map with things nobody asked about. */
|
|
79
|
+
function looksLikeTranslations(rel) {
|
|
80
|
+
if (!rel.endsWith(".json"))
|
|
81
|
+
return false;
|
|
82
|
+
const parts = rel.split("/");
|
|
83
|
+
const base = path.basename(rel, ".json");
|
|
84
|
+
const parent = parts.length > 1 ? parts[parts.length - 2] ?? "" : "";
|
|
85
|
+
return LOCALE_DIRS.has(parent.toLowerCase()) && LANGUAGE_NAME.test(base);
|
|
86
|
+
}
|
|
87
|
+
/** The language a translation file is for, from its own name. */
|
|
88
|
+
function languageOf(rel) {
|
|
89
|
+
return path.basename(rel, ".json");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Every leaf key in a translation file, with the line it sits on.
|
|
93
|
+
*
|
|
94
|
+
* A single pass over the text, tracking strings, escapes, nesting and lines. It
|
|
95
|
+
* reads the structure rather than the values: what a translation SAYS never
|
|
96
|
+
* leaves the machine, only that the key exists and where.
|
|
97
|
+
*/
|
|
98
|
+
function scanKeys(text) {
|
|
99
|
+
const out = [];
|
|
100
|
+
const stack = [];
|
|
101
|
+
let line = 1;
|
|
102
|
+
let i = 0;
|
|
103
|
+
/** The key whose value has not been seen yet. Until the value turns up we do
|
|
104
|
+
* not know whether this key is a group or a leaf. */
|
|
105
|
+
let pending = null;
|
|
106
|
+
let pendingLine = 0;
|
|
107
|
+
const at = (n) => text[n] ?? "";
|
|
108
|
+
/** Consume a string starting at the opening quote, counting any lines in it. */
|
|
109
|
+
const readString = () => {
|
|
110
|
+
i += 1;
|
|
111
|
+
let value = "";
|
|
112
|
+
while (i < text.length) {
|
|
113
|
+
const ch = at(i);
|
|
114
|
+
if (ch === "\\") {
|
|
115
|
+
i += 2;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (ch === '"') {
|
|
119
|
+
i += 1;
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
if (ch === "\n")
|
|
123
|
+
line += 1;
|
|
124
|
+
value += ch;
|
|
125
|
+
i += 1;
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
};
|
|
129
|
+
/** Consume an array, whatever is nested in it. Arrays hold no keys we want. */
|
|
130
|
+
const skipArray = () => {
|
|
131
|
+
let depth = 0;
|
|
132
|
+
while (i < text.length) {
|
|
133
|
+
const ch = at(i);
|
|
134
|
+
if (ch === '"') {
|
|
135
|
+
readString();
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ch === "\n")
|
|
139
|
+
line += 1;
|
|
140
|
+
if (ch === "[")
|
|
141
|
+
depth += 1;
|
|
142
|
+
if (ch === "]") {
|
|
143
|
+
depth -= 1;
|
|
144
|
+
i += 1;
|
|
145
|
+
if (depth === 0)
|
|
146
|
+
return;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
i += 1;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
while (i < text.length) {
|
|
153
|
+
const ch = at(i);
|
|
154
|
+
if (ch === "\n") {
|
|
155
|
+
line += 1;
|
|
156
|
+
i += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (ch === " " || ch === "\t" || ch === "\r" || ch === ",") {
|
|
160
|
+
i += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// A key is waiting, so whatever comes next is its VALUE. This is checked
|
|
164
|
+
// first, and that is the whole correctness of the scanner: a string value
|
|
165
|
+
// looks exactly like a key until you know which position you are in.
|
|
166
|
+
if (pending !== null) {
|
|
167
|
+
if (ch === "{") {
|
|
168
|
+
stack.push(pending);
|
|
169
|
+
pending = null;
|
|
170
|
+
i += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
out.push({ key: [...stack, pending].join("."), line: pendingLine });
|
|
174
|
+
pending = null;
|
|
175
|
+
if (ch === '"') {
|
|
176
|
+
readString();
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (ch === "[") {
|
|
180
|
+
skipArray();
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
while (i < text.length && !/[,}\]\s]/.test(at(i)))
|
|
184
|
+
i += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (ch === '"') {
|
|
188
|
+
const startLine = line;
|
|
189
|
+
const name = readString();
|
|
190
|
+
let j = i;
|
|
191
|
+
while (j < text.length && /\s/.test(at(j))) {
|
|
192
|
+
if (at(j) === "\n")
|
|
193
|
+
line += 1;
|
|
194
|
+
j += 1;
|
|
195
|
+
}
|
|
196
|
+
if (at(j) === ":") {
|
|
197
|
+
pending = name;
|
|
198
|
+
pendingLine = startLine;
|
|
199
|
+
i = j + 1;
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (ch === "}") {
|
|
204
|
+
stack.pop();
|
|
205
|
+
i += 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
i += 1;
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
function readOne(root, rel) {
|
|
213
|
+
const text = fs.readFileSync(path.join(root, rel), "utf8");
|
|
214
|
+
const language = languageOf(rel);
|
|
215
|
+
const objects = [{
|
|
216
|
+
id: rel, kind: "file", name: path.basename(rel), file: rel, line: 1,
|
|
217
|
+
lang: "translations", language,
|
|
218
|
+
lines: text.split("\n").length,
|
|
219
|
+
}];
|
|
220
|
+
for (const { key, line } of scanKeys(text)) {
|
|
221
|
+
objects.push({
|
|
222
|
+
// Unique per file, because facts are stored per file. The service
|
|
223
|
+
// collapses every file's copy of one key into a single object.
|
|
224
|
+
id: `i18n:${key}@${rel}`,
|
|
225
|
+
kind: "translation_key",
|
|
226
|
+
name: key,
|
|
227
|
+
file: rel,
|
|
228
|
+
line,
|
|
229
|
+
language,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return { objects, links: [] };
|
|
233
|
+
}
|
|
234
|
+
function readLocales(root, rels) {
|
|
235
|
+
const facts = {};
|
|
236
|
+
const failures = [];
|
|
237
|
+
for (const rel of rels) {
|
|
238
|
+
try {
|
|
239
|
+
facts[rel] = readOne(root, rel);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
failures.push({ file: rel, reason: err?.name || "Error" });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return { facts, failures };
|
|
246
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FactSet } from "./facts";
|
|
2
|
+
/** Which Python this machine has, or null. Asked once per process. */
|
|
3
|
+
export declare function findPython(): string | null;
|
|
4
|
+
export declare function readPython(root: string, rels: string[]): {
|
|
5
|
+
facts: Record<string, FactSet>;
|
|
6
|
+
failures: {
|
|
7
|
+
file: string;
|
|
8
|
+
reason: string;
|
|
9
|
+
}[];
|
|
10
|
+
available: boolean;
|
|
11
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findPython = findPython;
|
|
37
|
+
exports.readPython = readPython;
|
|
38
|
+
/**
|
|
39
|
+
* Run the Python reader and collect its facts.
|
|
40
|
+
*
|
|
41
|
+
* The parsing is done by Python itself, because the only reader that agrees
|
|
42
|
+
* with the language is the language's own. A regular expression reader would
|
|
43
|
+
* produce a map that disagrees with the code, and a map that disagrees with the
|
|
44
|
+
* code is worse than no map at all: the agent trusts it.
|
|
45
|
+
*
|
|
46
|
+
* A machine with no Python interpreter gets an honest answer rather than a
|
|
47
|
+
* guessed one. The map then says it covers no Python files, and the count is
|
|
48
|
+
* visible everywhere the map is described.
|
|
49
|
+
*/
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const os = __importStar(require("os"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const self_1 = require("../runtime/self");
|
|
55
|
+
/** The reader script, beside the built code in both a checkout and an install. */
|
|
56
|
+
function scriptPath() {
|
|
57
|
+
return (0, self_1.shippedFile)("read_python.py", path.resolve(__dirname, "..", "..", "..", "scripts", "read_python.py"));
|
|
58
|
+
}
|
|
59
|
+
let interpreter;
|
|
60
|
+
/** Which Python this machine has, or null. Asked once per process. */
|
|
61
|
+
function findPython() {
|
|
62
|
+
if (interpreter !== undefined)
|
|
63
|
+
return interpreter;
|
|
64
|
+
for (const candidate of ["python3", "python"]) {
|
|
65
|
+
try {
|
|
66
|
+
const probe = (0, child_process_1.spawnSync)(candidate, ["-c", "import ast,sys;print(sys.version_info[0])"], { encoding: "utf8", timeout: 10_000 });
|
|
67
|
+
if (probe.status === 0 && String(probe.stdout).trim() === "3") {
|
|
68
|
+
interpreter = candidate;
|
|
69
|
+
return interpreter;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* try the next one */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
interpreter = null;
|
|
77
|
+
return interpreter;
|
|
78
|
+
}
|
|
79
|
+
function readPython(root, rels) {
|
|
80
|
+
if (!rels.length)
|
|
81
|
+
return { facts: {}, failures: [], available: true };
|
|
82
|
+
const python = findPython();
|
|
83
|
+
const script = scriptPath();
|
|
84
|
+
if (!python || !fs.existsSync(script)) {
|
|
85
|
+
return { facts: {}, failures: [], available: false };
|
|
86
|
+
}
|
|
87
|
+
// The file list goes in a file, not on the command line. A project with three
|
|
88
|
+
// thousand Python files produces a command line longer than the operating
|
|
89
|
+
// system will accept, and the failure is a confusing one: the process simply
|
|
90
|
+
// does not start.
|
|
91
|
+
const listFile = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "comprism-map-")), "files.txt");
|
|
92
|
+
try {
|
|
93
|
+
fs.writeFileSync(listFile, `${rels.join("\n")}\n`, "utf8");
|
|
94
|
+
const out = (0, child_process_1.spawnSync)(python, [script, root, listFile], {
|
|
95
|
+
encoding: "utf8",
|
|
96
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
97
|
+
timeout: 300_000,
|
|
98
|
+
});
|
|
99
|
+
if (out.status !== 0 || !out.stdout) {
|
|
100
|
+
return { facts: {}, failures: [], available: false };
|
|
101
|
+
}
|
|
102
|
+
const parsed = JSON.parse(out.stdout);
|
|
103
|
+
return { facts: parsed.facts || {}, failures: parsed.failures || [], available: true };
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return { facts: {}, failures: [], available: false };
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
try {
|
|
110
|
+
fs.rmSync(path.dirname(listFile), { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* a temporary folder that will not delete is not worth failing a job over */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FactSet } from "./facts";
|
|
2
|
+
type Ts = any;
|
|
3
|
+
/** The TypeScript compiler, or null when this installation has none. */
|
|
4
|
+
export declare function loadCompiler(): Ts | null;
|
|
5
|
+
/** Read every named file. A file that cannot be parsed is REPORTED, never
|
|
6
|
+
* silently dropped: a map that quietly covers less than it claims is the one
|
|
7
|
+
* failure mode worth engineering against. */
|
|
8
|
+
export declare function readTypeScript(root: string, rels: string[]): {
|
|
9
|
+
facts: Record<string, FactSet>;
|
|
10
|
+
failures: {
|
|
11
|
+
file: string;
|
|
12
|
+
reason: string;
|
|
13
|
+
}[];
|
|
14
|
+
available: boolean;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadCompiler = loadCompiler;
|
|
37
|
+
exports.readTypeScript = readTypeScript;
|
|
38
|
+
/**
|
|
39
|
+
* Read TypeScript and JavaScript files into code map facts, using the
|
|
40
|
+
* TypeScript compiler's own parser.
|
|
41
|
+
*
|
|
42
|
+
* This guesses nothing. Every object and every link is taken from the syntax
|
|
43
|
+
* tree, so the same file always produces the same facts and two machines
|
|
44
|
+
* reading the same project agree. Nothing here calls a model, so it costs
|
|
45
|
+
* nothing and cannot drift.
|
|
46
|
+
*
|
|
47
|
+
* The compiler is loaded lazily and by name. A copy of the tool installed
|
|
48
|
+
* without it still works: the map simply says it covers no TypeScript, which is
|
|
49
|
+
* an honest answer, and it is a great deal better than a map built by regular
|
|
50
|
+
* expression that quietly disagrees with the code.
|
|
51
|
+
*/
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
let compiler;
|
|
55
|
+
/** The TypeScript compiler, or null when this installation has none. */
|
|
56
|
+
function loadCompiler() {
|
|
57
|
+
if (compiler !== undefined)
|
|
58
|
+
return compiler;
|
|
59
|
+
try {
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
61
|
+
compiler = require("typescript");
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
compiler = null;
|
|
65
|
+
}
|
|
66
|
+
return compiler;
|
|
67
|
+
}
|
|
68
|
+
/** The HTTP verbs a call on an API client uses. */
|
|
69
|
+
const HTTP_VERBS = new Set(["get", "post", "put", "patch", "delete", "head", "request"]);
|
|
70
|
+
/** Looks like an API path rather than an ordinary string.
|
|
71
|
+
*
|
|
72
|
+
* Scanning for anything that starts with a slash picks up image URLs, browser
|
|
73
|
+
* routes and CSS paths, and a map full of those is a map nobody trusts. */
|
|
74
|
+
function looksLikeEndpoint(value) {
|
|
75
|
+
if (!value.startsWith("/") || value.length < 2)
|
|
76
|
+
return false;
|
|
77
|
+
if (value.includes(" ") || value.includes("\n"))
|
|
78
|
+
return false;
|
|
79
|
+
if (/\.(png|jpg|jpeg|svg|css|ico|webp|woff2?|json|js|map|txt|pdf)$/i.test(value))
|
|
80
|
+
return false;
|
|
81
|
+
return /^\/[A-Za-z0-9_\-/{}$:.]+$/.test(value);
|
|
82
|
+
}
|
|
83
|
+
function resolveImport(root, fromFile, spec) {
|
|
84
|
+
if (!spec.startsWith("."))
|
|
85
|
+
return null;
|
|
86
|
+
const base = path.resolve(path.dirname(path.join(root, fromFile)), spec);
|
|
87
|
+
const candidates = [
|
|
88
|
+
base, `${base}.ts`, `${base}.tsx`, `${base}.js`, `${base}.jsx`,
|
|
89
|
+
path.join(base, "index.ts"), path.join(base, "index.tsx"),
|
|
90
|
+
path.join(base, "index.js"), path.join(base, "index.jsx"),
|
|
91
|
+
];
|
|
92
|
+
for (const candidate of candidates) {
|
|
93
|
+
try {
|
|
94
|
+
if (fs.statSync(candidate).isFile()) {
|
|
95
|
+
return path.relative(root, candidate).split(path.sep).join("/");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
/* not this one */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function readOne(ts, root, rel) {
|
|
105
|
+
const source = fs.readFileSync(path.join(root, rel), "utf8");
|
|
106
|
+
const kind = rel.endsWith(".tsx") || rel.endsWith(".jsx")
|
|
107
|
+
? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
108
|
+
const sf = ts.createSourceFile(rel, source, ts.ScriptTarget.Latest, true, kind);
|
|
109
|
+
const objects = [];
|
|
110
|
+
const links = [];
|
|
111
|
+
const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
112
|
+
objects.push({
|
|
113
|
+
id: rel, kind: "file", name: path.basename(rel), file: rel, line: 1,
|
|
114
|
+
lang: rel.endsWith("x") ? "tsx" : "ts",
|
|
115
|
+
lines: source.split("\n").length,
|
|
116
|
+
});
|
|
117
|
+
const owner = [rel];
|
|
118
|
+
/** The symbol a call or a rendered tag belongs to. Never empty: `rel` sits at
|
|
119
|
+
* the bottom of the stack and is only ever pushed onto. */
|
|
120
|
+
const holder = () => owner[owner.length - 1] ?? rel;
|
|
121
|
+
const declare = (name, node, objectKind, extra = {}) => {
|
|
122
|
+
const id = `${rel}::${name}`;
|
|
123
|
+
objects.push({ id, kind: objectKind, name, file: rel, line: lineOf(node), ...extra });
|
|
124
|
+
links.push({ from: rel, kind: "defines", to: id });
|
|
125
|
+
return id;
|
|
126
|
+
};
|
|
127
|
+
const exported = (node) => !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
128
|
+
const tagName = (node) => {
|
|
129
|
+
const tag = node.tagName;
|
|
130
|
+
if (ts.isIdentifier(tag))
|
|
131
|
+
return tag.text;
|
|
132
|
+
if (ts.isPropertyAccessExpression(tag))
|
|
133
|
+
return tag.getText();
|
|
134
|
+
return null;
|
|
135
|
+
};
|
|
136
|
+
const calleeName = (expr) => {
|
|
137
|
+
if (ts.isIdentifier(expr))
|
|
138
|
+
return { leaf: expr.text, full: expr.text };
|
|
139
|
+
if (ts.isPropertyAccessExpression(expr))
|
|
140
|
+
return { leaf: expr.name.text, full: expr.getText() };
|
|
141
|
+
return null;
|
|
142
|
+
};
|
|
143
|
+
/** The string value of a literal or a template, holes flattened. */
|
|
144
|
+
const pathText = (node) => {
|
|
145
|
+
if (!node)
|
|
146
|
+
return null;
|
|
147
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
148
|
+
return node.text;
|
|
149
|
+
if (ts.isTemplateExpression(node)) {
|
|
150
|
+
return node.head.text + node.templateSpans
|
|
151
|
+
.map((span) => `{}${span.literal.text}`).join("");
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
function visit(node) {
|
|
156
|
+
let pushed = false;
|
|
157
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
158
|
+
const spec = node.moduleSpecifier.text;
|
|
159
|
+
const resolved = resolveImport(root, rel, spec);
|
|
160
|
+
links.push({
|
|
161
|
+
from: rel, kind: "imports", line: lineOf(node),
|
|
162
|
+
...(resolved ? { to: resolved } : { to_name: spec }),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
166
|
+
const name = node.name.text;
|
|
167
|
+
const objectKind = /^[A-Z]/.test(name) && rel.endsWith("x") ? "component" : "function";
|
|
168
|
+
owner.push(declare(name, node, objectKind, { exported: exported(node) || undefined }));
|
|
169
|
+
pushed = true;
|
|
170
|
+
}
|
|
171
|
+
else if (ts.isClassDeclaration(node) && node.name) {
|
|
172
|
+
owner.push(declare(node.name.text, node, "class", { exported: exported(node) || undefined }));
|
|
173
|
+
pushed = true;
|
|
174
|
+
for (const base of node.heritageClauses || []) {
|
|
175
|
+
for (const t of base.types || []) {
|
|
176
|
+
const name = t.expression?.getText?.();
|
|
177
|
+
if (name)
|
|
178
|
+
links.push({ from: holder(), kind: "inherits", to_name: name, line: lineOf(node) });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (ts.isVariableStatement(node)) {
|
|
183
|
+
for (const decl of node.declarationList.declarations) {
|
|
184
|
+
if (!ts.isIdentifier(decl.name) || !decl.initializer)
|
|
185
|
+
continue;
|
|
186
|
+
const init = decl.initializer;
|
|
187
|
+
if (!ts.isArrowFunction(init) && !ts.isFunctionExpression(init))
|
|
188
|
+
continue;
|
|
189
|
+
const name = decl.name.text;
|
|
190
|
+
const objectKind = /^[A-Z]/.test(name) && rel.endsWith("x") ? "component" : "function";
|
|
191
|
+
const id = declare(name, decl, objectKind, { exported: exported(node) || undefined });
|
|
192
|
+
owner.push(id);
|
|
193
|
+
ts.forEachChild(init, visit);
|
|
194
|
+
owner.pop();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
198
|
+
const tag = tagName(node);
|
|
199
|
+
if (tag && /^[A-Z]/.test(tag)) {
|
|
200
|
+
links.push({ from: holder(), kind: "renders", to_name: tag, line: lineOf(node) });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (ts.isCallExpression(node)) {
|
|
204
|
+
const callee = calleeName(node.expression);
|
|
205
|
+
if (callee && !callee.leaf.startsWith("_")) {
|
|
206
|
+
links.push({
|
|
207
|
+
from: holder(), kind: "calls",
|
|
208
|
+
to_name: callee.leaf, full_name: callee.full, line: lineOf(node),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// An API call found by the CALL, never by the string. `api.get("/x")`,
|
|
212
|
+
// `client.post(...)`, `fetch(...)`. Looking for anything path shaped
|
|
213
|
+
// picks up image URLs and browser routes and the map stops being worth
|
|
214
|
+
// trusting.
|
|
215
|
+
const isFetch = callee && callee.full === "fetch";
|
|
216
|
+
const isVerb = callee && HTTP_VERBS.has(callee.leaf)
|
|
217
|
+
&& /api|client|http|request/i.test(callee.full);
|
|
218
|
+
if (isFetch || isVerb) {
|
|
219
|
+
const value = pathText(node.arguments[0]);
|
|
220
|
+
if (value && looksLikeEndpoint(value)) {
|
|
221
|
+
links.push({
|
|
222
|
+
from: holder(), kind: "calls_endpoint",
|
|
223
|
+
to_name: value, line: lineOf(node),
|
|
224
|
+
...(isVerb && callee ? { method: callee.leaf.toUpperCase() } : {}),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// A data fetching slice: { url: "/things" } or query: () => "/things".
|
|
230
|
+
if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) {
|
|
231
|
+
const key = node.name.text;
|
|
232
|
+
if (key === "url" || key === "query") {
|
|
233
|
+
let value = pathText(node.initializer);
|
|
234
|
+
if (!value && ts.isArrowFunction(node.initializer)
|
|
235
|
+
&& !ts.isBlock(node.initializer.body)) {
|
|
236
|
+
value = pathText(node.initializer.body);
|
|
237
|
+
}
|
|
238
|
+
if (value && looksLikeEndpoint(value)) {
|
|
239
|
+
links.push({
|
|
240
|
+
from: holder(), kind: "calls_endpoint",
|
|
241
|
+
to_name: value, line: lineOf(node),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (key === "path") {
|
|
246
|
+
const value = pathText(node.initializer);
|
|
247
|
+
if (value && value.startsWith("/")) {
|
|
248
|
+
links.push({
|
|
249
|
+
from: holder(), kind: "browser_route",
|
|
250
|
+
to_name: value, line: lineOf(node),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name)
|
|
256
|
+
&& node.name.text === "path" && node.initializer) {
|
|
257
|
+
const inner = ts.isJsxExpression(node.initializer)
|
|
258
|
+
? node.initializer.expression : node.initializer;
|
|
259
|
+
const value = pathText(inner);
|
|
260
|
+
if (value && value.startsWith("/")) {
|
|
261
|
+
links.push({
|
|
262
|
+
from: holder(), kind: "browser_route",
|
|
263
|
+
to_name: value, line: lineOf(node),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
ts.forEachChild(node, visit);
|
|
268
|
+
if (pushed)
|
|
269
|
+
owner.pop();
|
|
270
|
+
}
|
|
271
|
+
ts.forEachChild(sf, visit);
|
|
272
|
+
return { objects, links };
|
|
273
|
+
}
|
|
274
|
+
/** Read every named file. A file that cannot be parsed is REPORTED, never
|
|
275
|
+
* silently dropped: a map that quietly covers less than it claims is the one
|
|
276
|
+
* failure mode worth engineering against. */
|
|
277
|
+
function readTypeScript(root, rels) {
|
|
278
|
+
const ts = loadCompiler();
|
|
279
|
+
if (!ts)
|
|
280
|
+
return { facts: {}, failures: [], available: false };
|
|
281
|
+
const facts = {};
|
|
282
|
+
const failures = [];
|
|
283
|
+
for (const rel of rels) {
|
|
284
|
+
try {
|
|
285
|
+
facts[rel] = readOne(ts, root, rel);
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
failures.push({ file: rel, reason: err?.name || "Error" });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return { facts, failures, available: true };
|
|
292
|
+
}
|