ushman-ledger 1.2.1 → 1.2.2
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/ARCHITECTURE.md +79 -0
- package/README.md +87 -0
- package/TROUBLESHOOTING.md +170 -0
- package/dist/blobs.d.ts +3 -0
- package/dist/blobs.d.ts.map +1 -1
- package/dist/blobs.js +41 -15
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +85 -27
- package/dist/coverage.d.ts.map +1 -1
- package/dist/coverage.js +3 -2
- package/dist/doctor.d.ts +17 -4
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +224 -57
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/list.d.ts +2 -1
- package/dist/list.d.ts.map +1 -1
- package/dist/list.js +19 -9
- package/dist/patch-resolver.d.ts.map +1 -1
- package/dist/patch-resolver.js +193 -53
- package/dist/read-index.d.ts.map +1 -1
- package/dist/read-index.js +6 -5
- package/dist/record.d.ts.map +1 -1
- package/dist/record.js +2 -1
- package/dist/runtime-config.d.ts +12 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +83 -0
- package/dist/storage/filesystem.d.ts +1 -0
- package/dist/storage/filesystem.d.ts.map +1 -1
- package/dist/storage/filesystem.js +33 -3
- package/dist/text-lines.d.ts +8 -0
- package/dist/text-lines.d.ts.map +1 -0
- package/dist/text-lines.js +20 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -1
- package/package.json +4 -2
package/dist/patch-resolver.js
CHANGED
|
@@ -1,28 +1,94 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import * as v from 'valibot';
|
|
4
|
-
import { resolveBlobPath, storePatchBlob } from "./blobs.js";
|
|
4
|
+
import { assertPatchTextWithinLimit, readPatchTextFromFile, resolveBlobPath, storePatchBlob, } from "./blobs.js";
|
|
5
5
|
import { WorkspaceRelativePathSchema, } from "./schema/entry.js";
|
|
6
|
+
import { forEachLine } from "./text-lines.js";
|
|
6
7
|
const readBlobText = async (workspaceRoot, blobSha256) => readFile(resolveBlobPath(workspaceRoot, blobSha256), 'utf8');
|
|
7
8
|
const formatRecordContext = (record) => `${record.kind} "${record.summary}" [${record.phase}]`;
|
|
8
9
|
const normalizeWorkspaceRelativePath = (value) => v.parse(WorkspaceRelativePathSchema, value.replaceAll('\\', '/'));
|
|
9
|
-
const
|
|
10
|
+
const normalizeDiffPathToken = ({ headerPrefixes, side, value, }) => {
|
|
10
11
|
const normalized = value.trim().replace(/^"([^"]*)"$/u, '$1');
|
|
11
12
|
if (normalized === '/dev/null') {
|
|
12
13
|
return null;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const expectedPrefix = side === 'old' ? headerPrefixes?.oldPrefix : headerPrefixes?.newPrefix;
|
|
16
|
+
if (expectedPrefix && normalized.startsWith(expectedPrefix)) {
|
|
17
|
+
return normalized.slice(expectedPrefix.length);
|
|
16
18
|
}
|
|
17
19
|
return normalized;
|
|
18
20
|
};
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
21
|
+
const splitPrefixCandidate = (value) => {
|
|
22
|
+
const slashIndex = value.indexOf('/');
|
|
23
|
+
if (slashIndex <= 0) {
|
|
22
24
|
return null;
|
|
23
25
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
return {
|
|
27
|
+
prefix: value.slice(0, slashIndex + 1),
|
|
28
|
+
suffix: value.slice(slashIndex + 1),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const readDiffHeaderToken = (headerBody, startIndex) => {
|
|
32
|
+
let index = startIndex;
|
|
33
|
+
while (headerBody[index] === ' ') {
|
|
34
|
+
index += 1;
|
|
35
|
+
}
|
|
36
|
+
if (index >= headerBody.length) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (headerBody[index] === '"') {
|
|
40
|
+
const endIndex = headerBody.indexOf('"', index + 1);
|
|
41
|
+
if (endIndex === -1) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
nextIndex: endIndex + 1,
|
|
46
|
+
token: headerBody.slice(index + 1, endIndex),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
let endIndex = index;
|
|
50
|
+
while (endIndex < headerBody.length && headerBody[endIndex] !== ' ') {
|
|
51
|
+
endIndex += 1;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
nextIndex: endIndex + 1,
|
|
55
|
+
token: headerBody.slice(index, endIndex),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const resolveHeaderPrefixes = (oldPath, newPath) => {
|
|
59
|
+
if (oldPath.startsWith('a/') && newPath.startsWith('b/')) {
|
|
60
|
+
return {
|
|
61
|
+
newPrefix: 'b/',
|
|
62
|
+
oldPrefix: 'a/',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const oldCandidate = splitPrefixCandidate(oldPath);
|
|
66
|
+
const newCandidate = splitPrefixCandidate(newPath);
|
|
67
|
+
if (!oldCandidate ||
|
|
68
|
+
!newCandidate ||
|
|
69
|
+
oldCandidate.prefix === newCandidate.prefix ||
|
|
70
|
+
oldCandidate.suffix !== newCandidate.suffix) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
newPrefix: newCandidate.prefix,
|
|
75
|
+
oldPrefix: oldCandidate.prefix,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const parseDiffHeaderPaths = (line) => {
|
|
79
|
+
const headerBody = line.trim().slice('diff --git '.length);
|
|
80
|
+
const firstToken = readDiffHeaderToken(headerBody, 0);
|
|
81
|
+
const secondToken = firstToken ? readDiffHeaderToken(headerBody, firstToken.nextIndex) : null;
|
|
82
|
+
if (!firstToken?.token || !secondToken?.token) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const oldPath = firstToken.token;
|
|
86
|
+
const newPath = secondToken.token;
|
|
87
|
+
return {
|
|
88
|
+
headerPrefixes: resolveHeaderPrefixes(oldPath, newPath),
|
|
89
|
+
newPath,
|
|
90
|
+
oldPath,
|
|
91
|
+
};
|
|
26
92
|
};
|
|
27
93
|
const finalizeFileChange = (current, filesChanged) => {
|
|
28
94
|
if (!current) {
|
|
@@ -38,19 +104,33 @@ const finalizeFileChange = (current, filesChanged) => {
|
|
|
38
104
|
removed: current.removed > 0 ? current.removed : undefined,
|
|
39
105
|
});
|
|
40
106
|
};
|
|
41
|
-
const createDiffFileAccumulator = (line) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
107
|
+
const createDiffFileAccumulator = (line) => {
|
|
108
|
+
const headerPaths = parseDiffHeaderPaths(line);
|
|
109
|
+
return {
|
|
110
|
+
added: 0,
|
|
111
|
+
headerPath: headerPaths
|
|
112
|
+
? normalizeDiffPathToken({
|
|
113
|
+
headerPrefixes: headerPaths.headerPrefixes,
|
|
114
|
+
side: 'new',
|
|
115
|
+
value: headerPaths.newPath,
|
|
116
|
+
})
|
|
117
|
+
: null,
|
|
118
|
+
headerPrefixes: headerPaths?.headerPrefixes ?? null,
|
|
119
|
+
minusPath: null,
|
|
120
|
+
plusPath: null,
|
|
121
|
+
removed: 0,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
48
124
|
const applyPatchMetadataLine = (current, line) => {
|
|
49
125
|
if (line.startsWith('+++ ')) {
|
|
50
126
|
return {
|
|
51
127
|
next: {
|
|
52
128
|
...current,
|
|
53
|
-
plusPath:
|
|
129
|
+
plusPath: normalizeDiffPathToken({
|
|
130
|
+
headerPrefixes: current.headerPrefixes,
|
|
131
|
+
side: 'new',
|
|
132
|
+
value: line.slice(4),
|
|
133
|
+
}),
|
|
54
134
|
},
|
|
55
135
|
};
|
|
56
136
|
}
|
|
@@ -58,7 +138,35 @@ const applyPatchMetadataLine = (current, line) => {
|
|
|
58
138
|
return {
|
|
59
139
|
next: {
|
|
60
140
|
...current,
|
|
61
|
-
minusPath:
|
|
141
|
+
minusPath: normalizeDiffPathToken({
|
|
142
|
+
headerPrefixes: current.headerPrefixes,
|
|
143
|
+
side: 'old',
|
|
144
|
+
value: line.slice(4),
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (line.startsWith('rename from ')) {
|
|
150
|
+
return {
|
|
151
|
+
next: {
|
|
152
|
+
...current,
|
|
153
|
+
minusPath: normalizeDiffPathToken({
|
|
154
|
+
headerPrefixes: null,
|
|
155
|
+
side: 'old',
|
|
156
|
+
value: line.slice('rename from '.length),
|
|
157
|
+
}),
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (line.startsWith('rename to ')) {
|
|
162
|
+
return {
|
|
163
|
+
next: {
|
|
164
|
+
...current,
|
|
165
|
+
plusPath: normalizeDiffPathToken({
|
|
166
|
+
headerPrefixes: null,
|
|
167
|
+
side: 'new',
|
|
168
|
+
value: line.slice('rename to '.length),
|
|
169
|
+
}),
|
|
62
170
|
},
|
|
63
171
|
};
|
|
64
172
|
}
|
|
@@ -74,16 +182,12 @@ const applyPatchMetadataLine = (current, line) => {
|
|
|
74
182
|
};
|
|
75
183
|
const applyPatchHunkLine = (current, line) => {
|
|
76
184
|
if (line.startsWith('+')) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
added: current.added + 1,
|
|
80
|
-
};
|
|
185
|
+
current.added += 1;
|
|
186
|
+
return current;
|
|
81
187
|
}
|
|
82
188
|
if (line.startsWith('-')) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
removed: current.removed + 1,
|
|
86
|
-
};
|
|
189
|
+
current.removed += 1;
|
|
190
|
+
return current;
|
|
87
191
|
}
|
|
88
192
|
return current;
|
|
89
193
|
};
|
|
@@ -91,36 +195,56 @@ const mergeBlobLink = (links, blobSha256) => ({
|
|
|
91
195
|
...links,
|
|
92
196
|
blobs: [...new Set([...(links?.blobs ?? []), blobSha256])],
|
|
93
197
|
});
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
198
|
+
const resolvePatchSourceFromFile = async (record, workspaceRoot) => {
|
|
199
|
+
const resolvedPath = path.resolve(workspaceRoot, record.diffPath);
|
|
200
|
+
try {
|
|
97
201
|
return {
|
|
98
|
-
patchText: await
|
|
202
|
+
patchText: await readPatchTextFromFile(resolvedPath),
|
|
99
203
|
source: 'file',
|
|
100
204
|
sourceLabel: resolvedPath,
|
|
101
205
|
};
|
|
102
206
|
}
|
|
103
|
-
|
|
207
|
+
catch (error) {
|
|
208
|
+
throw new Error(`Failed to read diff file ${resolvedPath} for ${formatRecordContext(record)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const resolvePatchSourceFromInlineText = (record) => {
|
|
212
|
+
try {
|
|
213
|
+
assertPatchTextWithinLimit(record.diffText, 'inline diff text');
|
|
104
214
|
return {
|
|
105
215
|
patchText: record.diffText,
|
|
106
216
|
source: 'inline',
|
|
107
217
|
sourceLabel: 'inline diff text',
|
|
108
218
|
};
|
|
109
219
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
220
|
+
catch (error) {
|
|
221
|
+
throw new Error(`Failed to validate inline diff text for ${formatRecordContext(record)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const resolvePatchSourceFromStoredBlob = async (record, workspaceRoot) => {
|
|
225
|
+
try {
|
|
226
|
+
return {
|
|
227
|
+
patchText: await readBlobText(workspaceRoot, record.diff?.blobSha256),
|
|
228
|
+
source: 'blob',
|
|
229
|
+
sourceLabel: `blob ${record.diff?.blobSha256}`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (error.code === 'ENOENT') {
|
|
234
|
+
throw new Error(`Patch blob ${record.diff?.blobSha256} was not found for ${formatRecordContext(record)}. Store it first or use diffPath.`);
|
|
123
235
|
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const resolvePatchSource = async ({ record, workspaceRoot, }) => {
|
|
240
|
+
if (record.diffPath) {
|
|
241
|
+
return resolvePatchSourceFromFile(record, workspaceRoot);
|
|
242
|
+
}
|
|
243
|
+
if (record.diffText) {
|
|
244
|
+
return resolvePatchSourceFromInlineText(record);
|
|
245
|
+
}
|
|
246
|
+
if (record.diff) {
|
|
247
|
+
return resolvePatchSourceFromStoredBlob(record, workspaceRoot);
|
|
124
248
|
}
|
|
125
249
|
throw new Error(`${record.kind} records require diff, diffPath, or diffText.`);
|
|
126
250
|
};
|
|
@@ -140,34 +264,34 @@ export const deriveFilesChangedFromPatch = (patchText) => {
|
|
|
140
264
|
const filesChanged = [];
|
|
141
265
|
let current;
|
|
142
266
|
let insideHunk = false;
|
|
143
|
-
|
|
267
|
+
forEachLine(patchText, (line) => {
|
|
144
268
|
if (line.startsWith('diff --git ')) {
|
|
145
269
|
finalizeFileChange(current, filesChanged);
|
|
146
270
|
current = createDiffFileAccumulator(line);
|
|
147
271
|
insideHunk = false;
|
|
148
|
-
|
|
272
|
+
return;
|
|
149
273
|
}
|
|
150
274
|
if (!current) {
|
|
151
|
-
|
|
275
|
+
return;
|
|
152
276
|
}
|
|
153
277
|
if (insideHunk) {
|
|
154
278
|
if (line.startsWith('@@')) {
|
|
155
|
-
|
|
279
|
+
return;
|
|
156
280
|
}
|
|
157
281
|
current = applyPatchHunkLine(current, line);
|
|
158
|
-
|
|
282
|
+
return;
|
|
159
283
|
}
|
|
160
284
|
const metadataUpdate = applyPatchMetadataLine(current, line);
|
|
161
285
|
current = metadataUpdate.next;
|
|
162
286
|
if (metadataUpdate.startHunk) {
|
|
163
287
|
insideHunk = true;
|
|
164
|
-
|
|
288
|
+
return;
|
|
165
289
|
}
|
|
166
290
|
if (!insideHunk) {
|
|
167
|
-
|
|
291
|
+
return;
|
|
168
292
|
}
|
|
169
293
|
current = applyPatchHunkLine(current, line);
|
|
170
|
-
}
|
|
294
|
+
});
|
|
171
295
|
finalizeFileChange(current, filesChanged);
|
|
172
296
|
return filesChanged;
|
|
173
297
|
};
|
|
@@ -180,6 +304,22 @@ const buildResolvedPatchRecord = ({ links, record, storedDiff, }) => {
|
|
|
180
304
|
};
|
|
181
305
|
};
|
|
182
306
|
export async function resolvePatchRecord({ record, workspaceRoot, }) {
|
|
307
|
+
if (record.diff && !record.diffPath && !record.diffText) {
|
|
308
|
+
try {
|
|
309
|
+
await stat(resolveBlobPath(workspaceRoot, record.diff.blobSha256));
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
if (error.code === 'ENOENT') {
|
|
313
|
+
throw new Error(`Patch blob ${record.diff.blobSha256} was not found for ${formatRecordContext(record)}. Store it first or use diffPath.`);
|
|
314
|
+
}
|
|
315
|
+
throw new Error(`Failed to read patch blob ${record.diff.blobSha256} for ${formatRecordContext(record)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
316
|
+
}
|
|
317
|
+
return buildResolvedPatchRecord({
|
|
318
|
+
links: mergeBlobLink(record.links, record.diff.blobSha256),
|
|
319
|
+
record,
|
|
320
|
+
storedDiff: record.diff,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
183
323
|
const { patchText, sourceLabel } = await resolvePatchSource({
|
|
184
324
|
record,
|
|
185
325
|
workspaceRoot,
|
package/dist/read-index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-index.d.ts","sourceRoot":"","sources":["../src/read-index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"read-index.d.ts","sourceRoot":"","sources":["../src/read-index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAI7B,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AACxH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D,QAAA,MAAM,oBAAoB;;;;aAIxB,CAAC;AAEH,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sDAc1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACxE,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AA4HhG,eAAO,MAAM,0BAA0B,GAAU,eAAe,MAAM,EAAE,UAAU,cAAc;;;;;;;;;;;EAyB/F,CAAC;AAYF,eAAO,MAAM,kBAAkB,GAAI,OAAO,eAAe,EAAE,UAAU,cAAc,YAG1C,CAAC;AAE1C,eAAO,MAAM,aAAa,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAWzF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,eAAe,MAAM,EAAE,WAAW,eAAe,kBAGpF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,eAAe,MAAM,EAAE,UAAU,cAAc;;;;;;;;;;;EAiB7F,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,iCAIpC;IACC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;;;;;;;;;;;CAYA,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,uCAIpC;IACC,QAAQ,CAAC,MAAM,EAAE;QACb,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;QAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CAC3C,YAeA,CAAC"}
|
package/dist/read-index.js
CHANGED
|
@@ -2,11 +2,10 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import * as v from 'valibot';
|
|
3
3
|
import { mapWithConcurrencyLimit } from "./async.js";
|
|
4
4
|
import { stableStringify } from "./json.js";
|
|
5
|
+
import { getLedgerRuntimeConfig } from "./runtime-config.js";
|
|
5
6
|
import { LEDGER_KINDS, parseLedgerEntry } from "./schema/entry.js";
|
|
6
7
|
import { readPhaseEntryText, resolveLedgerPaths, writeAtomicTextFile } from "./storage/filesystem.js";
|
|
7
8
|
const READ_INDEX_SCHEMA_VERSION = 'ushman-ledger-read-index/v1';
|
|
8
|
-
const ENTRY_READ_BATCH_SIZE = 32;
|
|
9
|
-
const ENTRY_READ_CONCURRENCY = 16;
|
|
10
9
|
const ReadIndexEntrySchema = v.object({
|
|
11
10
|
id: v.pipe(v.string(), v.minLength(1)),
|
|
12
11
|
kind: v.picklist(LEDGER_KINDS),
|
|
@@ -83,7 +82,7 @@ const readIndexedEntry = async ({ entryId, phase, workspaceRoot, }) => {
|
|
|
83
82
|
const text = await readPhaseEntryText(workspaceRoot, phase, `${entryId}.json`);
|
|
84
83
|
return parseLedgerEntry(JSON.parse(text));
|
|
85
84
|
};
|
|
86
|
-
const readIndexedEntryBatch = async ({ entryLocations, workspaceRoot, }) => mapWithConcurrencyLimit(entryLocations,
|
|
85
|
+
const readIndexedEntryBatch = async ({ entryReadConcurrency, entryLocations, workspaceRoot, }) => mapWithConcurrencyLimit(entryLocations, entryReadConcurrency, async ([entryId, location]) => readIndexedEntry({
|
|
87
86
|
entryId,
|
|
88
87
|
phase: location.phase,
|
|
89
88
|
workspaceRoot,
|
|
@@ -97,12 +96,14 @@ const collectCoveredFiles = (entry, coveredFiles) => {
|
|
|
97
96
|
}
|
|
98
97
|
};
|
|
99
98
|
export const buildReadIndexFromManifest = async (workspaceRoot, manifest) => {
|
|
99
|
+
const { readIndexRebuildBatchSize, readIndexRebuildConcurrency } = getLedgerRuntimeConfig();
|
|
100
100
|
const orderedEntryLocations = getManifestSequenceLocations(manifest);
|
|
101
101
|
const entries = [];
|
|
102
102
|
const coveredFiles = new Set();
|
|
103
|
-
for (let index = 0; index < orderedEntryLocations.length; index +=
|
|
104
|
-
const batch = orderedEntryLocations.slice(index, index +
|
|
103
|
+
for (let index = 0; index < orderedEntryLocations.length; index += readIndexRebuildBatchSize) {
|
|
104
|
+
const batch = orderedEntryLocations.slice(index, index + readIndexRebuildBatchSize);
|
|
105
105
|
const resolvedEntries = await readIndexedEntryBatch({
|
|
106
|
+
entryReadConcurrency: readIndexRebuildConcurrency,
|
|
106
107
|
entryLocations: batch,
|
|
107
108
|
workspaceRoot,
|
|
108
109
|
});
|
package/dist/record.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AASA,OAAO,EACH,KAAK,WAAW,EAMnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAU3D,KAAK,qBAAqB,GAAG;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpF,CAAC;AAOF,eAAO,MAAM,wBAAwB,GAAI,eAAe,MAAM,EAAE,OAAO,qBAAqB,GAAG,IAAI,SAOlG,CAAC;AA0EF,eAAO,MAAM,YAAY,GACrB,eAAe,MAAM,EACrB,OAAO,OAAO,KACf,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAsF5C,CAAC;AAEF,eAAO,MAAM,aAAa,GACtB,eAAe,MAAM,EACrB,UAAU,cAAc,EACxB,SAAS,MAAM,KAChB,OAAO,CAAC,WAAW,CASrB,CAAC"}
|
package/dist/record.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import * as v from 'valibot';
|
|
4
4
|
import { sha256Hex, stableStringify } from "./json.js";
|
|
5
|
+
import { getNextManifestSequence } from "./manifest-update.js";
|
|
5
6
|
import { updateManifestForEntry } from "./manifest-update.js";
|
|
6
7
|
import { resolvePatchRecord } from "./patch-resolver.js";
|
|
7
8
|
import { appendEntryToReadIndex, saveReadIndex } from "./read-index.js";
|
|
@@ -105,7 +106,7 @@ export const appendRecord = async (workspaceRoot, input) => {
|
|
|
105
106
|
id: existingId,
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
|
-
const nextSequence = manifest.lastSequence
|
|
109
|
+
const nextSequence = getNextManifestSequence(manifest.lastSequence);
|
|
109
110
|
const { idempotencyKey: _idempotencyKey, ...recordWithoutIdempotencyKey } = normalizedRecord;
|
|
110
111
|
const entryWithoutId = {
|
|
111
112
|
...recordWithoutIdempotencyKey,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type LedgerRuntimeConfig = {
|
|
2
|
+
readonly blobHashConcurrency: number;
|
|
3
|
+
readonly coverageFileStatConcurrency: number;
|
|
4
|
+
readonly maxPatchBytes: number;
|
|
5
|
+
readonly readIndexRebuildBatchSize: number;
|
|
6
|
+
readonly readIndexRebuildConcurrency: number;
|
|
7
|
+
readonly scanBatchSize: number;
|
|
8
|
+
readonly scanConcurrency: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const validateLedgerRuntimeConfig: (env?: NodeJS.ProcessEnv) => void;
|
|
11
|
+
export declare const getLedgerRuntimeConfig: (env?: NodeJS.ProcessEnv) => LedgerRuntimeConfig;
|
|
12
|
+
//# sourceMappingURL=runtime-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-config.d.ts","sourceRoot":"","sources":["../src/runtime-config.ts"],"names":[],"mappings":"AAgCA,MAAM,MAAM,mBAAmB,GAAG;IAC9B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;IAC3C,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACpC,CAAC;AAuDF,eAAO,MAAM,2BAA2B,GAAI,MAAK,MAAM,CAAC,UAAwB,SAE/E,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,mBAgB7E,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const DEFAULT_SCAN_BATCH_SIZE = 32;
|
|
2
|
+
const DEFAULT_SCAN_CONCURRENCY = 16;
|
|
3
|
+
const DEFAULT_MAX_PATCH_BYTES = 10 * 1024 * 1024;
|
|
4
|
+
const RUNTIME_CONFIG_ENV_VARS = [
|
|
5
|
+
'USHMAN_LEDGER_BLOB_HASH_CONCURRENCY',
|
|
6
|
+
'USHMAN_LEDGER_COVERAGE_FILE_STAT_CONCURRENCY',
|
|
7
|
+
'USHMAN_LEDGER_MAX_PATCH_BYTES',
|
|
8
|
+
'USHMAN_LEDGER_READ_INDEX_REBUILD_BATCH_SIZE',
|
|
9
|
+
'USHMAN_LEDGER_READ_INDEX_REBUILD_CONCURRENCY',
|
|
10
|
+
'USHMAN_LEDGER_SCAN_BATCH_SIZE',
|
|
11
|
+
'USHMAN_LEDGER_SCAN_CONCURRENCY',
|
|
12
|
+
];
|
|
13
|
+
const parsePositiveIntegerEnv = ({ defaultValue, env, envVar, }) => {
|
|
14
|
+
const rawValue = env[envVar];
|
|
15
|
+
if (!rawValue) {
|
|
16
|
+
return defaultValue;
|
|
17
|
+
}
|
|
18
|
+
if (!/^[1-9]\d*$/u.test(rawValue)) {
|
|
19
|
+
throw new Error(`Invalid ${envVar} value: ${rawValue}. Expected a positive integer.`);
|
|
20
|
+
}
|
|
21
|
+
return Number.parseInt(rawValue, 10);
|
|
22
|
+
};
|
|
23
|
+
let processEnvRuntimeConfigCache;
|
|
24
|
+
const getRuntimeConfigSignature = (env) => RUNTIME_CONFIG_ENV_VARS.map((envVar) => `${envVar}=${env[envVar] ?? ''}`).join('\u0000');
|
|
25
|
+
const buildLedgerRuntimeConfig = (env) => {
|
|
26
|
+
const scanBatchSize = parsePositiveIntegerEnv({
|
|
27
|
+
defaultValue: DEFAULT_SCAN_BATCH_SIZE,
|
|
28
|
+
env,
|
|
29
|
+
envVar: 'USHMAN_LEDGER_SCAN_BATCH_SIZE',
|
|
30
|
+
});
|
|
31
|
+
const scanConcurrency = parsePositiveIntegerEnv({
|
|
32
|
+
defaultValue: DEFAULT_SCAN_CONCURRENCY,
|
|
33
|
+
env,
|
|
34
|
+
envVar: 'USHMAN_LEDGER_SCAN_CONCURRENCY',
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
blobHashConcurrency: parsePositiveIntegerEnv({
|
|
38
|
+
defaultValue: scanConcurrency,
|
|
39
|
+
env,
|
|
40
|
+
envVar: 'USHMAN_LEDGER_BLOB_HASH_CONCURRENCY',
|
|
41
|
+
}),
|
|
42
|
+
coverageFileStatConcurrency: parsePositiveIntegerEnv({
|
|
43
|
+
defaultValue: scanConcurrency,
|
|
44
|
+
env,
|
|
45
|
+
envVar: 'USHMAN_LEDGER_COVERAGE_FILE_STAT_CONCURRENCY',
|
|
46
|
+
}),
|
|
47
|
+
maxPatchBytes: parsePositiveIntegerEnv({
|
|
48
|
+
defaultValue: DEFAULT_MAX_PATCH_BYTES,
|
|
49
|
+
env,
|
|
50
|
+
envVar: 'USHMAN_LEDGER_MAX_PATCH_BYTES',
|
|
51
|
+
}),
|
|
52
|
+
readIndexRebuildBatchSize: parsePositiveIntegerEnv({
|
|
53
|
+
defaultValue: scanBatchSize,
|
|
54
|
+
env,
|
|
55
|
+
envVar: 'USHMAN_LEDGER_READ_INDEX_REBUILD_BATCH_SIZE',
|
|
56
|
+
}),
|
|
57
|
+
readIndexRebuildConcurrency: parsePositiveIntegerEnv({
|
|
58
|
+
defaultValue: scanConcurrency,
|
|
59
|
+
env,
|
|
60
|
+
envVar: 'USHMAN_LEDGER_READ_INDEX_REBUILD_CONCURRENCY',
|
|
61
|
+
}),
|
|
62
|
+
scanBatchSize,
|
|
63
|
+
scanConcurrency,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export const validateLedgerRuntimeConfig = (env = process.env) => {
|
|
67
|
+
void getLedgerRuntimeConfig(env);
|
|
68
|
+
};
|
|
69
|
+
export const getLedgerRuntimeConfig = (env = process.env) => {
|
|
70
|
+
if (env !== process.env) {
|
|
71
|
+
return buildLedgerRuntimeConfig(env);
|
|
72
|
+
}
|
|
73
|
+
const signature = getRuntimeConfigSignature(env);
|
|
74
|
+
if (processEnvRuntimeConfigCache?.signature === signature) {
|
|
75
|
+
return processEnvRuntimeConfigCache.config;
|
|
76
|
+
}
|
|
77
|
+
const config = buildLedgerRuntimeConfig(env);
|
|
78
|
+
processEnvRuntimeConfigCache = {
|
|
79
|
+
config,
|
|
80
|
+
signature,
|
|
81
|
+
};
|
|
82
|
+
return config;
|
|
83
|
+
};
|
|
@@ -5,6 +5,7 @@ export declare const LEDGER_ROOT_SEGMENTS: readonly [".lab", "ledger"];
|
|
|
5
5
|
export declare const LEDGER_STALE_TEMP_MS = 60000;
|
|
6
6
|
type AtomicWriteTestHook = {
|
|
7
7
|
readonly beforeRename?: () => Promise<void>;
|
|
8
|
+
readonly beforeWrite?: () => Promise<void>;
|
|
8
9
|
readonly retainOnThrow?: boolean;
|
|
9
10
|
};
|
|
10
11
|
export type LedgerPaths = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/storage/filesystem.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,uBAAuB,CAAC;AAElF,OAAO,EAAE,aAAa,EAAE,CAAC;AACzB,eAAO,MAAM,oBAAoB,6BAA8B,CAAC;AAChE,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C,KAAK,mBAAmB,GAAG;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/storage/filesystem.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,13 @@ const runAtomicWriteTestHook = async (filePath) => {
|
|
|
35
35
|
throw error;
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
+
const runAtomicWriteWriteTestHook = async (filePath) => {
|
|
39
|
+
const { hook } = getAtomicWriteTestHook(filePath);
|
|
40
|
+
if (!hook?.beforeWrite) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await hook.beforeWrite();
|
|
44
|
+
};
|
|
38
45
|
export const resolveLedgerPaths = (workspaceRoot) => {
|
|
39
46
|
const root = path.join(workspaceRoot, ...LEDGER_ROOT_SEGMENTS);
|
|
40
47
|
return {
|
|
@@ -115,6 +122,7 @@ export const createAtomicTextFileWriter = async (filePath) => {
|
|
|
115
122
|
const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}.${randomUUID()}`;
|
|
116
123
|
const handle = await open(tempPath, 'w');
|
|
117
124
|
let completed = false;
|
|
125
|
+
let pendingWriteError;
|
|
118
126
|
let writeChain = Promise.resolve();
|
|
119
127
|
const finalizeHandle = async () => {
|
|
120
128
|
if (completed) {
|
|
@@ -125,21 +133,40 @@ export const createAtomicTextFileWriter = async (filePath) => {
|
|
|
125
133
|
};
|
|
126
134
|
const waitForWrites = async () => {
|
|
127
135
|
await writeChain;
|
|
136
|
+
if (pendingWriteError) {
|
|
137
|
+
throw pendingWriteError;
|
|
138
|
+
}
|
|
128
139
|
};
|
|
129
140
|
return {
|
|
130
141
|
abort: async () => {
|
|
131
142
|
if (!(await finalizeHandle())) {
|
|
132
143
|
return;
|
|
133
144
|
}
|
|
134
|
-
|
|
145
|
+
let writeError;
|
|
146
|
+
try {
|
|
147
|
+
await waitForWrites();
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
writeError = error;
|
|
151
|
+
}
|
|
135
152
|
await handle.close();
|
|
136
153
|
await rm(tempPath, { force: true });
|
|
154
|
+
if (writeError) {
|
|
155
|
+
throw writeError;
|
|
156
|
+
}
|
|
137
157
|
},
|
|
138
158
|
close: async () => {
|
|
139
159
|
if (!(await finalizeHandle())) {
|
|
140
160
|
return;
|
|
141
161
|
}
|
|
142
|
-
|
|
162
|
+
try {
|
|
163
|
+
await waitForWrites();
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
await handle.close();
|
|
167
|
+
await rm(tempPath, { force: true });
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
143
170
|
try {
|
|
144
171
|
await handle.sync();
|
|
145
172
|
}
|
|
@@ -160,9 +187,12 @@ export const createAtomicTextFileWriter = async (filePath) => {
|
|
|
160
187
|
if (completed || chunk.length === 0) {
|
|
161
188
|
return;
|
|
162
189
|
}
|
|
190
|
+
await runAtomicWriteWriteTestHook(filePath);
|
|
163
191
|
await handle.writeFile(chunk, 'utf8');
|
|
164
192
|
});
|
|
165
|
-
writeChain = nextWrite.
|
|
193
|
+
writeChain = nextWrite.catch((error) => {
|
|
194
|
+
pendingWriteError ??= error;
|
|
195
|
+
});
|
|
166
196
|
await nextWrite;
|
|
167
197
|
},
|
|
168
198
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visit each logical line in `text`, normalizing trailing `\r` from CRLF input.
|
|
3
|
+
*
|
|
4
|
+
* The final unterminated line is still visited, which keeps diff parsing stable for
|
|
5
|
+
* patch content that does not end with a trailing newline.
|
|
6
|
+
*/
|
|
7
|
+
export declare const forEachLine: (text: string, visit: (line: string) => void) => void;
|
|
8
|
+
//# sourceMappingURL=text-lines.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-lines.d.ts","sourceRoot":"","sources":["../src/text-lines.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,SAatE,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visit each logical line in `text`, normalizing trailing `\r` from CRLF input.
|
|
3
|
+
*
|
|
4
|
+
* The final unterminated line is still visited, which keeps diff parsing stable for
|
|
5
|
+
* patch content that does not end with a trailing newline.
|
|
6
|
+
*/
|
|
7
|
+
export const forEachLine = (text, visit) => {
|
|
8
|
+
let lineStart = 0;
|
|
9
|
+
for (let index = 0; index <= text.length; index += 1) {
|
|
10
|
+
if (index !== text.length && text.charCodeAt(index) !== 10) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
let line = text.slice(lineStart, index);
|
|
14
|
+
if (line.endsWith('\r')) {
|
|
15
|
+
line = line.slice(0, -1);
|
|
16
|
+
}
|
|
17
|
+
visit(line);
|
|
18
|
+
lineStart = index + 1;
|
|
19
|
+
}
|
|
20
|
+
};
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const LEDGER_LIBRARY_VERSION
|
|
1
|
+
export declare const LEDGER_LIBRARY_VERSION: string;
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB,QAAsB,CAAC"}
|