@valbuild/server 0.101.1 → 0.103.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/dist/declarations/src/ValOps.d.ts +3 -3
- package/dist/declarations/src/createFixPatch.d.ts +12 -1
- package/dist/declarations/src/fixHandlers.d.ts +162 -0
- package/dist/declarations/src/getValidationErrorFileRef.d.ts +6 -0
- package/dist/declarations/src/index.d.ts +3 -1
- package/dist/declarations/src/patch/ts/syntax.d.ts +0 -14
- package/dist/valbuild-server.cjs.dev.js +872 -659
- package/dist/valbuild-server.cjs.prod.js +872 -659
- package/dist/valbuild-server.esm.js +860 -660
- package/package.json +4 -4
- package/dist/declarations/src/hasRemoteFileSchema.d.ts +0 -5
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var ts = require('typescript');
|
|
6
6
|
var fp = require('@valbuild/core/fp');
|
|
7
|
-
var core = require('@valbuild/core');
|
|
8
7
|
var patch = require('@valbuild/core/patch');
|
|
8
|
+
var core = require('@valbuild/core');
|
|
9
9
|
var path = require('path');
|
|
10
10
|
var fs = require('fs');
|
|
11
11
|
var vm = require('node:vm');
|
|
@@ -102,7 +102,7 @@ function validateObjectProperties(nodes) {
|
|
|
102
102
|
* validating its children.
|
|
103
103
|
*/
|
|
104
104
|
function shallowValidateExpression(value) {
|
|
105
|
-
return ts__default["default"].isStringLiteralLike(value) || ts__default["default"].isNumericLiteral(value) || value.kind === ts__default["default"].SyntaxKind.TrueKeyword || value.kind === ts__default["default"].SyntaxKind.FalseKeyword || value.kind === ts__default["default"].SyntaxKind.NullKeyword || ts__default["default"].isArrayLiteralExpression(value) || ts__default["default"].isObjectLiteralExpression(value)
|
|
105
|
+
return ts__default["default"].isStringLiteralLike(value) || ts__default["default"].isNumericLiteral(value) || value.kind === ts__default["default"].SyntaxKind.TrueKeyword || value.kind === ts__default["default"].SyntaxKind.FalseKeyword || value.kind === ts__default["default"].SyntaxKind.NullKeyword || ts__default["default"].isArrayLiteralExpression(value) || ts__default["default"].isObjectLiteralExpression(value) ? undefined : new ValSyntaxError("Expression must be a literal", value);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -128,55 +128,8 @@ function evaluateExpression(value) {
|
|
|
128
128
|
return fp.result.all(value.elements.map(evaluateExpression));
|
|
129
129
|
} else if (ts__default["default"].isObjectLiteralExpression(value)) {
|
|
130
130
|
return fp.pipe(validateObjectProperties(value.properties), fp.result.flatMap(assignments => fp.pipe(assignments.map(assignment => fp.pipe(evaluateExpression(assignment.initializer), fp.result.map(value => [assignment.name.text, value]))), fp.result.all)), fp.result.map(Object.fromEntries));
|
|
131
|
-
} else if (isValFileMethodCall(value)) {
|
|
132
|
-
return fp.pipe(findValFileNodeArg(value), fp.result.flatMap(ref => {
|
|
133
|
-
if (value.arguments.length === 2) {
|
|
134
|
-
return fp.pipe(evaluateExpression(value.arguments[1]), fp.result.map(metadata => ({
|
|
135
|
-
[core.FILE_REF_PROP]: ref.text,
|
|
136
|
-
_type: "file",
|
|
137
|
-
metadata
|
|
138
|
-
})));
|
|
139
|
-
} else {
|
|
140
|
-
return fp.result.ok({
|
|
141
|
-
[core.FILE_REF_PROP]: ref.text,
|
|
142
|
-
_type: "file"
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}));
|
|
146
|
-
} else if (isValImageMethodCall(value)) {
|
|
147
|
-
return fp.pipe(findValImageNodeArg(value), fp.result.flatMap(ref => {
|
|
148
|
-
if (value.arguments.length === 2) {
|
|
149
|
-
return fp.pipe(evaluateExpression(value.arguments[1]), fp.result.map(metadata => ({
|
|
150
|
-
[core.FILE_REF_PROP]: ref.text,
|
|
151
|
-
_type: "file",
|
|
152
|
-
[core.FILE_REF_SUBTYPE_TAG]: "image",
|
|
153
|
-
metadata
|
|
154
|
-
})));
|
|
155
|
-
} else {
|
|
156
|
-
return fp.result.ok({
|
|
157
|
-
[core.FILE_REF_PROP]: ref.text,
|
|
158
|
-
_type: "file",
|
|
159
|
-
[core.FILE_REF_SUBTYPE_TAG]: "image"
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}));
|
|
163
|
-
} else if (isValRemoteMethodCall(value)) {
|
|
164
|
-
return fp.pipe(findValRemoteNodeArg(value), fp.result.flatMap(ref => {
|
|
165
|
-
if (value.arguments.length === 2) {
|
|
166
|
-
return fp.pipe(evaluateExpression(value.arguments[1]), fp.result.map(metadata => ({
|
|
167
|
-
[core.FILE_REF_PROP]: ref.text,
|
|
168
|
-
_type: "remote",
|
|
169
|
-
metadata
|
|
170
|
-
})));
|
|
171
|
-
} else {
|
|
172
|
-
return fp.result.ok({
|
|
173
|
-
[core.FILE_REF_PROP]: ref.text,
|
|
174
|
-
_type: "remote"
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}));
|
|
178
131
|
} else {
|
|
179
|
-
return fp.result.err(new ValSyntaxError("Expression must be a literal
|
|
132
|
+
return fp.result.err(new ValSyntaxError("Expression must be a literal", value));
|
|
180
133
|
}
|
|
181
134
|
}
|
|
182
135
|
function findObjectPropertyAssignment(value, key) {
|
|
@@ -190,93 +143,6 @@ function findObjectPropertyAssignment(value, key) {
|
|
|
190
143
|
return fp.result.ok(assignment);
|
|
191
144
|
}));
|
|
192
145
|
}
|
|
193
|
-
function isValFileMethodCall(node) {
|
|
194
|
-
return ts__default["default"].isCallExpression(node) && ts__default["default"].isPropertyAccessExpression(node.expression) && ts__default["default"].isIdentifier(node.expression.expression) && node.expression.expression.text === "c" && node.expression.name.text === "file";
|
|
195
|
-
}
|
|
196
|
-
function isValImageMethodCall(node) {
|
|
197
|
-
return ts__default["default"].isCallExpression(node) && ts__default["default"].isPropertyAccessExpression(node.expression) && ts__default["default"].isIdentifier(node.expression.expression) && node.expression.expression.text === "c" && node.expression.name.text === "image";
|
|
198
|
-
}
|
|
199
|
-
function isValRemoteMethodCall(node) {
|
|
200
|
-
return ts__default["default"].isCallExpression(node) && ts__default["default"].isPropertyAccessExpression(node.expression) && ts__default["default"].isIdentifier(node.expression.expression) && node.expression.expression.text === "c" && node.expression.name.text === "remote";
|
|
201
|
-
}
|
|
202
|
-
function findValFileNodeArg(node) {
|
|
203
|
-
if (node.arguments.length === 0) {
|
|
204
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.file() call: missing ref argument`, node));
|
|
205
|
-
} else if (node.arguments.length > 2) {
|
|
206
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.file() call: too many arguments ${node.arguments.length}}`, node));
|
|
207
|
-
} else if (!ts__default["default"].isStringLiteral(node.arguments[0])) {
|
|
208
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.file() call: ref must be a string literal`, node));
|
|
209
|
-
}
|
|
210
|
-
const refNode = node.arguments[0];
|
|
211
|
-
return fp.result.ok(refNode);
|
|
212
|
-
}
|
|
213
|
-
function findValRemoteNodeArg(node) {
|
|
214
|
-
if (node.arguments.length === 0) {
|
|
215
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.remote() call: missing ref argument`, node));
|
|
216
|
-
} else if (node.arguments.length > 2) {
|
|
217
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.remote() call: too many arguments ${node.arguments.length}}`, node));
|
|
218
|
-
} else if (!ts__default["default"].isStringLiteral(node.arguments[0])) {
|
|
219
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.remote() call: ref must be a string literal`, node));
|
|
220
|
-
}
|
|
221
|
-
const refNode = node.arguments[0];
|
|
222
|
-
return fp.result.ok(refNode);
|
|
223
|
-
}
|
|
224
|
-
function findValImageNodeArg(node) {
|
|
225
|
-
if (node.arguments.length === 0) {
|
|
226
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.image() call: missing ref argument`, node));
|
|
227
|
-
} else if (node.arguments.length > 2) {
|
|
228
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.image() call: too many arguments ${node.arguments.length}}`, node));
|
|
229
|
-
} else if (!ts__default["default"].isStringLiteral(node.arguments[0])) {
|
|
230
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.image() call: ref must be a string literal`, node));
|
|
231
|
-
}
|
|
232
|
-
const refNode = node.arguments[0];
|
|
233
|
-
return fp.result.ok(refNode);
|
|
234
|
-
}
|
|
235
|
-
function findValFileMetadataArg(node) {
|
|
236
|
-
if (node.arguments.length === 0) {
|
|
237
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.file() call: missing ref argument`, node));
|
|
238
|
-
} else if (node.arguments.length > 2) {
|
|
239
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.file() call: too many arguments ${node.arguments.length}}`, node));
|
|
240
|
-
} else if (node.arguments.length === 2) {
|
|
241
|
-
if (!ts__default["default"].isObjectLiteralExpression(node.arguments[1])) {
|
|
242
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.file() call: metadata must be a object literal`, node));
|
|
243
|
-
}
|
|
244
|
-
return fp.result.ok(node.arguments[1]);
|
|
245
|
-
}
|
|
246
|
-
return fp.result.ok(undefined);
|
|
247
|
-
}
|
|
248
|
-
function findValImageMetadataArg(node) {
|
|
249
|
-
if (node.arguments.length === 0) {
|
|
250
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.image() call: missing ref argument`, node));
|
|
251
|
-
} else if (node.arguments.length > 2) {
|
|
252
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.image() call: too many arguments ${node.arguments.length}}`, node));
|
|
253
|
-
} else if (node.arguments.length === 2) {
|
|
254
|
-
if (!ts__default["default"].isObjectLiteralExpression(node.arguments[1])) {
|
|
255
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.image() call: metadata must be a object literal`, node));
|
|
256
|
-
}
|
|
257
|
-
return fp.result.ok(node.arguments[1]);
|
|
258
|
-
}
|
|
259
|
-
return fp.result.ok(undefined);
|
|
260
|
-
}
|
|
261
|
-
function findValRemoteMetadataArg(node) {
|
|
262
|
-
if (node.arguments.length === 0) {
|
|
263
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.remote() call: missing ref argument`, node));
|
|
264
|
-
} else if (node.arguments.length > 2) {
|
|
265
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.remote() call: too many arguments ${node.arguments.length}}`, node));
|
|
266
|
-
} else if (node.arguments.length === 2) {
|
|
267
|
-
if (!ts__default["default"].isObjectLiteralExpression(node.arguments[1])) {
|
|
268
|
-
return fp.result.err(new ValSyntaxError(`Invalid c.remote() call: metadata must be a object literal`, node));
|
|
269
|
-
}
|
|
270
|
-
return fp.result.ok(node.arguments[1]);
|
|
271
|
-
}
|
|
272
|
-
return fp.result.ok(undefined);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Given a list of expressions, validates that all the expressions are not
|
|
277
|
-
* spread elements. In other words, it ensures that the expressions are the
|
|
278
|
-
* initializers of the values at their respective indices in the evaluated list.
|
|
279
|
-
*/
|
|
280
146
|
function validateInitializers(nodes) {
|
|
281
147
|
for (const node of nodes) {
|
|
282
148
|
if (ts__default["default"].isSpreadElement(node)) {
|
|
@@ -358,27 +224,6 @@ function isValidIdentifier(text) {
|
|
|
358
224
|
function createPropertyAssignment(key, value) {
|
|
359
225
|
return ts__default["default"].factory.createPropertyAssignment(isValidIdentifier(key) ? ts__default["default"].factory.createIdentifier(key) : ts__default["default"].factory.createStringLiteral(key), toExpression(value));
|
|
360
226
|
}
|
|
361
|
-
function createValFileReference(value) {
|
|
362
|
-
const args = [ts__default["default"].factory.createStringLiteral(value[core.FILE_REF_PROP])];
|
|
363
|
-
if (value.metadata) {
|
|
364
|
-
args.push(toExpression(value.metadata));
|
|
365
|
-
}
|
|
366
|
-
return ts__default["default"].factory.createCallExpression(ts__default["default"].factory.createPropertyAccessExpression(ts__default["default"].factory.createIdentifier("c"), ts__default["default"].factory.createIdentifier("file")), undefined, args);
|
|
367
|
-
}
|
|
368
|
-
function createValImageReference(value) {
|
|
369
|
-
const args = [ts__default["default"].factory.createStringLiteral(value[core.FILE_REF_PROP])];
|
|
370
|
-
if (value.metadata) {
|
|
371
|
-
args.push(toExpression(value.metadata));
|
|
372
|
-
}
|
|
373
|
-
return ts__default["default"].factory.createCallExpression(ts__default["default"].factory.createPropertyAccessExpression(ts__default["default"].factory.createIdentifier("c"), ts__default["default"].factory.createIdentifier("image")), undefined, args);
|
|
374
|
-
}
|
|
375
|
-
function createValRemoteReference(value) {
|
|
376
|
-
const args = [ts__default["default"].factory.createStringLiteral(value[core.FILE_REF_PROP])];
|
|
377
|
-
if (value.metadata) {
|
|
378
|
-
args.push(toExpression(value.metadata));
|
|
379
|
-
}
|
|
380
|
-
return ts__default["default"].factory.createCallExpression(ts__default["default"].factory.createPropertyAccessExpression(ts__default["default"].factory.createIdentifier("c"), ts__default["default"].factory.createIdentifier("remote")), undefined, args);
|
|
381
|
-
}
|
|
382
227
|
|
|
383
228
|
/**
|
|
384
229
|
* Builds the expression `c.json(() => import("<importPath>"))` used to reference
|
|
@@ -464,16 +309,12 @@ function toExpression(value) {
|
|
|
464
309
|
} else if (Array.isArray(value)) {
|
|
465
310
|
return ts__default["default"].factory.createArrayLiteralExpression(value.map(toExpression));
|
|
466
311
|
} else if (typeof value === "object") {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (isValRemoteValue(value)) {
|
|
474
|
-
return createValRemoteReference(value);
|
|
475
|
-
}
|
|
476
|
-
return ts__default["default"].factory.createObjectLiteralExpression(Object.entries(value).map(([key, value]) => createPropertyAssignment(key, value)));
|
|
312
|
+
return ts__default["default"].factory.createObjectLiteralExpression(Object.entries(value)
|
|
313
|
+
// `patch_id` is injected server-side onto a media source whose bytes are
|
|
314
|
+
// not committed yet. It describes the draft, not the content, and a
|
|
315
|
+
// whole-object write built from the client's optimistic view would
|
|
316
|
+
// otherwise print it into the user's file.
|
|
317
|
+
.filter(([key]) => key !== "patch_id").map(([key, value]) => createPropertyAssignment(key, value)));
|
|
477
318
|
} else {
|
|
478
319
|
return ts__default["default"].factory.createStringLiteral(value);
|
|
479
320
|
}
|
|
@@ -606,63 +447,6 @@ function replaceInNode(document, node, key, value) {
|
|
|
606
447
|
}
|
|
607
448
|
return fp.result.ok(assignment);
|
|
608
449
|
}), fp.result.map(assignment => replaceNodeValue(document, assignment.initializer, value)));
|
|
609
|
-
} else if (isValFileMethodCall(node)) {
|
|
610
|
-
if (key === core.FILE_REF_PROP) {
|
|
611
|
-
if (typeof value !== "string") {
|
|
612
|
-
return fp.result.err(new patch.PatchError("Cannot replace c.file reference with non-string value"));
|
|
613
|
-
}
|
|
614
|
-
return fp.pipe(findValFileNodeArg(node), fp.result.map(refNode => replaceNodeValue(document, refNode, value)));
|
|
615
|
-
} else {
|
|
616
|
-
return fp.pipe(findValFileMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
617
|
-
if (!metadataArgNode) {
|
|
618
|
-
return fp.result.err(new patch.PatchError("Cannot replace in c.file metadata when it does not exist"));
|
|
619
|
-
}
|
|
620
|
-
if (key !== "metadata") {
|
|
621
|
-
return fp.result.err(new patch.PatchError(`Cannot replace c.file metadata key ${key} when it does not exist`));
|
|
622
|
-
}
|
|
623
|
-
return replaceInNode(document,
|
|
624
|
-
// TODO: creating a fake object here might not be right - seems to work though
|
|
625
|
-
ts__default["default"].factory.createObjectLiteralExpression([ts__default["default"].factory.createPropertyAssignment(key, metadataArgNode)]), key, value);
|
|
626
|
-
}));
|
|
627
|
-
}
|
|
628
|
-
} else if (isValImageMethodCall(node)) {
|
|
629
|
-
if (key === core.FILE_REF_PROP) {
|
|
630
|
-
if (typeof value !== "string") {
|
|
631
|
-
return fp.result.err(new patch.PatchError("Cannot replace c.image reference with non-string value"));
|
|
632
|
-
}
|
|
633
|
-
return fp.pipe(findValImageNodeArg(node), fp.result.map(refNode => replaceNodeValue(document, refNode, value)));
|
|
634
|
-
} else {
|
|
635
|
-
return fp.pipe(findValImageMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
636
|
-
if (!metadataArgNode) {
|
|
637
|
-
return fp.result.err(new patch.PatchError("Cannot replace in c.image metadata when it does not exist"));
|
|
638
|
-
}
|
|
639
|
-
if (key !== "metadata") {
|
|
640
|
-
return fp.result.err(new patch.PatchError(`Cannot replace c.image metadata key ${key} when it does not exist`));
|
|
641
|
-
}
|
|
642
|
-
return replaceInNode(document,
|
|
643
|
-
// TODO: creating a fake object here might not be right - seems to work though
|
|
644
|
-
ts__default["default"].factory.createObjectLiteralExpression([ts__default["default"].factory.createPropertyAssignment(key, metadataArgNode)]), key, value);
|
|
645
|
-
}));
|
|
646
|
-
}
|
|
647
|
-
} else if (isValRemoteMethodCall(node)) {
|
|
648
|
-
if (key === core.FILE_REF_PROP) {
|
|
649
|
-
if (typeof value !== "string") {
|
|
650
|
-
return fp.result.err(new patch.PatchError("Cannot replace c.image reference with non-string value"));
|
|
651
|
-
}
|
|
652
|
-
return fp.pipe(findValRemoteNodeArg(node), fp.result.map(refNode => replaceNodeValue(document, refNode, value)));
|
|
653
|
-
} else {
|
|
654
|
-
return fp.pipe(findValRemoteMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
655
|
-
if (!metadataArgNode) {
|
|
656
|
-
return fp.result.err(new patch.PatchError("Cannot replace in c.remote metadata when it does not exist"));
|
|
657
|
-
}
|
|
658
|
-
if (key !== "metadata") {
|
|
659
|
-
return fp.result.err(new patch.PatchError(`Cannot replace c.remote metadata key ${key} when it does not exist`));
|
|
660
|
-
}
|
|
661
|
-
return replaceInNode(document,
|
|
662
|
-
// TODO: creating a fake object here might not be right - seems to work though
|
|
663
|
-
ts__default["default"].factory.createObjectLiteralExpression([ts__default["default"].factory.createPropertyAssignment(key, metadataArgNode)]), key, value);
|
|
664
|
-
}));
|
|
665
|
-
}
|
|
666
450
|
} else {
|
|
667
451
|
return fp.result.err(shallowValidateExpression(node) ?? new patch.PatchError("Cannot replace in non-object/array"));
|
|
668
452
|
}
|
|
@@ -679,21 +463,6 @@ function getFromNode(node, key) {
|
|
|
679
463
|
return fp.pipe(parseAndValidateArrayInboundsIndex(key, node.elements), fp.result.map(index => node.elements[index]));
|
|
680
464
|
} else if (ts__default["default"].isObjectLiteralExpression(node)) {
|
|
681
465
|
return fp.pipe(findObjectPropertyAssignment(node, key), fp.result.map(assignment => assignment === null || assignment === void 0 ? void 0 : assignment.initializer));
|
|
682
|
-
} else if (isValFileMethodCall(node)) {
|
|
683
|
-
if (key === core.FILE_REF_PROP) {
|
|
684
|
-
return findValFileNodeArg(node);
|
|
685
|
-
}
|
|
686
|
-
return findValFileMetadataArg(node);
|
|
687
|
-
} else if (isValImageMethodCall(node)) {
|
|
688
|
-
if (key === core.FILE_REF_PROP) {
|
|
689
|
-
return findValImageNodeArg(node);
|
|
690
|
-
}
|
|
691
|
-
return findValImageMetadataArg(node);
|
|
692
|
-
} else if (isValRemoteMethodCall(node)) {
|
|
693
|
-
if (key === core.FILE_REF_PROP) {
|
|
694
|
-
return findValRemoteNodeArg(node);
|
|
695
|
-
}
|
|
696
|
-
return findValRemoteMetadataArg(node);
|
|
697
466
|
} else {
|
|
698
467
|
return fp.result.err(shallowValidateExpression(node) ?? new patch.PatchError("Cannot access non-object/array"));
|
|
699
468
|
}
|
|
@@ -726,39 +495,6 @@ function removeFromNode(document, node, key) {
|
|
|
726
495
|
}
|
|
727
496
|
return fp.result.ok(assignment);
|
|
728
497
|
}), fp.result.map(assignment => [removeAt(document, node.properties, node.properties.indexOf(assignment))[0], assignment.initializer]));
|
|
729
|
-
} else if (isValFileMethodCall(node)) {
|
|
730
|
-
if (key === core.FILE_REF_PROP) {
|
|
731
|
-
return fp.result.err(new patch.PatchError("Cannot remove a ref from c.file"));
|
|
732
|
-
} else {
|
|
733
|
-
return fp.pipe(findValFileMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
734
|
-
if (!metadataArgNode) {
|
|
735
|
-
return fp.result.err(new patch.PatchError("Cannot remove from c.file metadata when it does not exist"));
|
|
736
|
-
}
|
|
737
|
-
return removeFromNode(document, metadataArgNode, key);
|
|
738
|
-
}));
|
|
739
|
-
}
|
|
740
|
-
} else if (isValImageMethodCall(node)) {
|
|
741
|
-
if (key === core.FILE_REF_PROP) {
|
|
742
|
-
return fp.result.err(new patch.PatchError("Cannot remove a ref from c.image"));
|
|
743
|
-
} else {
|
|
744
|
-
return fp.pipe(findValImageMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
745
|
-
if (!metadataArgNode) {
|
|
746
|
-
return fp.result.err(new patch.PatchError("Cannot remove from c.image metadata when it does not exist"));
|
|
747
|
-
}
|
|
748
|
-
return removeFromNode(document, metadataArgNode, key);
|
|
749
|
-
}));
|
|
750
|
-
}
|
|
751
|
-
} else if (isValRemoteMethodCall(node)) {
|
|
752
|
-
if (key === core.FILE_REF_PROP) {
|
|
753
|
-
return fp.result.err(new patch.PatchError("Cannot remove a ref from c.remote"));
|
|
754
|
-
} else {
|
|
755
|
-
return fp.pipe(findValRemoteMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
756
|
-
if (!metadataArgNode) {
|
|
757
|
-
return fp.result.err(new patch.PatchError("Cannot remove from c.remote metadata when it does not exist"));
|
|
758
|
-
}
|
|
759
|
-
return removeFromNode(document, metadataArgNode, key);
|
|
760
|
-
}));
|
|
761
|
-
}
|
|
762
498
|
} else {
|
|
763
499
|
return fp.result.err(shallowValidateExpression(node) ?? new patch.PatchError("Cannot remove from non-object/array"));
|
|
764
500
|
}
|
|
@@ -766,22 +502,10 @@ function removeFromNode(document, node, key) {
|
|
|
766
502
|
function removeAtPath(document, rootNode, path) {
|
|
767
503
|
return fp.pipe(getPointerFromPath(rootNode, path), fp.result.flatMap(([node, key]) => removeFromNode(document, node, key)));
|
|
768
504
|
}
|
|
769
|
-
function isValFileValue(value) {
|
|
770
|
-
return !!(typeof value === "object" && value && core.VAL_EXTENSION in value && value[core.VAL_EXTENSION] === "file" && core.FILE_REF_PROP in value && typeof value[core.FILE_REF_PROP] === "string" && value[core.FILE_REF_SUBTYPE_TAG] !== "image");
|
|
771
|
-
}
|
|
772
|
-
function isValImageValue(value) {
|
|
773
|
-
return !!(typeof value === "object" && value && core.VAL_EXTENSION in value && value[core.VAL_EXTENSION] === "file" && core.FILE_REF_PROP in value && typeof value[core.FILE_REF_PROP] === "string" && value[core.FILE_REF_SUBTYPE_TAG] === "image");
|
|
774
|
-
}
|
|
775
|
-
function isValRemoteValue(value) {
|
|
776
|
-
return !!(typeof value === "object" && value && core.VAL_EXTENSION in value && value[core.VAL_EXTENSION] === "remote" && core.FILE_REF_PROP in value && typeof value[core.FILE_REF_PROP] === "string");
|
|
777
|
-
}
|
|
778
505
|
function addToNode(document, node, key, value) {
|
|
779
506
|
if (ts__default["default"].isArrayLiteralExpression(node)) {
|
|
780
507
|
return fp.pipe(parseAndValidateArrayInsertIndex(key, node.elements), fp.result.map(index => [insertAt(document, node.elements, index, toExpression(value))]));
|
|
781
508
|
} else if (ts__default["default"].isObjectLiteralExpression(node)) {
|
|
782
|
-
if (key === core.FILE_REF_PROP) {
|
|
783
|
-
return fp.result.err(new patch.PatchError("Cannot add a key ref to object"));
|
|
784
|
-
}
|
|
785
509
|
return fp.pipe(findObjectPropertyAssignment(node, key), fp.result.map(assignment => {
|
|
786
510
|
if (!assignment) {
|
|
787
511
|
return [insertAt(document, node.properties, node.properties.length, createPropertyAssignment(key, value))];
|
|
@@ -789,57 +513,6 @@ function addToNode(document, node, key, value) {
|
|
|
789
513
|
return replaceNodeValue(document, assignment.initializer, value);
|
|
790
514
|
}
|
|
791
515
|
}));
|
|
792
|
-
} else if (isValFileMethodCall(node)) {
|
|
793
|
-
if (key === core.FILE_REF_PROP) {
|
|
794
|
-
if (typeof value !== "string") {
|
|
795
|
-
return fp.result.err(new patch.PatchError(`Cannot add ${core.FILE_REF_PROP} key to c.file with non-string value`));
|
|
796
|
-
}
|
|
797
|
-
return fp.pipe(findValFileNodeArg(node), fp.result.map(arg => replaceNodeValue(document, arg, value)));
|
|
798
|
-
} else {
|
|
799
|
-
return fp.pipe(findValFileMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
800
|
-
if (metadataArgNode) {
|
|
801
|
-
return fp.result.err(new patch.PatchError("Cannot add metadata to c.file when it already exists"));
|
|
802
|
-
}
|
|
803
|
-
if (key !== "metadata") {
|
|
804
|
-
return fp.result.err(new patch.PatchError(`Cannot add ${key} key to c.file: only metadata is allowed`));
|
|
805
|
-
}
|
|
806
|
-
return fp.result.ok([insertAt(document, node.arguments, node.arguments.length, toExpression(value))]);
|
|
807
|
-
}));
|
|
808
|
-
}
|
|
809
|
-
} else if (isValImageMethodCall(node)) {
|
|
810
|
-
if (key === core.FILE_REF_PROP) {
|
|
811
|
-
if (typeof value !== "string") {
|
|
812
|
-
return fp.result.err(new patch.PatchError(`Cannot add ${core.FILE_REF_PROP} key to c.image with non-string value`));
|
|
813
|
-
}
|
|
814
|
-
return fp.pipe(findValImageNodeArg(node), fp.result.map(arg => replaceNodeValue(document, arg, value)));
|
|
815
|
-
} else {
|
|
816
|
-
return fp.pipe(findValImageMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
817
|
-
if (metadataArgNode) {
|
|
818
|
-
return fp.result.err(new patch.PatchError("Cannot add metadata to c.image when it already exists"));
|
|
819
|
-
}
|
|
820
|
-
if (key !== "metadata") {
|
|
821
|
-
return fp.result.err(new patch.PatchError(`Cannot add ${key} key to c.image: only metadata is allowed`));
|
|
822
|
-
}
|
|
823
|
-
return fp.result.ok([insertAt(document, node.arguments, node.arguments.length, toExpression(value))]);
|
|
824
|
-
}));
|
|
825
|
-
}
|
|
826
|
-
} else if (isValRemoteMethodCall(node)) {
|
|
827
|
-
if (key === core.FILE_REF_PROP) {
|
|
828
|
-
if (typeof value !== "string") {
|
|
829
|
-
return fp.result.err(new patch.PatchError(`Cannot add ${core.FILE_REF_PROP} key to c.remote with non-string value`));
|
|
830
|
-
}
|
|
831
|
-
return fp.pipe(findValRemoteNodeArg(node), fp.result.map(arg => replaceNodeValue(document, arg, value)));
|
|
832
|
-
} else {
|
|
833
|
-
return fp.pipe(findValRemoteMetadataArg(node), fp.result.flatMap(metadataArgNode => {
|
|
834
|
-
if (metadataArgNode) {
|
|
835
|
-
return fp.result.err(new patch.PatchError("Cannot add metadata to c.remote when it already exists"));
|
|
836
|
-
}
|
|
837
|
-
if (key !== "metadata") {
|
|
838
|
-
return fp.result.err(new patch.PatchError(`Cannot add ${key} key to c.remote: only metadata is allowed`));
|
|
839
|
-
}
|
|
840
|
-
return fp.result.ok([insertAt(document, node.arguments, node.arguments.length, toExpression(value))]);
|
|
841
|
-
}));
|
|
842
|
-
}
|
|
843
516
|
} else {
|
|
844
517
|
return fp.result.err(shallowValidateExpression(node) ?? new patch.PatchError("Cannot add to non-object/array"));
|
|
845
518
|
}
|
|
@@ -1421,7 +1094,10 @@ function rebaseContentOp(op, prefixLen) {
|
|
|
1421
1094
|
from: op.from.slice(prefixLen)
|
|
1422
1095
|
});
|
|
1423
1096
|
case "file":
|
|
1424
|
-
|
|
1097
|
+
// A file op carries bytes, not content. The binary goes through the
|
|
1098
|
+
// normal upload pipeline; what lands in the entry is the `patch_id` that
|
|
1099
|
+
// marks the bytes as not yet committed, which the caller writes.
|
|
1100
|
+
return fp.result.err(new patch.PatchError("A file op is not a content op: rebase it via its path"));
|
|
1425
1101
|
}
|
|
1426
1102
|
}
|
|
1427
1103
|
|
|
@@ -1455,9 +1131,6 @@ function applyJsonValuesEntryPatches(args) {
|
|
|
1455
1131
|
} of patches) {
|
|
1456
1132
|
let touched = false;
|
|
1457
1133
|
for (const op of patch$1) {
|
|
1458
|
-
if (op.op === "file") {
|
|
1459
|
-
continue;
|
|
1460
|
-
}
|
|
1461
1134
|
const cls = serializedSchema ? classifyJsonValuesOp(serializedSchema, op.path) : {
|
|
1462
1135
|
kind: "normal"
|
|
1463
1136
|
};
|
|
@@ -1465,6 +1138,32 @@ function applyJsonValuesEntryPatches(args) {
|
|
|
1465
1138
|
continue;
|
|
1466
1139
|
}
|
|
1467
1140
|
touched = true;
|
|
1141
|
+
// A file op does not edit the entry's content — its bytes go through the
|
|
1142
|
+
// upload pipeline. What the entry needs is the `patch_id` saying those
|
|
1143
|
+
// bytes are not committed yet, so `mediaUrl` serves them from the patch
|
|
1144
|
+
// directory instead of a `/public` path that holds nothing. Without this a
|
|
1145
|
+
// just-uploaded image inside an entry renders broken.
|
|
1146
|
+
if (op.op === "file") {
|
|
1147
|
+
if (op.value === null || content === undefined) {
|
|
1148
|
+
// A delete carries no bytes to point at, and there is nothing to mark
|
|
1149
|
+
// on an entry that does not exist.
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
const applied = patch.applyPatch(patch.deepClone(content), jsonOps$1, [{
|
|
1153
|
+
op: "add",
|
|
1154
|
+
path: cls.subPath.concat(...(op.nestedFilePath ?? [])).concat("patch_id"),
|
|
1155
|
+
value: patchId
|
|
1156
|
+
}]);
|
|
1157
|
+
if (fp.result.isErr(applied)) {
|
|
1158
|
+
return {
|
|
1159
|
+
kind: "error",
|
|
1160
|
+
message: applied.error.message,
|
|
1161
|
+
patchId
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
content = applied.value;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1468
1167
|
if (cls.subPath.length === 0) {
|
|
1469
1168
|
if (op.op === "add" || op.op === "replace") {
|
|
1470
1169
|
content = op.value;
|
|
@@ -1562,6 +1261,35 @@ function resolveExistingJsonPath(moduleFilePath, importPath) {
|
|
|
1562
1261
|
* module errors (see `findNestedJsonValuesRecords`), so they can never reach
|
|
1563
1262
|
* here — if that guard is ever relaxed, this must become a recursive visitor or
|
|
1564
1263
|
* nested entries silently get no content validation at all.
|
|
1264
|
+
*
|
|
1265
|
+
* ## NOT validated, and it should be: the entry's FILE PATH
|
|
1266
|
+
*
|
|
1267
|
+
* The key↔file mapping is canonical. `getNewJsonEntryPaths` derives it, and both
|
|
1268
|
+
* write paths use it — the `jsonValues:extract-entry` fix and the patch commit:
|
|
1269
|
+
*
|
|
1270
|
+
* /content/kb.val.ts + key "kb-000" -> /content/kb/kb-000.val.json
|
|
1271
|
+
*
|
|
1272
|
+
* This function does not check it. It calls the thunk and validates whatever
|
|
1273
|
+
* comes back, so an entry may point at ANY file in the project and pass. The
|
|
1274
|
+
* repo's own fixture does exactly that — `examples/next/content/kb.val.ts` has
|
|
1275
|
+
* `"kb-000": c.json(() => import("./kb/entry-000.val.json"))`, where the
|
|
1276
|
+
* canonical path is `./kb/kb-000.val.json`, and `val validate` calls it valid.
|
|
1277
|
+
*
|
|
1278
|
+
* Two consequences, the first a live bug:
|
|
1279
|
+
*
|
|
1280
|
+
* - **A key rename writes to the DERIVED path**, which for a non-canonical entry
|
|
1281
|
+
* is not the file the old key was reading. The old file is orphaned and the new
|
|
1282
|
+
* key reads a file that was never written.
|
|
1283
|
+
* - It blocks localising entry staleness. `jsonEntriesSha` cannot say which entry
|
|
1284
|
+
* changed, so the client drops a whole module's loaded content; with the mapping
|
|
1285
|
+
* guaranteed, a changed file resolves to one key by derivation alone.
|
|
1286
|
+
*
|
|
1287
|
+
* The fix belongs here and needs no new machinery: the module's `.val.ts` is
|
|
1288
|
+
* already parsed (`ValSourceFileHandler`), so the import specifier is readable
|
|
1289
|
+
* from the AST — no `thunk.toString()`, which a bundler would defeat. A
|
|
1290
|
+
* `jsonValues:rename-entry-file` fix could move the file and rewrite the import,
|
|
1291
|
+
* exactly as `extract-entry` writes both. See `packages/ui/spa/stores/openquestions.md`
|
|
1292
|
+
* item 9b.
|
|
1565
1293
|
*/
|
|
1566
1294
|
async function validateJsonValuesEntries(schema, source, modulePath) {
|
|
1567
1295
|
const out = {};
|
|
@@ -2408,6 +2136,17 @@ class ValOps {
|
|
|
2408
2136
|
const serializedSchema = jsonValuesSchemaFor(path);
|
|
2409
2137
|
for (const op of patchData.patch) {
|
|
2410
2138
|
if (op.op === "file") {
|
|
2139
|
+
// A file op inside a `.jsonValues()` entry has nothing to inject
|
|
2140
|
+
// HERE: the entry is an opaque marker in the module source, so an
|
|
2141
|
+
// `add` reaching into it fails and poisons the rest of this
|
|
2142
|
+
// module's chain. `applyJsonValuesEntryPatches` writes the patch_id
|
|
2143
|
+
// into the entry's draft content instead.
|
|
2144
|
+
const fileCls = serializedSchema ? classifyJsonValuesOp(serializedSchema, op.path) : {
|
|
2145
|
+
kind: "normal"
|
|
2146
|
+
};
|
|
2147
|
+
if (fileCls.kind === "entry" && fileCls.subPath.length > 0) {
|
|
2148
|
+
continue;
|
|
2149
|
+
}
|
|
2411
2150
|
if (op.value !== null) {
|
|
2412
2151
|
// NOTE: We insert the last patch_id that modify a file
|
|
2413
2152
|
// when constructing the url we use the patch id (and the file path)
|
|
@@ -2708,7 +2447,7 @@ class ValOps {
|
|
|
2708
2447
|
};
|
|
2709
2448
|
}
|
|
2710
2449
|
const type = schemaAtPath instanceof core.ImageSchema ? "image" : "file";
|
|
2711
|
-
const filePath = value
|
|
2450
|
+
const filePath = value.path;
|
|
2712
2451
|
const fileData = (fileLastUpdatedByPatchId === null || fileLastUpdatedByPatchId === void 0 ? void 0 : fileLastUpdatedByPatchId[filePath]) || null;
|
|
2713
2452
|
let metadata;
|
|
2714
2453
|
let metadataErrors;
|
|
@@ -2750,23 +2489,14 @@ class ValOps {
|
|
|
2750
2489
|
}]
|
|
2751
2490
|
};
|
|
2752
2491
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
const currentValueMetadata = value.metadata;
|
|
2758
|
-
if (!currentValueMetadata) {
|
|
2759
|
-
return {
|
|
2760
|
-
[metadataSourcePath]: [{
|
|
2761
|
-
message: "Missing metadata field: 'metadata'",
|
|
2762
|
-
value
|
|
2763
|
-
}]
|
|
2764
|
-
};
|
|
2765
|
-
}
|
|
2492
|
+
// The fields Val computes from the bytes sit next to `path` now, so the
|
|
2493
|
+
// error is reported at the field it is about rather than at a `metadata`
|
|
2494
|
+
// object that no longer exists.
|
|
2495
|
+
const currentValueMetadata = value;
|
|
2766
2496
|
const fieldErrors = {};
|
|
2767
2497
|
for (const field of getFieldsForType(type)) {
|
|
2768
2498
|
const fieldMetadata = metadata[field];
|
|
2769
|
-
const fieldSourcePath = core.Internal.createValPathOfItem(
|
|
2499
|
+
const fieldSourcePath = core.Internal.createValPathOfItem(sourcePath, field);
|
|
2770
2500
|
if (!fieldSourcePath) {
|
|
2771
2501
|
throw new Error("Could not create field path");
|
|
2772
2502
|
}
|
|
@@ -3358,11 +3088,15 @@ function isOnlyFileCheckValidationError(validationError) {
|
|
|
3358
3088
|
}
|
|
3359
3089
|
return false;
|
|
3360
3090
|
}
|
|
3091
|
+
|
|
3092
|
+
/**
|
|
3093
|
+
* Whether a flagged validation value is media.
|
|
3094
|
+
*
|
|
3095
|
+
* The caller already knows the schema said image or file — this only guards
|
|
3096
|
+
* against a value that never got that far.
|
|
3097
|
+
*/
|
|
3361
3098
|
function isFileSource(value) {
|
|
3362
|
-
|
|
3363
|
-
return true;
|
|
3364
|
-
}
|
|
3365
|
-
return false;
|
|
3099
|
+
return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string";
|
|
3366
3100
|
}
|
|
3367
3101
|
function formatPatchSourceError(error) {
|
|
3368
3102
|
if ("message" in error) {
|
|
@@ -4706,7 +4440,7 @@ const FSPatchBase = z.z.object({
|
|
|
4706
4440
|
timestamp: z.z.string().datetime()
|
|
4707
4441
|
});
|
|
4708
4442
|
|
|
4709
|
-
const textEncoder$
|
|
4443
|
+
const textEncoder$2 = new TextEncoder();
|
|
4710
4444
|
const PatchId = z.z.string().refine(s => !!s); // TODO: validate
|
|
4711
4445
|
const CommitSha = z.z.string().refine(s => !!s); // TODO: validate
|
|
4712
4446
|
z.z.string().refine(s => !!s); // TODO: validate
|
|
@@ -5397,7 +5131,7 @@ class ValOpsHttp extends ValOps {
|
|
|
5397
5131
|
});
|
|
5398
5132
|
params.set("body_sha",
|
|
5399
5133
|
// We use this for cache invalidation
|
|
5400
|
-
core.Internal.getSHA256Hash(textEncoder$
|
|
5134
|
+
core.Internal.getSHA256Hash(textEncoder$2.encode(stringifiedFiles)));
|
|
5401
5135
|
return fetch(`${this.contentUrl}/v1/${this.project}/files?${params}`, {
|
|
5402
5136
|
method: "PUT",
|
|
5403
5137
|
// Yes, PUT is weird. Weirder to have a body in a GET request.
|
|
@@ -5823,51 +5557,6 @@ function parsePersonalAccessTokenFile(content) {
|
|
|
5823
5557
|
}
|
|
5824
5558
|
}
|
|
5825
5559
|
|
|
5826
|
-
/**
|
|
5827
|
-
* Iterates through all schemas and find if there is 1 or more remote files in them
|
|
5828
|
-
*/
|
|
5829
|
-
function hasRemoteFileSchema(schema) {
|
|
5830
|
-
if (schema.type === "file" || schema.type === "image") {
|
|
5831
|
-
return !!schema.remote;
|
|
5832
|
-
} else if (schema.type === "richtext") {
|
|
5833
|
-
var _schema$options;
|
|
5834
|
-
if (typeof ((_schema$options = schema.options) === null || _schema$options === void 0 || (_schema$options = _schema$options.inline) === null || _schema$options === void 0 ? void 0 : _schema$options.img) === "object") {
|
|
5835
|
-
return hasRemoteFileSchema(schema.options.inline.img);
|
|
5836
|
-
}
|
|
5837
|
-
return false;
|
|
5838
|
-
} else if (schema.type === "array" || schema.type === "record") {
|
|
5839
|
-
return hasRemoteFileSchema(schema.item);
|
|
5840
|
-
} else if (schema.type === "object") {
|
|
5841
|
-
for (const key in schema.items) {
|
|
5842
|
-
const hasRemoteFile = hasRemoteFileSchema(schema.items[key]);
|
|
5843
|
-
if (hasRemoteFile) {
|
|
5844
|
-
return true;
|
|
5845
|
-
}
|
|
5846
|
-
}
|
|
5847
|
-
return false;
|
|
5848
|
-
} else if (schema.type === "union") {
|
|
5849
|
-
const unionStringSchema = typeof schema.key === "object" && schema.key.type === "literal" ? schema : undefined;
|
|
5850
|
-
const unionObjectSchema = typeof schema.key === "string" ? schema : undefined;
|
|
5851
|
-
if (unionStringSchema) {
|
|
5852
|
-
return false;
|
|
5853
|
-
}
|
|
5854
|
-
if (unionObjectSchema) {
|
|
5855
|
-
for (const key in unionObjectSchema.items) {
|
|
5856
|
-
const hasRemoteFile = hasRemoteFileSchema(unionObjectSchema.items[key]);
|
|
5857
|
-
if (hasRemoteFile) {
|
|
5858
|
-
return true;
|
|
5859
|
-
}
|
|
5860
|
-
}
|
|
5861
|
-
}
|
|
5862
|
-
return false;
|
|
5863
|
-
} else if (schema.type === "boolean" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "keyOf" || schema.type === "route") {
|
|
5864
|
-
return false;
|
|
5865
|
-
} else {
|
|
5866
|
-
const exhaustiveCheck = schema;
|
|
5867
|
-
throw new Error(`Unexpected schema: ${JSON.stringify(exhaustiveCheck)}`);
|
|
5868
|
-
}
|
|
5869
|
-
}
|
|
5870
|
-
|
|
5871
5560
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
5872
5561
|
const ValServer = (valModules, options, callbacks) => {
|
|
5873
5562
|
const AIContentBlock = z.z.union([z.z.object({
|
|
@@ -8584,7 +8273,7 @@ function getIsRemoteRequired(schemas) {
|
|
|
8584
8273
|
for (const moduleFilePathS in schemas) {
|
|
8585
8274
|
const moduleFilePath = moduleFilePathS;
|
|
8586
8275
|
const schema = schemas[moduleFilePath]["executeSerialize"]();
|
|
8587
|
-
const isRemoteRequired = hasRemoteFileSchema(schema);
|
|
8276
|
+
const isRemoteRequired = core.hasRemoteFileSchema(schema);
|
|
8588
8277
|
if (isRemoteRequired) {
|
|
8589
8278
|
return true;
|
|
8590
8279
|
}
|
|
@@ -9170,15 +8859,21 @@ _input // TODO: use buffer to determine mimetype
|
|
|
9170
8859
|
};
|
|
9171
8860
|
}
|
|
9172
8861
|
|
|
8862
|
+
/**
|
|
8863
|
+
* The media path a validation error is about, if it is about media at all.
|
|
8864
|
+
*
|
|
8865
|
+
* There is no schema in hand here — a `ValidationError` carries only the value
|
|
8866
|
+
* it flagged — so the shape is all there is to go on.
|
|
8867
|
+
*/
|
|
9173
8868
|
function getValidationErrorFileRef(validationError) {
|
|
9174
|
-
const
|
|
9175
|
-
if (!
|
|
8869
|
+
const maybePath = validationError.value && typeof validationError.value === "object" && "path" in validationError.value && typeof validationError.value.path === "string" ? validationError.value.path : undefined;
|
|
8870
|
+
if (!maybePath) {
|
|
9176
8871
|
return null;
|
|
9177
8872
|
}
|
|
9178
|
-
return
|
|
8873
|
+
return maybePath;
|
|
9179
8874
|
}
|
|
9180
8875
|
|
|
9181
|
-
const textEncoder = new TextEncoder();
|
|
8876
|
+
const textEncoder$1 = new TextEncoder();
|
|
9182
8877
|
async function checkRemoteRef(remoteHost, ref, projectRoot, schema, metadata) {
|
|
9183
8878
|
const remoteRefDataRes = core.Internal.remote.splitRemoteRef(ref);
|
|
9184
8879
|
if (remoteRefDataRes.status === "error") {
|
|
@@ -9208,10 +8903,10 @@ async function checkRemoteRef(remoteHost, ref, projectRoot, schema, metadata) {
|
|
|
9208
8903
|
const fileExt = getFileExt(relativeFilePath);
|
|
9209
8904
|
const currentValidationHash = core.Internal.remote.getValidationHash(coreVersion, schema, fileExt, metadata,
|
|
9210
8905
|
// TODO: validate
|
|
9211
|
-
fileHash, textEncoder);
|
|
8906
|
+
fileHash, textEncoder$1);
|
|
9212
8907
|
const validationHashFromRemoteRefValues = core.Internal.remote.getValidationHash(remoteRefDataRes.version, schema, getFileExt(remoteRefDataRes.filePath), metadata,
|
|
9213
8908
|
// TODO: validate
|
|
9214
|
-
fileHash, textEncoder);
|
|
8909
|
+
fileHash, textEncoder$1);
|
|
9215
8910
|
if (
|
|
9216
8911
|
// Current validation hash is the same as the remote validation hash
|
|
9217
8912
|
// We assume the uploaded file exists and is correct
|
|
@@ -9257,7 +8952,7 @@ async function checkRemoteRef(remoteHost, ref, projectRoot, schema, metadata) {
|
|
|
9257
8952
|
...metadata,
|
|
9258
8953
|
...currentMetadata
|
|
9259
8954
|
};
|
|
9260
|
-
const nextValidationHash = core.Internal.remote.getValidationHash(coreVersion, schema, fileExt, updatedMetadata, fileBufferRes.fileHash, textEncoder);
|
|
8955
|
+
const nextValidationHash = core.Internal.remote.getValidationHash(coreVersion, schema, fileExt, updatedMetadata, fileBufferRes.fileHash, textEncoder$1);
|
|
9261
8956
|
if (!updatedMetadata.mimeType) {
|
|
9262
8957
|
return {
|
|
9263
8958
|
status: "error",
|
|
@@ -9387,6 +9082,36 @@ function parentPatchPathOfMediaEntry(sourcePath, entryKey) {
|
|
|
9387
9082
|
}
|
|
9388
9083
|
|
|
9389
9084
|
// TODO: find a better name? transformFixesToPatch?
|
|
9085
|
+
/**
|
|
9086
|
+
* A whole media value, for the fixes that replace one outright.
|
|
9087
|
+
*
|
|
9088
|
+
* The three remote fixes each wrote this shape out by hand, and one of them was
|
|
9089
|
+
* left behind when the shape changed. One name, so the next change cannot miss a
|
|
9090
|
+
* site.
|
|
9091
|
+
*
|
|
9092
|
+
* `path` last is deliberate: it is the field the fix is about, and a stray
|
|
9093
|
+
* `path` inside the metadata must not win over it.
|
|
9094
|
+
*/
|
|
9095
|
+
function mediaValue(path, metadata) {
|
|
9096
|
+
const fields = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : {};
|
|
9097
|
+
return {
|
|
9098
|
+
...fields,
|
|
9099
|
+
path
|
|
9100
|
+
};
|
|
9101
|
+
}
|
|
9102
|
+
|
|
9103
|
+
/**
|
|
9104
|
+
* The media value a validation error flagged, read as a plain record.
|
|
9105
|
+
*
|
|
9106
|
+
* A `ValidationError` carries only a value, never the schema that rejected it,
|
|
9107
|
+
* so the shape is all there is to go on here.
|
|
9108
|
+
*/
|
|
9109
|
+
function mediaValueOf(value) {
|
|
9110
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
9111
|
+
return undefined;
|
|
9112
|
+
}
|
|
9113
|
+
return value;
|
|
9114
|
+
}
|
|
9390
9115
|
async function createFixPatch(config, apply, sourcePath, validationError, remoteFiles, moduleSource, moduleSchema) {
|
|
9391
9116
|
const remainingErrors = [];
|
|
9392
9117
|
const patch$1 = [];
|
|
@@ -9400,57 +9125,38 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9400
9125
|
fixes: undefined
|
|
9401
9126
|
});
|
|
9402
9127
|
} else if (fix === "image:check-metadata") {
|
|
9403
|
-
const currentValue = validationError.value;
|
|
9404
|
-
const
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
//
|
|
9410
|
-
|
|
9411
|
-
//
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
if (!metadataIsCorrect) {
|
|
9128
|
+
const currentValue = mediaValueOf(validationError.value);
|
|
9129
|
+
const derived = {
|
|
9130
|
+
width: imageMetadata.width,
|
|
9131
|
+
height: imageMetadata.height,
|
|
9132
|
+
mimeType: imageMetadata.mimeType
|
|
9133
|
+
};
|
|
9134
|
+
// One op per field, not one for the whole object: `alt` and `hotspot`
|
|
9135
|
+
// are authored and sit next to these, so a whole-object write would
|
|
9136
|
+
// throw away what a person typed.
|
|
9137
|
+
const wrong = Object.entries(derived).filter(([field, expected]) => (currentValue === null || currentValue === void 0 ? void 0 : currentValue[field]) !== expected);
|
|
9138
|
+
if (wrong.length > 0) {
|
|
9415
9139
|
if (apply) {
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
}
|
|
9426
|
-
});
|
|
9427
|
-
} else {
|
|
9428
|
-
if (typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object") {
|
|
9429
|
-
if (!("width" in currentValue.metadata) || currentValue.metadata.width !== imageMetadata.width) {
|
|
9430
|
-
remainingErrors.push({
|
|
9431
|
-
message: "Image metadata width is incorrect! Found: " + ("width" in currentValue.metadata ? currentValue.metadata.width : "<empty>") + ". Expected: " + imageMetadata.width,
|
|
9432
|
-
fixes: undefined
|
|
9433
|
-
});
|
|
9434
|
-
}
|
|
9435
|
-
if (!("height" in currentValue.metadata) || currentValue.metadata.height !== imageMetadata.height) {
|
|
9436
|
-
remainingErrors.push({
|
|
9437
|
-
message: "Image metadata height is incorrect! Found: " + ("height" in currentValue.metadata ? currentValue.metadata.height : "<empty>") + ". Expected: " + imageMetadata.height,
|
|
9438
|
-
fixes: undefined
|
|
9439
|
-
});
|
|
9440
|
-
}
|
|
9441
|
-
if (!("mimeType" in currentValue.metadata) || currentValue.metadata.mimeType !== imageMetadata.mimeType) {
|
|
9442
|
-
remainingErrors.push({
|
|
9443
|
-
message: "Image metadata mimeType is incorrect! Found: " + ("mimeType" in currentValue.metadata ? currentValue.metadata.mimeType : "<empty>") + ". Expected: " + imageMetadata.mimeType,
|
|
9444
|
-
fixes: undefined
|
|
9445
|
-
});
|
|
9446
|
-
}
|
|
9447
|
-
} else {
|
|
9140
|
+
for (const [field, expected] of wrong) {
|
|
9141
|
+
patch$1.push({
|
|
9142
|
+
op: "add",
|
|
9143
|
+
path: patch.sourceToPatchPath(sourcePath).concat(field),
|
|
9144
|
+
value: expected
|
|
9145
|
+
});
|
|
9146
|
+
}
|
|
9147
|
+
} else if (currentValue) {
|
|
9148
|
+
for (const [field, expected] of wrong) {
|
|
9448
9149
|
remainingErrors.push({
|
|
9449
|
-
|
|
9450
|
-
message: "Image metadata is not an object!",
|
|
9150
|
+
message: `Image ${field} is incorrect! Found: ` + (currentValue[field] ?? "<empty>") + ". Expected: " + expected,
|
|
9451
9151
|
fixes: undefined
|
|
9452
9152
|
});
|
|
9453
9153
|
}
|
|
9154
|
+
} else {
|
|
9155
|
+
remainingErrors.push({
|
|
9156
|
+
...validationError,
|
|
9157
|
+
message: "Image is not an object!",
|
|
9158
|
+
fixes: undefined
|
|
9159
|
+
});
|
|
9454
9160
|
}
|
|
9455
9161
|
}
|
|
9456
9162
|
} else if (fix === "image:add-metadata") {
|
|
@@ -9461,14 +9167,19 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9461
9167
|
fixes: undefined
|
|
9462
9168
|
});
|
|
9463
9169
|
} else {
|
|
9170
|
+
const patchPath = patch.sourceToPatchPath(sourcePath);
|
|
9464
9171
|
patch$1.push({
|
|
9465
9172
|
op: "add",
|
|
9466
|
-
path:
|
|
9467
|
-
value:
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9173
|
+
path: patchPath.concat("width"),
|
|
9174
|
+
value: imageMetadata.width
|
|
9175
|
+
}, {
|
|
9176
|
+
op: "add",
|
|
9177
|
+
path: patchPath.concat("height"),
|
|
9178
|
+
value: imageMetadata.height
|
|
9179
|
+
}, {
|
|
9180
|
+
op: "add",
|
|
9181
|
+
path: patchPath.concat("mimeType"),
|
|
9182
|
+
value: imageMetadata.mimeType
|
|
9472
9183
|
});
|
|
9473
9184
|
}
|
|
9474
9185
|
}
|
|
@@ -9481,53 +9192,32 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9481
9192
|
fixes: undefined
|
|
9482
9193
|
});
|
|
9483
9194
|
} else if (fix === "file:check-metadata") {
|
|
9484
|
-
const currentValue = validationError.value;
|
|
9485
|
-
|
|
9486
|
-
// metadata is a prop that is an object
|
|
9487
|
-
typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object" &&
|
|
9488
|
-
// mimeType is correct
|
|
9489
|
-
"mimeType" in currentValue.metadata && currentValue.metadata.mimeType === fileMetadata.mimeType;
|
|
9490
|
-
|
|
9491
|
-
// skips if the metadata is already correct
|
|
9492
|
-
if (!metadataIsCorrect) {
|
|
9195
|
+
const currentValue = mediaValueOf(validationError.value);
|
|
9196
|
+
if ((currentValue === null || currentValue === void 0 ? void 0 : currentValue.mimeType) !== fileMetadata.mimeType) {
|
|
9493
9197
|
if (apply) {
|
|
9494
9198
|
patch$1.push({
|
|
9495
|
-
op: "
|
|
9496
|
-
path: patch.sourceToPatchPath(sourcePath).concat("
|
|
9497
|
-
value:
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
}
|
|
9199
|
+
op: "add",
|
|
9200
|
+
path: patch.sourceToPatchPath(sourcePath).concat("mimeType"),
|
|
9201
|
+
value: fileMetadata.mimeType ?? null
|
|
9202
|
+
});
|
|
9203
|
+
} else if (currentValue) {
|
|
9204
|
+
remainingErrors.push({
|
|
9205
|
+
message: "File mimeType is incorrect! Found: " + (currentValue.mimeType ?? "<empty>") + ". Expected: " + fileMetadata.mimeType,
|
|
9206
|
+
fixes: undefined
|
|
9504
9207
|
});
|
|
9505
9208
|
} else {
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
});
|
|
9512
|
-
}
|
|
9513
|
-
} else {
|
|
9514
|
-
remainingErrors.push({
|
|
9515
|
-
...validationError,
|
|
9516
|
-
message: "Image metadata is not an object!",
|
|
9517
|
-
fixes: undefined
|
|
9518
|
-
});
|
|
9519
|
-
}
|
|
9209
|
+
remainingErrors.push({
|
|
9210
|
+
...validationError,
|
|
9211
|
+
message: "File is not an object!",
|
|
9212
|
+
fixes: undefined
|
|
9213
|
+
});
|
|
9520
9214
|
}
|
|
9521
9215
|
}
|
|
9522
9216
|
} else if (fix === "file:add-metadata") {
|
|
9523
9217
|
patch$1.push({
|
|
9524
9218
|
op: "add",
|
|
9525
|
-
path: patch.sourceToPatchPath(sourcePath).concat("
|
|
9526
|
-
value:
|
|
9527
|
-
...(fileMetadata.mimeType ? {
|
|
9528
|
-
mimeType: fileMetadata.mimeType
|
|
9529
|
-
} : {})
|
|
9530
|
-
}
|
|
9219
|
+
path: patch.sourceToPatchPath(sourcePath).concat("mimeType"),
|
|
9220
|
+
value: fileMetadata.mimeType ?? null
|
|
9531
9221
|
});
|
|
9532
9222
|
}
|
|
9533
9223
|
} else if (fix === "image:upload-remote" || fix === "file:upload-remote") {
|
|
@@ -9555,11 +9245,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9555
9245
|
} else {
|
|
9556
9246
|
patch$1.push({
|
|
9557
9247
|
op: "replace",
|
|
9558
|
-
value:
|
|
9559
|
-
_type: "remote",
|
|
9560
|
-
_ref: remoteFile.ref,
|
|
9561
|
-
metadata
|
|
9562
|
-
},
|
|
9248
|
+
value: mediaValue(remoteFile.ref, metadata),
|
|
9563
9249
|
path: patch.sourceToPatchPath(sourcePath)
|
|
9564
9250
|
});
|
|
9565
9251
|
}
|
|
@@ -9628,7 +9314,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9628
9314
|
});
|
|
9629
9315
|
continue;
|
|
9630
9316
|
}
|
|
9631
|
-
const splitRemoteRefDataRes = core.Internal.remote.splitRemoteRef(v.
|
|
9317
|
+
const splitRemoteRefDataRes = core.Internal.remote.splitRemoteRef(v.path);
|
|
9632
9318
|
if (splitRemoteRefDataRes.status === "error") {
|
|
9633
9319
|
remainingErrors.push({
|
|
9634
9320
|
...validationError,
|
|
@@ -9637,7 +9323,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9637
9323
|
});
|
|
9638
9324
|
continue;
|
|
9639
9325
|
}
|
|
9640
|
-
const url = v.
|
|
9326
|
+
const url = v.path;
|
|
9641
9327
|
const filePath = splitRemoteRefDataRes.filePath;
|
|
9642
9328
|
if (!filePath) {
|
|
9643
9329
|
remainingErrors.push({
|
|
@@ -9668,20 +9354,10 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9668
9354
|
});
|
|
9669
9355
|
continue;
|
|
9670
9356
|
}
|
|
9671
|
-
const value = {
|
|
9672
|
-
[core.VAL_EXTENSION]: "file",
|
|
9673
|
-
[core.FILE_REF_PROP]: `/${filePath}`,
|
|
9674
|
-
...(fix === "image:download-remote" ? {
|
|
9675
|
-
[core.FILE_REF_SUBTYPE_TAG]: "image"
|
|
9676
|
-
} : {})
|
|
9677
|
-
};
|
|
9678
9357
|
patch$1.push({
|
|
9679
9358
|
op: "replace",
|
|
9680
9359
|
path: patch.sourceToPatchPath(sourcePath),
|
|
9681
|
-
value: v.metadata
|
|
9682
|
-
...value,
|
|
9683
|
-
metadata: v.metadata
|
|
9684
|
-
} : value
|
|
9360
|
+
value: mediaValue(`/${filePath}`, v.metadata)
|
|
9685
9361
|
});
|
|
9686
9362
|
} else if (fix === "images:check-all-files" || fix === "files:check-all-files") {
|
|
9687
9363
|
if (!moduleSource || typeof moduleSource !== "object") {
|
|
@@ -9804,7 +9480,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9804
9480
|
schema: schemaAtPath
|
|
9805
9481
|
} = core.Internal.resolvePath(modulePath, moduleSource, moduleSchema);
|
|
9806
9482
|
if (schemaAtPath.type === "image" || schemaAtPath.type === "file") {
|
|
9807
|
-
const res = await checkRemoteRef(config.remoteHost, v.
|
|
9483
|
+
const res = await checkRemoteRef(config.remoteHost, v.path, config.projectRoot, schemaAtPath, v.metadata);
|
|
9808
9484
|
if (res.status === "success") ; else if (res.status === "error") {
|
|
9809
9485
|
remainingErrors.push({
|
|
9810
9486
|
...validationError,
|
|
@@ -9816,11 +9492,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
9816
9492
|
patch$1.push({
|
|
9817
9493
|
op: "replace",
|
|
9818
9494
|
path: patch.sourceToPatchPath(sourcePath),
|
|
9819
|
-
value:
|
|
9820
|
-
_type: "remote",
|
|
9821
|
-
_ref: res.ref,
|
|
9822
|
-
metadata: res.metadata
|
|
9823
|
-
}
|
|
9495
|
+
value: mediaValue(res.ref, res.metadata)
|
|
9824
9496
|
});
|
|
9825
9497
|
} else {
|
|
9826
9498
|
remainingErrors.push({
|
|
@@ -9867,37 +9539,26 @@ function getRemoteValueFromValidationError(v) {
|
|
|
9867
9539
|
message: "Unexpected error while checking remote (no value)"
|
|
9868
9540
|
};
|
|
9869
9541
|
}
|
|
9870
|
-
if (typeof v.value !== "object" || v.value === null || !(
|
|
9542
|
+
if (typeof v.value !== "object" || v.value === null || !("path" in v.value)) {
|
|
9871
9543
|
return {
|
|
9872
9544
|
success: false,
|
|
9873
|
-
message: "Unexpected error while checking remote (no
|
|
9545
|
+
message: "Unexpected error while checking remote (no path in value)"
|
|
9874
9546
|
};
|
|
9875
9547
|
}
|
|
9876
|
-
if (typeof v.value.
|
|
9548
|
+
if (typeof v.value.path !== "string") {
|
|
9877
9549
|
return {
|
|
9878
9550
|
success: false,
|
|
9879
|
-
message: "Unexpected error while checking remote (
|
|
9551
|
+
message: "Unexpected error while checking remote (path is not a string)"
|
|
9880
9552
|
};
|
|
9881
9553
|
}
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
message: "Unexpected error while checking remote (metadata is null)"
|
|
9888
|
-
};
|
|
9889
|
-
}
|
|
9890
|
-
if (Array.isArray(v.value.metadata)) {
|
|
9891
|
-
return {
|
|
9892
|
-
success: false,
|
|
9893
|
-
message: "Unexpected error while checking remote (metadata is an array)"
|
|
9894
|
-
};
|
|
9895
|
-
}
|
|
9896
|
-
metadata = v.value.metadata;
|
|
9897
|
-
}
|
|
9554
|
+
// Everything but the path is what Val read from the bytes.
|
|
9555
|
+
const {
|
|
9556
|
+
path,
|
|
9557
|
+
...metadata
|
|
9558
|
+
} = v.value;
|
|
9898
9559
|
return {
|
|
9899
9560
|
success: true,
|
|
9900
|
-
|
|
9561
|
+
path,
|
|
9901
9562
|
metadata
|
|
9902
9563
|
};
|
|
9903
9564
|
}
|
|
@@ -9922,23 +9583,649 @@ async function getFileMetadata(projectRoot, validationError) {
|
|
|
9922
9583
|
return extractFileMetadata(fileRef);
|
|
9923
9584
|
}
|
|
9924
9585
|
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9586
|
+
/**
|
|
9587
|
+
* Moves ONE `.jsonValues()` entry that was written inline in the `.val.ts` into
|
|
9588
|
+
* its own `*.val.json`, replacing the inline value with
|
|
9589
|
+
* `c.json(() => import("./<key>.val.json"))`.
|
|
9590
|
+
*
|
|
9591
|
+
* This is the fix for the `jsonValues:extract-entry` validation error. It is not
|
|
9592
|
+
* expressible as a patch (a patch edits one `.val.ts` and cannot create the
|
|
9593
|
+
* backing JSON file), so it writes both files directly — JSON first, so a
|
|
9594
|
+
* failure part-way through never leaves the module pointing at a file that does
|
|
9595
|
+
* not exist.
|
|
9596
|
+
*
|
|
9597
|
+
* Root-only, like the rest of the `.jsonValues()` machinery: the entry is looked
|
|
9598
|
+
* up in the module's root record/router object literal.
|
|
9599
|
+
*/
|
|
9600
|
+
function extractJsonValuesEntry(moduleFilePath, rootDir, entryKey, content, sourceFileHandler) {
|
|
9601
|
+
const valTsPath = sourceFileHandler.resolveSourceModulePath(getSyntheticContainingPath(rootDir), `.${moduleFilePath.replace(".val.ts", ".val").replace(".val.js", ".val").replace(".val.jsx", ".val").replace(".val.tsx", ".val")}`);
|
|
9602
|
+
const sourceFile = sourceFileHandler.getSourceFile(valTsPath);
|
|
9603
|
+
if (!sourceFile) {
|
|
9604
|
+
throw Error(`Source file ${valTsPath} not found`);
|
|
9605
|
+
}
|
|
9606
|
+
const pathsRes = getNewJsonEntryPaths(moduleFilePath, entryKey);
|
|
9607
|
+
if (fp.result.isErr(pathsRes)) {
|
|
9608
|
+
throw Error(formatPatchSourceError(pathsRes.error));
|
|
9609
|
+
}
|
|
9610
|
+
const {
|
|
9611
|
+
jsonPath,
|
|
9612
|
+
importPath
|
|
9613
|
+
} = pathsRes.value;
|
|
9614
|
+
const absoluteJsonPath = path__namespace["default"].join(rootDir, jsonPath);
|
|
9615
|
+
if (sourceFileHandler.host.fileExists(absoluteJsonPath)) {
|
|
9616
|
+
throw Error(`Cannot extract .jsonValues() entry '${entryKey}' of ${moduleFilePath}: '${jsonPath}' already exists`);
|
|
9617
|
+
}
|
|
9618
|
+
|
|
9619
|
+
// Remove the inline property, then add the `c.json(...)` reference back. The
|
|
9620
|
+
// entry moves to the end of the record: entry ORDER in a jsonValues record is
|
|
9621
|
+
// not meaningful (the Studio and the runtime key entries by name), and
|
|
9622
|
+
// insert-in-place would mean reimplementing insertValJsonEntry.
|
|
9623
|
+
const removed = removeValJsonEntry(sourceFile, [], entryKey);
|
|
9624
|
+
if (fp.result.isErr(removed)) {
|
|
9625
|
+
throw Error(`${valTsPath}\n${formatOpsError(removed.error, sourceFile)}`);
|
|
9626
|
+
}
|
|
9627
|
+
const inserted = insertValJsonEntry(removed.value, [], entryKey, importPath);
|
|
9628
|
+
if (fp.result.isErr(inserted)) {
|
|
9629
|
+
throw Error(`${valTsPath}\n${formatOpsError(inserted.error, removed.value)}`);
|
|
9630
|
+
}
|
|
9631
|
+
sourceFileHandler.writeFile(absoluteJsonPath, JSON.stringify(content, null, 2) + "\n", "utf8");
|
|
9632
|
+
sourceFileHandler.writeSourceFile(inserted.value);
|
|
9633
|
+
}
|
|
9634
|
+
function formatOpsError(error, sourceFile) {
|
|
9635
|
+
if (error instanceof patch.PatchError) {
|
|
9636
|
+
return error.message;
|
|
9637
|
+
}
|
|
9638
|
+
return flatMapErrors(error, e => formatSyntaxError(e, sourceFile)).join("\n");
|
|
9639
|
+
}
|
|
9640
|
+
|
|
9641
|
+
/**
|
|
9642
|
+
* The fix handlers behind `val validate --fix`, and the editor quick fixes that
|
|
9643
|
+
* must agree with it.
|
|
9644
|
+
*
|
|
9645
|
+
* ## Why this lives in `@valbuild/server`
|
|
9646
|
+
*
|
|
9647
|
+
* A fix is two layers. `createFixPatch` is the second: given a validation
|
|
9648
|
+
* error it produces a patch. The first is everything that has to happen before a
|
|
9649
|
+
* patch can be produced — read the file, check it is actually on disk, extract
|
|
9650
|
+
* metadata, pick a remote bucket, upload the bytes, download them back. That
|
|
9651
|
+
* layer used to live in `packages/cli/src/runValidation.ts`, which made it
|
|
9652
|
+
* reachable only from the CLI: `@valbuild/cli` exports nothing but `./cli`, and
|
|
9653
|
+
* `@valbuild/language-server` cannot depend on it anyway without creating a
|
|
9654
|
+
* cycle (the CLI depends on the language server).
|
|
9655
|
+
*
|
|
9656
|
+
* The consequence was that an editor could offer the fixes needing no
|
|
9657
|
+
* precondition and had to reimplement or skip the rest. Both happened: the
|
|
9658
|
+
* VS Code extension grew its own remote-upload client, and the language server
|
|
9659
|
+
* declined to offer remote fixes at all.
|
|
9660
|
+
*
|
|
9661
|
+
* So the layer sits here, next to `createFixPatch`, and the CLI and the language
|
|
9662
|
+
* server are both callers. {@link FixHandlerContext} is deliberately made of
|
|
9663
|
+
* things any caller already has — a `Service`, an `IValFSHost`, a project root —
|
|
9664
|
+
* rather than of CLI concepts.
|
|
9665
|
+
*
|
|
9666
|
+
* What stays in the CLI is the driver: the `runValidation` async generator that
|
|
9667
|
+
* walks every module, decides what to report, and renders it to a terminal.
|
|
9668
|
+
*/
|
|
9669
|
+
const textEncoder = new TextEncoder();
|
|
9670
|
+
|
|
9671
|
+
// Types for handler system
|
|
9672
|
+
|
|
9673
|
+
// Handler functions
|
|
9674
|
+
async function handleFileMetadata(ctx) {
|
|
9675
|
+
var _fileSource$source;
|
|
9676
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(ctx.sourcePath);
|
|
9677
|
+
if (!ctx.valModule.source || !ctx.valModule.schema) {
|
|
9678
|
+
return {
|
|
9679
|
+
success: false,
|
|
9680
|
+
errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`
|
|
9681
|
+
};
|
|
9682
|
+
}
|
|
9683
|
+
const fileSource = core.Internal.resolvePath(modulePath, ctx.valModule.source, ctx.valModule.schema);
|
|
9684
|
+
|
|
9685
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9686
|
+
const fileRefProp = (_fileSource$source = fileSource.source) === null || _fileSource$source === void 0 ? void 0 : _fileSource$source.path;
|
|
9687
|
+
if (!fileRefProp) {
|
|
9688
|
+
return {
|
|
9689
|
+
success: false,
|
|
9690
|
+
errorMessage: `Expected file to be defined at: ${ctx.sourcePath} but no file was found`
|
|
9691
|
+
};
|
|
9692
|
+
}
|
|
9693
|
+
const filePath = path__namespace["default"].join(ctx.projectRoot, fileRefProp);
|
|
9694
|
+
if (!ctx.fs.fileExists(filePath)) {
|
|
9695
|
+
return {
|
|
9696
|
+
success: false,
|
|
9697
|
+
errorMessage: `File ${filePath} does not exist`
|
|
9698
|
+
};
|
|
9699
|
+
}
|
|
9700
|
+
return {
|
|
9701
|
+
success: true,
|
|
9702
|
+
shouldApplyPatch: true
|
|
9703
|
+
};
|
|
9704
|
+
}
|
|
9705
|
+
|
|
9706
|
+
// Shared upload core used by both the single-field (handleRemoteFileUpload)
|
|
9707
|
+
// and gallery (handleRemoteGalleryFileUpload) handlers. The two differ only in
|
|
9708
|
+
// how they derive the local file ref, metadata and serialized image/file
|
|
9709
|
+
// schema; everything from auth through upload is identical.
|
|
9710
|
+
async function uploadRemoteFileCore(ctx, fileRef, metadata, schema) {
|
|
9711
|
+
const filePath = path__namespace["default"].join(ctx.projectRoot, fileRef);
|
|
9712
|
+
if (!ctx.fs.fileExists(filePath)) {
|
|
9713
|
+
return {
|
|
9714
|
+
success: false,
|
|
9715
|
+
errorMessage: `File ${filePath} does not exist`
|
|
9716
|
+
};
|
|
9717
|
+
}
|
|
9718
|
+
const patFile = getPersonalAccessTokenPath(ctx.projectRoot);
|
|
9719
|
+
if (!ctx.fs.fileExists(patFile)) {
|
|
9720
|
+
return {
|
|
9721
|
+
success: false,
|
|
9722
|
+
errorMessage: `File: ${path__namespace["default"].join(ctx.projectRoot, ctx.file)} has remote images that are not uploaded and you are not logged in.\n\nFix this error by logging in:\n\t"npx val login"\n`
|
|
9723
|
+
};
|
|
9724
|
+
}
|
|
9725
|
+
const patFileContent = ctx.fs.readFile(patFile);
|
|
9726
|
+
if (patFileContent === undefined) {
|
|
9727
|
+
return {
|
|
9728
|
+
success: false,
|
|
9729
|
+
errorMessage: `Could not read personal access token file at ${patFile}`
|
|
9730
|
+
};
|
|
9731
|
+
}
|
|
9732
|
+
const parsedPatFile = parsePersonalAccessTokenFile(patFileContent);
|
|
9733
|
+
if (!parsedPatFile.success) {
|
|
9734
|
+
return {
|
|
9735
|
+
success: false,
|
|
9736
|
+
errorMessage: `Error parsing personal access token file: ${parsedPatFile.error}. You need to login again.`
|
|
9737
|
+
};
|
|
9738
|
+
}
|
|
9739
|
+
const {
|
|
9740
|
+
pat
|
|
9741
|
+
} = parsedPatFile.data;
|
|
9742
|
+
if (ctx.remoteFiles[ctx.sourcePath]) {
|
|
9743
|
+
return {
|
|
9744
|
+
success: true,
|
|
9745
|
+
events: [{
|
|
9746
|
+
type: "remote-already-uploaded",
|
|
9747
|
+
filePath
|
|
9748
|
+
}]
|
|
9749
|
+
};
|
|
9750
|
+
}
|
|
9751
|
+
const projectName = ctx.project;
|
|
9752
|
+
let publicProjectId = ctx.publicProjectId;
|
|
9753
|
+
let remoteFileBuckets = ctx.remoteFileBuckets;
|
|
9754
|
+
let remoteFilesCounter = ctx.remoteFilesCounter;
|
|
9755
|
+
if (!publicProjectId || !remoteFileBuckets) {
|
|
9756
|
+
if (!projectName) {
|
|
9757
|
+
return {
|
|
9758
|
+
success: false,
|
|
9759
|
+
errorMessage: "Project name not found. Add project name to val.config or set the VAL_PROJECT environment variable"
|
|
9760
|
+
};
|
|
9761
|
+
}
|
|
9762
|
+
const settingsRes = await ctx.remote.getSettings(projectName, {
|
|
9763
|
+
pat
|
|
9764
|
+
});
|
|
9765
|
+
if (!settingsRes.success) {
|
|
9766
|
+
return {
|
|
9767
|
+
success: false,
|
|
9768
|
+
errorMessage: `Could not get public project id: ${settingsRes.message}.`
|
|
9769
|
+
};
|
|
9770
|
+
}
|
|
9771
|
+
publicProjectId = settingsRes.data.publicProjectId;
|
|
9772
|
+
remoteFileBuckets = settingsRes.data.remoteFileBuckets.map(b => b.bucket);
|
|
9773
|
+
}
|
|
9774
|
+
if (!publicProjectId) {
|
|
9775
|
+
return {
|
|
9776
|
+
success: false,
|
|
9777
|
+
errorMessage: "Could not get public project id"
|
|
9778
|
+
};
|
|
9779
|
+
}
|
|
9780
|
+
if (!projectName) {
|
|
9781
|
+
return {
|
|
9782
|
+
success: false,
|
|
9783
|
+
errorMessage: `Could not get project. Check that your val.config has the 'project' field set, or set it using the VAL_PROJECT environment variable`
|
|
9784
|
+
};
|
|
9785
|
+
}
|
|
9786
|
+
remoteFilesCounter += 1;
|
|
9787
|
+
const bucket = remoteFileBuckets[remoteFilesCounter % remoteFileBuckets.length];
|
|
9788
|
+
if (!bucket) {
|
|
9789
|
+
return {
|
|
9790
|
+
success: false,
|
|
9791
|
+
errorMessage: `Internal error: could not allocate a bucket for the remote file located at ${ctx.sourcePath}`
|
|
9792
|
+
};
|
|
9793
|
+
}
|
|
9794
|
+
const fileBuffer = ctx.fs.readBuffer(filePath);
|
|
9795
|
+
if (fileBuffer === undefined) {
|
|
9796
|
+
return {
|
|
9797
|
+
success: false,
|
|
9798
|
+
errorMessage: `Error reading file: ${filePath}`
|
|
9799
|
+
};
|
|
9800
|
+
}
|
|
9801
|
+
const relativeFilePath = path__namespace["default"].relative(ctx.projectRoot, filePath).split(path__namespace["default"].sep).join("/");
|
|
9802
|
+
if (!relativeFilePath.startsWith("public/")) {
|
|
9803
|
+
return {
|
|
9804
|
+
success: false,
|
|
9805
|
+
errorMessage: `File path must be within the public/ directory (e.g. public/path/to/file.txt). Got: ${relativeFilePath}`
|
|
9806
|
+
};
|
|
9807
|
+
}
|
|
9808
|
+
const fileHash = core.Internal.remote.getFileHash(fileBuffer);
|
|
9809
|
+
const coreVersion = core.Internal.VERSION.core || "unknown";
|
|
9810
|
+
const fileExt = getFileExt(filePath);
|
|
9811
|
+
const ref = core.Internal.remote.createRemoteRef(ctx.remote.remoteHost, {
|
|
9812
|
+
publicProjectId,
|
|
9813
|
+
coreVersion,
|
|
9814
|
+
bucket,
|
|
9815
|
+
validationHash: core.Internal.remote.getValidationHash(coreVersion, schema, fileExt, metadata, fileHash, textEncoder),
|
|
9816
|
+
fileHash,
|
|
9817
|
+
filePath: relativeFilePath
|
|
9818
|
+
});
|
|
9819
|
+
const remoteFileUpload = await ctx.remote.uploadFile(projectName, bucket, fileHash, fileExt, fileBuffer, {
|
|
9820
|
+
pat
|
|
9821
|
+
});
|
|
9822
|
+
if (!remoteFileUpload.success) {
|
|
9823
|
+
return {
|
|
9824
|
+
success: false,
|
|
9825
|
+
errorMessage: `Could not upload remote file: '${ref}'. Error: ${remoteFileUpload.error}`
|
|
9826
|
+
};
|
|
9827
|
+
}
|
|
9828
|
+
ctx.remoteFiles[ctx.sourcePath] = {
|
|
9829
|
+
ref,
|
|
9830
|
+
metadata
|
|
9831
|
+
};
|
|
9832
|
+
return {
|
|
9833
|
+
success: true,
|
|
9834
|
+
shouldApplyPatch: true,
|
|
9835
|
+
publicProjectId,
|
|
9836
|
+
remoteFileBuckets,
|
|
9837
|
+
remoteFilesCounter,
|
|
9838
|
+
events: [{
|
|
9839
|
+
type: "remote-uploading",
|
|
9840
|
+
ref
|
|
9841
|
+
}, {
|
|
9842
|
+
type: "remote-uploaded",
|
|
9843
|
+
ref
|
|
9844
|
+
}]
|
|
9845
|
+
};
|
|
9846
|
+
}
|
|
9847
|
+
async function handleRemoteFileUpload(ctx) {
|
|
9848
|
+
var _resolvedRemoteFileAt;
|
|
9849
|
+
if (!ctx.fix) {
|
|
9850
|
+
return {
|
|
9851
|
+
success: false,
|
|
9852
|
+
// No sourcePath in the message: the reported location already points at it.
|
|
9853
|
+
errorMessage: `Remote file needs to be uploaded (use --fix to upload)`
|
|
9854
|
+
};
|
|
9855
|
+
}
|
|
9856
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(ctx.sourcePath);
|
|
9857
|
+
if (!ctx.valModule.source || !ctx.valModule.schema) {
|
|
9858
|
+
return {
|
|
9859
|
+
success: false,
|
|
9860
|
+
errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`
|
|
9861
|
+
};
|
|
9862
|
+
}
|
|
9863
|
+
const resolvedRemoteFileAtSourcePath = core.Internal.resolvePath(modulePath, ctx.valModule.source, ctx.valModule.schema);
|
|
9864
|
+
|
|
9865
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9866
|
+
const fileRefProp = (_resolvedRemoteFileAt = resolvedRemoteFileAtSourcePath.source) === null || _resolvedRemoteFileAt === void 0 ? void 0 : _resolvedRemoteFileAt.path;
|
|
9867
|
+
if (!fileRefProp) {
|
|
9868
|
+
return {
|
|
9869
|
+
success: false,
|
|
9870
|
+
errorMessage: `Expected file to be defined at: ${ctx.sourcePath} but no file was found`
|
|
9871
|
+
};
|
|
9872
|
+
}
|
|
9873
|
+
const resolveRemoteFileSchema = resolvedRemoteFileAtSourcePath.schema;
|
|
9874
|
+
if (!resolveRemoteFileSchema) {
|
|
9875
|
+
return {
|
|
9876
|
+
success: false,
|
|
9877
|
+
errorMessage: `Cannot upload remote file: schema not found for ${ctx.sourcePath}`
|
|
9878
|
+
};
|
|
9879
|
+
}
|
|
9880
|
+
if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
|
|
9881
|
+
return {
|
|
9882
|
+
success: false,
|
|
9883
|
+
errorMessage: `The schema is the remote is neither image nor file: ${ctx.sourcePath}`
|
|
9884
|
+
};
|
|
9885
|
+
}
|
|
9886
|
+
|
|
9887
|
+
// The metadata is everything the media source carries besides its path: the
|
|
9888
|
+
// schema above already established that this IS media.
|
|
9889
|
+
const actualRemoteFileSource = resolvedRemoteFileAtSourcePath.source;
|
|
9890
|
+
const fileSourceMetadata = actualRemoteFileSource && typeof actualRemoteFileSource === "object" && "path" in actualRemoteFileSource ? (() => {
|
|
9891
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
9892
|
+
const {
|
|
9893
|
+
path: _path,
|
|
9894
|
+
...metadata
|
|
9895
|
+
} = actualRemoteFileSource;
|
|
9896
|
+
return metadata;
|
|
9897
|
+
})() : undefined;
|
|
9898
|
+
return uploadRemoteFileCore(ctx, fileRefProp, fileSourceMetadata, resolveRemoteFileSchema);
|
|
9899
|
+
}
|
|
9900
|
+
|
|
9901
|
+
// Gallery (s.images({ remote: true }) / s.files({ remote: true })) upload.
|
|
9902
|
+
// Unlike a single image/file field, a gallery entry is keyed by its local file
|
|
9903
|
+
// path and the value is bare metadata (no FileSource), so we derive the file
|
|
9904
|
+
// ref from the key and synthesize the image/file schema from the record's
|
|
9905
|
+
// media options.
|
|
9906
|
+
async function handleRemoteGalleryFileUpload(ctx) {
|
|
9907
|
+
var _ctx$validationError$;
|
|
9908
|
+
if (!ctx.fix) {
|
|
9909
|
+
return {
|
|
9910
|
+
success: false,
|
|
9911
|
+
// No sourcePath in the message: the reported location already points at it.
|
|
9912
|
+
errorMessage: `Remote file needs to be uploaded (use --fix to upload)`
|
|
9913
|
+
};
|
|
9914
|
+
}
|
|
9915
|
+
const fix = (_ctx$validationError$ = ctx.validationError.fixes) === null || _ctx$validationError$ === void 0 ? void 0 : _ctx$validationError$[0];
|
|
9916
|
+
const mediaType = fix === "files:upload-remote" ? "file" : "image";
|
|
9917
|
+
|
|
9918
|
+
// The gallery entry is keyed by its local file path; validateMediaKey carries
|
|
9919
|
+
// that key as the error value.
|
|
9920
|
+
const fileRef = ctx.validationError.value;
|
|
9921
|
+
if (typeof fileRef !== "string") {
|
|
9922
|
+
return {
|
|
9923
|
+
success: false,
|
|
9924
|
+
errorMessage: `Expected a local file path for gallery entry at ${ctx.sourcePath}`
|
|
9925
|
+
};
|
|
9926
|
+
}
|
|
9927
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(ctx.sourcePath);
|
|
9928
|
+
if (!ctx.valModule.source || !ctx.valModule.schema) {
|
|
9929
|
+
return {
|
|
9930
|
+
success: false,
|
|
9931
|
+
errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`
|
|
9932
|
+
};
|
|
9933
|
+
}
|
|
9934
|
+
const resolved = core.Internal.resolvePath(modulePath, ctx.valModule.source, ctx.valModule.schema);
|
|
9935
|
+
const entrySource = resolved.source;
|
|
9936
|
+
const metadata = entrySource && typeof entrySource === "object" && !Array.isArray(entrySource) ? entrySource : undefined;
|
|
9937
|
+
|
|
9938
|
+
// The gallery item schema is an ObjectSchema, not an image/file schema, so
|
|
9939
|
+
// synthesize the serialized image/file schema (matching how single fields
|
|
9940
|
+
// serialize) for the remote ref's validation hash.
|
|
9941
|
+
//
|
|
9942
|
+
// `accept`/`directory` come from the RECORD that holds the entry, resolved
|
|
9943
|
+
// from the entry's PARENT path - not from the module root, which is only the
|
|
9944
|
+
// record when the gallery is the whole module. A nested gallery
|
|
9945
|
+
// (s.object({ gallery: s.images(...) })) would otherwise synthesize a schema
|
|
9946
|
+
// with no options and bake a validation hash into the remote ref that can
|
|
9947
|
+
// never validate, so a mismatch fails fast instead of uploading.
|
|
9948
|
+
const [, parentModulePath] = core.Internal.splitModuleFilePathAndModulePath(core.Internal.parentOfSourcePath(ctx.sourcePath));
|
|
9949
|
+
const recordSchema = core.Internal.resolvePath(parentModulePath, ctx.valModule.source, ctx.valModule.schema).schema;
|
|
9950
|
+
if ((recordSchema === null || recordSchema === void 0 ? void 0 : recordSchema.type) !== "record") {
|
|
9951
|
+
return {
|
|
9952
|
+
success: false,
|
|
9953
|
+
errorMessage: `Expected a gallery record at the parent of ${ctx.sourcePath}, got ${(recordSchema === null || recordSchema === void 0 ? void 0 : recordSchema.type) ?? "nothing"}`
|
|
9954
|
+
};
|
|
9955
|
+
}
|
|
9956
|
+
const {
|
|
9957
|
+
accept,
|
|
9958
|
+
directory
|
|
9959
|
+
} = recordSchema;
|
|
9960
|
+
const schema = mediaType === "image" ? {
|
|
9961
|
+
type: "image",
|
|
9962
|
+
opt: false,
|
|
9963
|
+
options: {
|
|
9964
|
+
...(accept ? {
|
|
9965
|
+
accept
|
|
9966
|
+
} : {}),
|
|
9967
|
+
...(directory ? {
|
|
9968
|
+
directory
|
|
9969
|
+
} : {})
|
|
9970
|
+
}
|
|
9971
|
+
} : {
|
|
9972
|
+
type: "file",
|
|
9973
|
+
opt: false,
|
|
9974
|
+
options: {
|
|
9975
|
+
...(accept ? {
|
|
9976
|
+
accept
|
|
9977
|
+
} : {})
|
|
9978
|
+
}
|
|
9979
|
+
};
|
|
9980
|
+
return uploadRemoteFileCore(ctx, fileRef, metadata, schema);
|
|
9981
|
+
}
|
|
9982
|
+
async function handleRemoteFileDownload(ctx) {
|
|
9983
|
+
if (ctx.fix) {
|
|
9984
|
+
return {
|
|
9985
|
+
success: true,
|
|
9986
|
+
shouldApplyPatch: true,
|
|
9987
|
+
events: [{
|
|
9988
|
+
type: "remote-downloading",
|
|
9989
|
+
sourcePath: ctx.sourcePath
|
|
9990
|
+
}]
|
|
9991
|
+
};
|
|
9992
|
+
} else {
|
|
9993
|
+
return {
|
|
9994
|
+
success: false,
|
|
9995
|
+
errorMessage: `Remote file ${ctx.sourcePath} needs to be downloaded (use --fix to download)`
|
|
9996
|
+
};
|
|
9997
|
+
}
|
|
9998
|
+
}
|
|
9999
|
+
async function handleRemoteFileCheck() {
|
|
10000
|
+
// Skip - no action needed
|
|
10001
|
+
return {
|
|
10002
|
+
success: true,
|
|
10003
|
+
shouldApplyPatch: true
|
|
10004
|
+
};
|
|
10005
|
+
}
|
|
10006
|
+
async function handleUniqueFolderCheck(ctx) {
|
|
10007
|
+
const value = ctx.validationError.value;
|
|
10008
|
+
if (!value || typeof value.directory !== "string") {
|
|
10009
|
+
return {
|
|
10010
|
+
success: false,
|
|
10011
|
+
errorMessage: `Unexpected value in unique folder check for ${ctx.sourcePath}`
|
|
10012
|
+
};
|
|
10013
|
+
}
|
|
10014
|
+
const {
|
|
10015
|
+
directory
|
|
10016
|
+
} = value;
|
|
10017
|
+
const conflicts = [];
|
|
10018
|
+
for (const file of ctx.valFiles) {
|
|
10019
|
+
const otherModuleFilePath = `/${file}`;
|
|
10020
|
+
if (otherModuleFilePath === ctx.moduleFilePath) continue;
|
|
10021
|
+
const otherModule = await ctx.service.get(otherModuleFilePath, "", {
|
|
10022
|
+
validate: false
|
|
10023
|
+
});
|
|
10024
|
+
const schema = otherModule.schema;
|
|
10025
|
+
if ((schema === null || schema === void 0 ? void 0 : schema.type) === "record" && schema.directory === directory && schema.mediaType) {
|
|
10026
|
+
conflicts.push(otherModuleFilePath);
|
|
10027
|
+
}
|
|
10028
|
+
}
|
|
10029
|
+
if (conflicts.length > 0) {
|
|
10030
|
+
return {
|
|
10031
|
+
success: false,
|
|
10032
|
+
errorMessage: `Gallery directory '${directory}' in ${ctx.moduleFilePath} is also used by: ${conflicts.join(", ")}. Each gallery must use a unique directory.`
|
|
10033
|
+
};
|
|
10034
|
+
}
|
|
10035
|
+
return {
|
|
10036
|
+
success: true
|
|
10037
|
+
};
|
|
10038
|
+
}
|
|
10039
|
+
|
|
10040
|
+
// Maps a gallery key to its on-disk local path. Remote galleries key uploaded
|
|
10041
|
+
// entries by a remote URL while keeping the file on disk; everything else is
|
|
10042
|
+
// already a local path and is returned unchanged.
|
|
10043
|
+
function remoteKeyToLocalPath(key) {
|
|
10044
|
+
const remoteRefRes = core.Internal.remote.splitRemoteRef(key);
|
|
10045
|
+
if (remoteRefRes.status === "success") {
|
|
10046
|
+
return `/${remoteRefRes.filePath}`;
|
|
10047
|
+
}
|
|
10048
|
+
return key;
|
|
10049
|
+
}
|
|
10050
|
+
async function handleCheckAllFiles(ctx) {
|
|
10051
|
+
const value = ctx.validationError.value;
|
|
10052
|
+
if (!value || typeof value.directory !== "string") {
|
|
10053
|
+
return {
|
|
10054
|
+
success: false,
|
|
10055
|
+
errorMessage: `Unexpected value in check-all-files for ${ctx.sourcePath}`
|
|
10056
|
+
};
|
|
10057
|
+
}
|
|
10058
|
+
const {
|
|
10059
|
+
directory
|
|
10060
|
+
} = value;
|
|
10061
|
+
const source = ctx.valModule.source;
|
|
10062
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
10063
|
+
return {
|
|
10064
|
+
success: false,
|
|
10065
|
+
errorMessage: `Could not get source for ${ctx.sourcePath}`
|
|
10066
|
+
};
|
|
10067
|
+
}
|
|
10068
|
+
// Gallery entries are keyed by their file path. Remote galleries key uploaded
|
|
10069
|
+
// entries by a remote URL, but the file is kept on disk at its local path, so
|
|
10070
|
+
// normalize remote-URL keys back to that local path for the on-disk checks.
|
|
10071
|
+
const trackedFiles = new Set(Object.keys(source).map(remoteKeyToLocalPath));
|
|
10072
|
+
|
|
10073
|
+
// Check that all tracked files exist on disk
|
|
10074
|
+
const missingTrackedFiles = Array.from(trackedFiles).filter(f => {
|
|
10075
|
+
return !ctx.fs.fileExists(path__namespace["default"].join(ctx.projectRoot, f));
|
|
10076
|
+
});
|
|
10077
|
+
if (missingTrackedFiles.length > 0) {
|
|
10078
|
+
if (!ctx.fix) {
|
|
10079
|
+
return {
|
|
10080
|
+
success: false,
|
|
10081
|
+
errorMessage: `Gallery in ${ctx.moduleFilePath} has tracked files that do not exist on disk: ${missingTrackedFiles.join(", ")}. Add the files or remove them from the gallery.`
|
|
10082
|
+
};
|
|
10083
|
+
}
|
|
10084
|
+
// fix: true — let createFixPatch remove the missing entries
|
|
10085
|
+
return {
|
|
10086
|
+
success: true,
|
|
10087
|
+
shouldApplyPatch: true
|
|
10088
|
+
};
|
|
10089
|
+
}
|
|
10090
|
+
const dirPath = path__namespace["default"].join(ctx.projectRoot, directory);
|
|
10091
|
+
const filesInDir = [];
|
|
10092
|
+
try {
|
|
10093
|
+
const entries = ctx.fs.readDirectory(dirPath, undefined, undefined, ["**/*"]);
|
|
10094
|
+
for (const entry of entries) {
|
|
10095
|
+
const relPath = "/" + path__namespace["default"].relative(ctx.projectRoot, entry).split(path__namespace["default"].sep).join("/");
|
|
10096
|
+
filesInDir.push(relPath);
|
|
10097
|
+
}
|
|
10098
|
+
} catch {
|
|
10099
|
+
// directory doesn't exist — no untracked files possible
|
|
10100
|
+
}
|
|
10101
|
+
const untrackedFiles = filesInDir.filter(f => !trackedFiles.has(f));
|
|
10102
|
+
if (untrackedFiles.length > 0) {
|
|
10103
|
+
return {
|
|
10104
|
+
success: false,
|
|
10105
|
+
errorMessage: `Gallery in ${ctx.moduleFilePath} has files not tracked: ${untrackedFiles.join(", ")}. Add these files to the gallery or remove them from the directory.`
|
|
10106
|
+
};
|
|
10107
|
+
}
|
|
10108
|
+
|
|
10109
|
+
// All files accounted for — trigger metadata verification via createFixPatch
|
|
10110
|
+
return {
|
|
10111
|
+
success: true,
|
|
10112
|
+
shouldApplyPatch: true
|
|
10113
|
+
};
|
|
10114
|
+
}
|
|
10115
|
+
async function handleJsonValuesExtractEntry(ctx) {
|
|
10116
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(ctx.sourcePath);
|
|
10117
|
+
const parts = core.Internal.splitModulePath(modulePath);
|
|
10118
|
+
// Root-only by contract (see findNestedJsonValuesRecords): the entry is a
|
|
10119
|
+
// direct child of the module's root record/router, so the path is one segment.
|
|
10120
|
+
if (parts.length !== 1) {
|
|
10121
|
+
return {
|
|
10122
|
+
success: false,
|
|
10123
|
+
errorMessage: `Cannot extract .jsonValues() entry at ${ctx.sourcePath}: expected a root record entry`
|
|
10124
|
+
};
|
|
10125
|
+
}
|
|
10126
|
+
const entryKey = parts[0];
|
|
10127
|
+
const source = ctx.valModule.source;
|
|
10128
|
+
if (source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
10129
|
+
return {
|
|
10130
|
+
success: false,
|
|
10131
|
+
errorMessage: `Could not get source for ${ctx.moduleFilePath}`
|
|
10132
|
+
};
|
|
10133
|
+
}
|
|
10134
|
+
const content = source[entryKey];
|
|
10135
|
+
if (content === undefined) {
|
|
10136
|
+
return {
|
|
10137
|
+
success: false,
|
|
10138
|
+
errorMessage: `Could not find .jsonValues() entry '${entryKey}' in ${ctx.moduleFilePath}`
|
|
10139
|
+
};
|
|
10140
|
+
}
|
|
10141
|
+
if (!ctx.fix) {
|
|
10142
|
+
return {
|
|
10143
|
+
success: true,
|
|
10144
|
+
fixableErrorMessage: ctx.validationError.message
|
|
10145
|
+
};
|
|
10146
|
+
}
|
|
10147
|
+
try {
|
|
10148
|
+
extractJsonValuesEntry(ctx.moduleFilePath, ctx.projectRoot, entryKey, content, ctx.service.sourceFileHandler);
|
|
10149
|
+
} catch (err) {
|
|
10150
|
+
return {
|
|
10151
|
+
success: false,
|
|
10152
|
+
errorMessage: err instanceof Error ? err.message : String(err)
|
|
10153
|
+
};
|
|
10154
|
+
}
|
|
10155
|
+
return {
|
|
10156
|
+
success: true,
|
|
10157
|
+
appliedFix: true
|
|
10158
|
+
};
|
|
10159
|
+
}
|
|
10160
|
+
|
|
10161
|
+
// Fix handler registry. `keyof:check-keys` and `router:check-route` are
|
|
10162
|
+
// resolved upfront by the shared resolveSchemaSourceFixes — they never reach
|
|
10163
|
+
// this registry, so they're excluded from the key set.
|
|
10164
|
+
const currentFixHandlers = {
|
|
10165
|
+
"image:check-metadata": handleFileMetadata,
|
|
10166
|
+
"image:add-metadata": handleFileMetadata,
|
|
10167
|
+
"file:check-metadata": handleFileMetadata,
|
|
10168
|
+
"file:add-metadata": handleFileMetadata,
|
|
10169
|
+
"image:upload-remote": handleRemoteFileUpload,
|
|
10170
|
+
"file:upload-remote": handleRemoteFileUpload,
|
|
10171
|
+
"images:upload-remote": handleRemoteGalleryFileUpload,
|
|
10172
|
+
"files:upload-remote": handleRemoteGalleryFileUpload,
|
|
10173
|
+
"image:download-remote": handleRemoteFileDownload,
|
|
10174
|
+
"file:download-remote": handleRemoteFileDownload,
|
|
10175
|
+
"image:check-remote": handleRemoteFileCheck,
|
|
10176
|
+
"images:check-remote": handleRemoteFileCheck,
|
|
10177
|
+
"file:check-remote": handleRemoteFileCheck,
|
|
10178
|
+
"files:check-remote": handleRemoteFileCheck,
|
|
10179
|
+
"images:check-unique-folder": handleUniqueFolderCheck,
|
|
10180
|
+
"files:check-unique-folder": handleUniqueFolderCheck,
|
|
10181
|
+
"images:check-all-files": handleCheckAllFiles,
|
|
10182
|
+
"files:check-all-files": handleCheckAllFiles,
|
|
10183
|
+
"jsonValues:extract-entry": handleJsonValuesExtractEntry
|
|
10184
|
+
};
|
|
10185
|
+
const deprecatedFixHandlers = {
|
|
10186
|
+
"image:replace-metadata": handleFileMetadata
|
|
10187
|
+
};
|
|
10188
|
+
const fixHandlers = {
|
|
10189
|
+
...deprecatedFixHandlers,
|
|
10190
|
+
...currentFixHandlers
|
|
10191
|
+
};
|
|
10192
|
+
function createDefaultValFSHost() {
|
|
10193
|
+
return {
|
|
10194
|
+
...ts__default["default"].sys,
|
|
10195
|
+
writeFile: (fileName, data, encoding) => {
|
|
10196
|
+
fs__default["default"].mkdirSync(path__namespace["default"].dirname(fileName), {
|
|
10197
|
+
recursive: true
|
|
10198
|
+
});
|
|
10199
|
+
fs__default["default"].writeFileSync(fileName, typeof data === "string" ? data : new Uint8Array(data), encoding);
|
|
10200
|
+
},
|
|
10201
|
+
rmFile: fs__default["default"].rmSync,
|
|
10202
|
+
readBuffer: fileName => {
|
|
10203
|
+
try {
|
|
10204
|
+
return fs__default["default"].readFileSync(fileName);
|
|
10205
|
+
} catch {
|
|
10206
|
+
return undefined;
|
|
10207
|
+
}
|
|
10208
|
+
}
|
|
10209
|
+
};
|
|
10210
|
+
}
|
|
10211
|
+
|
|
10212
|
+
function validateMetadata(actualMetadata, expectedMetadata) {
|
|
10213
|
+
const missingMetadata = [];
|
|
10214
|
+
const erroneousMetadata = {};
|
|
10215
|
+
if (typeof actualMetadata !== "object" || actualMetadata === null) {
|
|
10216
|
+
return {
|
|
10217
|
+
globalErrors: ["Metadata is wrong type: must be an object."]
|
|
10218
|
+
};
|
|
10219
|
+
}
|
|
10220
|
+
if (Array.isArray(actualMetadata)) {
|
|
10221
|
+
return {
|
|
10222
|
+
globalErrors: ["Metadata is wrong type: cannot be an array."]
|
|
10223
|
+
};
|
|
10224
|
+
}
|
|
10225
|
+
const recordMetadata = actualMetadata;
|
|
10226
|
+
const globalErrors = [];
|
|
10227
|
+
for (const anyKey in expectedMetadata) {
|
|
10228
|
+
if (typeof anyKey !== "string") {
|
|
9942
10229
|
globalErrors.push(`Expected metadata has key '${anyKey}' that is not typeof 'string', but: '${typeof anyKey}'. This is most likely a Val bug.`);
|
|
9943
10230
|
} else {
|
|
9944
10231
|
if (anyKey in actualMetadata) {
|
|
@@ -10214,9 +10501,9 @@ function getModulePathRange(modulePath, modulePathMap,
|
|
|
10214
10501
|
// Which part of an object/record member to point at. For an object property
|
|
10215
10502
|
// the resolved node's own range is the *key* (property name); the *value*
|
|
10216
10503
|
// range is stored under `children.val`. Array elements, leaf literals and
|
|
10217
|
-
//
|
|
10218
|
-
//
|
|
10219
|
-
//
|
|
10504
|
+
// Array elements and leaf literals have no `val` child, so "value" falls back
|
|
10505
|
+
// to the node's own range for those. Defaults to "key" to preserve existing
|
|
10506
|
+
// callers.
|
|
10220
10507
|
target = "key") {
|
|
10221
10508
|
var _range$children;
|
|
10222
10509
|
// Handle invalid module paths gracefully
|
|
@@ -10269,7 +10556,7 @@ target = "key") {
|
|
|
10269
10556
|
*
|
|
10270
10557
|
* NOTE: do not compute the start as `end.character - node.getWidth()`. That
|
|
10271
10558
|
* identity only holds while the node stays on a single line - for a multi-line
|
|
10272
|
-
* node (an object inside an array, a
|
|
10559
|
+
* node (an object inside an array, a media object's `path`, ...) it
|
|
10273
10560
|
* reports the *closing* line and a negative character.
|
|
10274
10561
|
*/
|
|
10275
10562
|
function rangeOf(node, sourceFile) {
|
|
@@ -10333,39 +10620,6 @@ function traverse(node, sourceFile) {
|
|
|
10333
10620
|
if (ts__default["default"].isArrayLiteralExpression(node)) {
|
|
10334
10621
|
return traverseArrayLiteral(node, sourceFile);
|
|
10335
10622
|
}
|
|
10336
|
-
if (ts__default["default"].isCallExpression(node)) {
|
|
10337
|
-
return traverseCallExpression(node, sourceFile);
|
|
10338
|
-
}
|
|
10339
|
-
}
|
|
10340
|
-
function traverseCallExpression(node, sourceFile) {
|
|
10341
|
-
if (ts__default["default"].isPropertyAccessExpression(node.expression)) {
|
|
10342
|
-
if (node.expression.expression.getText(sourceFile) === "c" && (node.expression.name.getText(sourceFile) === "file" || node.expression.name.getText(sourceFile) === "image")) {
|
|
10343
|
-
const val = {
|
|
10344
|
-
children: {},
|
|
10345
|
-
...rangeOf(node, sourceFile)
|
|
10346
|
-
};
|
|
10347
|
-
if (node.arguments[0]) {
|
|
10348
|
-
const _ref = {
|
|
10349
|
-
children: {},
|
|
10350
|
-
...rangeOf(node.arguments[0], sourceFile)
|
|
10351
|
-
};
|
|
10352
|
-
if (!node.arguments[1]) {
|
|
10353
|
-
return {
|
|
10354
|
-
val,
|
|
10355
|
-
_ref
|
|
10356
|
-
};
|
|
10357
|
-
}
|
|
10358
|
-
return {
|
|
10359
|
-
val,
|
|
10360
|
-
_ref,
|
|
10361
|
-
metadata: {
|
|
10362
|
-
children: {},
|
|
10363
|
-
...rangeOf(node.arguments[1], sourceFile)
|
|
10364
|
-
}
|
|
10365
|
-
};
|
|
10366
|
-
}
|
|
10367
|
-
}
|
|
10368
|
-
}
|
|
10369
10623
|
}
|
|
10370
10624
|
function traverseArrayLiteral(node, sourceFile) {
|
|
10371
10625
|
return node.elements.reduce((acc, element, index) => {
|
|
@@ -10439,61 +10693,6 @@ function findJsonEntryFilePath(moduleFilePath, valTsSourceFile, entryKey) {
|
|
|
10439
10693
|
return resolveExistingJsonPath(moduleFilePath, entry.importPath);
|
|
10440
10694
|
}
|
|
10441
10695
|
|
|
10442
|
-
/**
|
|
10443
|
-
* Moves ONE `.jsonValues()` entry that was written inline in the `.val.ts` into
|
|
10444
|
-
* its own `*.val.json`, replacing the inline value with
|
|
10445
|
-
* `c.json(() => import("./<key>.val.json"))`.
|
|
10446
|
-
*
|
|
10447
|
-
* This is the fix for the `jsonValues:extract-entry` validation error. It is not
|
|
10448
|
-
* expressible as a patch (a patch edits one `.val.ts` and cannot create the
|
|
10449
|
-
* backing JSON file), so it writes both files directly — JSON first, so a
|
|
10450
|
-
* failure part-way through never leaves the module pointing at a file that does
|
|
10451
|
-
* not exist.
|
|
10452
|
-
*
|
|
10453
|
-
* Root-only, like the rest of the `.jsonValues()` machinery: the entry is looked
|
|
10454
|
-
* up in the module's root record/router object literal.
|
|
10455
|
-
*/
|
|
10456
|
-
function extractJsonValuesEntry(moduleFilePath, rootDir, entryKey, content, sourceFileHandler) {
|
|
10457
|
-
const valTsPath = sourceFileHandler.resolveSourceModulePath(getSyntheticContainingPath(rootDir), `.${moduleFilePath.replace(".val.ts", ".val").replace(".val.js", ".val").replace(".val.jsx", ".val").replace(".val.tsx", ".val")}`);
|
|
10458
|
-
const sourceFile = sourceFileHandler.getSourceFile(valTsPath);
|
|
10459
|
-
if (!sourceFile) {
|
|
10460
|
-
throw Error(`Source file ${valTsPath} not found`);
|
|
10461
|
-
}
|
|
10462
|
-
const pathsRes = getNewJsonEntryPaths(moduleFilePath, entryKey);
|
|
10463
|
-
if (fp.result.isErr(pathsRes)) {
|
|
10464
|
-
throw Error(formatPatchSourceError(pathsRes.error));
|
|
10465
|
-
}
|
|
10466
|
-
const {
|
|
10467
|
-
jsonPath,
|
|
10468
|
-
importPath
|
|
10469
|
-
} = pathsRes.value;
|
|
10470
|
-
const absoluteJsonPath = path__namespace["default"].join(rootDir, jsonPath);
|
|
10471
|
-
if (sourceFileHandler.host.fileExists(absoluteJsonPath)) {
|
|
10472
|
-
throw Error(`Cannot extract .jsonValues() entry '${entryKey}' of ${moduleFilePath}: '${jsonPath}' already exists`);
|
|
10473
|
-
}
|
|
10474
|
-
|
|
10475
|
-
// Remove the inline property, then add the `c.json(...)` reference back. The
|
|
10476
|
-
// entry moves to the end of the record: entry ORDER in a jsonValues record is
|
|
10477
|
-
// not meaningful (the Studio and the runtime key entries by name), and
|
|
10478
|
-
// insert-in-place would mean reimplementing insertValJsonEntry.
|
|
10479
|
-
const removed = removeValJsonEntry(sourceFile, [], entryKey);
|
|
10480
|
-
if (fp.result.isErr(removed)) {
|
|
10481
|
-
throw Error(`${valTsPath}\n${formatOpsError(removed.error, sourceFile)}`);
|
|
10482
|
-
}
|
|
10483
|
-
const inserted = insertValJsonEntry(removed.value, [], entryKey, importPath);
|
|
10484
|
-
if (fp.result.isErr(inserted)) {
|
|
10485
|
-
throw Error(`${valTsPath}\n${formatOpsError(inserted.error, removed.value)}`);
|
|
10486
|
-
}
|
|
10487
|
-
sourceFileHandler.writeFile(absoluteJsonPath, JSON.stringify(content, null, 2) + "\n", "utf8");
|
|
10488
|
-
sourceFileHandler.writeSourceFile(inserted.value);
|
|
10489
|
-
}
|
|
10490
|
-
function formatOpsError(error, sourceFile) {
|
|
10491
|
-
if (error instanceof patch.PatchError) {
|
|
10492
|
-
return error.message;
|
|
10493
|
-
}
|
|
10494
|
-
return flatMapErrors(error, e => formatSyntaxError(e, sourceFile)).join("\n");
|
|
10495
|
-
}
|
|
10496
|
-
|
|
10497
10696
|
/**
|
|
10498
10697
|
* Replays a `val debug` snapshot: applies its patches the way /save does and
|
|
10499
10698
|
* validates the result.
|
|
@@ -10593,6 +10792,10 @@ function readCapturedReport(snapshotDir) {
|
|
|
10593
10792
|
return JSON.parse(fs__default["default"].readFileSync(reportPath, "utf-8"));
|
|
10594
10793
|
}
|
|
10595
10794
|
|
|
10795
|
+
Object.defineProperty(exports, 'hasRemoteFileSchema', {
|
|
10796
|
+
enumerable: true,
|
|
10797
|
+
get: function () { return core.hasRemoteFileSchema; }
|
|
10798
|
+
});
|
|
10596
10799
|
exports.DEFAULT_LOGIN_HOST = DEFAULT_LOGIN_HOST;
|
|
10597
10800
|
exports.DEFAULT_LOGIN_MAX_DURATION = DEFAULT_LOGIN_MAX_DURATION;
|
|
10598
10801
|
exports.DEFAULT_LOGIN_POLL_INTERVAL = DEFAULT_LOGIN_POLL_INTERVAL;
|
|
@@ -10607,12 +10810,14 @@ exports.analyzeValModule = analyzeValModule;
|
|
|
10607
10810
|
exports.awaitValLoginConfirmation = awaitValLoginConfirmation;
|
|
10608
10811
|
exports.checkRemoteRef = checkRemoteRef;
|
|
10609
10812
|
exports.compareWithCapturedReport = compareWithCapturedReport;
|
|
10813
|
+
exports.createDefaultValFSHost = createDefaultValFSHost;
|
|
10610
10814
|
exports.createFixPatch = createFixPatch;
|
|
10611
10815
|
exports.createJsonEntryPathMap = createJsonEntryPathMap;
|
|
10612
10816
|
exports.createModulePathMap = createModulePathMap;
|
|
10613
10817
|
exports.createService = createService;
|
|
10614
10818
|
exports.createValApiRouter = createValApiRouter;
|
|
10615
10819
|
exports.createValServer = createValServer;
|
|
10820
|
+
exports.currentFixHandlers = currentFixHandlers;
|
|
10616
10821
|
exports.decodeJwt = decodeJwt;
|
|
10617
10822
|
exports.downloadFileFromRemote = downloadFileFromRemote;
|
|
10618
10823
|
exports.encodeJwt = encodeJwt;
|
|
@@ -10622,6 +10827,7 @@ exports.extractImageMetadata = extractImageMetadata;
|
|
|
10622
10827
|
exports.extractJsonValuesEntry = extractJsonValuesEntry;
|
|
10623
10828
|
exports.findAndEvalValConfigFile = findAndEvalValConfigFile;
|
|
10624
10829
|
exports.findJsonEntryFilePath = findJsonEntryFilePath;
|
|
10830
|
+
exports.fixHandlers = fixHandlers;
|
|
10625
10831
|
exports.formatPatchSourceError = formatPatchSourceError;
|
|
10626
10832
|
exports.formatSyntaxErrorTree = formatSyntaxErrorTree;
|
|
10627
10833
|
exports.getCachedRemoteFileDir = getCachedRemoteFileDir;
|
|
@@ -10633,7 +10839,14 @@ exports.getModulePathRange = getModulePathRange;
|
|
|
10633
10839
|
exports.getPersonalAccessTokenPath = getPersonalAccessTokenPath;
|
|
10634
10840
|
exports.getSettings = getSettings;
|
|
10635
10841
|
exports.getValidationErrorFileRef = getValidationErrorFileRef;
|
|
10636
|
-
exports.
|
|
10842
|
+
exports.handleCheckAllFiles = handleCheckAllFiles;
|
|
10843
|
+
exports.handleFileMetadata = handleFileMetadata;
|
|
10844
|
+
exports.handleJsonValuesExtractEntry = handleJsonValuesExtractEntry;
|
|
10845
|
+
exports.handleRemoteFileCheck = handleRemoteFileCheck;
|
|
10846
|
+
exports.handleRemoteFileDownload = handleRemoteFileDownload;
|
|
10847
|
+
exports.handleRemoteFileUpload = handleRemoteFileUpload;
|
|
10848
|
+
exports.handleRemoteGalleryFileUpload = handleRemoteGalleryFileUpload;
|
|
10849
|
+
exports.handleUniqueFolderCheck = handleUniqueFolderCheck;
|
|
10637
10850
|
exports.loadValModules = loadValModules;
|
|
10638
10851
|
exports.parsePersonalAccessTokenFile = parsePersonalAccessTokenFile;
|
|
10639
10852
|
exports.patchSourceFile = patchSourceFile;
|