@beignet/cli 0.0.50 → 0.0.52
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/CHANGELOG.md +32 -0
- package/README.md +64 -22
- package/dist/analysis/layers.d.ts.map +1 -1
- package/dist/analysis/layers.js +4 -2
- package/dist/analysis/layers.js.map +1 -1
- package/dist/analysis/port-wiring.d.ts.map +1 -1
- package/dist/analysis/port-wiring.js +2 -1
- package/dist/analysis/port-wiring.js.map +1 -1
- package/dist/analysis/source-files.d.ts +15 -0
- package/dist/analysis/source-files.d.ts.map +1 -0
- package/dist/analysis/source-files.js +52 -0
- package/dist/analysis/source-files.js.map +1 -0
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +21 -25
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map-changes.d.ts.map +1 -1
- package/dist/app-map-changes.js +5 -7
- package/dist/app-map-changes.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +33 -30
- package/dist/app-map.js.map +1 -1
- package/dist/check.d.ts +12 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +92 -25
- package/dist/check.js.map +1 -1
- package/dist/git-changes.d.ts +17 -1
- package/dist/git-changes.d.ts.map +1 -1
- package/dist/git-changes.js +118 -84
- package/dist/git-changes.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -6
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +436 -112
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +3 -6
- package/dist/lint.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +0 -2
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +23 -5
- package/dist/mcp.js.map +1 -1
- package/dist/operational-process.d.ts +1 -0
- package/dist/operational-process.d.ts.map +1 -1
- package/dist/operational-process.js.map +1 -1
- package/dist/operational-runner.js +1 -0
- package/dist/operational-runner.js.map +1 -1
- package/dist/outbox.d.ts +1 -0
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +58 -12
- package/dist/outbox.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +43 -15
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.js +5 -5
- package/dist/templates/db/mysql.d.ts.map +1 -1
- package/dist/templates/db/mysql.js +51 -22
- package/dist/templates/db/mysql.js.map +1 -1
- package/dist/templates/db/postgres.d.ts.map +1 -1
- package/dist/templates/db/postgres.js +60 -20
- package/dist/templates/db/postgres.js.map +1 -1
- package/dist/templates/db/sqlite.d.ts.map +1 -1
- package/dist/templates/db/sqlite.js +51 -23
- package/dist/templates/db/sqlite.js.map +1 -1
- package/dist/templates/shadcn.js +1 -1
- package/dist/templates/shadcn.js.map +1 -1
- package/dist/templates/shared.js +4 -4
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.d.ts.map +1 -1
- package/dist/templates/testing.js +7 -19
- package/dist/templates/testing.js.map +1 -1
- package/dist/test-discovery.d.ts +9 -0
- package/dist/test-discovery.d.ts.map +1 -0
- package/dist/test-discovery.js +40 -0
- package/dist/test-discovery.js.map +1 -0
- package/package.json +7 -5
- package/skills/app-structure/SKILL.md +37 -10
- package/src/analysis/layers.ts +4 -2
- package/src/analysis/port-wiring.ts +2 -1
- package/src/analysis/source-files.ts +61 -0
- package/src/analysis/workspace.ts +27 -31
- package/src/app-map-changes.ts +10 -8
- package/src/app-map.ts +43 -32
- package/src/check.ts +115 -25
- package/src/git-changes.ts +218 -86
- package/src/index.ts +53 -8
- package/src/inspect.ts +627 -146
- package/src/lint.ts +6 -5
- package/src/make.ts +0 -2
- package/src/mcp.ts +34 -11
- package/src/operational-process.ts +1 -0
- package/src/operational-runner.ts +1 -0
- package/src/outbox.ts +63 -12
- package/src/templates/agents.ts +43 -15
- package/src/templates/base.ts +5 -5
- package/src/templates/db/mysql.ts +51 -22
- package/src/templates/db/postgres.ts +60 -20
- package/src/templates/db/sqlite.ts +51 -23
- package/src/templates/shadcn.ts +1 -1
- package/src/templates/shared.ts +4 -4
- package/src/templates/testing.ts +12 -19
- package/src/test-discovery.ts +53 -0
package/src/git-changes.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { Stats } from "node:fs";
|
|
3
3
|
import { createReadStream } from "node:fs";
|
|
4
|
-
import { lstat, readlink } from "node:fs/promises";
|
|
4
|
+
import { lstat, readlink, realpath } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawnCommand } from "./db.js";
|
|
7
7
|
|
|
@@ -58,17 +58,35 @@ export type GitChangeSet = {
|
|
|
58
58
|
files: GitChangedFile[];
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
/** One reproducible snapshot of the repository state around an app check. */
|
|
62
|
+
export type GitSourceSnapshot =
|
|
63
|
+
| {
|
|
64
|
+
kind: "git";
|
|
65
|
+
repositoryRoot: string;
|
|
66
|
+
comparison: GitChangeComparison;
|
|
67
|
+
repositoryChangeHash: string;
|
|
68
|
+
}
|
|
69
|
+
| {
|
|
70
|
+
kind: "unavailable";
|
|
71
|
+
reason: "not-a-git-worktree";
|
|
72
|
+
};
|
|
73
|
+
|
|
61
74
|
export async function readGitChangeSet(options: {
|
|
62
75
|
cwd: string;
|
|
63
76
|
base?: string;
|
|
77
|
+
signal?: AbortSignal;
|
|
64
78
|
}): Promise<GitChangeSet> {
|
|
65
79
|
if (options.base !== undefined && options.base.length === 0) {
|
|
66
80
|
throw new Error("Git base must not be empty.");
|
|
67
81
|
}
|
|
68
82
|
const repositoryRoot = path.resolve(
|
|
69
|
-
await gitText(
|
|
83
|
+
await gitText(
|
|
84
|
+
options.cwd,
|
|
85
|
+
["rev-parse", "--show-toplevel"],
|
|
86
|
+
options.signal,
|
|
87
|
+
),
|
|
70
88
|
);
|
|
71
|
-
const head = await resolveHeadCommit(repositoryRoot);
|
|
89
|
+
const head = await resolveHeadCommit(repositoryRoot, options.signal);
|
|
72
90
|
|
|
73
91
|
let comparison: GitChangeComparison;
|
|
74
92
|
let comparisonCommit: string;
|
|
@@ -78,21 +96,22 @@ export async function readGitChangeSet(options: {
|
|
|
78
96
|
`Cannot compare --base ${JSON.stringify(options.base)} because this repository does not have a HEAD commit.`,
|
|
79
97
|
);
|
|
80
98
|
}
|
|
81
|
-
const baseCommit = await gitText(
|
|
82
|
-
|
|
83
|
-
"--verify",
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
const baseCommit = await gitText(
|
|
100
|
+
repositoryRoot,
|
|
101
|
+
["rev-parse", "--verify", "--end-of-options", `${options.base}^{commit}`],
|
|
102
|
+
options.signal,
|
|
103
|
+
).catch(() => {
|
|
104
|
+
options.signal?.throwIfAborted();
|
|
87
105
|
throw new Error(
|
|
88
106
|
`Could not resolve local Git base ${JSON.stringify(options.base)}. Fetch it before running Beignet; changed mapping never accesses the network.`,
|
|
89
107
|
);
|
|
90
108
|
});
|
|
91
|
-
const mergeBase = await gitText(
|
|
92
|
-
|
|
93
|
-
baseCommit,
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
const mergeBase = await gitText(
|
|
110
|
+
repositoryRoot,
|
|
111
|
+
["merge-base", baseCommit, head],
|
|
112
|
+
options.signal,
|
|
113
|
+
).catch(() => {
|
|
114
|
+
options.signal?.throwIfAborted();
|
|
96
115
|
throw new Error(
|
|
97
116
|
`Could not find a merge base between ${JSON.stringify(options.base)} and HEAD.`,
|
|
98
117
|
);
|
|
@@ -106,7 +125,8 @@ export async function readGitChangeSet(options: {
|
|
|
106
125
|
head,
|
|
107
126
|
};
|
|
108
127
|
} else {
|
|
109
|
-
comparisonCommit =
|
|
128
|
+
comparisonCommit =
|
|
129
|
+
head ?? (await emptyTreeObjectId(repositoryRoot, options.signal));
|
|
110
130
|
comparison = {
|
|
111
131
|
mode: "worktree",
|
|
112
132
|
comparisonCommit,
|
|
@@ -116,22 +136,23 @@ export async function readGitChangeSet(options: {
|
|
|
116
136
|
|
|
117
137
|
const committed =
|
|
118
138
|
comparison.mode === "base"
|
|
119
|
-
? await gitDiffOutput(
|
|
120
|
-
|
|
121
|
-
comparison.head,
|
|
122
|
-
|
|
139
|
+
? await gitDiffOutput(
|
|
140
|
+
repositoryRoot,
|
|
141
|
+
[comparison.mergeBase, comparison.head],
|
|
142
|
+
options.signal,
|
|
143
|
+
)
|
|
123
144
|
: "";
|
|
124
|
-
const staged = await gitDiffOutput(
|
|
125
|
-
|
|
126
|
-
head ?? comparisonCommit,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"--exclude-standard",
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
const staged = await gitDiffOutput(
|
|
146
|
+
repositoryRoot,
|
|
147
|
+
["--cached", head ?? comparisonCommit],
|
|
148
|
+
options.signal,
|
|
149
|
+
);
|
|
150
|
+
const unstaged = await gitDiffOutput(repositoryRoot, [], options.signal);
|
|
151
|
+
const untracked = await gitOutput(
|
|
152
|
+
repositoryRoot,
|
|
153
|
+
["ls-files", "--others", "--exclude-standard", "-z"],
|
|
154
|
+
options.signal,
|
|
155
|
+
);
|
|
135
156
|
const files = mergeChangedFiles(
|
|
136
157
|
parseNameStatus(committed),
|
|
137
158
|
parseNameStatus(staged),
|
|
@@ -142,12 +163,43 @@ export async function readGitChangeSet(options: {
|
|
|
142
163
|
return { repositoryRoot, comparison, files };
|
|
143
164
|
}
|
|
144
165
|
|
|
145
|
-
|
|
166
|
+
/** Capture Git-backed source state, or report that no worktree is available. */
|
|
167
|
+
export async function readGitSourceSnapshot(options: {
|
|
168
|
+
cwd: string;
|
|
169
|
+
signal?: AbortSignal;
|
|
170
|
+
}): Promise<GitSourceSnapshot> {
|
|
171
|
+
try {
|
|
172
|
+
const changeSet = await readGitChangeSet(options);
|
|
173
|
+
const repositoryChangeHash = await hashGitChanges(
|
|
174
|
+
changeSet,
|
|
175
|
+
changeSet.files,
|
|
176
|
+
options.signal,
|
|
177
|
+
);
|
|
178
|
+
options.signal?.throwIfAborted();
|
|
179
|
+
return {
|
|
180
|
+
kind: "git",
|
|
181
|
+
repositoryRoot: changeSet.repositoryRoot,
|
|
182
|
+
comparison: changeSet.comparison,
|
|
183
|
+
repositoryChangeHash,
|
|
184
|
+
};
|
|
185
|
+
} catch (error) {
|
|
186
|
+
options.signal?.throwIfAborted();
|
|
187
|
+
if (await hasGitMetadata(options.cwd, options.signal)) throw error;
|
|
188
|
+
options.signal?.throwIfAborted();
|
|
189
|
+
return { kind: "unavailable", reason: "not-a-git-worktree" };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function emptyTreeObjectId(
|
|
194
|
+
repositoryRoot: string,
|
|
195
|
+
signal?: AbortSignal,
|
|
196
|
+
): Promise<string> {
|
|
146
197
|
const objectFormat =
|
|
147
|
-
(await tryGitText(
|
|
148
|
-
|
|
149
|
-
"--show-object-format=storage",
|
|
150
|
-
|
|
198
|
+
(await tryGitText(
|
|
199
|
+
repositoryRoot,
|
|
200
|
+
["rev-parse", "--show-object-format=storage"],
|
|
201
|
+
signal,
|
|
202
|
+
)) ?? "sha1";
|
|
151
203
|
const objectId =
|
|
152
204
|
emptyTreeObjectIds[objectFormat as keyof typeof emptyTreeObjectIds];
|
|
153
205
|
if (!objectId) {
|
|
@@ -161,13 +213,18 @@ async function emptyTreeObjectId(repositoryRoot: string): Promise<string> {
|
|
|
161
213
|
export async function hashGitChanges(
|
|
162
214
|
changeSet: Pick<GitChangeSet, "comparison" | "files" | "repositoryRoot">,
|
|
163
215
|
files = changeSet.files,
|
|
216
|
+
signal?: AbortSignal,
|
|
164
217
|
): Promise<string> {
|
|
165
218
|
const hash = createHash("sha256");
|
|
166
|
-
const indexEntries = await readGitIndexEntries(
|
|
219
|
+
const indexEntries = await readGitIndexEntries(
|
|
220
|
+
changeSet.repositoryRoot,
|
|
221
|
+
signal,
|
|
222
|
+
);
|
|
167
223
|
hash.update(JSON.stringify(changeSet.comparison));
|
|
168
224
|
hash.update("\0");
|
|
169
225
|
|
|
170
226
|
for (const file of [...files].sort(compareChangedFiles)) {
|
|
227
|
+
signal?.throwIfAborted();
|
|
171
228
|
hash.update(
|
|
172
229
|
JSON.stringify({
|
|
173
230
|
status: file.status,
|
|
@@ -181,7 +238,12 @@ export async function hashGitChanges(
|
|
|
181
238
|
hash.update("\0index\0");
|
|
182
239
|
hash.update(indexEntries.get(relativePath) ?? "missing");
|
|
183
240
|
hash.update("\0worktree\0");
|
|
184
|
-
await hashCurrentPath(
|
|
241
|
+
await hashCurrentPath(
|
|
242
|
+
hash,
|
|
243
|
+
changeSet.repositoryRoot,
|
|
244
|
+
relativePath,
|
|
245
|
+
signal,
|
|
246
|
+
);
|
|
185
247
|
hash.update("\0");
|
|
186
248
|
}
|
|
187
249
|
}
|
|
@@ -191,8 +253,13 @@ export async function hashGitChanges(
|
|
|
191
253
|
|
|
192
254
|
async function readGitIndexEntries(
|
|
193
255
|
repositoryRoot: string,
|
|
256
|
+
signal?: AbortSignal,
|
|
194
257
|
): Promise<Map<string, string>> {
|
|
195
|
-
const output = await gitOutput(
|
|
258
|
+
const output = await gitOutput(
|
|
259
|
+
repositoryRoot,
|
|
260
|
+
["ls-files", "--stage", "-z"],
|
|
261
|
+
signal,
|
|
262
|
+
);
|
|
196
263
|
const entries = new Map<string, string[]>();
|
|
197
264
|
for (const field of output.split("\0").filter(Boolean)) {
|
|
198
265
|
const separator = field.indexOf("\t");
|
|
@@ -214,39 +281,44 @@ async function hashCurrentPath(
|
|
|
214
281
|
hash: ReturnType<typeof createHash>,
|
|
215
282
|
repositoryRoot: string,
|
|
216
283
|
relativePath: string,
|
|
284
|
+
signal?: AbortSignal,
|
|
217
285
|
): Promise<void> {
|
|
286
|
+
signal?.throwIfAborted();
|
|
218
287
|
const absolutePath = path.join(repositoryRoot, relativePath);
|
|
219
288
|
let stats: Stats;
|
|
220
289
|
try {
|
|
221
290
|
stats = await lstat(absolutePath);
|
|
222
291
|
} catch (error) {
|
|
292
|
+
signal?.throwIfAborted();
|
|
223
293
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
224
294
|
hash.update("missing");
|
|
225
295
|
return;
|
|
226
296
|
}
|
|
227
297
|
throw error;
|
|
228
298
|
}
|
|
299
|
+
signal?.throwIfAborted();
|
|
229
300
|
|
|
230
301
|
if (stats.isSymbolicLink()) {
|
|
302
|
+
const target = await readlink(absolutePath);
|
|
303
|
+
signal?.throwIfAborted();
|
|
231
304
|
hash.update("symlink\0");
|
|
232
305
|
hash.update(String(stats.mode));
|
|
233
306
|
hash.update("\0");
|
|
234
|
-
hash.update(
|
|
307
|
+
hash.update(target);
|
|
235
308
|
return;
|
|
236
309
|
}
|
|
237
310
|
if (stats.isDirectory()) {
|
|
238
311
|
hash.update("directory\0");
|
|
239
|
-
const head = await tryGitText(
|
|
240
|
-
|
|
241
|
-
"--verify",
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const status = await tryGitOutput(
|
|
245
|
-
|
|
246
|
-
"--porcelain=v1",
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
]);
|
|
312
|
+
const head = await tryGitText(
|
|
313
|
+
absolutePath,
|
|
314
|
+
["rev-parse", "--verify", "HEAD^{commit}"],
|
|
315
|
+
signal,
|
|
316
|
+
);
|
|
317
|
+
const status = await tryGitOutput(
|
|
318
|
+
absolutePath,
|
|
319
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all"],
|
|
320
|
+
signal,
|
|
321
|
+
);
|
|
250
322
|
hash.update(head ?? "unknown-head");
|
|
251
323
|
hash.update("\0");
|
|
252
324
|
hash.update(status ?? "unknown-status");
|
|
@@ -260,8 +332,47 @@ async function hashCurrentPath(
|
|
|
260
332
|
hash.update("file\0");
|
|
261
333
|
hash.update(String(stats.mode));
|
|
262
334
|
hash.update("\0");
|
|
263
|
-
|
|
264
|
-
|
|
335
|
+
try {
|
|
336
|
+
for await (const chunk of createReadStream(absolutePath, { signal })) {
|
|
337
|
+
hash.update(chunk as Buffer);
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
signal?.throwIfAborted();
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
signal?.throwIfAborted();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function hasGitMetadata(
|
|
347
|
+
cwd: string,
|
|
348
|
+
signal?: AbortSignal,
|
|
349
|
+
): Promise<boolean> {
|
|
350
|
+
signal?.throwIfAborted();
|
|
351
|
+
if (process.env.GIT_DIR || process.env.GIT_WORK_TREE) return true;
|
|
352
|
+
let root = path.resolve(cwd);
|
|
353
|
+
try {
|
|
354
|
+
root = await realpath(root);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
signal?.throwIfAborted();
|
|
357
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
358
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let current = root;
|
|
362
|
+
while (true) {
|
|
363
|
+
signal?.throwIfAborted();
|
|
364
|
+
try {
|
|
365
|
+
await lstat(path.join(current, ".git"));
|
|
366
|
+
signal?.throwIfAborted();
|
|
367
|
+
return true;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
signal?.throwIfAborted();
|
|
370
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
371
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
372
|
+
}
|
|
373
|
+
const parent = path.dirname(current);
|
|
374
|
+
if (parent === current) return false;
|
|
375
|
+
current = parent;
|
|
265
376
|
}
|
|
266
377
|
}
|
|
267
378
|
|
|
@@ -397,36 +508,45 @@ function normalizeGitPath(file: string): string {
|
|
|
397
508
|
return process.platform === "win32" ? file.replaceAll("\\", "/") : file;
|
|
398
509
|
}
|
|
399
510
|
|
|
400
|
-
async function gitText(
|
|
401
|
-
|
|
511
|
+
async function gitText(
|
|
512
|
+
cwd: string,
|
|
513
|
+
args: readonly string[],
|
|
514
|
+
signal?: AbortSignal,
|
|
515
|
+
): Promise<string> {
|
|
516
|
+
return stripFinalLineEnding(await gitOutput(cwd, args, signal));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function stripFinalLineEnding(output: string): string {
|
|
520
|
+
if (output.endsWith("\n")) return output.slice(0, -1);
|
|
521
|
+
return output;
|
|
402
522
|
}
|
|
403
523
|
|
|
404
524
|
async function resolveHeadCommit(
|
|
405
525
|
repositoryRoot: string,
|
|
526
|
+
signal?: AbortSignal,
|
|
406
527
|
): Promise<string | undefined> {
|
|
407
|
-
const head = await runGit(
|
|
408
|
-
|
|
409
|
-
"--verify",
|
|
410
|
-
|
|
411
|
-
|
|
528
|
+
const head = await runGit(
|
|
529
|
+
repositoryRoot,
|
|
530
|
+
["rev-parse", "--verify", "HEAD^{commit}"],
|
|
531
|
+
signal,
|
|
532
|
+
);
|
|
412
533
|
if (head.exitCode === 0) return head.stdout.trim();
|
|
413
534
|
|
|
414
|
-
const symbolicHead = await runGit(
|
|
415
|
-
|
|
416
|
-
"--quiet",
|
|
417
|
-
|
|
418
|
-
|
|
535
|
+
const symbolicHead = await runGit(
|
|
536
|
+
repositoryRoot,
|
|
537
|
+
["symbolic-ref", "--quiet", "HEAD"],
|
|
538
|
+
signal,
|
|
539
|
+
);
|
|
419
540
|
if (symbolicHead.exitCode !== 0) {
|
|
420
541
|
throw gitCommandError(["rev-parse", "--verify", "HEAD^{commit}"], head);
|
|
421
542
|
}
|
|
422
543
|
|
|
423
544
|
const ref = symbolicHead.stdout.trim();
|
|
424
|
-
const refExists = await runGit(
|
|
425
|
-
|
|
426
|
-
"--verify",
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
]);
|
|
545
|
+
const refExists = await runGit(
|
|
546
|
+
repositoryRoot,
|
|
547
|
+
["show-ref", "--verify", "--quiet", ref],
|
|
548
|
+
signal,
|
|
549
|
+
);
|
|
430
550
|
if (refExists.exitCode === 1) return undefined;
|
|
431
551
|
throw gitCommandError(["rev-parse", "--verify", "HEAD^{commit}"], head);
|
|
432
552
|
}
|
|
@@ -434,10 +554,12 @@ async function resolveHeadCommit(
|
|
|
434
554
|
async function tryGitText(
|
|
435
555
|
cwd: string,
|
|
436
556
|
args: readonly string[],
|
|
557
|
+
signal?: AbortSignal,
|
|
437
558
|
): Promise<string | undefined> {
|
|
438
559
|
try {
|
|
439
|
-
return await gitText(cwd, args);
|
|
560
|
+
return await gitText(cwd, args, signal);
|
|
440
561
|
} catch {
|
|
562
|
+
signal?.throwIfAborted();
|
|
441
563
|
return undefined;
|
|
442
564
|
}
|
|
443
565
|
}
|
|
@@ -445,10 +567,12 @@ async function tryGitText(
|
|
|
445
567
|
async function tryGitOutput(
|
|
446
568
|
cwd: string,
|
|
447
569
|
args: readonly string[],
|
|
570
|
+
signal?: AbortSignal,
|
|
448
571
|
): Promise<string | undefined> {
|
|
449
572
|
try {
|
|
450
|
-
return await gitOutput(cwd, args);
|
|
573
|
+
return await gitOutput(cwd, args, signal);
|
|
451
574
|
} catch {
|
|
575
|
+
signal?.throwIfAborted();
|
|
452
576
|
return undefined;
|
|
453
577
|
}
|
|
454
578
|
}
|
|
@@ -456,8 +580,9 @@ async function tryGitOutput(
|
|
|
456
580
|
async function gitOutput(
|
|
457
581
|
cwd: string,
|
|
458
582
|
args: readonly string[],
|
|
583
|
+
signal?: AbortSignal,
|
|
459
584
|
): Promise<string> {
|
|
460
|
-
const result = await runGit(cwd, args);
|
|
585
|
+
const result = await runGit(cwd, args, signal);
|
|
461
586
|
if (result.exitCode !== 0) throw gitCommandError(args, result);
|
|
462
587
|
return result.stdout;
|
|
463
588
|
}
|
|
@@ -465,14 +590,16 @@ async function gitOutput(
|
|
|
465
590
|
async function runGit(
|
|
466
591
|
cwd: string,
|
|
467
592
|
args: readonly string[],
|
|
593
|
+
signal?: AbortSignal,
|
|
468
594
|
): Promise<GitCommandResult> {
|
|
469
595
|
const result = await spawnCommand("git", args, cwd, {
|
|
470
596
|
captureOutput: true,
|
|
471
597
|
maxOutputBytes: maxGitOutputBytes,
|
|
598
|
+
signal,
|
|
472
599
|
});
|
|
473
600
|
if (result.outputTruncated) {
|
|
474
601
|
throw new Error(
|
|
475
|
-
`Git output exceeded ${maxGitOutputBytes} bytes;
|
|
602
|
+
`Git output exceeded ${maxGitOutputBytes} bytes; source inspection cannot produce a complete report.`,
|
|
476
603
|
);
|
|
477
604
|
}
|
|
478
605
|
return {
|
|
@@ -495,17 +622,22 @@ function gitCommandError(
|
|
|
495
622
|
async function gitDiffOutput(
|
|
496
623
|
repositoryRoot: string,
|
|
497
624
|
comparisonArgs: readonly string[],
|
|
625
|
+
signal?: AbortSignal,
|
|
498
626
|
): Promise<string> {
|
|
499
|
-
return gitOutput(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
627
|
+
return gitOutput(
|
|
628
|
+
repositoryRoot,
|
|
629
|
+
[
|
|
630
|
+
"diff",
|
|
631
|
+
"--no-ext-diff",
|
|
632
|
+
"--no-textconv",
|
|
633
|
+
"--name-status",
|
|
634
|
+
"-z",
|
|
635
|
+
"--find-renames=50%",
|
|
636
|
+
"--find-copies=50%",
|
|
637
|
+
"--find-copies-harder",
|
|
638
|
+
...comparisonArgs,
|
|
639
|
+
"--",
|
|
640
|
+
],
|
|
641
|
+
signal,
|
|
642
|
+
);
|
|
511
643
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type CommandContext,
|
|
9
9
|
type FlagParametersForType,
|
|
10
10
|
proposeCompletions,
|
|
11
|
+
type RouteMap,
|
|
11
12
|
run,
|
|
12
13
|
type StricliDynamicCommandContext,
|
|
13
14
|
type StricliProcess,
|
|
@@ -180,6 +181,7 @@ type OutboxDrainFlags = {
|
|
|
180
181
|
json?: boolean;
|
|
181
182
|
module?: string;
|
|
182
183
|
batchSize?: number;
|
|
184
|
+
concurrency?: number;
|
|
183
185
|
cwd?: string;
|
|
184
186
|
};
|
|
185
187
|
|
|
@@ -486,7 +488,13 @@ const outboxDrainFlagParameters = {
|
|
|
486
488
|
kind: "parsed",
|
|
487
489
|
parse: parsePositiveInteger,
|
|
488
490
|
optional: true,
|
|
489
|
-
brief: "Maximum messages to
|
|
491
|
+
brief: "Maximum eligible messages to handle in one drain pass.",
|
|
492
|
+
},
|
|
493
|
+
concurrency: {
|
|
494
|
+
kind: "parsed",
|
|
495
|
+
parse: parsePositiveInteger,
|
|
496
|
+
optional: true,
|
|
497
|
+
brief: "Maximum concurrent deliveries; values above one are unordered.",
|
|
490
498
|
},
|
|
491
499
|
cwd: cwdFlag,
|
|
492
500
|
} satisfies FlagParametersForType<OutboxDrainFlags, CliContext>;
|
|
@@ -1156,7 +1164,7 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
1156
1164
|
docs: {
|
|
1157
1165
|
brief: "Run the full app validation loop as one command.",
|
|
1158
1166
|
fullDescription:
|
|
1159
|
-
"Runs beignet lint, beignet doctor --strict, optional disconnected or connected preflight, and the app's lint, typecheck, and test package scripts through the detected package manager. Every step runs even when an earlier one fails, so one run reports everything. Missing package scripts are skipped, not failed.",
|
|
1167
|
+
"Runs beignet lint, beignet doctor --strict, optional disconnected or connected preflight, and the app's lint, typecheck, and test package scripts through the detected package manager. Every step runs even when an earlier one fails, so one run reports everything. Missing package scripts are skipped, not failed. Inside Git, the command fingerprints source before and after validation and fails stale results whose ending fingerprint differs from the baseline.",
|
|
1160
1168
|
},
|
|
1161
1169
|
parameters: {
|
|
1162
1170
|
flags: {
|
|
@@ -1186,9 +1194,12 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
1186
1194
|
} satisfies FlagParametersForType<CheckFlags, CliContext>,
|
|
1187
1195
|
},
|
|
1188
1196
|
loader: async () => {
|
|
1189
|
-
const {
|
|
1190
|
-
|
|
1191
|
-
|
|
1197
|
+
const {
|
|
1198
|
+
checkApp,
|
|
1199
|
+
formatCheckProvenance,
|
|
1200
|
+
formatCheckStep,
|
|
1201
|
+
formatCheckSummary,
|
|
1202
|
+
} = await import("./check.js");
|
|
1192
1203
|
|
|
1193
1204
|
return async function runCheck(this: CliContext, flags: CheckFlags) {
|
|
1194
1205
|
const color = useColor();
|
|
@@ -1210,7 +1221,7 @@ const checkCommand = buildCommand<CheckFlags, [], CliContext>({
|
|
|
1210
1221
|
this,
|
|
1211
1222
|
flags.json
|
|
1212
1223
|
? JSON.stringify(result, null, 2)
|
|
1213
|
-
: `\n${formatCheckSummary(result, { color })}`,
|
|
1224
|
+
: `\n${formatCheckProvenance(result, { color })}\n${formatCheckSummary(result, { color })}`,
|
|
1214
1225
|
);
|
|
1215
1226
|
if (!result.ok) {
|
|
1216
1227
|
this.process.exitCode = 1;
|
|
@@ -1597,6 +1608,7 @@ const outboxDrainCommand = buildCommand<OutboxDrainFlags, [], CliContext>({
|
|
|
1597
1608
|
cwd: flags.cwd,
|
|
1598
1609
|
modulePath: flags.module,
|
|
1599
1610
|
batchSize: flags.batchSize,
|
|
1611
|
+
concurrency: flags.concurrency,
|
|
1600
1612
|
});
|
|
1601
1613
|
|
|
1602
1614
|
writeOutput(
|
|
@@ -1605,6 +1617,9 @@ const outboxDrainCommand = buildCommand<OutboxDrainFlags, [], CliContext>({
|
|
|
1605
1617
|
? JSON.stringify(result, null, 2)
|
|
1606
1618
|
: outboxDrainNextSteps(result),
|
|
1607
1619
|
);
|
|
1620
|
+
if (result.result.settlementFailed > 0 || result.result.leaseLost > 0) {
|
|
1621
|
+
this.process.exitCode = 1;
|
|
1622
|
+
}
|
|
1608
1623
|
};
|
|
1609
1624
|
},
|
|
1610
1625
|
});
|
|
@@ -2448,6 +2463,27 @@ Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
|
|
|
2448
2463
|
},
|
|
2449
2464
|
});
|
|
2450
2465
|
|
|
2466
|
+
const completionProposalCommandPath = ["completion", "propose"] as const;
|
|
2467
|
+
|
|
2468
|
+
function commandPathsFromRouteMap(
|
|
2469
|
+
routeMap: RouteMap<CliContext>,
|
|
2470
|
+
prefix: readonly string[] = [],
|
|
2471
|
+
): string[][] {
|
|
2472
|
+
return routeMap.getAllEntries().flatMap((entry) => {
|
|
2473
|
+
const path = [...prefix, entry.name["convert-camel-to-kebab"]];
|
|
2474
|
+
if ("getAllEntries" in entry.target) {
|
|
2475
|
+
return commandPathsFromRouteMap(entry.target, path);
|
|
2476
|
+
}
|
|
2477
|
+
return [path];
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
/** Every canonical CLI command path, derived from runtime routing definitions. */
|
|
2482
|
+
export const cliCommandPaths: readonly (readonly string[])[] = [
|
|
2483
|
+
...commandPathsFromRouteMap(rootRoutes),
|
|
2484
|
+
completionProposalCommandPath,
|
|
2485
|
+
].sort((left, right) => left.join(" ").localeCompare(right.join(" ")));
|
|
2486
|
+
|
|
2451
2487
|
const cli = buildApplication(rootRoutes, {
|
|
2452
2488
|
name: "beignet",
|
|
2453
2489
|
versionInfo: {
|
|
@@ -2541,6 +2577,9 @@ type OutboxDrainNextStepsResult = {
|
|
|
2541
2577
|
delivered: number;
|
|
2542
2578
|
retried: number;
|
|
2543
2579
|
deadLettered: number;
|
|
2580
|
+
abandonedDeadLettered: number;
|
|
2581
|
+
settlementFailed: number;
|
|
2582
|
+
leaseLost: number;
|
|
2544
2583
|
};
|
|
2545
2584
|
};
|
|
2546
2585
|
|
|
@@ -2608,7 +2647,10 @@ Result:
|
|
|
2608
2647
|
claimed: ${result.result.claimed}
|
|
2609
2648
|
delivered: ${result.result.delivered}
|
|
2610
2649
|
retried: ${result.result.retried}
|
|
2611
|
-
deadLettered: ${result.result.deadLettered}
|
|
2650
|
+
deadLettered: ${result.result.deadLettered}
|
|
2651
|
+
abandonedDeadLettered: ${result.result.abandonedDeadLettered}
|
|
2652
|
+
settlementFailed: ${result.result.settlementFailed}
|
|
2653
|
+
leaseLost: ${result.result.leaseLost}`;
|
|
2612
2654
|
}
|
|
2613
2655
|
|
|
2614
2656
|
function formatOutboxDate(value: Date | string | null | undefined): string {
|
|
@@ -3053,7 +3095,10 @@ export async function main(
|
|
|
3053
3095
|
// Shell completion scripts call `beignet completion propose <words...>`.
|
|
3054
3096
|
// Handle it before stricli parses the inputs: the trailing words are a
|
|
3055
3097
|
// partial command line, not flags for this CLI.
|
|
3056
|
-
if (
|
|
3098
|
+
if (
|
|
3099
|
+
inputs[0] === completionProposalCommandPath[0] &&
|
|
3100
|
+
inputs[1] === completionProposalCommandPath[1]
|
|
3101
|
+
) {
|
|
3057
3102
|
await writeCompletionProposals(inputs.slice(2), context);
|
|
3058
3103
|
return;
|
|
3059
3104
|
}
|