@ebay-dt/bonfire-node 0.2.0-alpha.0 → 0.2.0-alpha.11

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.
Files changed (52) hide show
  1. package/README.md +18 -5
  2. package/dist/deployment-provider.d.ts +2 -1
  3. package/dist/deployment-provider.d.ts.map +1 -1
  4. package/dist/deployment-provider.js +2 -1
  5. package/dist/deployment-provider.js.map +1 -1
  6. package/dist/duplicate.d.ts.map +1 -1
  7. package/dist/duplicate.js +103 -47
  8. package/dist/duplicate.js.map +1 -1
  9. package/dist/git-repository.d.ts +16 -1
  10. package/dist/git-repository.d.ts.map +1 -1
  11. package/dist/git-repository.js +127 -1
  12. package/dist/git-repository.js.map +1 -1
  13. package/dist/github-cli.d.ts.map +1 -1
  14. package/dist/github-cli.js +3 -1
  15. package/dist/github-cli.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/internal/prototype-templates.d.ts +0 -1
  21. package/dist/internal/prototype-templates.d.ts.map +1 -1
  22. package/dist/internal/prototype-templates.js +0 -5
  23. package/dist/internal/prototype-templates.js.map +1 -1
  24. package/dist/lifecycle/deployment-worktree.d.ts +36 -0
  25. package/dist/lifecycle/deployment-worktree.d.ts.map +1 -0
  26. package/dist/lifecycle/deployment-worktree.js +503 -0
  27. package/dist/lifecycle/deployment-worktree.js.map +1 -0
  28. package/dist/lifecycle/local-prototype-lifecycle.d.ts +15 -1
  29. package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
  30. package/dist/lifecycle/local-prototype-lifecycle.js +738 -98
  31. package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
  32. package/dist/lifecycle/preparation-marker.d.ts +12 -0
  33. package/dist/lifecycle/preparation-marker.d.ts.map +1 -0
  34. package/dist/lifecycle/preparation-marker.js +76 -0
  35. package/dist/lifecycle/preparation-marker.js.map +1 -0
  36. package/dist/lifecycle/prototype-path-policy.d.ts +18 -0
  37. package/dist/lifecycle/prototype-path-policy.d.ts.map +1 -0
  38. package/dist/lifecycle/prototype-path-policy.js +429 -0
  39. package/dist/lifecycle/prototype-path-policy.js.map +1 -0
  40. package/dist/lifecycle/repository-summary.d.ts +7 -1
  41. package/dist/lifecycle/repository-summary.d.ts.map +1 -1
  42. package/dist/lifecycle/repository-summary.js +20 -4
  43. package/dist/lifecycle/repository-summary.js.map +1 -1
  44. package/dist/lifecycle/repository-synchronization.d.ts +13 -8
  45. package/dist/lifecycle/repository-synchronization.d.ts.map +1 -1
  46. package/dist/lifecycle/repository-synchronization.js +680 -139
  47. package/dist/lifecycle/repository-synchronization.js.map +1 -1
  48. package/dist/lifecycle/runtime-context.d.ts +2 -0
  49. package/dist/lifecycle/runtime-context.d.ts.map +1 -1
  50. package/dist/lifecycle/runtime-context.js +5 -1
  51. package/dist/lifecycle/runtime-context.js.map +1 -1
  52. package/package.json +2 -2
