@valbuild/server 0.60.3 → 0.60.5
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.
@@ -1,6 +1,5 @@
|
|
1
1
|
import { newQuickJSWASMModule } from 'quickjs-emscripten';
|
2
|
-
import
|
3
|
-
import ts__default from 'typescript';
|
2
|
+
import ts from 'typescript';
|
4
3
|
import { result, pipe } from '@valbuild/core/fp';
|
5
4
|
import { FILE_REF_PROP, FILE_REF_SUBTYPE_TAG, RT_IMAGE_TAG, VAL_EXTENSION, derefPatch, Internal, Schema, deserializeSchema } from '@valbuild/core';
|
6
5
|
import { deepEqual, isNotRoot, PatchError, parseAndValidateArrayIndex, applyPatch, JSONOps, sourceToPatchPath } from '@valbuild/core/patch';
|
@@ -43,12 +42,12 @@ function formatSyntaxErrorTree(tree, sourceFile) {
|
|
43
42
|
return flatMapErrors(tree, error => formatSyntaxError(error, sourceFile));
|
44
43
|
}
|
45
44
|
function isLiteralPropertyName(name) {
|
46
|
-
return
|
45
|
+
return ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name);
|
47
46
|
}
|
48
47
|
function validateObjectProperties(nodes) {
|
49
48
|
const errors = [];
|
50
49
|
for (const node of nodes) {
|
51
|
-
if (!
|
50
|
+
if (!ts.isPropertyAssignment(node)) {
|
52
51
|
errors.push(new ValSyntaxError("Object literal element must be property assignment", node));
|
53
52
|
} else if (!isLiteralPropertyName(node.name)) {
|
54
53
|
errors.push(new ValSyntaxError("Object literal element key must be an identifier or a literal", node));
|
@@ -65,7 +64,7 @@ function validateObjectProperties(nodes) {
|
|
65
64
|
* validating its children.
|
66
65
|
*/
|
67
66
|
function shallowValidateExpression(value) {
|
68
|
-
return
|
67
|
+
return ts.isStringLiteralLike(value) || ts.isNumericLiteral(value) || value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword || value.kind === ts.SyntaxKind.NullKeyword || ts.isArrayLiteralExpression(value) || ts.isObjectLiteralExpression(value) || isValFileMethodCall(value) ? undefined : new ValSyntaxError("Expression must be a literal or call c.file", value);
|
69
68
|
}
|
70
69
|
|
71
70
|
/**
|
@@ -77,19 +76,19 @@ function evaluateExpression(value) {
|
|
77
76
|
// For a NumericLiteral, the stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1".
|
78
77
|
// https://github.com/microsoft/TypeScript/blob/4b794fe1dd0d184d3f8f17e94d8187eace57c91e/src/compiler/types.ts#L2127-L2131
|
79
78
|
|
80
|
-
if (
|
79
|
+
if (ts.isStringLiteralLike(value)) {
|
81
80
|
return result.ok(value.text);
|
82
|
-
} else if (
|
81
|
+
} else if (ts.isNumericLiteral(value)) {
|
83
82
|
return result.ok(Number(value.text));
|
84
|
-
} else if (value.kind ===
|
83
|
+
} else if (value.kind === ts.SyntaxKind.TrueKeyword) {
|
85
84
|
return result.ok(true);
|
86
|
-
} else if (value.kind ===
|
85
|
+
} else if (value.kind === ts.SyntaxKind.FalseKeyword) {
|
87
86
|
return result.ok(false);
|
88
|
-
} else if (value.kind ===
|
87
|
+
} else if (value.kind === ts.SyntaxKind.NullKeyword) {
|
89
88
|
return result.ok(null);
|
90
|
-
} else if (
|
89
|
+
} else if (ts.isArrayLiteralExpression(value)) {
|
91
90
|
return result.all(value.elements.map(evaluateExpression));
|
92
|
-
} else if (
|
91
|
+
} else if (ts.isObjectLiteralExpression(value)) {
|
93
92
|
return pipe(validateObjectProperties(value.properties), result.flatMap(assignments => pipe(assignments.map(assignment => pipe(evaluateExpression(assignment.initializer), result.map(value => [assignment.name.text, value]))), result.all)), result.map(Object.fromEntries));
|
94
93
|
} else if (isValFileMethodCall(value)) {
|
95
94
|
return pipe(findValFileNodeArg(value), result.flatMap(ref => {
|
@@ -122,14 +121,14 @@ function findObjectPropertyAssignment(value, key) {
|
|
122
121
|
}));
|
123
122
|
}
|
124
123
|
function isValFileMethodCall(node) {
|
125
|
-
return
|
124
|
+
return ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "c" && node.expression.name.text === "file";
|
126
125
|
}
|
127
126
|
function findValFileNodeArg(node) {
|
128
127
|
if (node.arguments.length === 0) {
|
129
128
|
return result.err(new ValSyntaxError(`Invalid c.file() call: missing ref argument`, node));
|
130
129
|
} else if (node.arguments.length > 2) {
|
131
130
|
return result.err(new ValSyntaxError(`Invalid c.file() call: too many arguments ${node.arguments.length}}`, node));
|
132
|
-
} else if (!
|
131
|
+
} else if (!ts.isStringLiteral(node.arguments[0])) {
|
133
132
|
return result.err(new ValSyntaxError(`Invalid c.file() call: ref must be a string literal`, node));
|
134
133
|
}
|
135
134
|
const refNode = node.arguments[0];
|
@@ -141,7 +140,7 @@ function findValFileMetadataArg(node) {
|
|
141
140
|
} else if (node.arguments.length > 2) {
|
142
141
|
return result.err(new ValSyntaxError(`Invalid c.file() call: too many arguments ${node.arguments.length}}`, node));
|
143
142
|
} else if (node.arguments.length === 2) {
|
144
|
-
if (!
|
143
|
+
if (!ts.isObjectLiteralExpression(node.arguments[1])) {
|
145
144
|
return result.err(new ValSyntaxError(`Invalid c.file() call: metadata must be a object literal`, node));
|
146
145
|
}
|
147
146
|
return result.ok(node.arguments[1]);
|
@@ -156,7 +155,7 @@ function findValFileMetadataArg(node) {
|
|
156
155
|
*/
|
157
156
|
function validateInitializers(nodes) {
|
158
157
|
for (const node of nodes) {
|
159
|
-
if (
|
158
|
+
if (ts.isSpreadElement(node)) {
|
160
159
|
return new ValSyntaxError("Unexpected spread element", node);
|
161
160
|
}
|
162
161
|
}
|
@@ -167,22 +166,22 @@ function isPath(node, path) {
|
|
167
166
|
let currentNode = node;
|
168
167
|
for (let i = path.length - 1; i > 0; --i) {
|
169
168
|
const name = path[i];
|
170
|
-
if (!
|
169
|
+
if (!ts.isPropertyAccessExpression(currentNode)) {
|
171
170
|
return false;
|
172
171
|
}
|
173
|
-
if (!
|
172
|
+
if (!ts.isIdentifier(currentNode.name) || currentNode.name.text !== name) {
|
174
173
|
return false;
|
175
174
|
}
|
176
175
|
currentNode = currentNode.expression;
|
177
176
|
}
|
178
|
-
return
|
177
|
+
return ts.isIdentifier(currentNode) && currentNode.text === path[0];
|
179
178
|
}
|
180
179
|
function validateArguments(node, validators) {
|
181
180
|
return result.allV([node.arguments.length === validators.length ? result.voidOk : result.err(new ValSyntaxError(`Expected ${validators.length} arguments`, node)), ...node.arguments.slice(0, validators.length).map((argument, index) => validators[index](argument))]);
|
182
181
|
}
|
183
182
|
function analyzeDefaultExport(node) {
|
184
183
|
const cDefine = node.expression;
|
185
|
-
if (!
|
184
|
+
if (!ts.isCallExpression(cDefine)) {
|
186
185
|
return result.err(new ValSyntaxError("Expected default expression to be a call expression", cDefine));
|
187
186
|
}
|
188
187
|
if (!isPath(cDefine.expression, ["c", "define"])) {
|
@@ -190,7 +189,7 @@ function analyzeDefaultExport(node) {
|
|
190
189
|
}
|
191
190
|
return pipe(validateArguments(cDefine, [id => {
|
192
191
|
// TODO: validate ID value here?
|
193
|
-
if (!
|
192
|
+
if (!ts.isStringLiteralLike(id)) {
|
194
193
|
return result.err(new ValSyntaxError("Expected first argument to c.define to be a string literal", id));
|
195
194
|
}
|
196
195
|
return result.voidOk;
|
@@ -208,7 +207,7 @@ function analyzeDefaultExport(node) {
|
|
208
207
|
}
|
209
208
|
function analyzeValModule(sourceFile) {
|
210
209
|
const analysis = sourceFile.forEachChild(node => {
|
211
|
-
if (
|
210
|
+
if (ts.isExportAssignment(node)) {
|
212
211
|
return analyzeDefaultExport(node);
|
213
212
|
}
|
214
213
|
});
|
@@ -222,62 +221,62 @@ function isValidIdentifier(text) {
|
|
222
221
|
if (text.length === 0) {
|
223
222
|
return false;
|
224
223
|
}
|
225
|
-
if (!
|
224
|
+
if (!ts.isIdentifierStart(text.charCodeAt(0), ts.ScriptTarget.ES2020)) {
|
226
225
|
return false;
|
227
226
|
}
|
228
227
|
for (let i = 1; i < text.length; ++i) {
|
229
|
-
if (!
|
228
|
+
if (!ts.isIdentifierPart(text.charCodeAt(i), ts.ScriptTarget.ES2020)) {
|
230
229
|
return false;
|
231
230
|
}
|
232
231
|
}
|
233
232
|
return true;
|
234
233
|
}
|
235
234
|
function createPropertyAssignment(key, value) {
|
236
|
-
return
|
235
|
+
return ts.factory.createPropertyAssignment(isValidIdentifier(key) ? ts.factory.createIdentifier(key) : ts.factory.createStringLiteral(key), toExpression(value));
|
237
236
|
}
|
238
237
|
function createValFileReference(value) {
|
239
|
-
const args = [
|
238
|
+
const args = [ts.factory.createStringLiteral(value[FILE_REF_PROP])];
|
240
239
|
if (value.metadata) {
|
241
240
|
args.push(toExpression(value.metadata));
|
242
241
|
}
|
243
|
-
return
|
242
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("c"), ts.factory.createIdentifier("file")), undefined, args);
|
244
243
|
}
|
245
244
|
function createValRichTextImage(value) {
|
246
|
-
const args = [
|
245
|
+
const args = [ts.factory.createStringLiteral(value[FILE_REF_PROP])];
|
247
246
|
if (value.metadata) {
|
248
247
|
args.push(toExpression(value.metadata));
|
249
248
|
}
|
250
|
-
return
|
249
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("c"), "rt"), ts.factory.createIdentifier("image")), undefined, args);
|
251
250
|
}
|
252
251
|
function createValLink(value) {
|
253
|
-
const args = [
|
252
|
+
const args = [ts.factory.createStringLiteral(value.children[0]), toExpression({
|
254
253
|
href: value.href
|
255
254
|
})];
|
256
|
-
return
|
255
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("c"), "rt"), ts.factory.createIdentifier("link")), undefined, args);
|
257
256
|
}
|
258
257
|
function createValRichTextTaggedStringTemplate(value) {
|
259
258
|
const {
|
260
259
|
templateStrings: [head, ...others],
|
261
260
|
exprs
|
262
261
|
} = value;
|
263
|
-
const tag =
|
262
|
+
const tag = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("c"), ts.factory.createIdentifier("richtext"));
|
264
263
|
if (exprs.length > 0) {
|
265
|
-
return
|
264
|
+
return ts.factory.createTaggedTemplateExpression(tag, undefined, ts.factory.createTemplateExpression(ts.factory.createTemplateHead(head, head), others.map((s, i) => ts.factory.createTemplateSpan(toExpression(exprs[i]), i < others.length - 1 ? ts.factory.createTemplateMiddle(s, s) : ts.factory.createTemplateTail(s, s)))));
|
266
265
|
}
|
267
|
-
return
|
266
|
+
return ts.factory.createTaggedTemplateExpression(tag, undefined, ts.factory.createNoSubstitutionTemplateLiteral(head, head));
|
268
267
|
}
|
269
268
|
function toExpression(value) {
|
270
269
|
if (typeof value === "string") {
|
271
270
|
// TODO: Use configuration/heuristics to determine use of single quote or double quote
|
272
|
-
return
|
271
|
+
return ts.factory.createStringLiteral(value);
|
273
272
|
} else if (typeof value === "number") {
|
274
|
-
return
|
273
|
+
return ts.factory.createNumericLiteral(value);
|
275
274
|
} else if (typeof value === "boolean") {
|
276
|
-
return value ?
|
275
|
+
return value ? ts.factory.createTrue() : ts.factory.createFalse();
|
277
276
|
} else if (value === null) {
|
278
|
-
return
|
277
|
+
return ts.factory.createNull();
|
279
278
|
} else if (Array.isArray(value)) {
|
280
|
-
return
|
279
|
+
return ts.factory.createArrayLiteralExpression(value.map(toExpression));
|
281
280
|
} else if (typeof value === "object") {
|
282
281
|
if (isValFileValue(value)) {
|
283
282
|
if (isValRichTextImageValue(value)) {
|
@@ -289,24 +288,24 @@ function toExpression(value) {
|
|
289
288
|
} else if (isValRichTextValue(value)) {
|
290
289
|
return createValRichTextTaggedStringTemplate(value);
|
291
290
|
}
|
292
|
-
return
|
291
|
+
return ts.factory.createObjectLiteralExpression(Object.entries(value).map(([key, value]) => createPropertyAssignment(key, value)));
|
293
292
|
} else {
|
294
|
-
return
|
293
|
+
return ts.factory.createStringLiteral(value);
|
295
294
|
}
|
296
295
|
}
|
297
296
|
|
298
297
|
// TODO: Choose newline based on project settings/heuristics/system default?
|
299
|
-
const newLine =
|
298
|
+
const newLine = ts.NewLineKind.LineFeed;
|
300
299
|
// TODO: Handle indentation of printed code
|
301
|
-
const printer =
|
300
|
+
const printer = ts.createPrinter({
|
302
301
|
newLine: newLine
|
303
302
|
// neverAsciiEscape: true,
|
304
303
|
});
|
305
304
|
function replaceNodeValue(document, node, value) {
|
306
|
-
const replacementText = printer.printNode(
|
307
|
-
const span =
|
308
|
-
const newText = `${document.text.substring(0, span.start)}${replacementText}${document.text.substring(
|
309
|
-
return [document.update(newText,
|
305
|
+
const replacementText = printer.printNode(ts.EmitHint.Unspecified, toExpression(value), document);
|
306
|
+
const span = ts.createTextSpanFromBounds(node.getStart(document, false), node.end);
|
307
|
+
const newText = `${document.text.substring(0, span.start)}${replacementText}${document.text.substring(ts.textSpanEnd(span))}`;
|
308
|
+
return [document.update(newText, ts.createTextChangeRange(span, replacementText.length)), node];
|
310
309
|
}
|
311
310
|
function isIndentation(s) {
|
312
311
|
for (let i = 0; i < s.length; ++i) {
|
@@ -318,7 +317,7 @@ function isIndentation(s) {
|
|
318
317
|
return true;
|
319
318
|
}
|
320
319
|
function newLineStr(kind) {
|
321
|
-
if (kind ===
|
320
|
+
if (kind === ts.NewLineKind.CarriageReturnLineFeed) {
|
322
321
|
return "\r\n";
|
323
322
|
} else {
|
324
323
|
return "\n";
|
@@ -340,38 +339,38 @@ function insertAt(document, nodes, index, node) {
|
|
340
339
|
let replacementText;
|
341
340
|
if (nodes.length === 0) {
|
342
341
|
// Replace entire range of nodes
|
343
|
-
replacementText = printer.printNode(
|
344
|
-
span =
|
342
|
+
replacementText = printer.printNode(ts.EmitHint.Unspecified, node, document);
|
343
|
+
span = ts.createTextSpanFromBounds(nodes.pos, nodes.end);
|
345
344
|
} else if (index === nodes.length) {
|
346
345
|
// Insert after last node
|
347
346
|
const neighbor = nodes[nodes.length - 1];
|
348
|
-
replacementText = `${getSeparator(document, neighbor)}${printer.printNode(
|
349
|
-
span =
|
347
|
+
replacementText = `${getSeparator(document, neighbor)}${printer.printNode(ts.EmitHint.Unspecified, node, document)}`;
|
348
|
+
span = ts.createTextSpan(neighbor.end, 0);
|
350
349
|
} else {
|
351
350
|
// Insert before node
|
352
351
|
const neighbor = nodes[index];
|
353
|
-
replacementText = `${printer.printNode(
|
354
|
-
span =
|
352
|
+
replacementText = `${printer.printNode(ts.EmitHint.Unspecified, node, document)}${getSeparator(document, neighbor)}`;
|
353
|
+
span = ts.createTextSpan(neighbor.getStart(document, true), 0);
|
355
354
|
}
|
356
|
-
const newText = `${document.text.substring(0, span.start)}${replacementText}${document.text.substring(
|
357
|
-
return document.update(newText,
|
355
|
+
const newText = `${document.text.substring(0, span.start)}${replacementText}${document.text.substring(ts.textSpanEnd(span))}`;
|
356
|
+
return document.update(newText, ts.createTextChangeRange(span, replacementText.length));
|
358
357
|
}
|
359
358
|
function removeAt(document, nodes, index) {
|
360
359
|
const node = nodes[index];
|
361
360
|
let span;
|
362
361
|
if (nodes.length === 1) {
|
363
|
-
span =
|
362
|
+
span = ts.createTextSpanFromBounds(nodes.pos, nodes.end);
|
364
363
|
} else if (index === nodes.length - 1) {
|
365
364
|
// Remove until previous node
|
366
365
|
const neighbor = nodes[index - 1];
|
367
|
-
span =
|
366
|
+
span = ts.createTextSpanFromBounds(neighbor.end, node.end);
|
368
367
|
} else {
|
369
368
|
// Remove before next node
|
370
369
|
const neighbor = nodes[index + 1];
|
371
|
-
span =
|
370
|
+
span = ts.createTextSpanFromBounds(node.getStart(document, true), neighbor.getStart(document, true));
|
372
371
|
}
|
373
|
-
const newText = `${document.text.substring(0, span.start)}${document.text.substring(
|
374
|
-
return [document.update(newText,
|
372
|
+
const newText = `${document.text.substring(0, span.start)}${document.text.substring(ts.textSpanEnd(span))}`;
|
373
|
+
return [document.update(newText, ts.createTextChangeRange(span, 0)), node];
|
375
374
|
}
|
376
375
|
function parseAndValidateArrayInsertIndex(key, nodes) {
|
377
376
|
if (key === "-") {
|
@@ -413,9 +412,9 @@ function parseAndValidateArrayInboundsIndex(key, nodes) {
|
|
413
412
|
}));
|
414
413
|
}
|
415
414
|
function replaceInNode(document, node, key, value) {
|
416
|
-
if (
|
415
|
+
if (ts.isArrayLiteralExpression(node)) {
|
417
416
|
return pipe(parseAndValidateArrayInboundsIndex(key, node.elements), result.map(index => replaceNodeValue(document, node.elements[index], value)));
|
418
|
-
} else if (
|
417
|
+
} else if (ts.isObjectLiteralExpression(node)) {
|
419
418
|
return pipe(findObjectPropertyAssignment(node, key), result.flatMap(assignment => {
|
420
419
|
if (!assignment) {
|
421
420
|
return result.err(new PatchError("Cannot replace object element which does not exist"));
|
@@ -438,7 +437,7 @@ function replaceInNode(document, node, key, value) {
|
|
438
437
|
}
|
439
438
|
return replaceInNode(document,
|
440
439
|
// TODO: creating a fake object here might not be right - seems to work though
|
441
|
-
|
440
|
+
ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment(key, metadataArgNode)]), key, value);
|
442
441
|
}));
|
443
442
|
}
|
444
443
|
} else {
|
@@ -453,9 +452,9 @@ function replaceAtPath(document, rootNode, path, value) {
|
|
453
452
|
}
|
454
453
|
}
|
455
454
|
function getFromNode(node, key) {
|
456
|
-
if (
|
455
|
+
if (ts.isArrayLiteralExpression(node)) {
|
457
456
|
return pipe(parseAndValidateArrayInboundsIndex(key, node.elements), result.map(index => node.elements[index]));
|
458
|
-
} else if (
|
457
|
+
} else if (ts.isObjectLiteralExpression(node)) {
|
459
458
|
return pipe(findObjectPropertyAssignment(node, key), result.map(assignment => assignment === null || assignment === void 0 ? void 0 : assignment.initializer));
|
460
459
|
} else if (isValFileMethodCall(node)) {
|
461
460
|
if (key === FILE_REF_PROP) {
|
@@ -485,9 +484,9 @@ function getAtPath(rootNode, path) {
|
|
485
484
|
return pipe(path, result.flatMapReduce((node, key) => pipe(getFromNode(node, key), result.filterOrElse(childNode => childNode !== undefined, () => new PatchError("Path refers to non-existing object/array"))), rootNode));
|
486
485
|
}
|
487
486
|
function removeFromNode(document, node, key) {
|
488
|
-
if (
|
487
|
+
if (ts.isArrayLiteralExpression(node)) {
|
489
488
|
return pipe(parseAndValidateArrayInboundsIndex(key, node.elements), result.map(index => removeAt(document, node.elements, index)));
|
490
|
-
} else if (
|
489
|
+
} else if (ts.isObjectLiteralExpression(node)) {
|
491
490
|
return pipe(findObjectPropertyAssignment(node, key), result.flatMap(assignment => {
|
492
491
|
if (!assignment) {
|
493
492
|
return result.err(new PatchError("Cannot replace object element which does not exist"));
|
@@ -533,9 +532,9 @@ function isValRichTextValue(value) {
|
|
533
532
|
return !!(typeof value === "object" && value && VAL_EXTENSION in value && value[VAL_EXTENSION] === "richtext" && "templateStrings" in value && typeof value.templateStrings === "object" && Array.isArray(value.templateStrings));
|
534
533
|
}
|
535
534
|
function addToNode(document, node, key, value) {
|
536
|
-
if (
|
535
|
+
if (ts.isArrayLiteralExpression(node)) {
|
537
536
|
return pipe(parseAndValidateArrayInsertIndex(key, node.elements), result.map(index => [insertAt(document, node.elements, index, toExpression(value))]));
|
538
|
-
} else if (
|
537
|
+
} else if (ts.isObjectLiteralExpression(node)) {
|
539
538
|
if (key === FILE_REF_PROP) {
|
540
539
|
return result.err(new PatchError("Cannot add a key ref to object"));
|
541
540
|
}
|
@@ -657,7 +656,7 @@ function convertDataUrlToBase64(dataUrl) {
|
|
657
656
|
}
|
658
657
|
const patchSourceFile = (sourceFile, patch) => {
|
659
658
|
if (typeof sourceFile === "string") {
|
660
|
-
return applyPatch(
|
659
|
+
return applyPatch(ts.createSourceFile("<val>", sourceFile, ts.ScriptTarget.ES2015), ops$1, patch);
|
661
660
|
}
|
662
661
|
return applyPatch(sourceFile, ops$1, patch);
|
663
662
|
};
|
@@ -773,7 +772,7 @@ const getCompilerOptions = (rootDir, parseConfigHost) => {
|
|
773
772
|
const {
|
774
773
|
config,
|
775
774
|
error
|
776
|
-
} =
|
775
|
+
} = ts.readConfigFile(configFilePath, parseConfigHost.readFile.bind(parseConfigHost));
|
777
776
|
if (error) {
|
778
777
|
if (typeof error.messageText === "string") {
|
779
778
|
throw Error(`Could not parse config file: ${configFilePath}. Error: ${error.messageText}`);
|
@@ -781,7 +780,7 @@ const getCompilerOptions = (rootDir, parseConfigHost) => {
|
|
781
780
|
throw Error(`Could not parse config file: ${configFilePath}. Error: ${error.messageText.messageText}`);
|
782
781
|
}
|
783
782
|
const optionsOverrides = undefined;
|
784
|
-
const parsedConfigFile =
|
783
|
+
const parsedConfigFile = ts.parseJsonConfigFileContent(config, parseConfigHost, rootDir, optionsOverrides, configFilePath);
|
785
784
|
if (parsedConfigFile.errors.length > 0) {
|
786
785
|
throw Error(`Could not parse config file: ${configFilePath}. Errors: ${parsedConfigFile.errors.map(e => e.messageText).join("\n")}`);
|
787
786
|
}
|
@@ -790,7 +789,7 @@ const getCompilerOptions = (rootDir, parseConfigHost) => {
|
|
790
789
|
|
791
790
|
class ValSourceFileHandler {
|
792
791
|
constructor(projectRoot, compilerOptions, host = {
|
793
|
-
...
|
792
|
+
...ts.sys,
|
794
793
|
writeFile: (fileName, data, encoding) => {
|
795
794
|
fs.mkdirSync(path__default.dirname(fileName), {
|
796
795
|
recursive: true
|
@@ -805,9 +804,9 @@ class ValSourceFileHandler {
|
|
805
804
|
}
|
806
805
|
getSourceFile(filePath) {
|
807
806
|
const fileContent = this.host.readFile(filePath);
|
808
|
-
const scriptTarget = this.compilerOptions.target ??
|
807
|
+
const scriptTarget = this.compilerOptions.target ?? ts.ScriptTarget.ES2020;
|
809
808
|
if (fileContent) {
|
810
|
-
return
|
809
|
+
return ts.createSourceFile(filePath, fileContent, scriptTarget);
|
811
810
|
}
|
812
811
|
}
|
813
812
|
writeSourceFile(sourceFile) {
|
@@ -819,7 +818,7 @@ class ValSourceFileHandler {
|
|
819
818
|
this.host.writeFile(filePath, content, encoding);
|
820
819
|
}
|
821
820
|
resolveSourceModulePath(containingFilePath, requestedModuleName) {
|
822
|
-
const resolutionRes =
|
821
|
+
const resolutionRes = ts.resolveModuleName(requestedModuleName, path__default.isAbsolute(containingFilePath) ? containingFilePath : path__default.resolve(this.projectRoot, containingFilePath), this.compilerOptions, this.host, undefined, undefined, ts.ModuleKind.ESNext);
|
823
822
|
const resolvedModule = resolutionRes.resolvedModule;
|
824
823
|
if (!resolvedModule) {
|
825
824
|
throw Error(`Could not resolve module "${requestedModuleName}", base: "${containingFilePath}": No resolved modules returned: ${JSON.stringify(resolutionRes)}`);
|
@@ -840,7 +839,7 @@ class ValModuleLoader {
|
|
840
839
|
compilerOptions,
|
841
840
|
// TODO: remove this?
|
842
841
|
sourceFileHandler, host = {
|
843
|
-
...
|
842
|
+
...ts.sys,
|
844
843
|
writeFile: (fileName, data, encoding) => {
|
845
844
|
fs.mkdirSync(path__default.dirname(fileName), {
|
846
845
|
recursive: true
|
@@ -1100,7 +1099,7 @@ export const IS_DEV = false;2
|
|
1100
1099
|
}
|
1101
1100
|
|
1102
1101
|
async function createService(projectRoot, opts, host = {
|
1103
|
-
...
|
1102
|
+
...ts.sys,
|
1104
1103
|
writeFile: (fileName, data, encoding) => {
|
1105
1104
|
fs.mkdirSync(path__default.dirname(fileName), {
|
1106
1105
|
recursive: true
|
@@ -1752,7 +1751,11 @@ class ValServer {
|
|
1752
1751
|
}
|
1753
1752
|
let modules;
|
1754
1753
|
if (commit) {
|
1755
|
-
|
1754
|
+
const commitRes = await this.execCommit(patches, cookies);
|
1755
|
+
if (commitRes.status !== 200) {
|
1756
|
+
return commitRes;
|
1757
|
+
}
|
1758
|
+
modules = commitRes.json;
|
1756
1759
|
} else {
|
1757
1760
|
modules = await this.getPatchedModules(patches);
|
1758
1761
|
}
|
@@ -1950,6 +1953,9 @@ function guessMimeTypeFromPath(filePath) {
|
|
1950
1953
|
}
|
1951
1954
|
return null;
|
1952
1955
|
}
|
1956
|
+
function isCachedPatchFileOp(op) {
|
1957
|
+
return !!(op.op === "file" && typeof op.filePath === "string" && op.value && typeof op.value === "object" && !Array.isArray(op.value) && "sha256" in op.value && typeof op.value.sha256 === "string");
|
1958
|
+
}
|
1953
1959
|
|
1954
1960
|
const textEncoder = new TextEncoder();
|
1955
1961
|
class LocalValServer extends ValServer {
|
@@ -2218,7 +2224,10 @@ class LocalValServer extends ValServer {
|
|
2218
2224
|
this.host.rmFile(this.getPatchFilePath(patchId));
|
2219
2225
|
await this.options.service.patch(moduleId, patch);
|
2220
2226
|
}
|
2221
|
-
return
|
2227
|
+
return {
|
2228
|
+
status: 200,
|
2229
|
+
json: await this.getPatchedModules(patches)
|
2230
|
+
};
|
2222
2231
|
}
|
2223
2232
|
|
2224
2233
|
/* Bad requests on Local Server: */
|
@@ -2233,9 +2242,6 @@ class LocalValServer extends ValServer {
|
|
2233
2242
|
return this.badRequest();
|
2234
2243
|
}
|
2235
2244
|
}
|
2236
|
-
function isCachedPatchFileOp(op) {
|
2237
|
-
return !!(op.op === "file" && typeof op.filePath === "string" && op.value && typeof op.value === "object" && !Array.isArray(op.value) && "sha256" in op.value && typeof op.value.sha256 === "string");
|
2238
|
-
}
|
2239
2245
|
|
2240
2246
|
function decodeJwt(token, secretKey) {
|
2241
2247
|
const [headerBase64, payloadBase64, signatureBase64, ...rest] = token.split(".");
|
@@ -2313,8 +2319,12 @@ class RemoteFS {
|
|
2313
2319
|
}
|
2314
2320
|
async getPendingOperations() {
|
2315
2321
|
const modified = {};
|
2316
|
-
for (const modifiedFile
|
2317
|
-
|
2322
|
+
for (const modifiedFile of this.modifiedFiles) {
|
2323
|
+
const {
|
2324
|
+
directory,
|
2325
|
+
filename
|
2326
|
+
} = RemoteFS.parsePath(modifiedFile);
|
2327
|
+
modified[modifiedFile] = this.data[directory].utf8Files[filename];
|
2318
2328
|
}
|
2319
2329
|
return {
|
2320
2330
|
modified: modified,
|
@@ -2510,28 +2520,47 @@ class ProxyValServer extends ValServer {
|
|
2510
2520
|
}
|
2511
2521
|
};
|
2512
2522
|
}
|
2513
|
-
const params = `commit=${encodeURIComponent(commit)}`;
|
2523
|
+
const params = `commit=${encodeURIComponent(commit)}&root=${encodeURIComponent(this.apiOptions.root || "/")}&cwd=${encodeURIComponent(this.cwd)}`;
|
2514
2524
|
const url = new URL(`/v1/commit/${this.options.valName}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
|
2515
2525
|
|
2516
|
-
// Creates a fresh copy of the fs. We cannot touch the existing fs, since
|
2517
|
-
// other operations on it.
|
2526
|
+
// Creates a fresh copy of the fs. We cannot touch the existing fs, since there might be parallel operations?
|
2518
2527
|
// We could perhaps free up the other fs while doing this operation, but uncertain if we can actually do that and if that would actually help on memory.
|
2519
|
-
// It is a concern we have, since we might be using quite a lot of memory when having the whole FS in memory.
|
2528
|
+
// It is a concern we have, since we might be using quite a lot of memory when having the whole FS in memory.
|
2529
|
+
// NOTE that base64 values from patches are not part of the patches, nor are they part of the fs so at least we do not have to worry about them.
|
2530
|
+
// This NOTE was written after we wrote the comments above. We are a bit uncertain whether memory usage should be a concern at this point.
|
2520
2531
|
const remoteFS = new RemoteFS();
|
2521
2532
|
const initRes = await this.initRemoteFS(commit, remoteFS, token);
|
2522
2533
|
if (initRes.status !== 200) {
|
2523
2534
|
return initRes;
|
2524
2535
|
}
|
2525
2536
|
const service = await createService(this.cwd, this.apiOptions, remoteFS);
|
2526
|
-
|
2527
|
-
|
2537
|
+
// TODO: optimize patches, e.g. only take the last replace for a given thing, etc...
|
2538
|
+
const patchIds = [];
|
2539
|
+
const binaryFileUpdates = {};
|
2540
|
+
for (const [patchId, moduleId, patch] of patches) {
|
2541
|
+
const patchableOps = [];
|
2542
|
+
for (const op of patch) {
|
2543
|
+
if (isCachedPatchFileOp(op)) {
|
2544
|
+
binaryFileUpdates[op.filePath] = op.value;
|
2545
|
+
} else {
|
2546
|
+
if (Internal.isFileOp(op)) {
|
2547
|
+
throw new Error(`Val: Unexpected file operation (file: ${op.filePath}). This is likely a Val bug.`);
|
2548
|
+
}
|
2549
|
+
patchableOps.push(op);
|
2550
|
+
}
|
2551
|
+
}
|
2552
|
+
await service.patch(moduleId, patchableOps);
|
2553
|
+
patchIds.push(patchId);
|
2528
2554
|
}
|
2529
|
-
const
|
2555
|
+
const sourceFileUpdates = await remoteFS.getPendingOperations();
|
2530
2556
|
const fetchRes = await fetch(url, {
|
2531
2557
|
method: "POST",
|
2532
2558
|
headers: getAuthHeaders(token, "application/json"),
|
2533
2559
|
body: JSON.stringify({
|
2534
|
-
|
2560
|
+
sourceFileUpdates: sourceFileUpdates.modified,
|
2561
|
+
binaryFileUpdates,
|
2562
|
+
deletedFiles: sourceFileUpdates.deleted,
|
2563
|
+
patchIds
|
2535
2564
|
})
|
2536
2565
|
});
|
2537
2566
|
if (fetchRes.status === 200) {
|
@@ -2540,13 +2569,7 @@ class ProxyValServer extends ValServer {
|
|
2540
2569
|
json: await fetchRes.json()
|
2541
2570
|
};
|
2542
2571
|
} else {
|
2543
|
-
|
2544
|
-
return {
|
2545
|
-
status: fetchRes.status,
|
2546
|
-
json: {
|
2547
|
-
message: "Failed to get patches"
|
2548
|
-
}
|
2549
|
-
};
|
2572
|
+
return createJsonError(fetchRes);
|
2550
2573
|
}
|
2551
2574
|
});
|
2552
2575
|
}
|
@@ -2566,32 +2589,42 @@ class ProxyValServer extends ValServer {
|
|
2566
2589
|
});
|
2567
2590
|
if (fetchRes.status === 200) {
|
2568
2591
|
const json = await fetchRes.json();
|
2569
|
-
|
2592
|
+
let error = false;
|
2593
|
+
if (typeof json !== "object") {
|
2594
|
+
error = {
|
2595
|
+
details: "Invalid response: not an object"
|
2596
|
+
};
|
2597
|
+
}
|
2598
|
+
if (typeof json.git !== "object") {
|
2599
|
+
error = {
|
2600
|
+
details: "Invalid response: missing git"
|
2601
|
+
};
|
2602
|
+
}
|
2603
|
+
if (typeof json.git.commit !== "string") {
|
2604
|
+
error = {
|
2605
|
+
details: "Invalid response: missing git.commit"
|
2606
|
+
};
|
2607
|
+
}
|
2608
|
+
if (typeof json.directories !== "object" || json.directories === null) {
|
2609
|
+
error = {
|
2610
|
+
details: "Invalid response: missing directories"
|
2611
|
+
};
|
2612
|
+
}
|
2613
|
+
if (error) {
|
2614
|
+
return {
|
2615
|
+
status: 500,
|
2616
|
+
json: {
|
2617
|
+
message: "Failed to fetch remote files",
|
2618
|
+
...error
|
2619
|
+
}
|
2620
|
+
};
|
2621
|
+
}
|
2622
|
+
remoteFS.initializeWith(json.directories);
|
2570
2623
|
return {
|
2571
2624
|
status: 200
|
2572
2625
|
};
|
2573
2626
|
} else {
|
2574
|
-
|
2575
|
-
var _fetchRes$headers$get;
|
2576
|
-
if ((_fetchRes$headers$get = fetchRes.headers.get("Content-Type")) !== null && _fetchRes$headers$get !== void 0 && _fetchRes$headers$get.includes("application/json")) {
|
2577
|
-
const json = await fetchRes.json();
|
2578
|
-
return {
|
2579
|
-
status: fetchRes.status,
|
2580
|
-
json: {
|
2581
|
-
message: "Failed to fetch remote files",
|
2582
|
-
details: json
|
2583
|
-
}
|
2584
|
-
};
|
2585
|
-
}
|
2586
|
-
} catch (err) {
|
2587
|
-
console.error(err);
|
2588
|
-
}
|
2589
|
-
return {
|
2590
|
-
status: fetchRes.status,
|
2591
|
-
json: {
|
2592
|
-
message: "Unknown failure while fetching remote files"
|
2593
|
-
}
|
2594
|
-
};
|
2627
|
+
return createJsonError(fetchRes);
|
2595
2628
|
}
|
2596
2629
|
} catch (err) {
|
2597
2630
|
return {
|
@@ -2800,11 +2833,13 @@ class ProxyValServer extends ValServer {
|
|
2800
2833
|
token
|
2801
2834
|
}) => {
|
2802
2835
|
const patchIds = query.id || [];
|
2803
|
-
const
|
2804
|
-
const url = new URL(`/v1/patches/${this.options.valName}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
|
2836
|
+
const url = new URL(`/v1/patches/${this.options.valName}/heads/${this.options.git.branch}/~`, this.options.valContentUrl);
|
2805
2837
|
const fetchRes = await fetch(url, {
|
2806
|
-
method: "
|
2807
|
-
headers: getAuthHeaders(token, "application/json")
|
2838
|
+
method: "DELETE",
|
2839
|
+
headers: getAuthHeaders(token, "application/json"),
|
2840
|
+
body: JSON.stringify({
|
2841
|
+
patches: patchIds
|
2842
|
+
})
|
2808
2843
|
});
|
2809
2844
|
if (fetchRes.status === 200) {
|
2810
2845
|
return {
|
@@ -2812,13 +2847,7 @@ class ProxyValServer extends ValServer {
|
|
2812
2847
|
json: await fetchRes.json()
|
2813
2848
|
};
|
2814
2849
|
} else {
|
2815
|
-
|
2816
|
-
return {
|
2817
|
-
status: fetchRes.status,
|
2818
|
-
json: {
|
2819
|
-
message: "Failed to delete patches"
|
2820
|
-
}
|
2821
|
-
};
|
2850
|
+
return createJsonError(fetchRes);
|
2822
2851
|
}
|
2823
2852
|
});
|
2824
2853
|
}
|
@@ -2849,13 +2878,7 @@ class ProxyValServer extends ValServer {
|
|
2849
2878
|
json: await fetchRes.json()
|
2850
2879
|
};
|
2851
2880
|
} else {
|
2852
|
-
|
2853
|
-
return {
|
2854
|
-
status: fetchRes.status,
|
2855
|
-
json: {
|
2856
|
-
message: "Failed to get patches"
|
2857
|
-
}
|
2858
|
-
};
|
2881
|
+
return createJsonError(fetchRes);
|
2859
2882
|
}
|
2860
2883
|
});
|
2861
2884
|
}
|
@@ -2900,9 +2923,7 @@ class ProxyValServer extends ValServer {
|
|
2900
2923
|
json: await fetchRes.json()
|
2901
2924
|
};
|
2902
2925
|
} else {
|
2903
|
-
return
|
2904
|
-
status: fetchRes.status
|
2905
|
-
};
|
2926
|
+
return createJsonError(fetchRes);
|
2906
2927
|
}
|
2907
2928
|
});
|
2908
2929
|
}
|
@@ -3063,6 +3084,22 @@ function getStateFromCookie(stateCookie) {
|
|
3063
3084
|
};
|
3064
3085
|
}
|
3065
3086
|
}
|
3087
|
+
async function createJsonError(fetchRes) {
|
3088
|
+
var _fetchRes$headers$get;
|
3089
|
+
if ((_fetchRes$headers$get = fetchRes.headers.get("Content-Type")) !== null && _fetchRes$headers$get !== void 0 && _fetchRes$headers$get.includes("application/json")) {
|
3090
|
+
return {
|
3091
|
+
status: fetchRes.status,
|
3092
|
+
json: await fetchRes.json()
|
3093
|
+
};
|
3094
|
+
}
|
3095
|
+
console.error("Unexpected failure (did not get a json) - Val down?", fetchRes.status, await fetchRes.text());
|
3096
|
+
return {
|
3097
|
+
status: fetchRes.status,
|
3098
|
+
json: {
|
3099
|
+
message: "Unexpected failure (did not get a json) - Val down?"
|
3100
|
+
}
|
3101
|
+
};
|
3102
|
+
}
|
3066
3103
|
function createStateCookie(state) {
|
3067
3104
|
return Buffer.from(JSON.stringify(state), "utf8").toString("base64");
|
3068
3105
|
}
|