@opencode-ai/util 0.0.0-next-16027 → 0.0.0-next-16040
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/dist/patch.d.ts +1 -0
- package/dist/patch.js +153 -52
- package/package.json +1 -1
package/dist/patch.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class BoundaryError extends BoundaryError_base {
|
|
|
9
9
|
declare const InvalidHunkError_base: Schema.Class<InvalidHunkError, Schema.TaggedStruct<"Patch.InvalidHunkError", {
|
|
10
10
|
readonly line: Schema.String;
|
|
11
11
|
readonly lineNumber: Schema.Number;
|
|
12
|
+
readonly reason: Schema.optional<Schema.String>;
|
|
12
13
|
}>, import("effect/Cause").YieldableError>;
|
|
13
14
|
export declare class InvalidHunkError extends InvalidHunkError_base {
|
|
14
15
|
get message(): string;
|
package/dist/patch.js
CHANGED
|
@@ -10,8 +10,11 @@ export class BoundaryError extends Schema.TaggedErrorClass()("Patch.BoundaryErro
|
|
|
10
10
|
export class InvalidHunkError extends Schema.TaggedErrorClass()("Patch.InvalidHunkError", {
|
|
11
11
|
line: Schema.String,
|
|
12
12
|
lineNumber: Schema.Number,
|
|
13
|
+
reason: Schema.optional(Schema.String),
|
|
13
14
|
}) {
|
|
14
15
|
get message() {
|
|
16
|
+
if (this.reason)
|
|
17
|
+
return `Invalid hunk at line ${this.lineNumber}: ${this.reason}`;
|
|
15
18
|
return `Invalid hunk at line ${this.lineNumber}: '${this.line}' is not a valid hunk header. Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'`;
|
|
16
19
|
}
|
|
17
20
|
}
|
|
@@ -30,51 +33,49 @@ export function parse(patchText) {
|
|
|
30
33
|
while (index < end) {
|
|
31
34
|
const line = lines[index];
|
|
32
35
|
const header = line.trim();
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
if (index === begin + 1 &&
|
|
37
|
+
header.startsWith("*** Environment ID:") &&
|
|
38
|
+
header.slice("*** Environment ID:".length).trim()) {
|
|
39
|
+
index++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (header.startsWith("*** Add File: ")) {
|
|
43
|
+
const path = header.slice("*** Add File: ".length).trim();
|
|
39
44
|
const parsed = parseAdd(lines, index + 1, end);
|
|
45
|
+
if ("error" in parsed)
|
|
46
|
+
return Result.fail(parsed.error);
|
|
40
47
|
hunks.push({ type: "add", path, contents: parsed.content });
|
|
41
48
|
index = parsed.next;
|
|
42
49
|
continue;
|
|
43
50
|
}
|
|
44
|
-
if (header.startsWith("*** Delete File:")) {
|
|
45
|
-
const path = header.slice("*** Delete File:".length).trim();
|
|
46
|
-
if (!path) {
|
|
47
|
-
index++;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
51
|
+
if (header.startsWith("*** Delete File: ")) {
|
|
52
|
+
const path = header.slice("*** Delete File: ".length).trim();
|
|
50
53
|
hunks.push({ type: "delete", path });
|
|
51
54
|
index++;
|
|
52
55
|
continue;
|
|
53
56
|
}
|
|
54
|
-
if (header.startsWith("*** Update File:")) {
|
|
55
|
-
const path = header.slice("*** Update File:".length).trim();
|
|
56
|
-
if (!path) {
|
|
57
|
-
index++;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
57
|
+
if (header.startsWith("*** Update File: ")) {
|
|
58
|
+
const path = header.slice("*** Update File: ".length).trim();
|
|
60
59
|
let next = index + 1;
|
|
61
60
|
let movePath;
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
while (lines[next]?.trimEnd() === "*** End of File")
|
|
62
|
+
next++;
|
|
63
|
+
const move = lines[next]?.trimEnd();
|
|
64
|
+
if (move === "*** Move to:" || move?.startsWith("*** Move to: ")) {
|
|
65
|
+
movePath = move.slice("*** Move to: ".length).trim();
|
|
66
|
+
if (!movePath) {
|
|
67
|
+
return Result.fail(new InvalidHunkError({ line: lines[next].trim(), lineNumber: next + 1 }));
|
|
68
|
+
}
|
|
64
69
|
next++;
|
|
65
70
|
}
|
|
66
|
-
const parsed = parseUpdate(lines, next, end);
|
|
71
|
+
const parsed = parseUpdate(lines, next, end, path, index);
|
|
72
|
+
if ("error" in parsed)
|
|
73
|
+
return Result.fail(parsed.error);
|
|
67
74
|
hunks.push({ type: "update", path, movePath, chunks: parsed.chunks });
|
|
68
75
|
index = parsed.next;
|
|
69
76
|
continue;
|
|
70
77
|
}
|
|
71
|
-
index
|
|
72
|
-
}
|
|
73
|
-
if (hunks.length === 0) {
|
|
74
|
-
const invalid = lines.findIndex((line, index) => index > begin && index < end && line.trim() !== "");
|
|
75
|
-
if (invalid !== -1) {
|
|
76
|
-
return Result.fail(new InvalidHunkError({ line: lines[invalid].trim(), lineNumber: invalid + 1 }));
|
|
77
|
-
}
|
|
78
|
+
return Result.fail(new InvalidHunkError({ line: header, lineNumber: index + 1 }));
|
|
78
79
|
}
|
|
79
80
|
return Result.succeed(hunks);
|
|
80
81
|
}
|
|
@@ -99,46 +100,146 @@ export function joinBom(text, bom) {
|
|
|
99
100
|
function parseAdd(lines, start, end) {
|
|
100
101
|
const content = [];
|
|
101
102
|
let index = start;
|
|
102
|
-
while (index < end && !lines[index].
|
|
103
|
-
if (lines[index].startsWith("+"))
|
|
104
|
-
|
|
103
|
+
while (index < end && !isBoundary(lines[index].trim())) {
|
|
104
|
+
if (!lines[index].startsWith("+")) {
|
|
105
|
+
return { error: new InvalidHunkError({ line: lines[index].trim(), lineNumber: index + 1 }) };
|
|
106
|
+
}
|
|
107
|
+
content.push(lines[index].slice(1));
|
|
105
108
|
index++;
|
|
106
109
|
}
|
|
107
110
|
return { content: content.join("\n"), next: index };
|
|
108
111
|
}
|
|
109
|
-
function parseUpdate(lines, start, end) {
|
|
112
|
+
function parseUpdate(lines, start, end, path, hunk) {
|
|
110
113
|
const chunks = [];
|
|
111
114
|
let index = start;
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
let afterEndOfFile = false;
|
|
116
|
+
while (index < end) {
|
|
117
|
+
const line = lines[index];
|
|
118
|
+
const updateLine = line.trimEnd();
|
|
119
|
+
if (afterEndOfFile) {
|
|
120
|
+
if (updateLine === "") {
|
|
121
|
+
index++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (updateLine === "@@" || updateLine.startsWith("@@ "))
|
|
125
|
+
afterEndOfFile = false;
|
|
126
|
+
else if (isBoundary(updateLine))
|
|
127
|
+
break;
|
|
128
|
+
else {
|
|
129
|
+
return {
|
|
130
|
+
error: new InvalidHunkError({
|
|
131
|
+
line,
|
|
132
|
+
lineNumber: index + 1,
|
|
133
|
+
reason: `Expected update hunk to start with a @@ context marker, got: '${line}'`,
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (updateLine === "*** End of File") {
|
|
139
|
+
const chunk = chunks.at(-1);
|
|
140
|
+
if (chunk && chunk.oldLines.length === 0 && chunk.newLines.length === 0) {
|
|
141
|
+
return {
|
|
142
|
+
error: new InvalidHunkError({
|
|
143
|
+
line: updateLine,
|
|
144
|
+
lineNumber: index + 1,
|
|
145
|
+
reason: "Update hunk does not contain any lines",
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (chunk) {
|
|
150
|
+
chunk.endOfFile = true;
|
|
151
|
+
afterEndOfFile = true;
|
|
152
|
+
}
|
|
114
153
|
index++;
|
|
115
154
|
continue;
|
|
116
155
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
156
|
+
if (isBoundary(updateLine))
|
|
157
|
+
break;
|
|
158
|
+
if (updateLine === "@@" || updateLine.startsWith("@@ ")) {
|
|
159
|
+
const previous = chunks.at(-1);
|
|
160
|
+
if (previous && previous.oldLines.length === 0 && previous.newLines.length === 0) {
|
|
161
|
+
return {
|
|
162
|
+
error: new InvalidHunkError({
|
|
163
|
+
line,
|
|
164
|
+
lineNumber: index + 1,
|
|
165
|
+
reason: `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`,
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
127
168
|
}
|
|
128
|
-
|
|
129
|
-
oldLines
|
|
130
|
-
|
|
131
|
-
|
|
169
|
+
chunks.push({
|
|
170
|
+
oldLines: [],
|
|
171
|
+
newLines: [],
|
|
172
|
+
changeContext: updateLine === "@@" ? undefined : updateLine.slice("@@ ".length),
|
|
173
|
+
});
|
|
174
|
+
index++;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (chunks.length === 0)
|
|
178
|
+
chunks.push({ oldLines: [], newLines: [] });
|
|
179
|
+
const chunk = chunks.at(-1);
|
|
180
|
+
if (line === "") {
|
|
181
|
+
chunk.oldLines.push("");
|
|
182
|
+
chunk.newLines.push("");
|
|
183
|
+
index++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (line.startsWith(" ")) {
|
|
187
|
+
chunk.oldLines.push(line.slice(1));
|
|
188
|
+
chunk.newLines.push(line.slice(1));
|
|
189
|
+
index++;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (line.startsWith("-")) {
|
|
193
|
+
chunk.oldLines.push(line.slice(1));
|
|
132
194
|
index++;
|
|
195
|
+
continue;
|
|
133
196
|
}
|
|
134
|
-
if (
|
|
135
|
-
|
|
197
|
+
if (line.startsWith("+")) {
|
|
198
|
+
chunk.newLines.push(line.slice(1));
|
|
136
199
|
index++;
|
|
200
|
+
continue;
|
|
137
201
|
}
|
|
138
|
-
|
|
202
|
+
const populated = chunk.oldLines.length > 0 || chunk.newLines.length > 0;
|
|
203
|
+
return {
|
|
204
|
+
error: new InvalidHunkError({
|
|
205
|
+
line,
|
|
206
|
+
lineNumber: index + 1,
|
|
207
|
+
reason: populated
|
|
208
|
+
? `Expected update hunk to start with a @@ context marker, got: '${line}'`
|
|
209
|
+
: `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`,
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (chunks.length === 0) {
|
|
214
|
+
return {
|
|
215
|
+
error: new InvalidHunkError({
|
|
216
|
+
line: lines[hunk].trim(),
|
|
217
|
+
lineNumber: hunk + 1,
|
|
218
|
+
reason: `Update file hunk for path '${path}' is empty`,
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const last = chunks.at(-1);
|
|
223
|
+
if (last.oldLines.length === 0 && last.newLines.length === 0) {
|
|
224
|
+
const line = lines[index].trim();
|
|
225
|
+
return {
|
|
226
|
+
error: new InvalidHunkError({
|
|
227
|
+
line,
|
|
228
|
+
lineNumber: index + 1,
|
|
229
|
+
reason: line === "*** End Patch"
|
|
230
|
+
? "Update hunk does not contain any lines"
|
|
231
|
+
: `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`,
|
|
232
|
+
}),
|
|
233
|
+
};
|
|
139
234
|
}
|
|
140
235
|
return { chunks, next: index };
|
|
141
236
|
}
|
|
237
|
+
function isBoundary(line) {
|
|
238
|
+
return (line === "*** End Patch" ||
|
|
239
|
+
line.startsWith("*** Add File: ") ||
|
|
240
|
+
line.startsWith("*** Delete File: ") ||
|
|
241
|
+
line.startsWith("*** Update File: "));
|
|
242
|
+
}
|
|
142
243
|
function computeReplacements(lines, path, chunks) {
|
|
143
244
|
const replacements = [];
|
|
144
245
|
let lineIndex = 0;
|
|
@@ -203,4 +304,4 @@ const normalize = (value) => value
|
|
|
203
304
|
.replace(/[‐‑‒–—―−]/g, "-")
|
|
204
305
|
.replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " ");
|
|
205
306
|
const splitBom = (text) => text.startsWith("\uFEFF") ? { bom: true, text: text.slice(1) } : { bom: false, text };
|
|
206
|
-
const stripHeredoc = (input) => input.match(/^(?:cat\s+)?<<['"]?(\w+)
|
|
307
|
+
const stripHeredoc = (input) => input.match(/^(?:cat\s+)?<<(['"]?)(\w+)\1\s*\n([\s\S]*?)\n\2\s*$/)?.[3] ?? input;
|