@@ -0,0 +1,429 @@
1
+ import { lstat, readdir, rmdir, unlink } from "node:fs/promises";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import { GitRepositoryError, } from "../git-repository.js";
4
+ const PROTECTED_DIRECTORY_NAMES = new Set([
5
+ ".git",
6
+ ".next",
7
+ ".turbo",
8
+ ".vercel",
9
+ "build",
10
+ "coverage",
11
+ "dist",
12
+ "node_modules",
13
+ "out",
14
+ ]);
15
+ const PROTECTED_FILE_NAMES = new Set([
16
+ ".env",
17
+ "preferences.json",
18
+ "id_rsa",
19
+ "id_ed25519",
20
+ "id_ecdsa",
21
+ "id_dsa",
22
+ ]);
23
+ const PROTECTED_FILE_EXTENSIONS = new Set([".key", ".pem", ".p12", ".pfx"]);
24
+ const SYMLINK_MODE = "120000";
25
+ const REGULAR_MODE_PREFIX = "100";
26
+ function assertPathspec(pathspec) {
27
+ if (pathspec.length === 0 ||
28
+ isAbsolute(pathspec) ||
29
+ pathspec.startsWith("-") ||
30
+ pathspec.startsWith(".") ||
31
+ pathspec.includes("\\") ||
32
+ pathspec.includes("\0") ||
33
+ pathspec
34
+ .split("/")
35
+ .some((segment) => segment.length === 0 || segment === "..")) {
36
+ throw new GitRepositoryError("invalid-configuration", "pathspec");
37
+ }
38
+ }
39
+ function normalizePath(path) {
40
+ return path.replaceAll("\\", "/").replace(/\/+$/u, "");
41
+ }
42
+ function pathIsWithin(path, parent) {
43
+ return path === parent || path.startsWith(`${parent}/`);
44
+ }
45
+ function statusPaths(file) {
46
+ return [file.path, ...(file.from === undefined ? [] : [file.from])].map(normalizePath);
47
+ }
48
+ function pathsOverlap(left, right) {
49
+ return (left === right || pathIsWithin(left, right) || pathIsWithin(right, left));
50
+ }
51
+ function parseNulPaths(output) {
52
+ return output
53
+ .split("\0")
54
+ .filter(Boolean)
55
+ .map((entry) => {
56
+ const tab = entry.indexOf("\t");
57
+ return normalizePath(tab < 0 ? entry : entry.slice(tab + 1));
58
+ });
59
+ }
60
+ function parseIndexEntries(output) {
61
+ return output
62
+ .split("\0")
63
+ .filter(Boolean)
64
+ .flatMap((entry) => {
65
+ const tab = entry.indexOf("\t");
66
+ if (tab < 0)
67
+ return [];
68
+ const header = entry.slice(0, tab).split(/\s+/u);
69
+ const mode = header[0];
70
+ const path = normalizePath(entry.slice(tab + 1));
71
+ if (mode === undefined || path.length === 0)
72
+ return [];
73
+ return [{ mode, path }];
74
+ });
75
+ }
76
+ function parseTreeEntries(output) {
77
+ return output
78
+ .split("\0")
79
+ .filter(Boolean)
80
+ .flatMap((entry) => {
81
+ const tab = entry.indexOf("\t");
82
+ if (tab < 0)
83
+ return [];
84
+ const header = entry.slice(0, tab).split(/\s+/u);
85
+ const mode = header[0];
86
+ const path = normalizePath(entry.slice(tab + 1));
87
+ if (mode === undefined || path.length === 0)
88
+ return [];
89
+ return [{ mode, path }];
90
+ });
91
+ }
92
+ async function gitPaths(repository, args) {
93
+ const output = await repository.raw([...args]);
94
+ return parseNulPaths(output);
95
+ }
96
+ async function gitIndexEntries(repository, pathspec) {
97
+ return parseIndexEntries(await repository.raw(["ls-files", "-s", "-z", "--", pathspec]));
98
+ }
99
+ async function gitTreeEntries(repository, pathspec) {
100
+ return parseTreeEntries(await repository.raw([
101
+ "ls-tree",
102
+ "-r",
103
+ "-z",
104
+ "--full-tree",
105
+ "HEAD",
106
+ "--",
107
+ pathspec,
108
+ ]));
109
+ }
110
+ function basename(path) {
111
+ const separator = path.lastIndexOf("/");
112
+ return separator < 0 ? path : path.slice(separator + 1);
113
+ }
114
+ export function isProtectedPrototypePath(path) {
115
+ const segments = path.split("/");
116
+ if (segments.some((segment) => PROTECTED_DIRECTORY_NAMES.has(segment))) {
117
+ return true;
118
+ }
119
+ const name = basename(path);
120
+ if (PROTECTED_FILE_NAMES.has(name))
121
+ return true;
122
+ if (name.startsWith(".env") && !name.endsWith(".example"))
123
+ return true;
124
+ if (name.endsWith(".local.json"))
125
+ return true;
126
+ const extension = name.slice(name.lastIndexOf(".")).toLowerCase();
127
+ return PROTECTED_FILE_EXTENSIONS.has(extension);
128
+ }
129
+ export function isRegularPrototypeMode(mode) {
130
+ return mode !== undefined && mode.startsWith(REGULAR_MODE_PREFIX);
131
+ }
132
+ const isProtectedPath = isProtectedPrototypePath;
133
+ const isRegularMode = isRegularPrototypeMode;
134
+ async function walkFilesystem(root, pathspec) {
135
+ const entries = [];
136
+ const absoluteRoot = resolve(root, ...pathspec.split("/"));
137
+ const rootRelative = relative(root, absoluteRoot).split(sep).join("/");
138
+ const visit = async (absolutePath, relativePath) => {
139
+ let stats;
140
+ try {
141
+ stats = await lstat(absolutePath);
142
+ }
143
+ catch (error) {
144
+ if (error &&
145
+ typeof error === "object" &&
146
+ "code" in error &&
147
+ error.code === "ENOENT") {
148
+ return;
149
+ }
150
+ throw error;
151
+ }
152
+ if (stats.isSymbolicLink()) {
153
+ entries.push({ path: relativePath, kind: "symlink" });
154
+ return;
155
+ }
156
+ if (stats.isDirectory()) {
157
+ entries.push({ path: relativePath, kind: "directory" });
158
+ if (relativePath !== rootRelative && isProtectedPath(relativePath))
159
+ return;
160
+ for (const entry of await readdir(absolutePath)) {
161
+ const childPath = `${relativePath}/${entry}`;
162
+ await visit(join(absolutePath, entry), childPath);
163
+ }
164
+ return;
165
+ }
166
+ entries.push({
167
+ path: relativePath,
168
+ kind: stats.isFile() ? "file" : "special",
169
+ });
170
+ };
171
+ await visit(absoluteRoot, rootRelative);
172
+ return entries;
173
+ }
174
+ function throwProtected(paths) {
175
+ if (paths.length > 0) {
176
+ throw new GitRepositoryError("conflict", "protected-prototype-path");
177
+ }
178
+ }
179
+ function changedPaths(files, eligiblePaths) {
180
+ const status = files.flatMap(statusPaths);
181
+ return eligiblePaths.filter((path) => status.some((statusPath) => pathsOverlap(path, statusPath)));
182
+ }
183
+ export async function inspectPrototypePath(repository, pathspec) {
184
+ assertPathspec(pathspec);
185
+ const normalizedPath = normalizePath(pathspec);
186
+ const [status, indexEntries, treeEntries, untrackedPaths, filesystem] = await Promise.all([
187
+ repository.status([normalizedPath]),
188
+ gitIndexEntries(repository, normalizedPath),
189
+ gitTreeEntries(repository, normalizedPath),
190
+ gitPaths(repository, [
191
+ "ls-files",
192
+ "--others",
193
+ "--exclude-standard",
194
+ "-z",
195
+ "--",
196
+ normalizedPath,
197
+ ]),
198
+ walkFilesystem(repository.repositoryRoot, normalizedPath),
199
+ ]);
200
+ const indexModes = new Map(indexEntries.map((entry) => [entry.path, entry.mode]));
201
+ const treeModes = new Map(treeEntries.map((entry) => [entry.path, entry.mode]));
202
+ const filesystemEntries = new Map(filesystem.map((entry) => [entry.path, entry]));
203
+ const trackedPaths = new Set([...treeModes.keys(), ...indexModes.keys()]);
204
+ const untracked = new Set(untrackedPaths);
205
+ const eligible = new Set();
206
+ const trackedEligible = new Set();
207
+ const newPaths = new Set();
208
+ const stagedPaths = new Set();
209
+ const existing = new Set();
210
+ const protectedPaths = new Set();
211
+ const trackedProtectedPaths = new Set();
212
+ const classifyTracked = (path) => {
213
+ const indexMode = indexModes.get(path);
214
+ const treeMode = treeModes.get(path);
215
+ if (isProtectedPath(path) ||
216
+ (indexMode !== undefined && !isRegularMode(indexMode)) ||
217
+ (treeMode !== undefined && !isRegularMode(treeMode))) {
218
+ protectedPaths.add(path);
219
+ trackedProtectedPaths.add(path);
220
+ return;
221
+ }
222
+ const filesystemEntry = filesystemEntries.get(path);
223
+ if (filesystemEntry !== undefined && filesystemEntry.kind !== "file") {
224
+ protectedPaths.add(path);
225
+ trackedProtectedPaths.add(path);
226
+ return;
227
+ }
228
+ eligible.add(path);
229
+ trackedEligible.add(path);
230
+ };
231
+ for (const path of treeModes.keys())
232
+ classifyTracked(path);
233
+ for (const path of indexModes.keys()) {
234
+ if (treeModes.has(path)) {
235
+ classifyTracked(path);
236
+ }
237
+ else if (isProtectedPath(path) ||
238
+ !isRegularMode(indexModes.get(path)) ||
239
+ (filesystemEntries.get(path) !== undefined &&
240
+ filesystemEntries.get(path)?.kind !== "file")) {
241
+ protectedPaths.add(path);
242
+ trackedProtectedPaths.add(path);
243
+ }
244
+ else {
245
+ eligible.add(path);
246
+ newPaths.add(path);
247
+ stagedPaths.add(path);
248
+ }
249
+ }
250
+ for (const entry of filesystem) {
251
+ const path = entry.path;
252
+ if (entry.kind === "directory") {
253
+ if (path !== normalizedPath && isProtectedPath(path)) {
254
+ protectedPaths.add(path);
255
+ }
256
+ continue;
257
+ }
258
+ if (entry.kind !== "file") {
259
+ protectedPaths.add(path);
260
+ if (trackedPaths.has(path))
261
+ trackedProtectedPaths.add(path);
262
+ continue;
263
+ }
264
+ if (trackedPaths.has(path)) {
265
+ if (isProtectedPath(path)) {
266
+ protectedPaths.add(path);
267
+ trackedProtectedPaths.add(path);
268
+ }
269
+ else if (!isRegularMode(indexModes.get(path) ?? treeModes.get(path)) ||
270
+ indexModes.get(path) === SYMLINK_MODE ||
271
+ treeModes.get(path) === SYMLINK_MODE) {
272
+ protectedPaths.add(path);
273
+ trackedProtectedPaths.add(path);
274
+ }
275
+ else {
276
+ eligible.add(path);
277
+ existing.add(path);
278
+ }
279
+ continue;
280
+ }
281
+ // Files absent from --exclude-standard are ignored content. Leave them
282
+ // alone without making Delete fail on them.
283
+ if (!untracked.has(path)) {
284
+ if (isProtectedPath(path))
285
+ protectedPaths.add(path);
286
+ continue;
287
+ }
288
+ if (isProtectedPath(path)) {
289
+ protectedPaths.add(path);
290
+ }
291
+ else {
292
+ eligible.add(path);
293
+ newPaths.add(path);
294
+ existing.add(path);
295
+ }
296
+ }
297
+ const eligiblePaths = [...eligible].sort();
298
+ return {
299
+ eligiblePaths,
300
+ trackedEligiblePaths: [...trackedEligible].sort(),
301
+ changedEligiblePaths: changedPaths(status.files, eligiblePaths),
302
+ newEligiblePaths: [...newPaths].sort(),
303
+ stagedEligiblePaths: [...stagedPaths].sort(),
304
+ existingEligiblePaths: [...existing].sort(),
305
+ protectedPaths: [...protectedPaths].sort(),
306
+ trackedProtectedPaths: [...trackedProtectedPaths].sort(),
307
+ };
308
+ }
309
+ export async function commitPrototypePath(repository, pathspec, message) {
310
+ const plan = await inspectPrototypePath(repository, pathspec);
311
+ throwProtected(plan.trackedProtectedPaths);
312
+ if (plan.changedEligiblePaths.length === 0)
313
+ return null;
314
+ await repository.add(plan.changedEligiblePaths);
315
+ return repository.commit(message, plan.changedEligiblePaths);
316
+ }
317
+ async function assertSafeParents(repository, absolutePath) {
318
+ const relativePath = relative(repository.repositoryRoot, absolutePath);
319
+ const segments = relativePath.split(sep);
320
+ let current = repository.repositoryRoot;
321
+ for (const segment of segments.slice(0, -1)) {
322
+ current = join(current, segment);
323
+ const stats = await lstat(current);
324
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
325
+ throw new GitRepositoryError("conflict", "prototype-path-changed");
326
+ }
327
+ }
328
+ }
329
+ async function removeFiles(repository, paths) {
330
+ for (const path of paths) {
331
+ const absolutePath = resolve(repository.repositoryRoot, ...path.split("/"));
332
+ await assertSafeParents(repository, absolutePath);
333
+ let stats;
334
+ try {
335
+ stats = await lstat(absolutePath);
336
+ }
337
+ catch (error) {
338
+ if (error &&
339
+ typeof error === "object" &&
340
+ "code" in error &&
341
+ error.code === "ENOENT") {
342
+ continue;
343
+ }
344
+ throw error;
345
+ }
346
+ if (!stats.isFile() || stats.isSymbolicLink()) {
347
+ throw new GitRepositoryError("conflict", "prototype-path-changed");
348
+ }
349
+ await unlink(absolutePath);
350
+ }
351
+ }
352
+ async function removeEmptyDirectories(repository, pathspec) {
353
+ const root = resolve(repository.repositoryRoot, ...pathspec.split("/"));
354
+ const directories = [];
355
+ const visit = async (absolutePath) => {
356
+ let stats;
357
+ try {
358
+ stats = await lstat(absolutePath);
359
+ }
360
+ catch {
361
+ return;
362
+ }
363
+ if (stats.isSymbolicLink() || !stats.isDirectory())
364
+ return;
365
+ const relativePath = relative(repository.repositoryRoot, absolutePath)
366
+ .split(sep)
367
+ .join("/");
368
+ if (relativePath !== pathspec && isProtectedPath(relativePath))
369
+ return;
370
+ for (const entry of await readdir(absolutePath)) {
371
+ await visit(join(absolutePath, entry));
372
+ }
373
+ directories.push(absolutePath);
374
+ };
375
+ await visit(root);
376
+ for (const directory of directories) {
377
+ try {
378
+ await assertSafeParents(repository, directory);
379
+ await rmdir(directory);
380
+ }
381
+ catch {
382
+ // Non-empty directories contain protected or unrelated content.
383
+ }
384
+ }
385
+ }
386
+ export async function discardPrototypePath(repository, pathspec) {
387
+ const plan = await inspectPrototypePath(repository, pathspec);
388
+ if (plan.changedEligiblePaths.length === 0 &&
389
+ plan.newEligiblePaths.length === 0) {
390
+ return;
391
+ }
392
+ const trackedChanged = plan.changedEligiblePaths.filter((path) => plan.trackedEligiblePaths.includes(path));
393
+ const stagedAdditions = plan.stagedEligiblePaths;
394
+ if (stagedAdditions.length > 0) {
395
+ await repository.raw(["reset", "HEAD", "--", ...stagedAdditions]);
396
+ }
397
+ if (trackedChanged.length > 0) {
398
+ await repository.raw(["reset", "HEAD", "--", ...trackedChanged]);
399
+ await repository.restore(trackedChanged);
400
+ }
401
+ await removeFiles(repository, plan.newEligiblePaths);
402
+ await removeEmptyDirectories(repository, pathspec);
403
+ }
404
+ export async function deletePrototypePath(repository, pathspec, message) {
405
+ const before = await inspectPrototypePath(repository, pathspec);
406
+ // Untracked excluded files can safely remain in place while ordinary
407
+ // prototype content is deleted. Tracked exclusions still block because
408
+ // staging their surrounding type/deletion could silently alter protected
409
+ // repository state.
410
+ throwProtected(before.trackedProtectedPaths);
411
+ if (before.stagedEligiblePaths.length > 0) {
412
+ await repository.raw([
413
+ "reset",
414
+ "HEAD",
415
+ "--",
416
+ ...before.stagedEligiblePaths,
417
+ ]);
418
+ }
419
+ await removeFiles(repository, before.existingEligiblePaths);
420
+ await removeEmptyDirectories(repository, pathspec);
421
+ const after = await inspectPrototypePath(repository, pathspec);
422
+ throwProtected(after.trackedProtectedPaths);
423
+ if (message === undefined || after.trackedEligiblePaths.length === 0) {
424
+ return null;
425
+ }
426
+ await repository.add(after.trackedEligiblePaths);
427
+ return repository.commit(message, after.trackedEligiblePaths);
428
+ }
429
+ //# sourceMappingURL=prototype-path-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prototype-path-policy.js","sourceRoot":"","sources":["../../src/lifecycle/prototype-path-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, readdir, rmdir, unlink } from \"node:fs/promises\";\nimport { isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport {\n GitRepositoryError,\n type GitRepository,\n type GitStatusFile,\n} from \"../git-repository.js\";\n\nconst PROTECTED_DIRECTORY_NAMES = new Set([\n \".git\",\n \".next\",\n \".turbo\",\n \".vercel\",\n \"build\",\n \"coverage\",\n \"dist\",\n \"node_modules\",\n \"out\",\n]);\n\nconst PROTECTED_FILE_NAMES = new Set([\n \".env\",\n \"preferences.json\",\n \"id_rsa\",\n \"id_ed25519\",\n \"id_ecdsa\",\n \"id_dsa\",\n]);\n\nconst PROTECTED_FILE_EXTENSIONS = new Set([\".key\", \".pem\", \".p12\", \".pfx\"]);\n\nconst SYMLINK_MODE = \"120000\";\nconst REGULAR_MODE_PREFIX = \"100\";\n\ntype GitIndexEntry = {\n path: string;\n mode: string;\n};\n\ntype FilesystemEntry = {\n path: string;\n kind: \"directory\" | \"file\" | \"symlink\" | \"special\";\n};\n\nexport type PrototypePathPlan = {\n eligiblePaths: readonly string[];\n trackedEligiblePaths: readonly string[];\n changedEligiblePaths: readonly string[];\n newEligiblePaths: readonly string[];\n stagedEligiblePaths: readonly string[];\n existingEligiblePaths: readonly string[];\n protectedPaths: readonly string[];\n trackedProtectedPaths: readonly string[];\n};\n\nfunction assertPathspec(pathspec: string) {\n if (\n pathspec.length === 0 ||\n isAbsolute(pathspec) ||\n pathspec.startsWith(\"-\") ||\n pathspec.startsWith(\".\") ||\n pathspec.includes(\"\\\\\") ||\n pathspec.includes(\"\\0\") ||\n pathspec\n .split(\"/\")\n .some((segment) => segment.length === 0 || segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n}\n\nfunction normalizePath(path: string) {\n return path.replaceAll(\"\\\\\", \"/\").replace(/\\/+$/u, \"\");\n}\n\nfunction pathIsWithin(path: string, parent: string) {\n return path === parent || path.startsWith(`${parent}/`);\n}\n\nfunction statusPaths(file: GitStatusFile) {\n return [file.path, ...(file.from === undefined ? [] : [file.from])].map(\n normalizePath,\n );\n}\n\nfunction pathsOverlap(left: string, right: string) {\n return (\n left === right || pathIsWithin(left, right) || pathIsWithin(right, left)\n );\n}\n\nfunction parseNulPaths(output: string) {\n return output\n .split(\"\\0\")\n .filter(Boolean)\n .map((entry) => {\n const tab = entry.indexOf(\"\\t\");\n return normalizePath(tab < 0 ? entry : entry.slice(tab + 1));\n });\n}\n\nfunction parseIndexEntries(output: string): GitIndexEntry[] {\n return output\n .split(\"\\0\")\n .filter(Boolean)\n .flatMap((entry) => {\n const tab = entry.indexOf(\"\\t\");\n if (tab < 0) return [];\n const header = entry.slice(0, tab).split(/\\s+/u);\n const mode = header[0];\n const path = normalizePath(entry.slice(tab + 1));\n if (mode === undefined || path.length === 0) return [];\n return [{ mode, path }];\n });\n}\n\nfunction parseTreeEntries(output: string): GitIndexEntry[] {\n return output\n .split(\"\\0\")\n .filter(Boolean)\n .flatMap((entry) => {\n const tab = entry.indexOf(\"\\t\");\n if (tab < 0) return [];\n const header = entry.slice(0, tab).split(/\\s+/u);\n const mode = header[0];\n const path = normalizePath(entry.slice(tab + 1));\n if (mode === undefined || path.length === 0) return [];\n return [{ mode, path }];\n });\n}\n\nasync function gitPaths(\n repository: GitRepository,\n args: readonly string[],\n): Promise<string[]> {\n const output = await repository.raw([...args]);\n return parseNulPaths(output);\n}\n\nasync function gitIndexEntries(\n repository: GitRepository,\n pathspec: string,\n): Promise<GitIndexEntry[]> {\n return parseIndexEntries(\n await repository.raw([\"ls-files\", \"-s\", \"-z\", \"--\", pathspec]),\n );\n}\n\nasync function gitTreeEntries(\n repository: GitRepository,\n pathspec: string,\n): Promise<GitIndexEntry[]> {\n return parseTreeEntries(\n await repository.raw([\n \"ls-tree\",\n \"-r\",\n \"-z\",\n \"--full-tree\",\n \"HEAD\",\n \"--\",\n pathspec,\n ]),\n );\n}\n\nfunction basename(path: string) {\n const separator = path.lastIndexOf(\"/\");\n return separator < 0 ? path : path.slice(separator + 1);\n}\n\nexport function isProtectedPrototypePath(path: string) {\n const segments = path.split(\"/\");\n if (segments.some((segment) => PROTECTED_DIRECTORY_NAMES.has(segment))) {\n return true;\n }\n\n const name = basename(path);\n if (PROTECTED_FILE_NAMES.has(name)) return true;\n if (name.startsWith(\".env\") && !name.endsWith(\".example\")) return true;\n if (name.endsWith(\".local.json\")) return true;\n const extension = name.slice(name.lastIndexOf(\".\")).toLowerCase();\n return PROTECTED_FILE_EXTENSIONS.has(extension);\n}\n\nexport function isRegularPrototypeMode(mode: string | undefined) {\n return mode !== undefined && mode.startsWith(REGULAR_MODE_PREFIX);\n}\n\nconst isProtectedPath = isProtectedPrototypePath;\nconst isRegularMode = isRegularPrototypeMode;\n\nasync function walkFilesystem(\n root: string,\n pathspec: string,\n): Promise<FilesystemEntry[]> {\n const entries: FilesystemEntry[] = [];\n const absoluteRoot = resolve(root, ...pathspec.split(\"/\"));\n const rootRelative = relative(root, absoluteRoot).split(sep).join(\"/\");\n const visit = async (absolutePath: string, relativePath: string) => {\n let stats;\n try {\n stats = await lstat(absolutePath);\n } catch (error) {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return;\n }\n throw error;\n }\n\n if (stats.isSymbolicLink()) {\n entries.push({ path: relativePath, kind: \"symlink\" });\n return;\n }\n if (stats.isDirectory()) {\n entries.push({ path: relativePath, kind: \"directory\" });\n if (relativePath !== rootRelative && isProtectedPath(relativePath))\n return;\n for (const entry of await readdir(absolutePath)) {\n const childPath = `${relativePath}/${entry}`;\n await visit(join(absolutePath, entry), childPath);\n }\n return;\n }\n entries.push({\n path: relativePath,\n kind: stats.isFile() ? \"file\" : \"special\",\n });\n };\n\n await visit(absoluteRoot, rootRelative);\n return entries;\n}\n\nfunction throwProtected(paths: readonly string[]) {\n if (paths.length > 0) {\n throw new GitRepositoryError(\"conflict\", \"protected-prototype-path\");\n }\n}\n\nfunction changedPaths(\n files: readonly GitStatusFile[],\n eligiblePaths: readonly string[],\n) {\n const status = files.flatMap(statusPaths);\n return eligiblePaths.filter((path) =>\n status.some((statusPath) => pathsOverlap(path, statusPath)),\n );\n}\n\nexport async function inspectPrototypePath(\n repository: GitRepository,\n pathspec: string,\n): Promise<PrototypePathPlan> {\n assertPathspec(pathspec);\n const normalizedPath = normalizePath(pathspec);\n const [status, indexEntries, treeEntries, untrackedPaths, filesystem] =\n await Promise.all([\n repository.status([normalizedPath]),\n gitIndexEntries(repository, normalizedPath),\n gitTreeEntries(repository, normalizedPath),\n gitPaths(repository, [\n \"ls-files\",\n \"--others\",\n \"--exclude-standard\",\n \"-z\",\n \"--\",\n normalizedPath,\n ]),\n walkFilesystem(repository.repositoryRoot, normalizedPath),\n ]);\n\n const indexModes = new Map(\n indexEntries.map((entry) => [entry.path, entry.mode]),\n );\n const treeModes = new Map(\n treeEntries.map((entry) => [entry.path, entry.mode]),\n );\n const filesystemEntries = new Map(\n filesystem.map((entry) => [entry.path, entry]),\n );\n const trackedPaths = new Set([...treeModes.keys(), ...indexModes.keys()]);\n const untracked = new Set(untrackedPaths);\n const eligible = new Set<string>();\n const trackedEligible = new Set<string>();\n const newPaths = new Set<string>();\n const stagedPaths = new Set<string>();\n const existing = new Set<string>();\n const protectedPaths = new Set<string>();\n const trackedProtectedPaths = new Set<string>();\n\n const classifyTracked = (path: string) => {\n const indexMode = indexModes.get(path);\n const treeMode = treeModes.get(path);\n if (\n isProtectedPath(path) ||\n (indexMode !== undefined && !isRegularMode(indexMode)) ||\n (treeMode !== undefined && !isRegularMode(treeMode))\n ) {\n protectedPaths.add(path);\n trackedProtectedPaths.add(path);\n return;\n }\n const filesystemEntry = filesystemEntries.get(path);\n if (filesystemEntry !== undefined && filesystemEntry.kind !== \"file\") {\n protectedPaths.add(path);\n trackedProtectedPaths.add(path);\n return;\n }\n eligible.add(path);\n trackedEligible.add(path);\n };\n\n for (const path of treeModes.keys()) classifyTracked(path);\n for (const path of indexModes.keys()) {\n if (treeModes.has(path)) {\n classifyTracked(path);\n } else if (\n isProtectedPath(path) ||\n !isRegularMode(indexModes.get(path)) ||\n (filesystemEntries.get(path) !== undefined &&\n filesystemEntries.get(path)?.kind !== \"file\")\n ) {\n protectedPaths.add(path);\n trackedProtectedPaths.add(path);\n } else {\n eligible.add(path);\n newPaths.add(path);\n stagedPaths.add(path);\n }\n }\n\n for (const entry of filesystem) {\n const path = entry.path;\n if (entry.kind === \"directory\") {\n if (path !== normalizedPath && isProtectedPath(path)) {\n protectedPaths.add(path);\n }\n continue;\n }\n if (entry.kind !== \"file\") {\n protectedPaths.add(path);\n if (trackedPaths.has(path)) trackedProtectedPaths.add(path);\n continue;\n }\n\n if (trackedPaths.has(path)) {\n if (isProtectedPath(path)) {\n protectedPaths.add(path);\n trackedProtectedPaths.add(path);\n } else if (\n !isRegularMode(indexModes.get(path) ?? treeModes.get(path)) ||\n indexModes.get(path) === SYMLINK_MODE ||\n treeModes.get(path) === SYMLINK_MODE\n ) {\n protectedPaths.add(path);\n trackedProtectedPaths.add(path);\n } else {\n eligible.add(path);\n existing.add(path);\n }\n continue;\n }\n\n // Files absent from --exclude-standard are ignored content. Leave them\n // alone without making Delete fail on them.\n if (!untracked.has(path)) {\n if (isProtectedPath(path)) protectedPaths.add(path);\n continue;\n }\n if (isProtectedPath(path)) {\n protectedPaths.add(path);\n } else {\n eligible.add(path);\n newPaths.add(path);\n existing.add(path);\n }\n }\n\n const eligiblePaths = [...eligible].sort();\n return {\n eligiblePaths,\n trackedEligiblePaths: [...trackedEligible].sort(),\n changedEligiblePaths: changedPaths(status.files, eligiblePaths),\n newEligiblePaths: [...newPaths].sort(),\n stagedEligiblePaths: [...stagedPaths].sort(),\n existingEligiblePaths: [...existing].sort(),\n protectedPaths: [...protectedPaths].sort(),\n trackedProtectedPaths: [...trackedProtectedPaths].sort(),\n };\n}\n\nexport async function commitPrototypePath(\n repository: GitRepository,\n pathspec: string,\n message: string,\n) {\n const plan = await inspectPrototypePath(repository, pathspec);\n throwProtected(plan.trackedProtectedPaths);\n if (plan.changedEligiblePaths.length === 0) return null;\n await repository.add(plan.changedEligiblePaths);\n return repository.commit(message, plan.changedEligiblePaths);\n}\n\nasync function assertSafeParents(\n repository: GitRepository,\n absolutePath: string,\n) {\n const relativePath = relative(repository.repositoryRoot, absolutePath);\n const segments = relativePath.split(sep);\n let current = repository.repositoryRoot;\n for (const segment of segments.slice(0, -1)) {\n current = join(current, segment);\n const stats = await lstat(current);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new GitRepositoryError(\"conflict\", \"prototype-path-changed\");\n }\n }\n}\n\nasync function removeFiles(\n repository: GitRepository,\n paths: readonly string[],\n) {\n for (const path of paths) {\n const absolutePath = resolve(repository.repositoryRoot, ...path.split(\"/\"));\n await assertSafeParents(repository, absolutePath);\n let stats;\n try {\n stats = await lstat(absolutePath);\n } catch (error) {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n continue;\n }\n throw error;\n }\n if (!stats.isFile() || stats.isSymbolicLink()) {\n throw new GitRepositoryError(\"conflict\", \"prototype-path-changed\");\n }\n await unlink(absolutePath);\n }\n}\n\nasync function removeEmptyDirectories(\n repository: GitRepository,\n pathspec: string,\n) {\n const root = resolve(repository.repositoryRoot, ...pathspec.split(\"/\"));\n const directories: string[] = [];\n const visit = async (absolutePath: string) => {\n let stats;\n try {\n stats = await lstat(absolutePath);\n } catch {\n return;\n }\n if (stats.isSymbolicLink() || !stats.isDirectory()) return;\n const relativePath = relative(repository.repositoryRoot, absolutePath)\n .split(sep)\n .join(\"/\");\n if (relativePath !== pathspec && isProtectedPath(relativePath)) return;\n for (const entry of await readdir(absolutePath)) {\n await visit(join(absolutePath, entry));\n }\n directories.push(absolutePath);\n };\n await visit(root);\n for (const directory of directories) {\n try {\n await assertSafeParents(repository, directory);\n await rmdir(directory);\n } catch {\n // Non-empty directories contain protected or unrelated content.\n }\n }\n}\n\nexport async function discardPrototypePath(\n repository: GitRepository,\n pathspec: string,\n) {\n const plan = await inspectPrototypePath(repository, pathspec);\n if (\n plan.changedEligiblePaths.length === 0 &&\n plan.newEligiblePaths.length === 0\n ) {\n return;\n }\n\n const trackedChanged = plan.changedEligiblePaths.filter((path) =>\n plan.trackedEligiblePaths.includes(path),\n );\n const stagedAdditions = plan.stagedEligiblePaths;\n if (stagedAdditions.length > 0) {\n await repository.raw([\"reset\", \"HEAD\", \"--\", ...stagedAdditions]);\n }\n if (trackedChanged.length > 0) {\n await repository.raw([\"reset\", \"HEAD\", \"--\", ...trackedChanged]);\n await repository.restore(trackedChanged);\n }\n await removeFiles(repository, plan.newEligiblePaths);\n await removeEmptyDirectories(repository, pathspec);\n}\n\nexport async function deletePrototypePath(\n repository: GitRepository,\n pathspec: string,\n message?: string,\n) {\n const before = await inspectPrototypePath(repository, pathspec);\n // Untracked excluded files can safely remain in place while ordinary\n // prototype content is deleted. Tracked exclusions still block because\n // staging their surrounding type/deletion could silently alter protected\n // repository state.\n throwProtected(before.trackedProtectedPaths);\n if (before.stagedEligiblePaths.length > 0) {\n await repository.raw([\n \"reset\",\n \"HEAD\",\n \"--\",\n ...before.stagedEligiblePaths,\n ]);\n }\n await removeFiles(repository, before.existingEligiblePaths);\n await removeEmptyDirectories(repository, pathspec);\n\n const after = await inspectPrototypePath(repository, pathspec);\n throwProtected(after.trackedProtectedPaths);\n if (message === undefined || after.trackedEligiblePaths.length === 0) {\n return null;\n }\n await repository.add(after.trackedEligiblePaths);\n return repository.commit(message, after.trackedEligiblePaths);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { type BonfireIdentity, type GitMode, type RepositorySummary } from "@ebay-dt/bonfire-core";
1
+ import { type BonfireIdentity, type GitMode, type BranchPreparationAttention, type RepositorySummary } from "@ebay-dt/bonfire-core";
2
2
  import type { PrototypeCatalogLike } from "../prototype-catalog.js";
