@valbuild/server 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +7 -4
  2. package/.babelrc.json +0 -5
  3. package/CHANGELOG.md +0 -0
  4. package/jest.config.js +0 -4
  5. package/src/LocalValServer.ts +0 -167
  6. package/src/ProxyValServer.ts +0 -542
  7. package/src/SerializedModuleContent.ts +0 -36
  8. package/src/Service.ts +0 -126
  9. package/src/ValFS.ts +0 -22
  10. package/src/ValFSHost.ts +0 -66
  11. package/src/ValModuleLoader.test.ts +0 -75
  12. package/src/ValModuleLoader.ts +0 -158
  13. package/src/ValQuickJSRuntime.ts +0 -85
  14. package/src/ValServer.ts +0 -24
  15. package/src/ValSourceFileHandler.ts +0 -57
  16. package/src/createFixPatch.ts +0 -170
  17. package/src/createRequestHandler.ts +0 -27
  18. package/src/expressHelpers.ts +0 -5
  19. package/src/getCompilerOptions.ts +0 -50
  20. package/src/hosting.ts +0 -290
  21. package/src/index.ts +0 -16
  22. package/src/jwt.ts +0 -93
  23. package/src/patch/ts/ops.test.ts +0 -937
  24. package/src/patch/ts/ops.ts +0 -897
  25. package/src/patch/ts/syntax.ts +0 -371
  26. package/src/patch/ts/valModule.test.ts +0 -26
  27. package/src/patch/ts/valModule.ts +0 -110
  28. package/src/patch/validation.ts +0 -81
  29. package/src/patchValFile.ts +0 -110
  30. package/src/readValFile.test.ts +0 -49
  31. package/src/readValFile.ts +0 -96
  32. package/test/example-projects/basic-next-javascript/jsconfig.json +0 -8
  33. package/test/example-projects/basic-next-javascript/package.json +0 -23
  34. package/test/example-projects/basic-next-javascript/pages/blogs.val.js +0 -20
  35. package/test/example-projects/basic-next-javascript/val.config.js +0 -4
  36. package/test/example-projects/basic-next-src-typescript/package.json +0 -23
  37. package/test/example-projects/basic-next-src-typescript/src/pages/blogs.val.ts +0 -20
  38. package/test/example-projects/basic-next-src-typescript/src/val.config.ts +0 -5
  39. package/test/example-projects/basic-next-src-typescript/tsconfig.json +0 -24
  40. package/test/example-projects/basic-next-typescript/package.json +0 -23
  41. package/test/example-projects/basic-next-typescript/pages/blogs.val.ts +0 -20
  42. package/test/example-projects/basic-next-typescript/tsconfig.json +0 -25
  43. package/test/example-projects/basic-next-typescript/val.config.ts +0 -5
  44. package/test/example-projects/typescript-description-files/README.md +0 -2
  45. package/test/example-projects/typescript-description-files/jsconfig.json +0 -8
  46. package/test/example-projects/typescript-description-files/package.json +0 -23
  47. package/test/example-projects/typescript-description-files/pages/blogs.val.d.ts +0 -7
  48. package/test/example-projects/typescript-description-files/pages/blogs.val.js +0 -19
  49. package/test/example-projects/typescript-description-files/val.config.d.ts +0 -3
  50. package/test/example-projects/typescript-description-files/val.config.js +0 -5
  51. package/tsconfig.json +0 -12
