@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.
- package/package.json +7 -4
- package/.babelrc.json +0 -5
- package/CHANGELOG.md +0 -0
- package/jest.config.js +0 -4
- package/src/LocalValServer.ts +0 -167
- package/src/ProxyValServer.ts +0 -542
- package/src/SerializedModuleContent.ts +0 -36
- package/src/Service.ts +0 -126
- package/src/ValFS.ts +0 -22
- package/src/ValFSHost.ts +0 -66
- package/src/ValModuleLoader.test.ts +0 -75
- package/src/ValModuleLoader.ts +0 -158
- package/src/ValQuickJSRuntime.ts +0 -85
- package/src/ValServer.ts +0 -24
- package/src/ValSourceFileHandler.ts +0 -57
- package/src/createFixPatch.ts +0 -170
- package/src/createRequestHandler.ts +0 -27
- package/src/expressHelpers.ts +0 -5
- package/src/getCompilerOptions.ts +0 -50
- package/src/hosting.ts +0 -290
- package/src/index.ts +0 -16
- package/src/jwt.ts +0 -93
- package/src/patch/ts/ops.test.ts +0 -937
- package/src/patch/ts/ops.ts +0 -897
- package/src/patch/ts/syntax.ts +0 -371
- package/src/patch/ts/valModule.test.ts +0 -26
- package/src/patch/ts/valModule.ts +0 -110
- package/src/patch/validation.ts +0 -81
- package/src/patchValFile.ts +0 -110
- package/src/readValFile.test.ts +0 -49
- package/src/readValFile.ts +0 -96
- package/test/example-projects/basic-next-javascript/jsconfig.json +0 -8
- package/test/example-projects/basic-next-javascript/package.json +0 -23
- package/test/example-projects/basic-next-javascript/pages/blogs.val.js +0 -20
- package/test/example-projects/basic-next-javascript/val.config.js +0 -4
- package/test/example-projects/basic-next-src-typescript/package.json +0 -23
- package/test/example-projects/basic-next-src-typescript/src/pages/blogs.val.ts +0 -20
- package/test/example-projects/basic-next-src-typescript/src/val.config.ts +0 -5
- package/test/example-projects/basic-next-src-typescript/tsconfig.json +0 -24
- package/test/example-projects/basic-next-typescript/package.json +0 -23
- package/test/example-projects/basic-next-typescript/pages/blogs.val.ts +0 -20
- package/test/example-projects/basic-next-typescript/tsconfig.json +0 -25
- package/test/example-projects/basic-next-typescript/val.config.ts +0 -5
- package/test/example-projects/typescript-description-files/README.md +0 -2
- package/test/example-projects/typescript-description-files/jsconfig.json +0 -8
- package/test/example-projects/typescript-description-files/package.json +0 -23
- package/test/example-projects/typescript-description-files/pages/blogs.val.d.ts +0 -7
- package/test/example-projects/typescript-description-files/pages/blogs.val.js +0 -19
- package/test/example-projects/typescript-description-files/val.config.d.ts +0 -3
- package/test/example-projects/typescript-description-files/val.config.js +0 -5
- package/tsconfig.json +0 -12
package/src/patch/ts/syntax.ts
DELETED
@@ -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
|
-
}
|
package/src/patch/validation.ts
DELETED
@@ -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;
|
package/src/patchValFile.ts
DELETED
@@ -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
|
-
};
|