3
3
  import { type GitRepository } from "../git-repository.js";
4
4
  export type RepositorySummaryOptions = {
@@ -10,8 +10,14 @@ export type RepositorySummaryOptions = {
10
10
  mode: GitMode;
11
11
  remote: string;
12
12
  primaryBranch: string;
13
+ /** True when the running server must restart to load updated dependencies. */
14
+ restartRequired?: boolean;
13
15
  /** Fetch remote-tracking refs before comparing managed update state. */
14
16
  refresh?: boolean;
17
+ /** Latest non-ready preparation attempt retained across clean reads. */
18
+ preparationAttention?: BranchPreparationAttention;
19
+ /** Protected dependency-installation marker, if present. */
20
+ preparationPending?: boolean;
15
21
  };
16
22
  export declare function getRepositorySummary(options: RepositorySummaryOptions): Promise<RepositorySummary>;
17
23
  //# sourceMappingURL=repository-summary.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repository-summary.d.ts","sourceRoot":"","sources":["../../src/lifecycle/repository-summary.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,OAAO,EAOZ,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,aAAa,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAoHF,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAsI5B","sourcesContent":["import { relative } from \"node:path\";\nimport {\n getGitManagementCapabilities,\n parseRepositoryRelativePath,\n type BonfireIdentity,\n type GitMode,\n type PrototypeRef,\n type PrototypeSummary,\n type RepositoryAttentionCode,\n type RepositoryChange,\n type RepositoryChangeScope,\n type RepositoryChangeStatus,\n type RepositorySummary,\n} from \"@ebay-dt/bonfire-core\";\nimport type { PrototypeCatalogLike } from \"../prototype-catalog.js\";\nimport {\n type GitRepository,\n type GitStatusFile,\n} from \"../git-repository.js\";\nimport { hasUnresolvedRepositoryOperation } from \"./repository-synchronization.js\";\n\nexport type RepositorySummaryOptions = {\n repository: GitRepository;\n catalog: PrototypeCatalogLike;\n repositoryRoot: string;\n prototypeRoot: string;\n identity: BonfireIdentity;\n mode: GitMode;\n remote: string;\n primaryBranch: string;\n /** Fetch remote-tracking refs before comparing managed update state. */\n refresh?: boolean;\n};\n\ntype SummaryPathContext = {\n scope: RepositoryChangeScope;\n prototype: PrototypeRef | undefined;\n};\n\nfunction normalizePath(value: string) {\n return value.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//u, \"\");\n}\n\nfunction pathPrefix(repositoryRoot: string, prototypeRoot: string) {\n const value = normalizePath(relative(repositoryRoot, prototypeRoot));\n return value === \".\" ? \"\" : value.replace(/\\/+$/u, \"\");\n}\n\nfunction summaryKey(ref: PrototypeRef) {\n return `${ref.login}/${ref.slug}`;\n}\n\nfunction contextForPath(\n value: string,\n prefix: string,\n summaries: ReadonlyMap<string, PrototypeSummary>,\n identity: BonfireIdentity,\n): SummaryPathContext {\n const path = normalizePath(value);\n const rest = prefix === \"\" ? path : path.startsWith(`${prefix}/`) ? path.slice(prefix.length + 1) : null;\n if (rest === null) return { scope: \"out-of-scope\", prototype: undefined };\n\n const segments = rest.split(\"/\");\n const login = segments[0];\n const slug = segments[1];\n if (login === undefined || slug === undefined) {\n return { scope: \"unknown\", prototype: undefined };\n }\n\n const ref = { login, slug } satisfies PrototypeRef;\n const summary = summaries.get(summaryKey(ref));\n if (summary === undefined) return { scope: \"unknown\", prototype: undefined };\n\n return {\n scope: login === identity.login ? \"owned-prototype\" : \"other-prototype\",\n prototype: ref,\n };\n}\n\nfunction safePath(value: string) {\n try {\n return parseRepositoryRelativePath(normalizePath(value));\n } catch {\n return null;\n }\n}\n\nfunction addChange(\n changes: Map<string, RepositoryChange>,\n path: string,\n status: RepositoryChangeStatus,\n context: SummaryPathContext,\n) {\n const safe = safePath(path);\n if (safe === null) return;\n const key = `${status}:${safe}`;\n changes.set(key, {\n path: safe,\n status,\n scope: context.scope,\n ...(context.prototype === undefined ? {} : { prototype: context.prototype }),\n });\n}\n\nfunction statusPaths(file: GitStatusFile) {\n return [file.path, ...(file.from === undefined ? [] : [file.from])];\n}\n\nasync function diffPaths(repository: GitRepository, range: string) {\n const output = await repository.raw([\"diff\", \"--name-only\", \"-z\", range]);\n return output.split(\"\\0\").filter((path) => path.length > 0);\n}\n\nasync function refRelation(repository: GitRepository, left: string, right: string) {\n const output = await repository.raw([\n \"rev-list\",\n \"--left-right\",\n \"--count\",\n `${left}...${right}`,\n ]);\n const values = output.trim().split(/\\s+/u).map(Number);\n if (\n values.length !== 2 ||\n values.some((value) => !Number.isSafeInteger(value) || value < 0)\n ) {\n throw new Error(\"Malformed Git ref relation\");\n }\n return { ahead: values[0]!, behind: values[1]! };\n}\n\nfunction addDiffChanges(\n changes: Map<string, RepositoryChange>,\n paths: readonly string[],\n status: RepositoryChangeStatus,\n prefix: string,\n summaries: ReadonlyMap<string, PrototypeSummary>,\n identity: BonfireIdentity,\n) {\n for (const path of paths) {\n addChange(\n changes,\n path,\n status,\n contextForPath(path, prefix, summaries, identity),\n );\n }\n}\n\nexport async function getRepositorySummary(\n options: RepositorySummaryOptions,\n): Promise<RepositorySummary> {\n const summaries = new Map(\n (await options.catalog.scan()).map((summary) => [\n summaryKey(summary.ref),\n summary,\n ]),\n );\n const prefix = pathPrefix(options.repositoryRoot, options.prototypeRoot);\n const changes = new Map<string, RepositoryChange>();\n const attention = new Set<RepositoryAttentionCode>();\n const status = await options.repository.status();\n const currentBranch = await options.repository.currentBranch();\n const unresolved = await hasUnresolvedRepositoryOperation(options.repository);\n const detached = currentBranch === \"HEAD\" || currentBranch.length === 0;\n const wrongBranch = !detached && currentBranch !== options.identity.login;\n\n for (const file of status.files) {\n const fileStatus =\n file.index.includes(\"U\") || file.workingDirectory.includes(\"U\")\n ? \"unresolved\"\n : \"uncommitted\";\n for (const path of statusPaths(file)) {\n addChange(\n changes,\n path,\n fileStatus,\n contextForPath(path, prefix, summaries, options.identity),\n );\n }\n }\n\n if (status.files.length > 0) attention.add(\"dirty-worktree\");\n if (unresolved) attention.add(\"unresolved-operation\");\n if (detached) attention.add(\"detached-head\");\n if (wrongBranch) attention.add(\"wrong-branch\");\n\n const remotes = await options.repository.remotes();\n const hasRemote = remotes.some((remote) => remote.name === options.remote);\n let fetchFailed = false;\n if (options.refresh && options.mode === \"managed\" && hasRemote) {\n try {\n await options.repository.fetch(options.remote);\n } catch {\n fetchFailed = true;\n attention.add(\"fetch-failed\");\n }\n }\n const branches = await options.repository.branches();\n const localIdentity = branches.all.includes(options.identity.login);\n const remoteIdentity = `remotes/${options.remote}/${options.identity.login}`;\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemoteIdentity = hasRemote && branches.all.includes(remoteIdentity);\n const hasRemotePrimary = hasRemote && branches.all.includes(remotePrimary);\n let updateCheck: RepositorySummary[\"updateCheck\"] =\n options.mode === \"manual\" ? \"not-checked\" : \"unavailable\";\n\n if (options.mode === \"managed\") {\n if (fetchFailed) {\n updateCheck = \"attention\";\n } else if (!hasRemote) {\n updateCheck = \"unavailable\";\n } else if (!hasRemotePrimary || !localIdentity) {\n updateCheck = \"attention\";\n attention.add(\"missing-primary\");\n } else {\n try {\n const primaryRelation = await refRelation(\n options.repository,\n options.identity.login,\n remotePrimary,\n );\n updateCheck =\n primaryRelation.behind > 0 ? \"updates-available\" : \"up-to-date\";\n if (primaryRelation.behind > 0) {\n addDiffChanges(\n changes,\n await diffPaths(\n options.repository,\n `${options.identity.login}..${remotePrimary}`,\n ),\n \"updates-available\",\n prefix,\n summaries,\n options.identity,\n );\n }\n } catch {\n updateCheck = \"attention\";\n attention.add(\"unknown\");\n }\n }\n\n if (hasRemoteIdentity && localIdentity) {\n try {\n const identityRelation = await refRelation(\n options.repository,\n options.identity.login,\n remoteIdentity,\n );\n if (identityRelation.ahead > 0) {\n addDiffChanges(\n changes,\n await diffPaths(\n options.repository,\n `${remoteIdentity}..${options.identity.login}`,\n ),\n \"committed-unpushed\",\n prefix,\n summaries,\n options.identity,\n );\n }\n } catch {\n attention.add(\"unknown\");\n }\n }\n }\n\n let readiness: RepositorySummary[\"readiness\"] = \"ready\";\n if (detached || wrongBranch || unresolved) {\n readiness = \"blocked\";\n } else if (!status.clean) {\n readiness = \"attention\";\n }\n return {\n mode: options.mode,\n readiness,\n updateCheck,\n changes: [...changes.values()].sort((left, right) =>\n left.path < right.path ? -1 : left.path > right.path ? 1 : 0,\n ),\n attention: [...attention].sort(),\n capabilities: getGitManagementCapabilities(options.mode),\n };\n}\n"]}
1
+ {"version":3,"file":"repository-summary.d.ts","sourceRoot":"","sources":["../../src/lifecycle/repository-summary.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,OAAO,EAOZ,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,sBAAsB,CAAC;AAG9E,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,aAAa,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,4DAA4D;IAC5D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AA+HF,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAgJ5B","sourcesContent":["import { relative } from \"node:path\";\nimport {\n getGitManagementCapabilities,\n parseRepositoryRelativePath,\n type BonfireIdentity,\n type GitMode,\n type PrototypeRef,\n type PrototypeSummary,\n type RepositoryAttentionCode,\n type RepositoryChange,\n type RepositoryChangeScope,\n type RepositoryChangeStatus,\n type BranchPreparationAttention,\n type RepositorySummary,\n} from \"@ebay-dt/bonfire-core\";\nimport type { PrototypeCatalogLike } from \"../prototype-catalog.js\";\nimport { type GitRepository, type GitStatusFile } from \"../git-repository.js\";\nimport { hasUnresolvedRepositoryOperation } from \"./repository-synchronization.js\";\n\nexport type RepositorySummaryOptions = {\n repository: GitRepository;\n catalog: PrototypeCatalogLike;\n repositoryRoot: string;\n prototypeRoot: string;\n identity: BonfireIdentity;\n mode: GitMode;\n remote: string;\n primaryBranch: string;\n /** True when the running server must restart to load updated dependencies. */\n restartRequired?: boolean;\n /** Fetch remote-tracking refs before comparing managed update state. */\n refresh?: boolean;\n /** Latest non-ready preparation attempt retained across clean reads. */\n preparationAttention?: BranchPreparationAttention;\n /** Protected dependency-installation marker, if present. */\n preparationPending?: boolean;\n};\n\ntype SummaryPathContext = {\n scope: RepositoryChangeScope;\n prototype: PrototypeRef | undefined;\n};\n\nfunction normalizePath(value: string) {\n return value.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//u, \"\");\n}\n\nfunction pathPrefix(repositoryRoot: string, prototypeRoot: string) {\n const value = normalizePath(relative(repositoryRoot, prototypeRoot));\n return value === \".\" ? \"\" : value.replace(/\\/+$/u, \"\");\n}\n\nfunction summaryKey(ref: PrototypeRef) {\n return `${ref.login}/${ref.slug}`;\n}\n\nfunction contextForPath(\n value: string,\n prefix: string,\n summaries: ReadonlyMap<string, PrototypeSummary>,\n identity: BonfireIdentity,\n): SummaryPathContext {\n const path = normalizePath(value);\n const rest =\n prefix === \"\"\n ? path\n : path.startsWith(`${prefix}/`)\n ? path.slice(prefix.length + 1)\n : null;\n if (rest === null) return { scope: \"out-of-scope\", prototype: undefined };\n\n const segments = rest.split(\"/\");\n const login = segments[0];\n const slug = segments[1];\n if (login === undefined || slug === undefined) {\n return { scope: \"unknown\", prototype: undefined };\n }\n\n const ref = { login, slug } satisfies PrototypeRef;\n const summary = summaries.get(summaryKey(ref));\n if (summary === undefined) return { scope: \"unknown\", prototype: undefined };\n\n return {\n scope: login === identity.login ? \"owned-prototype\" : \"other-prototype\",\n prototype: ref,\n };\n}\n\nfunction safePath(value: string) {\n try {\n return parseRepositoryRelativePath(normalizePath(value));\n } catch {\n return null;\n }\n}\n\nfunction addChange(\n changes: Map<string, RepositoryChange>,\n path: string,\n status: RepositoryChangeStatus,\n context: SummaryPathContext,\n) {\n const safe = safePath(path);\n if (safe === null) return;\n const key = `${status}:${safe}`;\n changes.set(key, {\n path: safe,\n status,\n scope: context.scope,\n ...(context.prototype === undefined\n ? {}\n : { prototype: context.prototype }),\n });\n}\n\nfunction statusPaths(file: GitStatusFile) {\n return [file.path, ...(file.from === undefined ? [] : [file.from])];\n}\n\nasync function diffPaths(repository: GitRepository, range: string) {\n const output = await repository.raw([\"diff\", \"--name-only\", \"-z\", range]);\n return output.split(\"\\0\").filter((path) => path.length > 0);\n}\n\nasync function refRelation(\n repository: GitRepository,\n left: string,\n right: string,\n) {\n const output = await repository.raw([\n \"rev-list\",\n \"--left-right\",\n \"--count\",\n `${left}...${right}`,\n ]);\n const values = output.trim().split(/\\s+/u).map(Number);\n if (\n values.length !== 2 ||\n values.some((value) => !Number.isSafeInteger(value) || value < 0)\n ) {\n throw new Error(\"Malformed Git ref relation\");\n }\n return { ahead: values[0]!, behind: values[1]! };\n}\n\nfunction addDiffChanges(\n changes: Map<string, RepositoryChange>,\n paths: readonly string[],\n status: RepositoryChangeStatus,\n prefix: string,\n summaries: ReadonlyMap<string, PrototypeSummary>,\n identity: BonfireIdentity,\n) {\n for (const path of paths) {\n addChange(\n changes,\n path,\n status,\n contextForPath(path, prefix, summaries, identity),\n );\n }\n}\n\nexport async function getRepositorySummary(\n options: RepositorySummaryOptions,\n): Promise<RepositorySummary> {\n const summaries = new Map(\n (await options.catalog.scan()).map((summary) => [\n summaryKey(summary.ref),\n summary,\n ]),\n );\n const prefix = pathPrefix(options.repositoryRoot, options.prototypeRoot);\n const changes = new Map<string, RepositoryChange>();\n const attention = new Set<RepositoryAttentionCode>();\n const status = await options.repository.status();\n const currentBranch = await options.repository.currentBranch();\n const unresolved = await hasUnresolvedRepositoryOperation(options.repository);\n const detached = currentBranch === \"HEAD\" || currentBranch.length === 0;\n const wrongBranch = !detached && currentBranch !== options.identity.login;\n\n for (const file of status.files) {\n const fileStatus =\n file.index.includes(\"U\") || file.workingDirectory.includes(\"U\")\n ? \"unresolved\"\n : \"uncommitted\";\n for (const path of statusPaths(file)) {\n addChange(\n changes,\n path,\n fileStatus,\n contextForPath(path, prefix, summaries, options.identity),\n );\n }\n }\n\n const dirtyWorktree = status.files.length > 0;\n if (unresolved) attention.add(\"unresolved-operation\");\n if (detached) attention.add(\"detached-head\");\n if (wrongBranch) attention.add(\"wrong-branch\");\n\n const remotes = await options.repository.remotes();\n const hasRemote = remotes.some((remote) => remote.name === options.remote);\n let fetchFailed = false;\n if (options.refresh && options.mode === \"managed\" && hasRemote) {\n try {\n await options.repository.fetch(options.remote);\n } catch {\n fetchFailed = true;\n attention.add(\"fetch-failed\");\n }\n }\n const branches = await options.repository.branches();\n const localIdentity = branches.all.includes(options.identity.login);\n const remoteIdentity = `remotes/${options.remote}/${options.identity.login}`;\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemoteIdentity = hasRemote && branches.all.includes(remoteIdentity);\n const hasRemotePrimary = hasRemote && branches.all.includes(remotePrimary);\n let updateCheck: RepositorySummary[\"updateCheck\"] =\n options.mode === \"manual\" ? \"not-checked\" : \"unavailable\";\n\n if (options.mode === \"managed\") {\n if (fetchFailed) {\n updateCheck = \"attention\";\n } else if (!hasRemote) {\n updateCheck = \"unavailable\";\n } else if (!hasRemotePrimary || !localIdentity) {\n updateCheck = \"attention\";\n attention.add(\"missing-primary\");\n } else {\n try {\n const primaryRelation = await refRelation(\n options.repository,\n options.identity.login,\n remotePrimary,\n );\n updateCheck =\n primaryRelation.behind > 0 ? \"updates-available\" : \"up-to-date\";\n if (primaryRelation.behind > 0) {\n if (dirtyWorktree) attention.add(\"dirty-worktree\");\n addDiffChanges(\n changes,\n await diffPaths(\n options.repository,\n `${options.identity.login}..${remotePrimary}`,\n ),\n \"updates-available\",\n prefix,\n summaries,\n options.identity,\n );\n }\n } catch {\n updateCheck = \"attention\";\n attention.add(\"unknown\");\n }\n }\n\n if (hasRemoteIdentity && localIdentity) {\n try {\n const identityRelation = await refRelation(\n options.repository,\n options.identity.login,\n remoteIdentity,\n );\n if (identityRelation.ahead > 0) {\n addDiffChanges(\n changes,\n await diffPaths(\n options.repository,\n `${remoteIdentity}..${options.identity.login}`,\n ),\n \"committed-unpushed\",\n prefix,\n summaries,\n options.identity,\n );\n }\n } catch {\n attention.add(\"unknown\");\n }\n }\n }\n\n let readiness: RepositorySummary[\"readiness\"] = \"ready\";\n if (detached || wrongBranch || unresolved) {\n readiness = \"blocked\";\n } else if (!status.clean) {\n readiness = \"attention\";\n }\n return {\n mode: options.mode,\n ...(options.preparationAttention === undefined\n ? {}\n : { preparationAttention: options.preparationAttention }),\n ...(options.preparationPending === undefined\n ? {}\n : { preparationPending: options.preparationPending }),\n ...(options.restartRequired === undefined\n ? {}\n : { restartRequired: options.restartRequired }),\n readiness,\n updateCheck,\n changes: [...changes.values()].sort((left, right) =>\n left.path < right.path ? -1 : left.path > right.path ? 1 : 0,\n ),\n attention: [...attention].sort(),\n capabilities: getGitManagementCapabilities(options.mode),\n };\n}\n"]}
@@ -13,7 +13,11 @@ function summaryKey(ref) {
13
13
  }
14
14
  function contextForPath(value, prefix, summaries, identity) {
15
15
  const path = normalizePath(value);
16
- const rest = prefix === "" ? path : path.startsWith(`${prefix}/`) ? path.slice(prefix.length + 1) : null;
16
+ const rest = prefix === ""
17
+ ? path
18
+ : path.startsWith(`${prefix}/`)
19
+ ? path.slice(prefix.length + 1)
20
+ : null;
17
21
  if (rest === null)
18
22
  return { scope: "out-of-scope", prototype: undefined };
19
23
  const segments = rest.split("/");
@@ -48,7 +52,9 @@ function addChange(changes, path, status, context) {
48
52
  path: safe,
49
53
  status,
50
54
  scope: context.scope,
51
- ...(context.prototype === undefined ? {} : { prototype: context.prototype }),
55
+ ...(context.prototype === undefined
56
+ ? {}
57
+ : { prototype: context.prototype }),
52
58
  });
53
59
  }
54
60
  function statusPaths(file) {
@@ -98,8 +104,7 @@ export async function getRepositorySummary(options) {
98
104
  addChange(changes, path, fileStatus, contextForPath(path, prefix, summaries, options.identity));
99
105
  }
100
106
  }
101
- if (status.files.length > 0)
102
- attention.add("dirty-worktree");
107
+ const dirtyWorktree = status.files.length > 0;
103
108
  if (unresolved)
104
109
  attention.add("unresolved-operation");
105
110
  if (detached)
@@ -142,6 +147,8 @@ export async function getRepositorySummary(options) {
142
147
  updateCheck =
143
148
  primaryRelation.behind > 0 ? "updates-available" : "up-to-date";
144
149
  if (primaryRelation.behind > 0) {
150
+ if (dirtyWorktree)
151
+ attention.add("dirty-worktree");
145
152
  addDiffChanges(changes, await diffPaths(options.repository, `${options.identity.login}..${remotePrimary}`), "updates-available", prefix, summaries, options.identity);
146
153
  }
147
154
  }
@@ -171,6 +178,15 @@ export async function getRepositorySummary(options) {
171
178
  }
172
179
  return {
173
180
  mode: options.mode,
181
+ ...(options.preparationAttention === undefined
182
+ ? {}
183
+ : { preparationAttention: options.preparationAttention }),
184
+ ...(options.preparationPending === undefined
185
+ ? {}
186
+ : { preparationPending: options.preparationPending }),
187
+ ...(options.restartRequired === undefined
188
+ ? {}
189
+ : { restartRequired: options.restartRequired }),
174
190
  readiness,
175
191
  updateCheck,
176
192
  changes: [...changes.values()].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0),