clanka 0.2.10 → 0.2.12
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/AgentExecutor.d.ts +1 -4
- package/dist/AgentExecutor.d.ts.map +1 -1
- package/dist/AgentExecutor.js +25 -3
- package/dist/AgentExecutor.js.map +1 -1
- package/dist/AgentTools.d.ts +3 -12
- package/dist/AgentTools.d.ts.map +1 -1
- package/dist/AgentTools.js +7 -12
- package/dist/AgentTools.js.map +1 -1
- package/dist/CodeChunker.test.js +2 -1
- package/dist/CodeChunker.test.js.map +1 -1
- package/dist/OutputFormatter.d.ts +3 -1
- package/dist/OutputFormatter.d.ts.map +1 -1
- package/dist/OutputFormatter.js +60 -47
- package/dist/OutputFormatter.js.map +1 -1
- package/dist/ScriptPreprocessing.d.ts +2 -0
- package/dist/ScriptPreprocessing.d.ts.map +1 -0
- package/dist/ScriptPreprocessing.js +314 -0
- package/dist/ScriptPreprocessing.js.map +1 -0
- package/dist/ScriptPreprocessing.test.d.ts +2 -0
- package/dist/ScriptPreprocessing.test.d.ts.map +1 -0
- package/dist/ScriptPreprocessing.test.js +108 -0
- package/dist/ScriptPreprocessing.test.js.map +1 -0
- package/package.json +1 -1
- package/src/AgentExecutor.ts +27 -3
- package/src/AgentTools.ts +8 -12
- package/src/CodeChunker.test.ts +2 -1
- package/src/OutputFormatter.ts +75 -57
- package/src/ScriptPreprocessing.test.ts +171 -0
- package/src/ScriptPreprocessing.ts +420 -0
- package/src/fixtures/patch-broken.txt +187 -0
- package/src/fixtures/patch-fixed.txt +187 -0
- package/src/fixtures/patch2-broken.txt +257 -0
- package/src/fixtures/patch2-fixed.txt +257 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
const isIdentifierChar = (char: string | undefined): boolean =>
|
|
2
|
+
char !== undefined && /[A-Za-z0-9_$]/.test(char)
|
|
3
|
+
|
|
4
|
+
const isIdentifierStartChar = (char: string | undefined): boolean =>
|
|
5
|
+
char !== undefined && /[A-Za-z_$]/.test(char)
|
|
6
|
+
|
|
7
|
+
const hasIdentifierBoundary = (
|
|
8
|
+
text: string,
|
|
9
|
+
index: number,
|
|
10
|
+
length: number,
|
|
11
|
+
): boolean =>
|
|
12
|
+
!isIdentifierChar(text[index - 1]) && !isIdentifierChar(text[index + length])
|
|
13
|
+
|
|
14
|
+
const findNextIdentifier = (
|
|
15
|
+
text: string,
|
|
16
|
+
identifier: string,
|
|
17
|
+
from: number,
|
|
18
|
+
): number => {
|
|
19
|
+
let index = text.indexOf(identifier, from)
|
|
20
|
+
while (index !== -1) {
|
|
21
|
+
if (hasIdentifierBoundary(text, index, identifier.length)) {
|
|
22
|
+
return index
|
|
23
|
+
}
|
|
24
|
+
index = text.indexOf(identifier, index + identifier.length)
|
|
25
|
+
}
|
|
26
|
+
return -1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const skipWhitespace = (text: string, start: number): number => {
|
|
30
|
+
let i = start
|
|
31
|
+
while (i < text.length && /\s/.test(text[i]!)) {
|
|
32
|
+
i++
|
|
33
|
+
}
|
|
34
|
+
return i
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const parseIdentifier = (
|
|
38
|
+
text: string,
|
|
39
|
+
start: number,
|
|
40
|
+
): { readonly name: string; readonly end: number } | undefined => {
|
|
41
|
+
if (!isIdentifierStartChar(text[start])) {
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
let end = start + 1
|
|
45
|
+
while (end < text.length && isIdentifierChar(text[end])) {
|
|
46
|
+
end++
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
name: text.slice(start, end),
|
|
50
|
+
end,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const isEscaped = (text: string, index: number): boolean => {
|
|
55
|
+
let slashCount = 0
|
|
56
|
+
let i = index - 1
|
|
57
|
+
while (i >= 0 && text[i] === "\\") {
|
|
58
|
+
slashCount++
|
|
59
|
+
i--
|
|
60
|
+
}
|
|
61
|
+
return slashCount % 2 === 1
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const needsTemplateEscaping = (text: string): boolean => {
|
|
65
|
+
for (let i = 0; i < text.length; i++) {
|
|
66
|
+
const char = text[i]!
|
|
67
|
+
if (char === "`" && !isEscaped(text, i)) {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
if (char === "$" && text[i + 1] === "{" && !isEscaped(text, i)) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const escapeTemplateLiteralContent = (text: string): string => {
|
|
78
|
+
if (!needsTemplateEscaping(text)) {
|
|
79
|
+
return text
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let out = ""
|
|
83
|
+
for (let i = 0; i < text.length; i++) {
|
|
84
|
+
const char = text[i]!
|
|
85
|
+
if (char === "\\") {
|
|
86
|
+
out += "\\\\"
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
if (char === "`" && !isEscaped(text, i)) {
|
|
90
|
+
out += "\\`"
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
if (char === "$" && text[i + 1] === "{" && !isEscaped(text, i)) {
|
|
94
|
+
out += "\\$"
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
out += char
|
|
98
|
+
}
|
|
99
|
+
return out
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const findTemplateEnd = (
|
|
103
|
+
text: string,
|
|
104
|
+
start: number,
|
|
105
|
+
isTerminator: (char: string | undefined) => boolean,
|
|
106
|
+
): number => {
|
|
107
|
+
for (let i = start + 1; i < text.length; i++) {
|
|
108
|
+
if (text[i] !== "`" || isEscaped(text, i)) {
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
const next = skipWhitespace(text, i + 1)
|
|
112
|
+
if (isTerminator(text[next])) {
|
|
113
|
+
return i
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return -1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const findTypeAnnotationAssignment = (text: string, start: number): number => {
|
|
120
|
+
let i = start
|
|
121
|
+
while (i < text.length) {
|
|
122
|
+
const char = text[i]!
|
|
123
|
+
if (char === "=") {
|
|
124
|
+
return i
|
|
125
|
+
}
|
|
126
|
+
if (char === "\n" || char === ";") {
|
|
127
|
+
return -1
|
|
128
|
+
}
|
|
129
|
+
i++
|
|
130
|
+
}
|
|
131
|
+
return -1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fixCallTemplateArgument = (
|
|
135
|
+
script: string,
|
|
136
|
+
functionName: string,
|
|
137
|
+
isTerminator: (char: string | undefined) => boolean,
|
|
138
|
+
): string => {
|
|
139
|
+
let out = script
|
|
140
|
+
let cursor = 0
|
|
141
|
+
|
|
142
|
+
while (cursor < out.length) {
|
|
143
|
+
const callStart = findNextIdentifier(out, functionName, cursor)
|
|
144
|
+
if (callStart === -1) {
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const openParen = skipWhitespace(out, callStart + functionName.length)
|
|
149
|
+
if (out[openParen] !== "(") {
|
|
150
|
+
cursor = callStart + functionName.length
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const templateStart = skipWhitespace(out, openParen + 1)
|
|
155
|
+
if (out[templateStart] !== "`") {
|
|
156
|
+
cursor = openParen + 1
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const templateEnd = findTemplateEnd(out, templateStart, isTerminator)
|
|
161
|
+
if (templateEnd === -1) {
|
|
162
|
+
cursor = templateStart + 1
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const original = out.slice(templateStart + 1, templateEnd)
|
|
167
|
+
const escaped = escapeTemplateLiteralContent(original)
|
|
168
|
+
if (escaped !== original) {
|
|
169
|
+
out = `${out.slice(0, templateStart + 1)}${escaped}${out.slice(templateEnd)}`
|
|
170
|
+
cursor = templateEnd + (escaped.length - original.length) + 1
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
cursor = templateEnd + 1
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return out
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const collectCallArgumentIdentifiers = (
|
|
181
|
+
script: string,
|
|
182
|
+
functionName: string,
|
|
183
|
+
): ReadonlySet<string> => {
|
|
184
|
+
const out = new Set<string>()
|
|
185
|
+
let cursor = 0
|
|
186
|
+
|
|
187
|
+
while (cursor < script.length) {
|
|
188
|
+
const callStart = findNextIdentifier(script, functionName, cursor)
|
|
189
|
+
if (callStart === -1) {
|
|
190
|
+
break
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const openParen = skipWhitespace(script, callStart + functionName.length)
|
|
194
|
+
if (script[openParen] !== "(") {
|
|
195
|
+
cursor = callStart + functionName.length
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const argumentStart = skipWhitespace(script, openParen + 1)
|
|
200
|
+
const identifier = parseIdentifier(script, argumentStart)
|
|
201
|
+
if (identifier === undefined) {
|
|
202
|
+
cursor = openParen + 1
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const argumentEnd = skipWhitespace(script, identifier.end)
|
|
207
|
+
if (script[argumentEnd] === ")" || script[argumentEnd] === ",") {
|
|
208
|
+
out.add(identifier.name)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
cursor = identifier.end
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return out
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const fixWriteFileContentTemplates = (script: string): string => {
|
|
218
|
+
let out = script
|
|
219
|
+
let cursor = 0
|
|
220
|
+
|
|
221
|
+
while (cursor < out.length) {
|
|
222
|
+
const callStart = findNextIdentifier(out, "writeFile", cursor)
|
|
223
|
+
if (callStart === -1) {
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const openParen = skipWhitespace(out, callStart + "writeFile".length)
|
|
228
|
+
if (out[openParen] !== "(") {
|
|
229
|
+
cursor = callStart + "writeFile".length
|
|
230
|
+
continue
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const contentKey = findNextIdentifier(out, "content", openParen + 1)
|
|
234
|
+
if (contentKey === -1) {
|
|
235
|
+
cursor = openParen + 1
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const colon = skipWhitespace(out, contentKey + "content".length)
|
|
240
|
+
if (out[colon] !== ":") {
|
|
241
|
+
cursor = contentKey + "content".length
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const templateStart = skipWhitespace(out, colon + 1)
|
|
246
|
+
if (out[templateStart] !== "`") {
|
|
247
|
+
cursor = templateStart + 1
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const templateEnd = findTemplateEnd(
|
|
252
|
+
out,
|
|
253
|
+
templateStart,
|
|
254
|
+
(char) => char === "}" || char === ",",
|
|
255
|
+
)
|
|
256
|
+
if (templateEnd === -1) {
|
|
257
|
+
cursor = templateStart + 1
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const original = out.slice(templateStart + 1, templateEnd)
|
|
262
|
+
const escaped = escapeTemplateLiteralContent(original)
|
|
263
|
+
if (escaped !== original) {
|
|
264
|
+
out = `${out.slice(0, templateStart + 1)}${escaped}${out.slice(templateEnd)}`
|
|
265
|
+
cursor = templateEnd + (escaped.length - original.length) + 1
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
cursor = templateEnd + 1
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return out
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const collectWriteFileContentIdentifiers = (
|
|
276
|
+
script: string,
|
|
277
|
+
): ReadonlySet<string> => {
|
|
278
|
+
const out = new Set<string>()
|
|
279
|
+
let cursor = 0
|
|
280
|
+
|
|
281
|
+
while (cursor < script.length) {
|
|
282
|
+
const callStart = findNextIdentifier(script, "writeFile", cursor)
|
|
283
|
+
if (callStart === -1) {
|
|
284
|
+
break
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const openParen = skipWhitespace(script, callStart + "writeFile".length)
|
|
288
|
+
if (script[openParen] !== "(") {
|
|
289
|
+
cursor = callStart + "writeFile".length
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const contentKey = findNextIdentifier(script, "content", openParen + 1)
|
|
294
|
+
if (contentKey === -1) {
|
|
295
|
+
cursor = openParen + 1
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const afterContent = skipWhitespace(script, contentKey + "content".length)
|
|
300
|
+
if (script[afterContent] === ":") {
|
|
301
|
+
const valueStart = skipWhitespace(script, afterContent + 1)
|
|
302
|
+
const identifier = parseIdentifier(script, valueStart)
|
|
303
|
+
if (identifier !== undefined) {
|
|
304
|
+
const valueEnd = skipWhitespace(script, identifier.end)
|
|
305
|
+
if (script[valueEnd] === "}" || script[valueEnd] === ",") {
|
|
306
|
+
out.add(identifier.name)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
cursor = valueStart + 1
|
|
310
|
+
continue
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (script[afterContent] === "}" || script[afterContent] === ",") {
|
|
314
|
+
out.add("content")
|
|
315
|
+
cursor = afterContent + 1
|
|
316
|
+
continue
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
cursor = afterContent + 1
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return out
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const fixAssignedTemplate = (script: string, variableName: string): string => {
|
|
326
|
+
let out = script
|
|
327
|
+
let cursor = 0
|
|
328
|
+
|
|
329
|
+
while (cursor < out.length) {
|
|
330
|
+
const variableStart = findNextIdentifier(out, variableName, cursor)
|
|
331
|
+
if (variableStart === -1) {
|
|
332
|
+
break
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let assignmentStart = skipWhitespace(
|
|
336
|
+
out,
|
|
337
|
+
variableStart + variableName.length,
|
|
338
|
+
)
|
|
339
|
+
if (out[assignmentStart] === ":") {
|
|
340
|
+
assignmentStart = findTypeAnnotationAssignment(out, assignmentStart + 1)
|
|
341
|
+
if (assignmentStart === -1) {
|
|
342
|
+
cursor = variableStart + variableName.length
|
|
343
|
+
continue
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (
|
|
348
|
+
out[assignmentStart] !== "=" ||
|
|
349
|
+
out[assignmentStart + 1] === "=" ||
|
|
350
|
+
out[assignmentStart + 1] === ">"
|
|
351
|
+
) {
|
|
352
|
+
cursor = variableStart + variableName.length
|
|
353
|
+
continue
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const templateStart = skipWhitespace(out, assignmentStart + 1)
|
|
357
|
+
if (out[templateStart] !== "`") {
|
|
358
|
+
cursor = templateStart + 1
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const templateEnd = findTemplateEnd(
|
|
363
|
+
out,
|
|
364
|
+
templateStart,
|
|
365
|
+
(char) =>
|
|
366
|
+
char === undefined ||
|
|
367
|
+
char === ";" ||
|
|
368
|
+
char === "," ||
|
|
369
|
+
char === ")" ||
|
|
370
|
+
char === "}" ||
|
|
371
|
+
char === "]",
|
|
372
|
+
)
|
|
373
|
+
if (templateEnd === -1) {
|
|
374
|
+
cursor = templateStart + 1
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const original = out.slice(templateStart + 1, templateEnd)
|
|
379
|
+
const escaped = escapeTemplateLiteralContent(original)
|
|
380
|
+
if (escaped !== original) {
|
|
381
|
+
out = `${out.slice(0, templateStart + 1)}${escaped}${out.slice(templateEnd)}`
|
|
382
|
+
cursor = templateEnd + (escaped.length - original.length) + 1
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
cursor = templateEnd + 1
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return out
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const fixAssignedTemplatesForToolCalls = (script: string): string => {
|
|
393
|
+
const identifiers = new Set<string>()
|
|
394
|
+
for (const functionName of ["applyPatch", "taskComplete"] as const) {
|
|
395
|
+
for (const identifier of collectCallArgumentIdentifiers(
|
|
396
|
+
script,
|
|
397
|
+
functionName,
|
|
398
|
+
)) {
|
|
399
|
+
identifiers.add(identifier)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (const identifier of collectWriteFileContentIdentifiers(script)) {
|
|
403
|
+
identifiers.add(identifier)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let out = script
|
|
407
|
+
for (const identifier of identifiers) {
|
|
408
|
+
out = fixAssignedTemplate(out, identifier)
|
|
409
|
+
}
|
|
410
|
+
return out
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export const preprocessScript = (script: string): string =>
|
|
414
|
+
fixAssignedTemplatesForToolCalls(
|
|
415
|
+
["applyPatch", "taskComplete"].reduce(
|
|
416
|
+
(current, functionName) =>
|
|
417
|
+
fixCallTemplateArgument(current, functionName, (char) => char === ")"),
|
|
418
|
+
fixWriteFileContentTemplates(script),
|
|
419
|
+
),
|
|
420
|
+
)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const patch = `*** Begin Patch
|
|
2
|
+
*** Add File: src/ScriptPreprocessing.ts
|
|
3
|
+
+const isIdentifierChar = (char: string | undefined): boolean =>
|
|
4
|
+
+ char !== undefined && /[A-Za-z0-9_$]/.test(char)
|
|
5
|
+
+
|
|
6
|
+
+const hasIdentifierBoundary = (
|
|
7
|
+
+ text: string,
|
|
8
|
+
+ index: number,
|
|
9
|
+
+ length: number,
|
|
10
|
+
+): boolean =>
|
|
11
|
+
+ !isIdentifierChar(text[index - 1]) && !isIdentifierChar(text[index + length])
|
|
12
|
+
+
|
|
13
|
+
+const findNextIdentifier = (
|
|
14
|
+
+ text: string,
|
|
15
|
+
+ identifier: string,
|
|
16
|
+
+ from: number,
|
|
17
|
+
+): number => {
|
|
18
|
+
+ let index = text.indexOf(identifier, from)
|
|
19
|
+
+ while (index !== -1) {
|
|
20
|
+
+ if (hasIdentifierBoundary(text, index, identifier.length)) {
|
|
21
|
+
+ return index
|
|
22
|
+
+ }
|
|
23
|
+
+ index = text.indexOf(identifier, index + identifier.length)
|
|
24
|
+
+ }
|
|
25
|
+
+ return -1
|
|
26
|
+
+}
|
|
27
|
+
+
|
|
28
|
+
+const skipWhitespace = (text: string, start: number): number => {
|
|
29
|
+
+ let i = start
|
|
30
|
+
+ while (i < text.length && /\s/.test(text[i]!)) {
|
|
31
|
+
+ i++
|
|
32
|
+
+ }
|
|
33
|
+
+ return i
|
|
34
|
+
+}
|
|
35
|
+
+
|
|
36
|
+
+const isEscaped = (text: string, index: number): boolean => {
|
|
37
|
+
+ let slashCount = 0
|
|
38
|
+
+ let i = index - 1
|
|
39
|
+
+ while (i >= 0 && text[i] === "\\") {
|
|
40
|
+
+ slashCount++
|
|
41
|
+
+ i--
|
|
42
|
+
+ }
|
|
43
|
+
+ return slashCount % 2 === 1
|
|
44
|
+
+}
|
|
45
|
+
+
|
|
46
|
+
+const escapeUnescapedBackticks = (text: string): string => {
|
|
47
|
+
+ let out = ""
|
|
48
|
+
+ for (let i = 0; i < text.length; i++) {
|
|
49
|
+
+ const char = text[i]!
|
|
50
|
+
+ if (char === "`" && !isEscaped(text, i)) {
|
|
51
|
+
+ out += "\\`"
|
|
52
|
+
+ continue
|
|
53
|
+
+ }
|
|
54
|
+
+ out += char
|
|
55
|
+
+ }
|
|
56
|
+
+ return out
|
|
57
|
+
+}
|
|
58
|
+
+
|
|
59
|
+
+const findTemplateEnd = (
|
|
60
|
+
+ text: string,
|
|
61
|
+
+ start: number,
|
|
62
|
+
+ isTerminator: (char: string | undefined) => boolean,
|
|
63
|
+
+): number => {
|
|
64
|
+
+ for (let i = start + 1; i < text.length; i++) {
|
|
65
|
+
+ if (text[i] !== "`" || isEscaped(text, i)) {
|
|
66
|
+
+ continue
|
|
67
|
+
+ }
|
|
68
|
+
+ const next = skipWhitespace(text, i + 1)
|
|
69
|
+
+ if (isTerminator(text[next])) {
|
|
70
|
+
+ return i
|
|
71
|
+
+ }
|
|
72
|
+
+ }
|
|
73
|
+
+ return -1
|
|
74
|
+
+}
|
|
75
|
+
+
|
|
76
|
+
+const fixCallTemplateArgument = (
|
|
77
|
+
+ script: string,
|
|
78
|
+
+ functionName: string,
|
|
79
|
+
+ isTerminator: (char: string | undefined) => boolean,
|
|
80
|
+
+): string => {
|
|
81
|
+
+ let out = script
|
|
82
|
+
+ let cursor = 0
|
|
83
|
+
+
|
|
84
|
+
+ while (cursor < out.length) {
|
|
85
|
+
+ const callStart = findNextIdentifier(out, functionName, cursor)
|
|
86
|
+
+ if (callStart === -1) {
|
|
87
|
+
+ break
|
|
88
|
+
+ }
|
|
89
|
+
+
|
|
90
|
+
+ const openParen = skipWhitespace(out, callStart + functionName.length)
|
|
91
|
+
+ if (out[openParen] !== "(") {
|
|
92
|
+
+ cursor = callStart + functionName.length
|
|
93
|
+
+ continue
|
|
94
|
+
+ }
|
|
95
|
+
+
|
|
96
|
+
+ const templateStart = skipWhitespace(out, openParen + 1)
|
|
97
|
+
+ if (out[templateStart] !== "`") {
|
|
98
|
+
+ cursor = openParen + 1
|
|
99
|
+
+ continue
|
|
100
|
+
+ }
|
|
101
|
+
+
|
|
102
|
+
+ const templateEnd = findTemplateEnd(out, templateStart, isTerminator)
|
|
103
|
+
+ if (templateEnd === -1) {
|
|
104
|
+
+ cursor = templateStart + 1
|
|
105
|
+
+ continue
|
|
106
|
+
+ }
|
|
107
|
+
+
|
|
108
|
+
+ const original = out.slice(templateStart + 1, templateEnd)
|
|
109
|
+
+ const escaped = escapeUnescapedBackticks(original)
|
|
110
|
+
+ if (escaped !== original) {
|
|
111
|
+
+ out = `${out.slice(0, templateStart + 1)}${escaped}${out.slice(templateEnd)}`
|
|
112
|
+
+ cursor = templateEnd + (escaped.length - original.length) + 1
|
|
113
|
+
+ continue
|
|
114
|
+
+ }
|
|
115
|
+
+
|
|
116
|
+
+ cursor = templateEnd + 1
|
|
117
|
+
+ }
|
|
118
|
+
+
|
|
119
|
+
+ return out
|
|
120
|
+
+}
|
|
121
|
+
+
|
|
122
|
+
+const fixWriteFileContentTemplates = (script: string): string => {
|
|
123
|
+
+ let out = script
|
|
124
|
+
+ let cursor = 0
|
|
125
|
+
+
|
|
126
|
+
+ while (cursor < out.length) {
|
|
127
|
+
+ const callStart = findNextIdentifier(out, "writeFile", cursor)
|
|
128
|
+
+ if (callStart === -1) {
|
|
129
|
+
+ break
|
|
130
|
+
+ }
|
|
131
|
+
+
|
|
132
|
+
+ const openParen = skipWhitespace(out, callStart + "writeFile".length)
|
|
133
|
+
+ if (out[openParen] !== "(") {
|
|
134
|
+
+ cursor = callStart + "writeFile".length
|
|
135
|
+
+ continue
|
|
136
|
+
+ }
|
|
137
|
+
+
|
|
138
|
+
+ const contentKey = findNextIdentifier(out, "content", openParen + 1)
|
|
139
|
+
+ if (contentKey === -1) {
|
|
140
|
+
+ cursor = openParen + 1
|
|
141
|
+
+ continue
|
|
142
|
+
+ }
|
|
143
|
+
+
|
|
144
|
+
+ const colon = skipWhitespace(out, contentKey + "content".length)
|
|
145
|
+
+ if (out[colon] !== ":") {
|
|
146
|
+
+ cursor = contentKey + "content".length
|
|
147
|
+
+ continue
|
|
148
|
+
+ }
|
|
149
|
+
+
|
|
150
|
+
+ const templateStart = skipWhitespace(out, colon + 1)
|
|
151
|
+
+ if (out[templateStart] !== "`") {
|
|
152
|
+
+ cursor = templateStart + 1
|
|
153
|
+
+ continue
|
|
154
|
+
+ }
|
|
155
|
+
+
|
|
156
|
+
+ const templateEnd = findTemplateEnd(
|
|
157
|
+
+ out,
|
|
158
|
+
+ templateStart,
|
|
159
|
+
+ (char) => char === "}" || char === ",",
|
|
160
|
+
+ )
|
|
161
|
+
+ if (templateEnd === -1) {
|
|
162
|
+
+ cursor = templateStart + 1
|
|
163
|
+
+ continue
|
|
164
|
+
+ }
|
|
165
|
+
+
|
|
166
|
+
+ const original = out.slice(templateStart + 1, templateEnd)
|
|
167
|
+
+ const escaped = escapeUnescapedBackticks(original)
|
|
168
|
+
+ if (escaped !== original) {
|
|
169
|
+
+ out = `${out.slice(0, templateStart + 1)}${escaped}${out.slice(templateEnd)}`
|
|
170
|
+
+ cursor = templateEnd + (escaped.length - original.length) + 1
|
|
171
|
+
+ continue
|
|
172
|
+
+ }
|
|
173
|
+
+
|
|
174
|
+
+ cursor = templateEnd + 1
|
|
175
|
+
+ }
|
|
176
|
+
+
|
|
177
|
+
+ return out
|
|
178
|
+
+}
|
|
179
|
+
+
|
|
180
|
+
+export const preprocessScript = (script: string): string =>
|
|
181
|
+
+ ["applyPatch", "taskComplete"].reduce(
|
|
182
|
+
+ (current, functionName) =>
|
|
183
|
+
+ fixCallTemplateArgument(current, functionName, (char) => char === ")"),
|
|
184
|
+
+ fixWriteFileContentTemplates(script),
|
|
185
|
+
+ )
|
|
186
|
+
*** End Patch`;
|
|
187
|
+
console.log(await applyPatch(patch));
|