@@ -1,371 +0,0 @@
1
- import ts from "typescript";
2
- import { result, pipe } from "@valbuild/core/fp";
3
- import { JSONValue } from "@valbuild/core/patch";
4
- import { FileSource, FILE_REF_PROP } from "@valbuild/core";
5
- import { JsonPrimitive } from "@valbuild/core/src/Json";
6
-
7
- export class ValSyntaxError {
8
- constructor(public message: string, public node: ts.Node) {}
9
- }
10
-
11
- export type ValSyntaxErrorTree =
12
- | ValSyntaxError
13
- | [ValSyntaxErrorTree, ...ValSyntaxErrorTree[]];
14
-
15
- function forEachError(
16
- tree: ValSyntaxErrorTree,
17
- callback: (error: ValSyntaxError) => void
18
- ) {
19
- if (Array.isArray(tree)) {
20
- for (const subtree of tree) {
21
- forEachError(subtree, callback);
22
- }
23
- } else {
24
- callback(tree);
25
- }
26
- }
27
-
28
- export function flattenErrors(
29
- tree: ValSyntaxErrorTree
30
- ): [ValSyntaxError, ...ValSyntaxError[]] {
31
- const result: ValSyntaxError[] = [];
32
- forEachError(tree, result.push.bind(result));
33
- return result as [ValSyntaxError, ...ValSyntaxError[]];
34
- }
35
-
36
- export function flatMapErrors<T>(
37
- tree: ValSyntaxErrorTree,
38
- cb: (error: ValSyntaxError) => T
39
- ): [T, ...T[]] {
40
- const result: T[] = [];
41
- forEachError(tree, (error) => result.push(cb(error)));
42
- return result as [T, ...T[]];
43
- }
44
-
45
- export function formatSyntaxError(
46
- error: ValSyntaxError,
47
- sourceFile: ts.SourceFile
48
- ): string {
49
- const pos = sourceFile.getLineAndCharacterOfPosition(error.node.pos);
50
- return `${pos.line}:${pos.character} ${error.message}`;
51
- }
52
-
53
- export function formatSyntaxErrorTree(
54
- tree: ValSyntaxErrorTree,
55
- sourceFile: ts.SourceFile
56
- ): string[] {
57
- return flatMapErrors(tree, (error) => formatSyntaxError(error, sourceFile));
58
- }
59
-
60
- type LiteralPropertyName = (
61
- | ts.Identifier
62
- | ts.StringLiteral
63
- | ts.NumericLiteral
64
- ) & {
65
- /**
66
- * The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
67
- * or any literal of a template, this means quotes have been removed and escapes have been converted to actual characters.
68
- * 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".
69
- * https://github.com/microsoft/TypeScript/blob/4b794fe1dd0d184d3f8f17e94d8187eace57c91e/src/compiler/types.ts#L2127-L2131
70
- */
71
- name: string;
72
- };
73
-
74
- function isLiteralPropertyName(
75
- name: ts.PropertyName
76
- ): name is LiteralPropertyName {
77
- return (
78
- ts.isIdentifier(name) ||
79
- ts.isStringLiteral(name) ||
80
- ts.isNumericLiteral(name)
81
- );
82
- }
83
-
84
- type LiteralPropertyAssignment = ts.PropertyAssignment & {
85
- name: LiteralPropertyName;
86
- };
87
-
88
- function validateObjectProperties<
89
- T extends readonly ts.ObjectLiteralElementLike[]
90
- >(
91
- nodes: T
92
- ): result.Result<T & readonly LiteralPropertyAssignment[], ValSyntaxErrorTree> {
93
- const errors: ValSyntaxError[] = [];
94
- for (const node of nodes) {
95
- if (!ts.isPropertyAssignment(node)) {
96
- errors.push(
97
- new ValSyntaxError(
98
- "Object literal element must be property assignment",
99
- node
100
- )
101
- );
102
- } else if (!isLiteralPropertyName(node.name)) {
103
- errors.push(
104
- new ValSyntaxError(
105
- "Object literal element key must be an identifier or a literal",
106
- node
107
- )
108
- );
109
- }
110
- }
111
- if (errors.length > 0) {
112
- return result.err(errors as ValSyntaxErrorTree);
113
- }
114
- return result.ok(nodes as T & LiteralPropertyAssignment[]);
115
- }
116
-
117
- /**
118
- * Validates that the expression is a JSON compatible type of expression without
119
- * validating its children.
120
- */
121
- export function shallowValidateExpression(
122
- value: ts.Expression
123
- ): ValSyntaxError | undefined {
124
- return ts.isStringLiteralLike(value) ||
125
- ts.isNumericLiteral(value) ||
126
- value.kind === ts.SyntaxKind.TrueKeyword ||
127
- value.kind === ts.SyntaxKind.FalseKeyword ||
128
- value.kind === ts.SyntaxKind.NullKeyword ||
129
- ts.isArrayLiteralExpression(value) ||
130
- ts.isObjectLiteralExpression(value) ||
131
- isValFileMethodCall(value)
132
- ? undefined
133
- : new ValSyntaxError(
134
- "Expression must be a literal or call val.file",
135
- value
136
- );
137
- }
138
-
139
- /**
140
- * Validates that the expression is JSON compatible.
141
- */
142
- export function deepValidateExpression(
143
- value: ts.Expression
144
- ): result.Result<void, ValSyntaxErrorTree> {
145
- if (ts.isStringLiteralLike(value)) {
146
- return result.voidOk;
147
- } else if (ts.isNumericLiteral(value)) {
148
- return result.voidOk;
149
- } else if (value.kind === ts.SyntaxKind.TrueKeyword) {
150
- return result.voidOk;
151
- } else if (value.kind === ts.SyntaxKind.FalseKeyword) {
152
- return result.voidOk;
153
- } else if (value.kind === ts.SyntaxKind.NullKeyword) {
154
- return result.voidOk;
155
- } else if (ts.isArrayLiteralExpression(value)) {
156
- return result.allV(value.elements.map(deepValidateExpression));
157
- } else if (ts.isObjectLiteralExpression(value)) {
158
- return pipe(
159
- validateObjectProperties(value.properties),
160
- result.flatMap((assignments: ts.NodeArray<LiteralPropertyAssignment>) =>
161
- pipe(
162
- assignments.map((assignment) =>
163
- deepValidateExpression(assignment.initializer)
164
- ),
165
- result.allV
166
- )
167
- )
168
- );
169
- } else if (isValFileMethodCall(value)) {
170
- if (value.arguments.length >= 1) {
171
- if (!ts.isStringLiteralLike(value.arguments[0])) {
172
- return result.err(
173
- new ValSyntaxError(
174
- "First argument of val.file must be a string literal",
175
- value.arguments[0]
176
- )
177
- );
178
- }
179
- }
180
- if (value.arguments.length === 2) {
181
- if (!ts.isObjectLiteralExpression(value.arguments[1])) {
182
- return result.err(
183
- new ValSyntaxError(
184
- "Second argument of val.file must be an object literal",
185
- value.arguments[1]
186
- )
187
- );
188
- }
189
- }
190
- return result.voidOk;
191
- } else {
192
- return result.err(
193
- new ValSyntaxError("Expression must be a literal or call val.file", value)
194
- );
195
- }
196
- }
197
-
198
- /**
199
- * Evaluates the expression as a JSON value
200
- */
201
- export function evaluateExpression(
202
- value: ts.Expression
203
- ): result.Result<JSONValue, ValSyntaxErrorTree> {
204
- // The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
205
- // or any literal of a template, this means quotes have been removed and escapes have been converted to actual characters.
206
- // 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".
207
- // https://github.com/microsoft/TypeScript/blob/4b794fe1dd0d184d3f8f17e94d8187eace57c91e/src/compiler/types.ts#L2127-L2131
208
-
209
- if (ts.isStringLiteralLike(value)) {
210
- return result.ok(value.text);
211
- } else if (ts.isNumericLiteral(value)) {
212
- return result.ok(Number(value.text));
213
- } else if (value.kind === ts.SyntaxKind.TrueKeyword) {
214
- return result.ok(true);
215
- } else if (value.kind === ts.SyntaxKind.FalseKeyword) {
216
- return result.ok(false);
217
- } else if (value.kind === ts.SyntaxKind.NullKeyword) {
218
- return result.ok(null);
219
- } else if (ts.isArrayLiteralExpression(value)) {
220
- return result.all(value.elements.map(evaluateExpression));
221
- } else if (ts.isObjectLiteralExpression(value)) {
222
- return pipe(
223
- validateObjectProperties(value.properties),
224
- result.flatMap((assignments: ts.NodeArray<LiteralPropertyAssignment>) =>
225
- pipe(
226
- assignments.map((assignment) =>
227
- pipe(
228
- evaluateExpression(assignment.initializer),
229
- result.map<JSONValue, [key: string, value: JSONValue]>(
230
- (value) => [assignment.name.text, value]
231
- )
232
- )
233
- ),
234
- result.all
235
- )
236
- ),
237
- result.map(Object.fromEntries)
238
- );
239
- } else if (isValFileMethodCall(value)) {
240
- return pipe(
241
- findValFileNodeArg(value),
242
- result.flatMap((ref) => {
243
- if (value.arguments.length === 2) {
244
- return pipe(
245
- evaluateExpression(value.arguments[1]),
246
- result.map(
247
- (metadata) =>
248
- ({
249
- [FILE_REF_PROP]: ref.text,
250
- _type: "file",
251
- metadata,
252
- } as FileSource<{ [key: string]: JsonPrimitive }>)
253
- )
254
- );
255
- } else {
256
- return result.ok({
257
- [FILE_REF_PROP]: ref.text,
258
- _type: "file",
259
- } as FileSource<{ [key: string]: JsonPrimitive }>);
260
- }
261
- })
262
- );
263
- } else {
264
- return result.err(
265
- new ValSyntaxError("Expression must be a literal or call val.file", value)
266
- );
267
- }
268
- }
269
-
270
- export function findObjectPropertyAssignment(
271
- value: ts.ObjectLiteralExpression,
272
- key: string
273
- ): result.Result<LiteralPropertyAssignment | undefined, ValSyntaxErrorTree> {
274
- return pipe(
275
- validateObjectProperties(value.properties),
276
- result.flatMap((assignments: ts.NodeArray<LiteralPropertyAssignment>) => {
277
- const matchingAssignments = assignments.filter(
278
- (assignment) => assignment.name.text === key
279
- );
280
- if (matchingAssignments.length === 0) return result.ok(undefined);
281
- if (matchingAssignments.length > 1) {
282
- return result.err(
283
- new ValSyntaxError(`Object key "${key}" is ambiguous`, value)
284
- );
285
- }
286
- const [assignment] = matchingAssignments;
287
- return result.ok(assignment);
288
- })
289
- );
290
- }
291
-
292
- export function isValFileMethodCall(
293
- node: ts.Expression
294
- ): node is ts.CallExpression {
295
- return (
296
- ts.isCallExpression(node) &&
297
- ts.isPropertyAccessExpression(node.expression) &&
298
- ts.isIdentifier(node.expression.expression) &&
299
- node.expression.expression.text === "val" &&
300
- node.expression.name.text === "file"
301
- );
302
- }
303
-
304
- export function findValFileNodeArg(
305
- node: ts.CallExpression
306
- ): result.Result<ts.StringLiteral, ValSyntaxErrorTree> {
307
- if (node.arguments.length === 0) {
308
- return result.err(
309
- new ValSyntaxError(`Invalid val.file() call: missing ref argument`, node)
310
- );
311
- } else if (node.arguments.length > 2) {
312
- return result.err(
313
- new ValSyntaxError(
314
- `Invalid val.file() call: too many arguments ${node.arguments.length}}`,
315
- node
316
- )
317
- );
318
- } else if (!ts.isStringLiteral(node.arguments[0])) {
319
- return result.err(
320
- new ValSyntaxError(
321
- `Invalid val.file() call: ref must be a string literal`,
322
- node
323
- )
324
- );
325
- }
326
- const refNode = node.arguments[0];
327
- return result.ok(refNode);
328
- }
329
- export function findValFileMetadataArg(
330
- node: ts.CallExpression
331
- ): result.Result<ts.ObjectLiteralExpression | undefined, ValSyntaxErrorTree> {
332
- if (node.arguments.length === 0) {
333
- return result.err(
334
- new ValSyntaxError(`Invalid val.file() call: missing ref argument`, node)
335
- );
336
- } else if (node.arguments.length > 2) {
337
- return result.err(
338
- new ValSyntaxError(
339
- `Invalid val.file() call: too many arguments ${node.arguments.length}}`,
340
- node
341
- )
342
- );
343
- } else if (node.arguments.length === 2) {
344
- if (!ts.isObjectLiteralExpression(node.arguments[1])) {
345
- return result.err(
346
- new ValSyntaxError(
347
- `Invalid val.file() call: metadata must be a object literal`,
348
- node
349
- )
350
- );
351
- }
352
- return result.ok(node.arguments[1]);
353
- }
354
- return result.ok(undefined);
355
- }
356
-
357
- /**
358
- * Given a list of expressions, validates that all the expressions are not
359
- * spread elements. In other words, it ensures that the expressions are the
360
- * initializers of the values at their respective indices in the evaluated list.
361
- */
362
- export function validateInitializers(
363
- nodes: ReadonlyArray<ts.Expression>
364
- ): ValSyntaxErrorTree | undefined {
365
- for (const node of nodes) {
366
- if (ts.isSpreadElement(node)) {
367
- return new ValSyntaxError("Unexpected spread element", node);
368
- }
369
- }
370
- return undefined;
371
- }
@@ -1,26 +0,0 @@
1
- import { result } from "@valbuild/core/fp";
2
- import ts from "typescript";
3
- import { analyzeValModule, ValModuleAnalysis } from "./valModule";
4
-
5
- test("analyzeValModule", () => {
6
- const sourceText = `import {s, val } from "./val.config";
7
- export default val.content("/test", s.string(), "test");`;
8
- const sourceFile = ts.createSourceFile(
9
- "test.ts",
10
- sourceText,
11
- ts.ScriptTarget.ES2020,
12
- true
13
- );
14
-
15
- const analysis = analyzeValModule(sourceFile);
16
- expect(analysis).toEqual(
17
- result.ok<ValModuleAnalysis>({
18
- schema: expect.anything(),
19
- source: expect.anything(),
20
- })
21
- );
22
-
23
- const { schema, source } = (analysis as result.Ok<ValModuleAnalysis>).value;
24
- expect(schema.getStart(sourceFile)).toBe(sourceText.indexOf("s.string()"));
25
- expect(source.getStart(sourceFile)).toBe(sourceText.indexOf(`"test"`));
26
- });
@@ -1,110 +0,0 @@
1
- import ts from "typescript";
2
- import { result, pipe } from "@valbuild/core/fp";
3
- import { ValSyntaxError, ValSyntaxErrorTree } from "./syntax";
4
-
5
- export type ValModuleAnalysis = {
6
- schema: ts.Expression;
7
- source: ts.Expression;
8
- };
9
-
10
- function isPath(
11
- node: ts.Expression,
12
- path: readonly [string, ...string[]]
13
- ): boolean {
14
- let currentNode = node;
15
- for (let i = path.length - 1; i > 0; --i) {
16
- const name = path[i];
17
- if (!ts.isPropertyAccessExpression(currentNode)) {
18
- return false;
19
- }
20
- if (!ts.isIdentifier(currentNode.name) || currentNode.name.text !== name) {
21
- return false;
22
- }
23
- currentNode = currentNode.expression;
24
- }
25
- return ts.isIdentifier(currentNode) && currentNode.text === path[0];
26
- }
27
-
28
- function validateArguments(
29
- node: ts.CallExpression,
30
- validators: readonly ((
31
- node: ts.Expression
32
- ) => result.Result<void, ValSyntaxError>)[]
33
- ): result.Result<void, ValSyntaxErrorTree> {
34
- return result.allV<ValSyntaxError>([
35
- node.arguments.length === validators.length
36
- ? result.voidOk
37
- : result.err(
38
- new ValSyntaxError(`Expected ${validators.length} arguments`, node)
39
- ),
40
- ...node.arguments
41
- .slice(0, validators.length)
42
- .map((argument, index) => validators[index](argument)),
43
- ]);
44
- }
45
-
46
- function analyzeDefaultExport(
47
- node: ts.ExportAssignment
48
- ): result.Result<ValModuleAnalysis, ValSyntaxErrorTree> {
49
- const valContentCall = node.expression;
50
- if (!ts.isCallExpression(valContentCall)) {
51
- return result.err(
52
- new ValSyntaxError(
53
- "Expected default expression to be a call expression",
54
- valContentCall
55
- )
56
- );
57
- }
58
-
59
- if (!isPath(valContentCall.expression, ["val", "content"])) {
60
- return result.err(
61
- new ValSyntaxError(
62
- "Expected default expression to be calling val.content",
63
- valContentCall.expression
64
- )
65
- );
66
- }
67
-
68
- return pipe(
69
- validateArguments(valContentCall, [
70
- (id: ts.Node) => {
71
- // TODO: validate ID value here?
72
- if (!ts.isStringLiteralLike(id)) {
73
- return result.err(
74
- new ValSyntaxError(
75
- "Expected first argument to val.content to be a string literal",
76
- id
77
- )
78
- );
79
- }
80
- return result.voidOk;
81
- },
82
- () => {
83
- return result.voidOk;
84
- },
85
- () => {
86
- return result.voidOk;
87
- },
88
- ]),
89
- result.map(() => {
90
- const [, schema, source] = valContentCall.arguments;
91
- return { schema, source };
92
- })
93
- );
94
- }
95
-
96
- export function analyzeValModule(
97
- sourceFile: ts.SourceFile
98
- ): result.Result<ValModuleAnalysis, ValSyntaxErrorTree> {
99
- const analysis = sourceFile.forEachChild((node) => {
100
- if (ts.isExportAssignment(node)) {
101
- return analyzeDefaultExport(node);
102
- }
103
- });
104
-
105
- if (!analysis) {
106
- throw Error("Failed to find fixed content node in val module");
107
- }
108
-
109
- return analysis;
110
- }
@@ -1,81 +0,0 @@
1
- import type {
2
- JSONValue as JSONValueT,
3
- OperationJSON as OperationJSONT,
4
- PatchJSON as PatchJSONT,
5
- } from "@valbuild/core/patch";
6
- import z from "zod";
7
-
8
- const JSONValueT: z.ZodType<JSONValueT> = z.lazy(() =>
9
- z.union([
10
- z.string(),
11
- z.number(),
12
- z.boolean(),
13
- z.null(),
14
- z.array(JSONValueT),
15
- z.record(JSONValueT),
16
- ])
17
- );
18
-
19
- /**
20
- * Raw JSON patch operation.
21
- */
22
- const OperationJSONT: z.ZodType<OperationJSONT> = z.discriminatedUnion("op", [
23
- z
24
- .object({
25
- op: z.literal("add"),
26
- path: z.string(),
27
- value: JSONValueT,
28
- })
29
- .strict(),
30
- z
31
- .object({
32
- op: z.literal("remove"),
33
- /**
34
- * Must be non-root
35
- */
36
- path: z.string(),
37
- })
38
- .strict(),
39
- z
40
- .object({
41
- op: z.literal("replace"),
42
- path: z.string(),
43
- value: JSONValueT,
44
- })
45
- .strict(),
46
- z
47
- .object({
48
- op: z.literal("move"),
49
- /**
50
- * Must be non-root and not a proper prefix of "path".
51
- */
52
- from: z.string(),
53
- path: z.string(),
54
- })
55
- .strict(),
56
- z
57
- .object({
58
- op: z.literal("copy"),
59
- from: z.string(),
60
- path: z.string(),
61
- })
62
- .strict(),
63
- z
64
- .object({
65
- op: z.literal("test"),
66
- path: z.string(),
67
- value: JSONValueT,
68
- })
69
- .strict(),
70
- z
71
- .object({
72
- op: z.literal("file"),
73
- path: z.string(),
74
- filePath: z.string(),
75
- value: z.string(),
76
- })
77
- .strict(),
78
- ]);
79
-
80
- export const PatchJSON: z.ZodType<PatchJSONT> = z.array(OperationJSONT);
81
- export type PatchJSON = PatchJSONT;
@@ -1,110 +0,0 @@
1
- import { analyzeValModule } from "./patch/ts/valModule";
2
- import { applyPatch, Patch, PatchError } from "@valbuild/core/patch";
3
- import { TSOps } from "./patch/ts/ops";
4
- import { result, pipe } from "@valbuild/core/fp";
5
- import {
6
- flatMapErrors,
7
- formatSyntaxError,
8
- type ValSyntaxErrorTree,
9
- } from "./patch/ts/syntax";
10
- import { ValSourceFileHandler } from "./ValSourceFileHandler";
11
- import { derefPatch } from "@valbuild/core";
12
- import { readValFile } from "./readValFile";
13
- import { QuickJSRuntime } from "quickjs-emscripten";
14
- import ts from "typescript";
15
- import { SerializedModuleContent } from "./SerializedModuleContent";
16
-
17
- const ops = new TSOps((document) => {
18
- return pipe(
19
- analyzeValModule(document),
20
- result.map(({ source }) => source)
21
- );
22
- });
23
-
24
- // TODO: rename to patchValFiles since we may write multiple files
25
- export const patchValFile = async (
26
- id: string,
27
- valConfigPath: string,
28
- patch: Patch,
29
- sourceFileHandler: ValSourceFileHandler,
30
- runtime: QuickJSRuntime
31
- ): Promise<SerializedModuleContent> => {
32
- const filePath = sourceFileHandler.resolveSourceModulePath(
33
- valConfigPath,
34
- `.${id}.val`
35
- );
36
-
37
- const sourceFile = sourceFileHandler.getSourceFile(filePath);
38
-
39
- if (!sourceFile) {
40
- throw Error(`Source file ${filePath} not found`);
41
- }
42
-
43
- const derefRes = derefPatch(patch, sourceFile, ops);
44
- if (result.isErr(derefRes)) {
45
- throw derefRes.error;
46
- }
47
-
48
- const dereferencedPatch = derefRes.value.dereferencedPatch; // TODO: add ref changes to remote replace/add, ...
49
- const newSourceFile = patchSourceFile(sourceFile, dereferencedPatch);
50
- if (result.isErr(newSourceFile)) {
51
- if (newSourceFile.error instanceof PatchError) {
52
- throw newSourceFile.error;
53
- } else {
54
- throw new Error(
55
- `${filePath}\n${flatMapErrors(newSourceFile.error, (error) =>
56
- formatSyntaxError(error, sourceFile)
57
- ).join("\n")}`
58
- );
59
- }
60
- }
61
- for (const [filePath, content] of Object.entries(
62
- derefRes.value.fileUpdates
63
- )) {
64
- // Evaluate if we want to make these writes (more) atomic with a temp file and a move.
65
- // This can potentially fill mid-way if there is not enough space on disk for example...
66
- // However, that might be add add bit more complexity in our host and virtual file systems?
67
- // Example:
68
- // const tempFilePath = sourceFileHandler.writeTempFile(
69
- // Buffer.from(content, "base64").toString("binary")
70
- // );
71
- // sourceFileHandler.moveFile(tempFilePath, "." + filePath);
72
- // TODO: ensure that directory exists
73
- if (content.startsWith("data:/image/svg+xml")) {
74
- sourceFileHandler.writeFile(
75
- "." + filePath,
76
- convertDataUrlToBase64(content).toString("utf8"),
77
- "utf8"
78
- );
79
- } else {
80
- sourceFileHandler.writeFile(
81
- "." + filePath,
82
- convertDataUrlToBase64(content).toString("binary"),
83
- "binary"
84
- );
85
- }
86
- }
87
-
88
- sourceFileHandler.writeSourceFile(newSourceFile.value);
89
-
90
- return readValFile(id, valConfigPath, runtime);
91
- };
92
-
93
- function convertDataUrlToBase64(dataUrl: string): Buffer {
94
- const base64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
95
- return Buffer.from(base64, "base64");
96
- }
97
-
98
- export const patchSourceFile = (
99
- sourceFile: ts.SourceFile | string,
100
- patch: Patch
101
- ): result.Result<ts.SourceFile, ValSyntaxErrorTree | PatchError> => {
102
- if (typeof sourceFile === "string") {
103
- return applyPatch(
104
- ts.createSourceFile("<val>", sourceFile, ts.ScriptTarget.ES2015),
105
- ops,
106
- patch
107
- );
108
- }
109
- return applyPatch(sourceFile, ops, patch);
110
- };