@abaigram/abaikit 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 +15 -2
- package/abai/identity.md +1 -0
- package/abai/learnabai.md +474 -0
- package/abai/use.md +1 -0
- package/abaigram.yaml +208 -0
- package/dist/abaify.d.ts +68 -0
- package/dist/abaify.d.ts.map +1 -0
- package/dist/abaify.js +310 -0
- package/dist/abaify.js.map +1 -0
- package/dist/abaigram.d.ts +18 -0
- package/dist/abaigram.d.ts.map +1 -0
- package/dist/abaigram.js +153 -0
- package/dist/abaigram.js.map +1 -0
- package/dist/app.d.ts +19 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +29 -0
- package/dist/app.js.map +1 -0
- package/dist/bin.d.ts +45 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +274 -0
- package/dist/bin.js.map +1 -0
- package/dist/check.d.ts +119 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +416 -0
- package/dist/check.js.map +1 -0
- package/dist/command.d.ts +41 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +179 -0
- package/dist/command.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +12 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +20 -0
- package/dist/io.js.map +1 -0
- package/dist/learnabai.d.ts +60 -0
- package/dist/learnabai.d.ts.map +1 -0
- package/dist/learnabai.js +197 -0
- package/dist/learnabai.js.map +1 -0
- package/dist/new.d.ts +26 -0
- package/dist/new.d.ts.map +1 -0
- package/dist/new.js +87 -0
- package/dist/new.js.map +1 -0
- package/dist/repo.d.ts +99 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +482 -0
- package/dist/repo.js.map +1 -0
- package/dist/run.d.ts +6 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +47 -0
- package/dist/run.js.map +1 -0
- package/dist/subject.d.ts +15 -0
- package/dist/subject.d.ts.map +1 -0
- package/dist/subject.js +53 -0
- package/dist/subject.js.map +1 -0
- package/dist/template-ts.d.ts +21 -0
- package/dist/template-ts.d.ts.map +1 -0
- package/dist/template-ts.js +322 -0
- package/dist/template-ts.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/examples/hello/abai/identity.md +1 -0
- package/examples/hello/abaigram.yaml +24 -0
- package/examples/hello/hello.ts +18 -0
- package/package.json +40 -5
package/dist/repo.js
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// The repository capabilities an abaify agent works through, and nothing
|
|
2
|
+
// else: it reads what it likes within bounds and changes a repository only
|
|
3
|
+
// by adding. A new file; text inserted before or after a unique anchor
|
|
4
|
+
// line, with the original bytes kept in order; a dependency added to
|
|
5
|
+
// package.json, which is re-serialized by a structured edit; the manifest
|
|
6
|
+
// regenerated by the program's own entry, replacing abaigram.yaml when
|
|
7
|
+
// there was one; the dependencies installed, which rewrites the lock file
|
|
8
|
+
// and node_modules. No source line the repository had is ever removed or
|
|
9
|
+
// reordered. Every change is recorded for the report and emitted as
|
|
10
|
+
// abaikit's own `repo.changed` event.
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
13
|
+
import { runCommand, subjectEnv } from "./command.js";
|
|
14
|
+
import { UsageError } from "./io.js";
|
|
15
|
+
import { packageSpecs } from "./learnabai.js";
|
|
16
|
+
import { shellQuote } from "./subject.js";
|
|
17
|
+
export const LIST_CAP = 400;
|
|
18
|
+
export const LIST_CAP_BYTES = 12_000;
|
|
19
|
+
export const READ_CAP_LINES = 400;
|
|
20
|
+
export const READ_CAP_BYTES = 12_000;
|
|
21
|
+
export const INSTALL_TIMEOUT_MS = 10 * 60_000;
|
|
22
|
+
export const MANIFEST_TIMEOUT_MS = 60_000;
|
|
23
|
+
/** Trees skipped everywhere. */
|
|
24
|
+
export const SKIPPED_EVERYWHERE = new Set([".git", "node_modules"]);
|
|
25
|
+
/** Trees skipped at the root: build output, vendored and generated sites (a `src/build/` is the program's own). */
|
|
26
|
+
export const SKIPPED_AT_ROOT = new Set([
|
|
27
|
+
"dist",
|
|
28
|
+
"build",
|
|
29
|
+
"third_party",
|
|
30
|
+
"vendor",
|
|
31
|
+
"website",
|
|
32
|
+
"docs",
|
|
33
|
+
"coverage",
|
|
34
|
+
".turbo",
|
|
35
|
+
".next",
|
|
36
|
+
"out",
|
|
37
|
+
]);
|
|
38
|
+
function str(params, name, required = true) {
|
|
39
|
+
const value = params[name];
|
|
40
|
+
if (typeof value === "string" && value !== "")
|
|
41
|
+
return value;
|
|
42
|
+
if (required)
|
|
43
|
+
throw new UsageError(`${name} is required`);
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
function text(params, name) {
|
|
47
|
+
const value = params[name];
|
|
48
|
+
if (typeof value !== "string")
|
|
49
|
+
throw new UsageError(`${name} is required (a string, empty allowed)`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function num(params, name) {
|
|
53
|
+
const value = params[name];
|
|
54
|
+
if (value === undefined || value === null)
|
|
55
|
+
return undefined;
|
|
56
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0)
|
|
57
|
+
throw new UsageError(`${name} must be a whole number`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
/** The repository root, an existing directory; and a path inside it, never outside. */
|
|
61
|
+
function repoRoot(ctx, params) {
|
|
62
|
+
const root = resolve(ctx.cwd, str(params, "repo"));
|
|
63
|
+
if (!existsSync(root) || !statSync(root).isDirectory())
|
|
64
|
+
throw new UsageError(`${root} is not a directory`);
|
|
65
|
+
return root;
|
|
66
|
+
}
|
|
67
|
+
/** The real path of the nearest existing ancestor plus the rest, so a symlink pointing out is seen. */
|
|
68
|
+
function realish(path) {
|
|
69
|
+
let existing = path;
|
|
70
|
+
const rest = [];
|
|
71
|
+
while (!existsSync(existing)) {
|
|
72
|
+
const parent = dirname(existing);
|
|
73
|
+
if (parent === existing)
|
|
74
|
+
break;
|
|
75
|
+
rest.unshift(existing.slice(parent.length + 1));
|
|
76
|
+
existing = parent;
|
|
77
|
+
}
|
|
78
|
+
return join(realpathSync(existing), ...rest);
|
|
79
|
+
}
|
|
80
|
+
function inside(root, path) {
|
|
81
|
+
const realRoot = realpathSync(root);
|
|
82
|
+
const full = realish(resolve(root, path));
|
|
83
|
+
if (full !== realRoot && !full.startsWith(realRoot + sep))
|
|
84
|
+
throw new UsageError(`${path} is outside the repository`);
|
|
85
|
+
return resolve(root, path);
|
|
86
|
+
}
|
|
87
|
+
function globToRegex(glob) {
|
|
88
|
+
let out = "";
|
|
89
|
+
for (let i = 0; i < glob.length; i++) {
|
|
90
|
+
const c = glob[i];
|
|
91
|
+
if (c === "*") {
|
|
92
|
+
if (glob[i + 1] === "*") {
|
|
93
|
+
out += ".*";
|
|
94
|
+
i += 1;
|
|
95
|
+
if (glob[i + 1] === "/")
|
|
96
|
+
i += 1;
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
out += "[^/]*";
|
|
100
|
+
}
|
|
101
|
+
else if (c === "?")
|
|
102
|
+
out += "[^/]";
|
|
103
|
+
else
|
|
104
|
+
out += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
export function ignoreRules(root) {
|
|
109
|
+
const file = join(root, ".gitignore");
|
|
110
|
+
if (!existsSync(file))
|
|
111
|
+
return [];
|
|
112
|
+
const rules = [];
|
|
113
|
+
for (const raw of readFileSync(file, "utf8").split("\n")) {
|
|
114
|
+
const line = raw.trim();
|
|
115
|
+
if (line === "" || line.startsWith("#") || line.startsWith("!"))
|
|
116
|
+
continue;
|
|
117
|
+
const dirOnly = line.endsWith("/");
|
|
118
|
+
let pattern = dirOnly ? line.slice(0, -1) : line;
|
|
119
|
+
const anchored = pattern.startsWith("/") || pattern.includes("/");
|
|
120
|
+
if (pattern.startsWith("/"))
|
|
121
|
+
pattern = pattern.slice(1);
|
|
122
|
+
rules.push({ regex: new RegExp(`^${globToRegex(pattern)}$`), dirOnly, anchored });
|
|
123
|
+
}
|
|
124
|
+
return rules;
|
|
125
|
+
}
|
|
126
|
+
function ignored(rules, relativePath, isDir) {
|
|
127
|
+
const name = relativePath.split("/").at(-1) ?? "";
|
|
128
|
+
return rules.some((rule) => {
|
|
129
|
+
if (rule.dirOnly && !isDir)
|
|
130
|
+
return false;
|
|
131
|
+
return rule.anchored ? rule.regex.test(relativePath) : rule.regex.test(name);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function skipped(rules, name, rel, depth) {
|
|
135
|
+
if (SKIPPED_EVERYWHERE.has(name))
|
|
136
|
+
return true;
|
|
137
|
+
if (depth === 0 && SKIPPED_AT_ROOT.has(name))
|
|
138
|
+
return true;
|
|
139
|
+
return ignored(rules, rel, true);
|
|
140
|
+
}
|
|
141
|
+
/** Every file under `dir` (relative paths), the skips applied, in sorted order. */
|
|
142
|
+
function walk(root, rules, dir, depth, visit) {
|
|
143
|
+
let entries;
|
|
144
|
+
try {
|
|
145
|
+
entries = readdirSync(dir).sort();
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (const name of entries) {
|
|
151
|
+
const full = join(dir, name);
|
|
152
|
+
const rel = relative(root, full).split(sep).join("/");
|
|
153
|
+
let isDir;
|
|
154
|
+
try {
|
|
155
|
+
isDir = statSync(full).isDirectory();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (isDir) {
|
|
161
|
+
if (!skipped(rules, name, rel, depth))
|
|
162
|
+
walk(root, rules, full, depth + 1, visit);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (!ignored(rules, rel, false))
|
|
166
|
+
visit(rel);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* `repo.list`: files under the root, skipping what no retrofit reads,
|
|
171
|
+
* bounded by entries and bytes, the bound said first. A glob narrows:
|
|
172
|
+
* `*.json` by name anywhere, `src/**` or `scripts/*.mjs` by path.
|
|
173
|
+
*/
|
|
174
|
+
export function listRepo(root, glob) {
|
|
175
|
+
const rules = ignoreRules(root);
|
|
176
|
+
const filter = glob ? new RegExp(`^${globToRegex(glob)}$`) : undefined;
|
|
177
|
+
const found = [];
|
|
178
|
+
let total = 0;
|
|
179
|
+
let bytes = 0;
|
|
180
|
+
walk(root, rules, root, 0, (rel) => {
|
|
181
|
+
if (filter && !filter.test(rel) && !filter.test(rel.split("/").at(-1) ?? ""))
|
|
182
|
+
return;
|
|
183
|
+
total += 1;
|
|
184
|
+
if (found.length >= LIST_CAP || bytes + rel.length + 1 > LIST_CAP_BYTES)
|
|
185
|
+
return;
|
|
186
|
+
found.push(rel);
|
|
187
|
+
bytes += rel.length + 1;
|
|
188
|
+
});
|
|
189
|
+
const truncated = total - found.length;
|
|
190
|
+
const head = truncated > 0
|
|
191
|
+
? `[${found.length} of ${total} files listed; ${truncated} more: narrow with glob, such as src/** or *.ts]\n`
|
|
192
|
+
: "";
|
|
193
|
+
return { count: found.length, total, listing: head + found.join("\n"), truncated };
|
|
194
|
+
}
|
|
195
|
+
/** The shape of a repository for a brief: the files at the root, and each directory with its file count. */
|
|
196
|
+
export function summarizeRepo(root) {
|
|
197
|
+
const rules = ignoreRules(root);
|
|
198
|
+
const files = [];
|
|
199
|
+
const dirs = [];
|
|
200
|
+
let total = 0;
|
|
201
|
+
for (const name of readdirSync(root).sort()) {
|
|
202
|
+
const full = join(root, name);
|
|
203
|
+
let isDir;
|
|
204
|
+
try {
|
|
205
|
+
isDir = statSync(full).isDirectory();
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (isDir) {
|
|
211
|
+
if (skipped(rules, name, name, 0))
|
|
212
|
+
continue;
|
|
213
|
+
let n = 0;
|
|
214
|
+
walk(root, rules, full, 1, () => {
|
|
215
|
+
n += 1;
|
|
216
|
+
});
|
|
217
|
+
dirs.push({ name, files: n });
|
|
218
|
+
total += n;
|
|
219
|
+
}
|
|
220
|
+
else if (!ignored(rules, name, false)) {
|
|
221
|
+
files.push(name);
|
|
222
|
+
total += 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const lines = [...files, ...dirs.map((d) => `${d.name}/ (${d.files} file${d.files === 1 ? "" : "s"})`)];
|
|
226
|
+
return { total, summary: lines.join("\n") };
|
|
227
|
+
}
|
|
228
|
+
/** `repo.read`: a window of a file from line `start` (1-based), at most `READ_CAP_LINES` lines and `READ_CAP_BYTES` bytes. */
|
|
229
|
+
export function readRepo(root, path, start = 1, lines = READ_CAP_LINES) {
|
|
230
|
+
const full = inside(root, path);
|
|
231
|
+
if (!existsSync(full) || statSync(full).isDirectory())
|
|
232
|
+
throw new UsageError(`${path}: no such file`);
|
|
233
|
+
const all = readFileSync(full, "utf8").split("\n");
|
|
234
|
+
const count = Math.min(Math.max(1, lines), READ_CAP_LINES);
|
|
235
|
+
const from = Math.max(1, start);
|
|
236
|
+
const picked = [];
|
|
237
|
+
let bytes = 0;
|
|
238
|
+
for (const line of all.slice(from - 1, from - 1 + count)) {
|
|
239
|
+
if (picked.length > 0 && bytes + line.length + 1 > READ_CAP_BYTES)
|
|
240
|
+
break;
|
|
241
|
+
picked.push(line);
|
|
242
|
+
bytes += line.length + 1;
|
|
243
|
+
}
|
|
244
|
+
return { text: picked.join("\n"), total_lines: all.length, from, to: from - 1 + picked.length };
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// Adding
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
function record(ctx, change) {
|
|
250
|
+
const full = { ...change, at: new Date().toISOString() };
|
|
251
|
+
ctx.changes.push(full);
|
|
252
|
+
ctx.onChange?.(full);
|
|
253
|
+
ctx.app?.event("repo.changed").emit({ path: change.path, how: change.how });
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* `repo.write`: a new file, or one this run made and may now correct; a
|
|
257
|
+
* file the repository had is refused, since abaify adds and never replaces.
|
|
258
|
+
*/
|
|
259
|
+
export function writeRepo(ctx, root, path, content, why) {
|
|
260
|
+
const full = inside(root, path);
|
|
261
|
+
const own = ctx.created?.has(full) === true;
|
|
262
|
+
if (existsSync(full) && !own) {
|
|
263
|
+
throw new UsageError(`${path} exists already; repo.write makes new files only (repo.insert adds to an existing one)`);
|
|
264
|
+
}
|
|
265
|
+
const lines = content.split("\n").length;
|
|
266
|
+
if (ctx.dryRun) {
|
|
267
|
+
record(ctx, {
|
|
268
|
+
how: "write",
|
|
269
|
+
path,
|
|
270
|
+
why,
|
|
271
|
+
kind: "added",
|
|
272
|
+
detail: `dry run: would write ${content.length} characters`,
|
|
273
|
+
});
|
|
274
|
+
ctx.created?.add(full);
|
|
275
|
+
return { written: false, path };
|
|
276
|
+
}
|
|
277
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
278
|
+
writeFileSync(full, content);
|
|
279
|
+
ctx.created?.add(full);
|
|
280
|
+
record(ctx, {
|
|
281
|
+
how: "write",
|
|
282
|
+
path,
|
|
283
|
+
why,
|
|
284
|
+
kind: "added",
|
|
285
|
+
detail: own ? `${lines} lines (this run's own file, written again)` : `${lines} lines`,
|
|
286
|
+
});
|
|
287
|
+
return { written: true, path };
|
|
288
|
+
}
|
|
289
|
+
/** `repo.insert`: `text` before or after the one line equal to `anchor` (trimmed); the original bytes stay, in order. */
|
|
290
|
+
export function insertRepo(ctx, root, path, anchor, where, text, why) {
|
|
291
|
+
if (where !== "before" && where !== "after")
|
|
292
|
+
throw new UsageError(`where must be before or after, not ${where}`);
|
|
293
|
+
const full = inside(root, path);
|
|
294
|
+
if (!existsSync(full) || statSync(full).isDirectory())
|
|
295
|
+
throw new UsageError(`${path}: no such file (repo.write makes a new one)`);
|
|
296
|
+
const original = readFileSync(full, "utf8");
|
|
297
|
+
const lines = original.split("\n");
|
|
298
|
+
const wanted = anchor.trim();
|
|
299
|
+
if (wanted === "")
|
|
300
|
+
throw new UsageError("anchor must be a line of the file");
|
|
301
|
+
const hits = lines.map((l, i) => (l.trim() === wanted ? i : -1)).filter((i) => i >= 0);
|
|
302
|
+
if (hits.length === 0)
|
|
303
|
+
throw new UsageError(`${path}: no line reads ${JSON.stringify(wanted)}`);
|
|
304
|
+
if (hits.length > 1)
|
|
305
|
+
throw new UsageError(`${path}: ${hits.length} lines read ${JSON.stringify(wanted)} (lines ${hits.map((i) => i + 1).join(", ")}); the anchor must be unique`);
|
|
306
|
+
const at = hits[0] + (where === "after" ? 1 : 0);
|
|
307
|
+
const inserted = text.replace(/\n$/, "").split("\n");
|
|
308
|
+
if (ctx.dryRun) {
|
|
309
|
+
record(ctx, {
|
|
310
|
+
how: "insert",
|
|
311
|
+
path,
|
|
312
|
+
why,
|
|
313
|
+
kind: "added",
|
|
314
|
+
detail: `dry run: would insert ${inserted.length} lines ${where} line ${hits[0] + 1}`,
|
|
315
|
+
});
|
|
316
|
+
return { line: at + 1, path };
|
|
317
|
+
}
|
|
318
|
+
const before = lines.slice(0, at).join("\n");
|
|
319
|
+
const after = lines.slice(at).join("\n");
|
|
320
|
+
const updated = [...lines.slice(0, at), ...inserted, ...lines.slice(at)].join("\n");
|
|
321
|
+
// the additive guarantee, checked, not assumed: what was there is still there, in order, around the insert
|
|
322
|
+
if (!updated.startsWith(before) || !updated.endsWith(after)) {
|
|
323
|
+
throw new Error(`${path}: the insert would not have been additive; nothing was written`);
|
|
324
|
+
}
|
|
325
|
+
writeFileSync(full, updated);
|
|
326
|
+
record(ctx, {
|
|
327
|
+
how: "insert",
|
|
328
|
+
path,
|
|
329
|
+
why,
|
|
330
|
+
kind: "added",
|
|
331
|
+
detail: `${inserted.length} lines ${where} line ${hits[0] + 1}`,
|
|
332
|
+
});
|
|
333
|
+
return { line: at + 1, path };
|
|
334
|
+
}
|
|
335
|
+
/** The indentation a JSON file uses, so an edit keeps its shape. */
|
|
336
|
+
function indentOf(text) {
|
|
337
|
+
const match = /^(\t+| +)"/m.exec(text);
|
|
338
|
+
return match ? match[1] : " ";
|
|
339
|
+
}
|
|
340
|
+
/** `repo.add_dependency`: one entry added to `dependencies`, in sorted place; an existing one is left as it is and said. */
|
|
341
|
+
export function addDependency(ctx, root, name, spec, why) {
|
|
342
|
+
const file = inside(root, "package.json");
|
|
343
|
+
if (!existsSync(file))
|
|
344
|
+
throw new UsageError("package.json: no such file; repo.add_dependency needs one");
|
|
345
|
+
const original = readFileSync(file, "utf8");
|
|
346
|
+
let pkg;
|
|
347
|
+
try {
|
|
348
|
+
pkg = JSON.parse(original);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
throw new UsageError(`package.json does not parse: ${error instanceof Error ? error.message : String(error)}`);
|
|
352
|
+
}
|
|
353
|
+
const known = packageSpecs().dependency;
|
|
354
|
+
const chosen = spec ??
|
|
355
|
+
(name === "@abaigram/abai-stub"
|
|
356
|
+
? known.abai
|
|
357
|
+
: name === "@abaigram/abai-embedded-harness"
|
|
358
|
+
? known.harness
|
|
359
|
+
: name === "@abaigram/abaikit"
|
|
360
|
+
? known.abaikit
|
|
361
|
+
: undefined);
|
|
362
|
+
if (chosen === undefined)
|
|
363
|
+
throw new UsageError(`${name} needs a spec; only @abaigram/abai-stub, @abaigram/abai-embedded-harness and @abaigram/abaikit have a default here`);
|
|
364
|
+
const deps = (pkg.dependencies ?? {});
|
|
365
|
+
if (deps[name] !== undefined)
|
|
366
|
+
return { spec: deps[name], added: false };
|
|
367
|
+
if (ctx.dryRun) {
|
|
368
|
+
record(ctx, {
|
|
369
|
+
how: "add_dependency",
|
|
370
|
+
path: "package.json",
|
|
371
|
+
why,
|
|
372
|
+
kind: "rewritten",
|
|
373
|
+
detail: `dry run: would add ${name}: ${chosen}`,
|
|
374
|
+
});
|
|
375
|
+
return { spec: chosen, added: false };
|
|
376
|
+
}
|
|
377
|
+
const sorted = {};
|
|
378
|
+
for (const key of [...Object.keys(deps), name].sort())
|
|
379
|
+
sorted[key] = key === name ? chosen : deps[key];
|
|
380
|
+
pkg.dependencies = sorted;
|
|
381
|
+
const eol = original.includes("\r\n") ? "\r\n" : "\n";
|
|
382
|
+
const serialized = JSON.stringify(pkg, null, indentOf(original)).split("\n").join(eol);
|
|
383
|
+
writeFileSync(file, `${serialized}${/\r?\n$/.test(original) ? eol : ""}`);
|
|
384
|
+
record(ctx, {
|
|
385
|
+
how: "add_dependency",
|
|
386
|
+
path: "package.json",
|
|
387
|
+
why,
|
|
388
|
+
kind: "rewritten",
|
|
389
|
+
detail: `${name}: ${chosen} (the file re-serialized, keys and indentation kept)`,
|
|
390
|
+
});
|
|
391
|
+
return { spec: chosen, added: true };
|
|
392
|
+
}
|
|
393
|
+
/** `repo.manifest`: the program's own `<entry> manifest`, written to abaigram.yaml, the one file never written by hand. */
|
|
394
|
+
export async function regenerateManifest(ctx, root, entry, why) {
|
|
395
|
+
const run = await runCommand(`${entry} manifest`, {
|
|
396
|
+
cwd: root,
|
|
397
|
+
env: subjectEnv(ctx.env),
|
|
398
|
+
timeoutMs: MANIFEST_TIMEOUT_MS,
|
|
399
|
+
});
|
|
400
|
+
if (run.code !== 0 || run.timedOut) {
|
|
401
|
+
const last = run.stderr.trim().split("\n").at(-1) ?? "";
|
|
402
|
+
throw new UsageError(`${entry} manifest ${run.timedOut ? "did not finish" : `exited ${run.code ?? run.signal}`}: ${last || "no output"}`);
|
|
403
|
+
}
|
|
404
|
+
if (!run.stdout.includes("abaigrammar:"))
|
|
405
|
+
throw new UsageError(`${entry} manifest printed no manifest: ${run.stdout.slice(0, 200)}`);
|
|
406
|
+
const target = join(root, "abaigram.yaml");
|
|
407
|
+
const previous = existsSync(target) ? readFileSync(target, "utf8").split("\n").length : undefined;
|
|
408
|
+
const lines = run.stdout.split("\n").length;
|
|
409
|
+
if (ctx.dryRun) {
|
|
410
|
+
record(ctx, {
|
|
411
|
+
how: "manifest",
|
|
412
|
+
path: "abaigram.yaml",
|
|
413
|
+
why,
|
|
414
|
+
kind: previous === undefined ? "added" : "rewritten",
|
|
415
|
+
detail: "dry run (the entry was run, read-only): would write what it prints",
|
|
416
|
+
});
|
|
417
|
+
return { written: false, lines, text: run.stdout };
|
|
418
|
+
}
|
|
419
|
+
writeFileSync(target, run.stdout);
|
|
420
|
+
record(ctx, {
|
|
421
|
+
how: "manifest",
|
|
422
|
+
path: "abaigram.yaml",
|
|
423
|
+
why,
|
|
424
|
+
kind: previous === undefined ? "added" : "rewritten",
|
|
425
|
+
detail: `${lines} lines, from ${entry} manifest${previous === undefined ? "" : ` (replaced a previous ${previous}-line file)`}`,
|
|
426
|
+
});
|
|
427
|
+
return { written: true, lines, text: run.stdout };
|
|
428
|
+
}
|
|
429
|
+
/** `repo.install`: `npm install` in the root, bounded; the one step that reaches the network and runs the repository's own install scripts. */
|
|
430
|
+
export async function installRepo(ctx, root, why) {
|
|
431
|
+
if (ctx.dryRun) {
|
|
432
|
+
record(ctx, {
|
|
433
|
+
how: "install",
|
|
434
|
+
path: "node_modules",
|
|
435
|
+
why,
|
|
436
|
+
kind: "rewritten",
|
|
437
|
+
detail: "dry run: would run npm install",
|
|
438
|
+
});
|
|
439
|
+
return { ok: true, output: "dry run" };
|
|
440
|
+
}
|
|
441
|
+
const run = await runCommand(`npm install ${shellQuote("--no-audit")} ${shellQuote("--no-fund")}`, {
|
|
442
|
+
cwd: root,
|
|
443
|
+
env: subjectEnv(ctx.env),
|
|
444
|
+
timeoutMs: INSTALL_TIMEOUT_MS,
|
|
445
|
+
});
|
|
446
|
+
const tail = `${run.stdout}\n${run.stderr}`.trim().split("\n").slice(-12).join("\n");
|
|
447
|
+
record(ctx, {
|
|
448
|
+
how: "install",
|
|
449
|
+
path: "node_modules",
|
|
450
|
+
why,
|
|
451
|
+
kind: "rewritten",
|
|
452
|
+
detail: run.code === 0 ? "npm install ok" : `npm install failed (${run.code ?? run.signal})`,
|
|
453
|
+
});
|
|
454
|
+
if (existsSync(join(root, "package-lock.json"))) {
|
|
455
|
+
record(ctx, {
|
|
456
|
+
how: "install",
|
|
457
|
+
path: "package-lock.json",
|
|
458
|
+
why,
|
|
459
|
+
kind: "rewritten",
|
|
460
|
+
detail: "written by npm install",
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (run.code !== 0)
|
|
464
|
+
throw new UsageError(`npm install failed: ${tail}`);
|
|
465
|
+
return { ok: true, output: tail };
|
|
466
|
+
}
|
|
467
|
+
/** The capabilities, bound to a context, in the shape abaikit's manifest declares them. */
|
|
468
|
+
export function repoHandlers(ctx) {
|
|
469
|
+
return {
|
|
470
|
+
"repo.list": (p) => {
|
|
471
|
+
const { count, total, listing } = listRepo(repoRoot(ctx, p), str(p, "glob", false) || undefined);
|
|
472
|
+
return { count, total, listing };
|
|
473
|
+
},
|
|
474
|
+
"repo.read": (p) => readRepo(repoRoot(ctx, p), str(p, "path"), num(p, "start") ?? 1, num(p, "lines") ?? READ_CAP_LINES),
|
|
475
|
+
"repo.write": (p) => writeRepo(ctx, repoRoot(ctx, p), str(p, "path"), text(p, "content"), str(p, "why")),
|
|
476
|
+
"repo.insert": (p) => insertRepo(ctx, repoRoot(ctx, p), str(p, "path"), str(p, "anchor"), str(p, "where"), text(p, "text"), str(p, "why")),
|
|
477
|
+
"repo.add_dependency": (p) => addDependency(ctx, repoRoot(ctx, p), str(p, "name"), str(p, "spec", false) || undefined, str(p, "why")),
|
|
478
|
+
"repo.manifest": (p) => regenerateManifest(ctx, repoRoot(ctx, p), str(p, "entry"), str(p, "why")),
|
|
479
|
+
"repo.install": (p) => installRepo(ctx, repoRoot(ctx, p), str(p, "why")),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
//# sourceMappingURL=repo.js.map
|
package/dist/repo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../src/repo.ts"],"names":[],"mappings":"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","sourcesContent":["// The repository capabilities an abaify agent works through, and nothing\n// else: it reads what it likes within bounds and changes a repository only\n// by adding. A new file; text inserted before or after a unique anchor\n// line, with the original bytes kept in order; a dependency added to\n// package.json, which is re-serialized by a structured edit; the manifest\n// regenerated by the program's own entry, replacing abaigram.yaml when\n// there was one; the dependencies installed, which rewrites the lock file\n// and node_modules. No source line the repository had is ever removed or\n// reordered. Every change is recorded for the report and emitted as\n// abaikit's own `repo.changed` event.\nimport { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport type { Abaigram } from \"@abaigram/abai-stub\";\nimport { runCommand, subjectEnv } from \"./command.ts\";\nimport { UsageError } from \"./io.ts\";\nimport { packageSpecs } from \"./learnabai.ts\";\nimport { shellQuote } from \"./subject.ts\";\n\nexport const LIST_CAP = 400;\nexport const LIST_CAP_BYTES = 12_000;\nexport const READ_CAP_LINES = 400;\nexport const READ_CAP_BYTES = 12_000;\nexport const INSTALL_TIMEOUT_MS = 10 * 60_000;\nexport const MANIFEST_TIMEOUT_MS = 60_000;\n\n/** Trees skipped everywhere. */\nexport const SKIPPED_EVERYWHERE = new Set([\".git\", \"node_modules\"]);\n/** Trees skipped at the root: build output, vendored and generated sites (a `src/build/` is the program's own). */\nexport const SKIPPED_AT_ROOT = new Set([\n\t\"dist\",\n\t\"build\",\n\t\"third_party\",\n\t\"vendor\",\n\t\"website\",\n\t\"docs\",\n\t\"coverage\",\n\t\".turbo\",\n\t\".next\",\n\t\"out\",\n]);\n\nexport interface Change {\n\thow: \"write\" | \"insert\" | \"add_dependency\" | \"manifest\" | \"install\";\n\tpath: string;\n\twhy: string;\n\tdetail: string;\n\t/** Added to the repository (a new file, inserted lines) or rewritten in place by a structured tool. */\n\tkind: \"added\" | \"rewritten\";\n\tat: string;\n}\n\nexport interface RepoContext {\n\t/** Where relative `repo` arguments resolve. */\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n\t/** The abaigram whose `repo.changed` event every change goes to; undefined under a plain unit test. */\n\tapp?: Abaigram;\n\t/** Nothing is written; the write capabilities say what they would do. */\n\tdryRun?: boolean;\n\tchanges: Change[];\n\t/** Told of each change as it happens, for a person watching. */\n\tonChange?: (change: Change) => void;\n\t/** Files this run created, which it may write again: nothing the repository had. */\n\tcreated?: Set<string>;\n}\n\nfunction str(params: Record<string, unknown>, name: string, required = true): string {\n\tconst value = params[name];\n\tif (typeof value === \"string\" && value !== \"\") return value;\n\tif (required) throw new UsageError(`${name} is required`);\n\treturn \"\";\n}\n\nfunction text(params: Record<string, unknown>, name: string): string {\n\tconst value = params[name];\n\tif (typeof value !== \"string\") throw new UsageError(`${name} is required (a string, empty allowed)`);\n\treturn value;\n}\n\nfunction num(params: Record<string, unknown>, name: string): number | undefined {\n\tconst value = params[name];\n\tif (value === undefined || value === null) return undefined;\n\tif (typeof value !== \"number\" || !Number.isInteger(value) || value < 0)\n\t\tthrow new UsageError(`${name} must be a whole number`);\n\treturn value;\n}\n\n/** The repository root, an existing directory; and a path inside it, never outside. */\nfunction repoRoot(ctx: RepoContext, params: Record<string, unknown>): string {\n\tconst root = resolve(ctx.cwd, str(params, \"repo\"));\n\tif (!existsSync(root) || !statSync(root).isDirectory()) throw new UsageError(`${root} is not a directory`);\n\treturn root;\n}\n\n/** The real path of the nearest existing ancestor plus the rest, so a symlink pointing out is seen. */\nfunction realish(path: string): string {\n\tlet existing = path;\n\tconst rest: string[] = [];\n\twhile (!existsSync(existing)) {\n\t\tconst parent = dirname(existing);\n\t\tif (parent === existing) break;\n\t\trest.unshift(existing.slice(parent.length + 1));\n\t\texisting = parent;\n\t}\n\treturn join(realpathSync(existing), ...rest);\n}\n\nfunction inside(root: string, path: string): string {\n\tconst realRoot = realpathSync(root);\n\tconst full = realish(resolve(root, path));\n\tif (full !== realRoot && !full.startsWith(realRoot + sep)) throw new UsageError(`${path} is outside the repository`);\n\treturn resolve(root, path);\n}\n\n// ---------------------------------------------------------------------------\n// .gitignore, enough of it: names, anchored paths, directory rules, `*` and `?`;\n// no negations, no character classes (they are escaped, so never match)\n// ---------------------------------------------------------------------------\n\ninterface IgnoreRule {\n\tregex: RegExp;\n\tdirOnly: boolean;\n\tanchored: boolean;\n}\n\nfunction globToRegex(glob: string): string {\n\tlet out = \"\";\n\tfor (let i = 0; i < glob.length; i++) {\n\t\tconst c = glob[i];\n\t\tif (c === \"*\") {\n\t\t\tif (glob[i + 1] === \"*\") {\n\t\t\t\tout += \".*\";\n\t\t\t\ti += 1;\n\t\t\t\tif (glob[i + 1] === \"/\") i += 1;\n\t\t\t} else out += \"[^/]*\";\n\t\t} else if (c === \"?\") out += \"[^/]\";\n\t\telse out += c.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t}\n\treturn out;\n}\n\nexport function ignoreRules(root: string): IgnoreRule[] {\n\tconst file = join(root, \".gitignore\");\n\tif (!existsSync(file)) return [];\n\tconst rules: IgnoreRule[] = [];\n\tfor (const raw of readFileSync(file, \"utf8\").split(\"\\n\")) {\n\t\tconst line = raw.trim();\n\t\tif (line === \"\" || line.startsWith(\"#\") || line.startsWith(\"!\")) continue;\n\t\tconst dirOnly = line.endsWith(\"/\");\n\t\tlet pattern = dirOnly ? line.slice(0, -1) : line;\n\t\tconst anchored = pattern.startsWith(\"/\") || pattern.includes(\"/\");\n\t\tif (pattern.startsWith(\"/\")) pattern = pattern.slice(1);\n\t\trules.push({ regex: new RegExp(`^${globToRegex(pattern)}$`), dirOnly, anchored });\n\t}\n\treturn rules;\n}\n\nfunction ignored(rules: IgnoreRule[], relativePath: string, isDir: boolean): boolean {\n\tconst name = relativePath.split(\"/\").at(-1) ?? \"\";\n\treturn rules.some((rule) => {\n\t\tif (rule.dirOnly && !isDir) return false;\n\t\treturn rule.anchored ? rule.regex.test(relativePath) : rule.regex.test(name);\n\t});\n}\n\nfunction skipped(rules: IgnoreRule[], name: string, rel: string, depth: number): boolean {\n\tif (SKIPPED_EVERYWHERE.has(name)) return true;\n\tif (depth === 0 && SKIPPED_AT_ROOT.has(name)) return true;\n\treturn ignored(rules, rel, true);\n}\n\n// ---------------------------------------------------------------------------\n// Reading\n// ---------------------------------------------------------------------------\n\nexport interface Listing {\n\t/** Entries in `listing`. */\n\tcount: number;\n\t/** Files that matched, listed or not. */\n\ttotal: number;\n\tlisting: string;\n\ttruncated: number;\n}\n\n/** Every file under `dir` (relative paths), the skips applied, in sorted order. */\nfunction walk(root: string, rules: IgnoreRule[], dir: string, depth: number, visit: (rel: string) => void): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(dir).sort();\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const name of entries) {\n\t\tconst full = join(dir, name);\n\t\tconst rel = relative(root, full).split(sep).join(\"/\");\n\t\tlet isDir: boolean;\n\t\ttry {\n\t\t\tisDir = statSync(full).isDirectory();\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isDir) {\n\t\t\tif (!skipped(rules, name, rel, depth)) walk(root, rules, full, depth + 1, visit);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!ignored(rules, rel, false)) visit(rel);\n\t}\n}\n\n/**\n * `repo.list`: files under the root, skipping what no retrofit reads,\n * bounded by entries and bytes, the bound said first. A glob narrows:\n * `*.json` by name anywhere, `src/**` or `scripts/*.mjs` by path.\n */\nexport function listRepo(root: string, glob?: string): Listing {\n\tconst rules = ignoreRules(root);\n\tconst filter = glob ? new RegExp(`^${globToRegex(glob)}$`) : undefined;\n\tconst found: string[] = [];\n\tlet total = 0;\n\tlet bytes = 0;\n\twalk(root, rules, root, 0, (rel) => {\n\t\tif (filter && !filter.test(rel) && !filter.test(rel.split(\"/\").at(-1) ?? \"\")) return;\n\t\ttotal += 1;\n\t\tif (found.length >= LIST_CAP || bytes + rel.length + 1 > LIST_CAP_BYTES) return;\n\t\tfound.push(rel);\n\t\tbytes += rel.length + 1;\n\t});\n\tconst truncated = total - found.length;\n\tconst head =\n\t\ttruncated > 0\n\t\t\t? `[${found.length} of ${total} files listed; ${truncated} more: narrow with glob, such as src/** or *.ts]\\n`\n\t\t\t: \"\";\n\treturn { count: found.length, total, listing: head + found.join(\"\\n\"), truncated };\n}\n\n/** The shape of a repository for a brief: the files at the root, and each directory with its file count. */\nexport function summarizeRepo(root: string): { total: number; summary: string } {\n\tconst rules = ignoreRules(root);\n\tconst files: string[] = [];\n\tconst dirs: Array<{ name: string; files: number }> = [];\n\tlet total = 0;\n\tfor (const name of readdirSync(root).sort()) {\n\t\tconst full = join(root, name);\n\t\tlet isDir: boolean;\n\t\ttry {\n\t\t\tisDir = statSync(full).isDirectory();\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isDir) {\n\t\t\tif (skipped(rules, name, name, 0)) continue;\n\t\t\tlet n = 0;\n\t\t\twalk(root, rules, full, 1, () => {\n\t\t\t\tn += 1;\n\t\t\t});\n\t\t\tdirs.push({ name, files: n });\n\t\t\ttotal += n;\n\t\t} else if (!ignored(rules, name, false)) {\n\t\t\tfiles.push(name);\n\t\t\ttotal += 1;\n\t\t}\n\t}\n\tconst lines = [...files, ...dirs.map((d) => `${d.name}/ (${d.files} file${d.files === 1 ? \"\" : \"s\"})`)];\n\treturn { total, summary: lines.join(\"\\n\") };\n}\n\n/** `repo.read`: a window of a file from line `start` (1-based), at most `READ_CAP_LINES` lines and `READ_CAP_BYTES` bytes. */\nexport function readRepo(\n\troot: string,\n\tpath: string,\n\tstart = 1,\n\tlines = READ_CAP_LINES,\n): { text: string; total_lines: number; from: number; to: number } {\n\tconst full = inside(root, path);\n\tif (!existsSync(full) || statSync(full).isDirectory()) throw new UsageError(`${path}: no such file`);\n\tconst all = readFileSync(full, \"utf8\").split(\"\\n\");\n\tconst count = Math.min(Math.max(1, lines), READ_CAP_LINES);\n\tconst from = Math.max(1, start);\n\tconst picked: string[] = [];\n\tlet bytes = 0;\n\tfor (const line of all.slice(from - 1, from - 1 + count)) {\n\t\tif (picked.length > 0 && bytes + line.length + 1 > READ_CAP_BYTES) break;\n\t\tpicked.push(line);\n\t\tbytes += line.length + 1;\n\t}\n\treturn { text: picked.join(\"\\n\"), total_lines: all.length, from, to: from - 1 + picked.length };\n}\n\n// ---------------------------------------------------------------------------\n// Adding\n// ---------------------------------------------------------------------------\n\nfunction record(ctx: RepoContext, change: Omit<Change, \"at\">): void {\n\tconst full = { ...change, at: new Date().toISOString() };\n\tctx.changes.push(full);\n\tctx.onChange?.(full);\n\tctx.app?.event(\"repo.changed\").emit({ path: change.path, how: change.how });\n}\n\n/**\n * `repo.write`: a new file, or one this run made and may now correct; a\n * file the repository had is refused, since abaify adds and never replaces.\n */\nexport function writeRepo(\n\tctx: RepoContext,\n\troot: string,\n\tpath: string,\n\tcontent: string,\n\twhy: string,\n): { written: boolean; path: string } {\n\tconst full = inside(root, path);\n\tconst own = ctx.created?.has(full) === true;\n\tif (existsSync(full) && !own) {\n\t\tthrow new UsageError(\n\t\t\t`${path} exists already; repo.write makes new files only (repo.insert adds to an existing one)`,\n\t\t);\n\t}\n\tconst lines = content.split(\"\\n\").length;\n\tif (ctx.dryRun) {\n\t\trecord(ctx, {\n\t\t\thow: \"write\",\n\t\t\tpath,\n\t\t\twhy,\n\t\t\tkind: \"added\",\n\t\t\tdetail: `dry run: would write ${content.length} characters`,\n\t\t});\n\t\tctx.created?.add(full);\n\t\treturn { written: false, path };\n\t}\n\tmkdirSync(dirname(full), { recursive: true });\n\twriteFileSync(full, content);\n\tctx.created?.add(full);\n\trecord(ctx, {\n\t\thow: \"write\",\n\t\tpath,\n\t\twhy,\n\t\tkind: \"added\",\n\t\tdetail: own ? `${lines} lines (this run's own file, written again)` : `${lines} lines`,\n\t});\n\treturn { written: true, path };\n}\n\n/** `repo.insert`: `text` before or after the one line equal to `anchor` (trimmed); the original bytes stay, in order. */\nexport function insertRepo(\n\tctx: RepoContext,\n\troot: string,\n\tpath: string,\n\tanchor: string,\n\twhere: string,\n\ttext: string,\n\twhy: string,\n): { line: number; path: string } {\n\tif (where !== \"before\" && where !== \"after\") throw new UsageError(`where must be before or after, not ${where}`);\n\tconst full = inside(root, path);\n\tif (!existsSync(full) || statSync(full).isDirectory())\n\t\tthrow new UsageError(`${path}: no such file (repo.write makes a new one)`);\n\tconst original = readFileSync(full, \"utf8\");\n\tconst lines = original.split(\"\\n\");\n\tconst wanted = anchor.trim();\n\tif (wanted === \"\") throw new UsageError(\"anchor must be a line of the file\");\n\tconst hits = lines.map((l, i) => (l.trim() === wanted ? i : -1)).filter((i) => i >= 0);\n\tif (hits.length === 0) throw new UsageError(`${path}: no line reads ${JSON.stringify(wanted)}`);\n\tif (hits.length > 1)\n\t\tthrow new UsageError(\n\t\t\t`${path}: ${hits.length} lines read ${JSON.stringify(wanted)} (lines ${hits.map((i) => i + 1).join(\", \")}); the anchor must be unique`,\n\t\t);\n\tconst at = hits[0] + (where === \"after\" ? 1 : 0);\n\tconst inserted = text.replace(/\\n$/, \"\").split(\"\\n\");\n\tif (ctx.dryRun) {\n\t\trecord(ctx, {\n\t\t\thow: \"insert\",\n\t\t\tpath,\n\t\t\twhy,\n\t\t\tkind: \"added\",\n\t\t\tdetail: `dry run: would insert ${inserted.length} lines ${where} line ${hits[0] + 1}`,\n\t\t});\n\t\treturn { line: at + 1, path };\n\t}\n\tconst before = lines.slice(0, at).join(\"\\n\");\n\tconst after = lines.slice(at).join(\"\\n\");\n\tconst updated = [...lines.slice(0, at), ...inserted, ...lines.slice(at)].join(\"\\n\");\n\t// the additive guarantee, checked, not assumed: what was there is still there, in order, around the insert\n\tif (!updated.startsWith(before) || !updated.endsWith(after)) {\n\t\tthrow new Error(`${path}: the insert would not have been additive; nothing was written`);\n\t}\n\twriteFileSync(full, updated);\n\trecord(ctx, {\n\t\thow: \"insert\",\n\t\tpath,\n\t\twhy,\n\t\tkind: \"added\",\n\t\tdetail: `${inserted.length} lines ${where} line ${hits[0] + 1}`,\n\t});\n\treturn { line: at + 1, path };\n}\n\n/** The indentation a JSON file uses, so an edit keeps its shape. */\nfunction indentOf(text: string): string {\n\tconst match = /^(\\t+| +)\"/m.exec(text);\n\treturn match ? match[1] : \" \";\n}\n\n/** `repo.add_dependency`: one entry added to `dependencies`, in sorted place; an existing one is left as it is and said. */\nexport function addDependency(\n\tctx: RepoContext,\n\troot: string,\n\tname: string,\n\tspec: string | undefined,\n\twhy: string,\n): { spec: string; added: boolean } {\n\tconst file = inside(root, \"package.json\");\n\tif (!existsSync(file)) throw new UsageError(\"package.json: no such file; repo.add_dependency needs one\");\n\tconst original = readFileSync(file, \"utf8\");\n\tlet pkg: Record<string, unknown>;\n\ttry {\n\t\tpkg = JSON.parse(original) as Record<string, unknown>;\n\t} catch (error) {\n\t\tthrow new UsageError(`package.json does not parse: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tconst known = packageSpecs().dependency;\n\tconst chosen =\n\t\tspec ??\n\t\t(name === \"@abaigram/abai-stub\"\n\t\t\t? known.abai\n\t\t\t: name === \"@abaigram/abai-embedded-harness\"\n\t\t\t\t? known.harness\n\t\t\t\t: name === \"@abaigram/abaikit\"\n\t\t\t\t\t? known.abaikit\n\t\t\t\t\t: undefined);\n\tif (chosen === undefined)\n\t\tthrow new UsageError(\n\t\t\t`${name} needs a spec; only @abaigram/abai-stub, @abaigram/abai-embedded-harness and @abaigram/abaikit have a default here`,\n\t\t);\n\tconst deps = (pkg.dependencies ?? {}) as Record<string, string>;\n\tif (deps[name] !== undefined) return { spec: deps[name], added: false };\n\tif (ctx.dryRun) {\n\t\trecord(ctx, {\n\t\t\thow: \"add_dependency\",\n\t\t\tpath: \"package.json\",\n\t\t\twhy,\n\t\t\tkind: \"rewritten\",\n\t\t\tdetail: `dry run: would add ${name}: ${chosen}`,\n\t\t});\n\t\treturn { spec: chosen, added: false };\n\t}\n\tconst sorted: Record<string, string> = {};\n\tfor (const key of [...Object.keys(deps), name].sort()) sorted[key] = key === name ? chosen : deps[key];\n\tpkg.dependencies = sorted;\n\tconst eol = original.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n\tconst serialized = JSON.stringify(pkg, null, indentOf(original)).split(\"\\n\").join(eol);\n\twriteFileSync(file, `${serialized}${/\\r?\\n$/.test(original) ? eol : \"\"}`);\n\trecord(ctx, {\n\t\thow: \"add_dependency\",\n\t\tpath: \"package.json\",\n\t\twhy,\n\t\tkind: \"rewritten\",\n\t\tdetail: `${name}: ${chosen} (the file re-serialized, keys and indentation kept)`,\n\t});\n\treturn { spec: chosen, added: true };\n}\n\n/** `repo.manifest`: the program's own `<entry> manifest`, written to abaigram.yaml, the one file never written by hand. */\nexport async function regenerateManifest(\n\tctx: RepoContext,\n\troot: string,\n\tentry: string,\n\twhy: string,\n): Promise<{ written: boolean; lines: number; text: string }> {\n\tconst run = await runCommand(`${entry} manifest`, {\n\t\tcwd: root,\n\t\tenv: subjectEnv(ctx.env),\n\t\ttimeoutMs: MANIFEST_TIMEOUT_MS,\n\t});\n\tif (run.code !== 0 || run.timedOut) {\n\t\tconst last = run.stderr.trim().split(\"\\n\").at(-1) ?? \"\";\n\t\tthrow new UsageError(\n\t\t\t`${entry} manifest ${run.timedOut ? \"did not finish\" : `exited ${run.code ?? run.signal}`}: ${last || \"no output\"}`,\n\t\t);\n\t}\n\tif (!run.stdout.includes(\"abaigrammar:\"))\n\t\tthrow new UsageError(`${entry} manifest printed no manifest: ${run.stdout.slice(0, 200)}`);\n\tconst target = join(root, \"abaigram.yaml\");\n\tconst previous = existsSync(target) ? readFileSync(target, \"utf8\").split(\"\\n\").length : undefined;\n\tconst lines = run.stdout.split(\"\\n\").length;\n\tif (ctx.dryRun) {\n\t\trecord(ctx, {\n\t\t\thow: \"manifest\",\n\t\t\tpath: \"abaigram.yaml\",\n\t\t\twhy,\n\t\t\tkind: previous === undefined ? \"added\" : \"rewritten\",\n\t\t\tdetail: \"dry run (the entry was run, read-only): would write what it prints\",\n\t\t});\n\t\treturn { written: false, lines, text: run.stdout };\n\t}\n\twriteFileSync(target, run.stdout);\n\trecord(ctx, {\n\t\thow: \"manifest\",\n\t\tpath: \"abaigram.yaml\",\n\t\twhy,\n\t\tkind: previous === undefined ? \"added\" : \"rewritten\",\n\t\tdetail: `${lines} lines, from ${entry} manifest${previous === undefined ? \"\" : ` (replaced a previous ${previous}-line file)`}`,\n\t});\n\treturn { written: true, lines, text: run.stdout };\n}\n\n/** `repo.install`: `npm install` in the root, bounded; the one step that reaches the network and runs the repository's own install scripts. */\nexport async function installRepo(\n\tctx: RepoContext,\n\troot: string,\n\twhy: string,\n): Promise<{ ok: boolean; output: string }> {\n\tif (ctx.dryRun) {\n\t\trecord(ctx, {\n\t\t\thow: \"install\",\n\t\t\tpath: \"node_modules\",\n\t\t\twhy,\n\t\t\tkind: \"rewritten\",\n\t\t\tdetail: \"dry run: would run npm install\",\n\t\t});\n\t\treturn { ok: true, output: \"dry run\" };\n\t}\n\tconst run = await runCommand(`npm install ${shellQuote(\"--no-audit\")} ${shellQuote(\"--no-fund\")}`, {\n\t\tcwd: root,\n\t\tenv: subjectEnv(ctx.env),\n\t\ttimeoutMs: INSTALL_TIMEOUT_MS,\n\t});\n\tconst tail = `${run.stdout}\\n${run.stderr}`.trim().split(\"\\n\").slice(-12).join(\"\\n\");\n\trecord(ctx, {\n\t\thow: \"install\",\n\t\tpath: \"node_modules\",\n\t\twhy,\n\t\tkind: \"rewritten\",\n\t\tdetail: run.code === 0 ? \"npm install ok\" : `npm install failed (${run.code ?? run.signal})`,\n\t});\n\tif (existsSync(join(root, \"package-lock.json\"))) {\n\t\trecord(ctx, {\n\t\t\thow: \"install\",\n\t\t\tpath: \"package-lock.json\",\n\t\t\twhy,\n\t\t\tkind: \"rewritten\",\n\t\t\tdetail: \"written by npm install\",\n\t\t});\n\t}\n\tif (run.code !== 0) throw new UsageError(`npm install failed: ${tail}`);\n\treturn { ok: true, output: tail };\n}\n\n/** The capabilities, bound to a context, in the shape abaikit's manifest declares them. */\nexport function repoHandlers(ctx: RepoContext): Record<string, (params: Record<string, unknown>) => unknown> {\n\treturn {\n\t\t\"repo.list\": (p) => {\n\t\t\tconst { count, total, listing } = listRepo(repoRoot(ctx, p), str(p, \"glob\", false) || undefined);\n\t\t\treturn { count, total, listing };\n\t\t},\n\t\t\"repo.read\": (p) =>\n\t\t\treadRepo(repoRoot(ctx, p), str(p, \"path\"), num(p, \"start\") ?? 1, num(p, \"lines\") ?? READ_CAP_LINES),\n\t\t\"repo.write\": (p) => writeRepo(ctx, repoRoot(ctx, p), str(p, \"path\"), text(p, \"content\"), str(p, \"why\")),\n\t\t\"repo.insert\": (p) =>\n\t\t\tinsertRepo(\n\t\t\t\tctx,\n\t\t\t\trepoRoot(ctx, p),\n\t\t\t\tstr(p, \"path\"),\n\t\t\t\tstr(p, \"anchor\"),\n\t\t\t\tstr(p, \"where\"),\n\t\t\t\ttext(p, \"text\"),\n\t\t\t\tstr(p, \"why\"),\n\t\t\t),\n\t\t\"repo.add_dependency\": (p) =>\n\t\t\taddDependency(ctx, repoRoot(ctx, p), str(p, \"name\"), str(p, \"spec\", false) || undefined, str(p, \"why\")),\n\t\t\"repo.manifest\": (p) => regenerateManifest(ctx, repoRoot(ctx, p), str(p, \"entry\"), str(p, \"why\")),\n\t\t\"repo.install\": (p) => installRepo(ctx, repoRoot(ctx, p), str(p, \"why\")),\n\t};\n}\n"]}
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,SAAS,CAAC;AAGrD,wBAAsB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCzG","sourcesContent":["// abaikit run: sidecar launch of a repository's program from outside the\n// repository. It is the program's own `<entry> run -- ARGS`, run by the\n// shell in the repository root with this terminal, so everything M8 built\n// (the launcher, the lifecycle channel, outside observation) serves it;\n// nothing is added here. The exit code is the entry's.\nimport { spawn } from \"node:child_process\";\nimport { constants as osConstants } from \"node:os\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type AbaikitIo, UsageError } from \"./io.ts\";\nimport { entryCommand, loadSubject } from \"./subject.ts\";\n\nexport async function runRun(parsed: Extract<ParsedArgs, { verb: \"run\" }>, io: AbaikitIo): Promise<number> {\n\tconst subject = loadSubject(parsed.repo, io.cwd);\n\t// run is sidecar launch (4.4): a program that declares no sidecar mode would refuse at its entry; say it here\n\tif (!subject.manifest.modes.includes(\"sidecar\")) {\n\t\tthrow new UsageError(\n\t\t\t`run is sidecar launch, and ${subject.manifest.name} declares ${subject.manifest.modes.join(\", \")} only`,\n\t\t);\n\t}\n\tconst command = entryCommand(subject.manifest, \"run\", \"--\", ...parsed.args);\n\t// POSIX sh is the contract for the entry's command line; a compound entry (`cd x && node ...`) keeps the\n\t// shell alive, so a forwarded signal reaches the shell, not what it runs: keep the entry one command\n\tconst child = spawn(\"sh\", [\"-c\", command], { cwd: subject.root, stdio: \"inherit\", env: io.env });\n\t// whoever ends this process ends the launch behind it\n\tconst forward = (signal: NodeJS.Signals): void => {\n\t\tif (child.exitCode === null && child.signalCode === null) child.kill(signal);\n\t};\n\tconst forwarded: NodeJS.Signals[] = [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"];\n\tfor (const signal of forwarded) process.on(signal, forward);\n\ttry {\n\t\treturn await new Promise<number>((resolve) => {\n\t\t\tchild.once(\"error\", (error) => {\n\t\t\t\tio.err(`abaikit run: the shell could not be started: ${error.message}`);\n\t\t\t\tresolve(1);\n\t\t\t});\n\t\t\tchild.once(\"exit\", (code, signal) => {\n\t\t\t\tif (code !== null) resolve(code);\n\t\t\t\telse resolve(128 + (signal ? (osConstants.signals[signal] ?? 0) : 0));\n\t\t\t});\n\t\t});\n\t} finally {\n\t\tfor (const signal of forwarded) process.off(signal, forward);\n\t}\n}\n"]}
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// abaikit run: sidecar launch of a repository's program from outside the
|
|
2
|
+
// repository. It is the program's own `<entry> run -- ARGS`, run by the
|
|
3
|
+
// shell in the repository root with this terminal, so everything M8 built
|
|
4
|
+
// (the launcher, the lifecycle channel, outside observation) serves it;
|
|
5
|
+
// nothing is added here. The exit code is the entry's.
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { constants as osConstants } from "node:os";
|
|
8
|
+
import { UsageError } from "./io.js";
|
|
9
|
+
import { entryCommand, loadSubject } from "./subject.js";
|
|
10
|
+
export async function runRun(parsed, io) {
|
|
11
|
+
const subject = loadSubject(parsed.repo, io.cwd);
|
|
12
|
+
// run is sidecar launch (4.4): a program that declares no sidecar mode would refuse at its entry; say it here
|
|
13
|
+
if (!subject.manifest.modes.includes("sidecar")) {
|
|
14
|
+
throw new UsageError(`run is sidecar launch, and ${subject.manifest.name} declares ${subject.manifest.modes.join(", ")} only`);
|
|
15
|
+
}
|
|
16
|
+
const command = entryCommand(subject.manifest, "run", "--", ...parsed.args);
|
|
17
|
+
// POSIX sh is the contract for the entry's command line; a compound entry (`cd x && node ...`) keeps the
|
|
18
|
+
// shell alive, so a forwarded signal reaches the shell, not what it runs: keep the entry one command
|
|
19
|
+
const child = spawn("sh", ["-c", command], { cwd: subject.root, stdio: "inherit", env: io.env });
|
|
20
|
+
// whoever ends this process ends the launch behind it
|
|
21
|
+
const forward = (signal) => {
|
|
22
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
23
|
+
child.kill(signal);
|
|
24
|
+
};
|
|
25
|
+
const forwarded = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
26
|
+
for (const signal of forwarded)
|
|
27
|
+
process.on(signal, forward);
|
|
28
|
+
try {
|
|
29
|
+
return await new Promise((resolve) => {
|
|
30
|
+
child.once("error", (error) => {
|
|
31
|
+
io.err(`abaikit run: the shell could not be started: ${error.message}`);
|
|
32
|
+
resolve(1);
|
|
33
|
+
});
|
|
34
|
+
child.once("exit", (code, signal) => {
|
|
35
|
+
if (code !== null)
|
|
36
|
+
resolve(code);
|
|
37
|
+
else
|
|
38
|
+
resolve(128 + (signal ? (osConstants.signals[signal] ?? 0) : 0));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
for (const signal of forwarded)
|
|
44
|
+
process.off(signal, forward);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=run.js.map
|
package/dist/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AACxE,0EAA0E;AAC1E,wEAAwE;AACxE,uDAAuD;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAA4C,EAAE,EAAa,EAAmB;IAC1G,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IACjD,8GAA8G;IAC9G,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,UAAU,CACnB,8BAA8B,OAAO,CAAC,QAAQ,CAAC,IAAI,aAAa,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CACxG,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5E,yGAAyG;IACzG,qGAAqG;IACrG,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IACjG,sDAAsD;IACtD,MAAM,OAAO,GAAG,CAAC,MAAsB,EAAQ,EAAE,CAAC;QACjD,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAAA,CAC7E,CAAC;IACF,MAAM,SAAS,GAAqB,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACpE,KAAK,MAAM,MAAM,IAAI,SAAS;QAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5D,IAAI,CAAC;QACJ,OAAO,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;gBAC9B,EAAE,CAAC,GAAG,CAAC,gDAAgD,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBACxE,OAAO,CAAC,CAAC,CAAC,CAAC;YAAA,CACX,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;gBACpC,IAAI,IAAI,KAAK,IAAI;oBAAE,OAAO,CAAC,IAAI,CAAC,CAAC;;oBAC5B,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAAA,CACtE,CAAC,CAAC;QAAA,CACH,CAAC,CAAC;IACJ,CAAC;YAAS,CAAC;QACV,KAAK,MAAM,MAAM,IAAI,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC;AAAA,CACD","sourcesContent":["// abaikit run: sidecar launch of a repository's program from outside the\n// repository. It is the program's own `<entry> run -- ARGS`, run by the\n// shell in the repository root with this terminal, so everything M8 built\n// (the launcher, the lifecycle channel, outside observation) serves it;\n// nothing is added here. The exit code is the entry's.\nimport { spawn } from \"node:child_process\";\nimport { constants as osConstants } from \"node:os\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type AbaikitIo, UsageError } from \"./io.ts\";\nimport { entryCommand, loadSubject } from \"./subject.ts\";\n\nexport async function runRun(parsed: Extract<ParsedArgs, { verb: \"run\" }>, io: AbaikitIo): Promise<number> {\n\tconst subject = loadSubject(parsed.repo, io.cwd);\n\t// run is sidecar launch (4.4): a program that declares no sidecar mode would refuse at its entry; say it here\n\tif (!subject.manifest.modes.includes(\"sidecar\")) {\n\t\tthrow new UsageError(\n\t\t\t`run is sidecar launch, and ${subject.manifest.name} declares ${subject.manifest.modes.join(\", \")} only`,\n\t\t);\n\t}\n\tconst command = entryCommand(subject.manifest, \"run\", \"--\", ...parsed.args);\n\t// POSIX sh is the contract for the entry's command line; a compound entry (`cd x && node ...`) keeps the\n\t// shell alive, so a forwarded signal reaches the shell, not what it runs: keep the entry one command\n\tconst child = spawn(\"sh\", [\"-c\", command], { cwd: subject.root, stdio: \"inherit\", env: io.env });\n\t// whoever ends this process ends the launch behind it\n\tconst forward = (signal: NodeJS.Signals): void => {\n\t\tif (child.exitCode === null && child.signalCode === null) child.kill(signal);\n\t};\n\tconst forwarded: NodeJS.Signals[] = [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"];\n\tfor (const signal of forwarded) process.on(signal, forward);\n\ttry {\n\t\treturn await new Promise<number>((resolve) => {\n\t\t\tchild.once(\"error\", (error) => {\n\t\t\t\tio.err(`abaikit run: the shell could not be started: ${error.message}`);\n\t\t\t\tresolve(1);\n\t\t\t});\n\t\t\tchild.once(\"exit\", (code, signal) => {\n\t\t\t\tif (code !== null) resolve(code);\n\t\t\t\telse resolve(128 + (signal ? (osConstants.signals[signal] ?? 0) : 0));\n\t\t\t});\n\t\t});\n\t} finally {\n\t\tfor (const signal of forwarded) process.off(signal, forward);\n\t}\n}\n"]}
|