@valbuild/shared 0.122.0 → 0.123.2
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/CHANGELOG.md +131 -0
- package/client/dist/valbuild-shared-client.cjs.d.ts +2 -0
- package/client/dist/valbuild-shared-client.cjs.dev.js +22 -0
- package/client/dist/valbuild-shared-client.cjs.js +7 -0
- package/client/dist/valbuild-shared-client.cjs.prod.js +22 -0
- package/client/dist/valbuild-shared-client.esm.js +2 -0
- package/client/package.json +4 -0
- package/dist/declarations/src/client/index.d.ts +21 -0
- package/dist/declarations/src/internal/index.d.ts +5 -0
- package/dist/declarations/src/internal/media/encodeImageDecisions.d.ts +89 -0
- package/dist/declarations/src/internal/search/getFilenameFromRef.d.ts +17 -0
- package/dist/declarations/src/internal/search/searchIndex.d.ts +76 -0
- package/dist/declarations/src/internal/search/sourcePath.d.ts +30 -0
- package/dist/declarations/src/internal/search/traverseSchemaSource.d.ts +20 -0
- package/dist/getSitemapTree-77b1203a.cjs.prod.js +389 -0
- package/dist/getSitemapTree-a0c9a0e3.cjs.dev.js +389 -0
- package/dist/getSitemapTree-d3e98a2e.esm.js +369 -0
- package/internal/dist/valbuild-shared-internal.cjs.dev.js +811 -445
- package/internal/dist/valbuild-shared-internal.cjs.prod.js +811 -445
- package/internal/dist/valbuild-shared-internal.esm.js +712 -369
- package/package.json +10 -1
|
@@ -1,117 +1,10 @@
|
|
|
1
1
|
import { Internal, CODE_LANGUAGES, DEFAULT_COLOR_FORMAT } from '@valbuild/core';
|
|
2
|
-
import {
|
|
2
|
+
import { _ as _createForOfIteratorHelper, k as _objectSpread2, l as _arrayLikeToArray, m as _unsupportedIterableToArray, n as _defineProperty, o as _typeof } from '../../dist/getSitemapTree-d3e98a2e.esm.js';
|
|
3
|
+
export { b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, h as getNextAppRouterSitemapTree, g as getNextAppRouterSourceFolder, j as getPatternFromModuleFilePath, f as isValCanvasFrame, i as isValCanvasPageMessage, e as isValCanvasStudioMessage, p as parseRoutePattern, w as withValCanvasParam } from '../../dist/getSitemapTree-d3e98a2e.esm.js';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import { fromError } from 'zod-validation-error';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if ("object" != typeof t || !t) return t;
|
|
8
|
-
var e = t[Symbol.toPrimitive];
|
|
9
|
-
if (void 0 !== e) {
|
|
10
|
-
var i = e.call(t, r || "default");
|
|
11
|
-
if ("object" != typeof i) return i;
|
|
12
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
13
|
-
}
|
|
14
|
-
return ("string" === r ? String : Number)(t);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function _toPropertyKey(t) {
|
|
18
|
-
var i = _toPrimitive(t, "string");
|
|
19
|
-
return "symbol" == typeof i ? i : i + "";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function _defineProperty(e, r, t) {
|
|
23
|
-
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
24
|
-
value: t,
|
|
25
|
-
enumerable: !0,
|
|
26
|
-
configurable: !0,
|
|
27
|
-
writable: !0
|
|
28
|
-
}) : e[r] = t, e;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function ownKeys(e, r) {
|
|
32
|
-
var t = Object.keys(e);
|
|
33
|
-
if (Object.getOwnPropertySymbols) {
|
|
34
|
-
var o = Object.getOwnPropertySymbols(e);
|
|
35
|
-
r && (o = o.filter(function (r) {
|
|
36
|
-
return Object.getOwnPropertyDescriptor(e, r).enumerable;
|
|
37
|
-
})), t.push.apply(t, o);
|
|
38
|
-
}
|
|
39
|
-
return t;
|
|
40
|
-
}
|
|
41
|
-
function _objectSpread2(e) {
|
|
42
|
-
for (var r = 1; r < arguments.length; r++) {
|
|
43
|
-
var t = null != arguments[r] ? arguments[r] : {};
|
|
44
|
-
r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
|
|
45
|
-
_defineProperty(e, r, t[r]);
|
|
46
|
-
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
|
|
47
|
-
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return e;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function _arrayLikeToArray(r, a) {
|
|
54
|
-
(null == a || a > r.length) && (a = r.length);
|
|
55
|
-
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
56
|
-
return n;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function _unsupportedIterableToArray(r, a) {
|
|
60
|
-
if (r) {
|
|
61
|
-
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
62
|
-
var t = {}.toString.call(r).slice(8, -1);
|
|
63
|
-
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function _createForOfIteratorHelper(r, e) {
|
|
68
|
-
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
69
|
-
if (!t) {
|
|
70
|
-
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
|
|
71
|
-
t && (r = t);
|
|
72
|
-
var n = 0,
|
|
73
|
-
F = function () {};
|
|
74
|
-
return {
|
|
75
|
-
s: F,
|
|
76
|
-
n: function () {
|
|
77
|
-
return n >= r.length ? {
|
|
78
|
-
done: !0
|
|
79
|
-
} : {
|
|
80
|
-
done: !1,
|
|
81
|
-
value: r[n++]
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
e: function (r) {
|
|
85
|
-
throw r;
|
|
86
|
-
},
|
|
87
|
-
f: F
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
91
|
-
}
|
|
92
|
-
var o,
|
|
93
|
-
a = !0,
|
|
94
|
-
u = !1;
|
|
95
|
-
return {
|
|
96
|
-
s: function () {
|
|
97
|
-
t = t.call(r);
|
|
98
|
-
},
|
|
99
|
-
n: function () {
|
|
100
|
-
var r = t.next();
|
|
101
|
-
return a = r.done, r;
|
|
102
|
-
},
|
|
103
|
-
e: function (r) {
|
|
104
|
-
u = !0, o = r;
|
|
105
|
-
},
|
|
106
|
-
f: function () {
|
|
107
|
-
try {
|
|
108
|
-
a || null == t.return || t.return();
|
|
109
|
-
} finally {
|
|
110
|
-
if (u) throw o;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
}
|
|
6
|
+
import FlexSearch from 'flexsearch';
|
|
7
|
+
import { b as base64DataUrlToUint8Array } from '../../dist/utils-00f3e1df.esm.js';
|
|
115
8
|
|
|
116
9
|
function remirrorToRichTextSource(node, configDirectory, remoteOptions) {
|
|
117
10
|
var files = {};
|
|
@@ -2962,16 +2855,6 @@ var Api = {
|
|
|
2962
2855
|
// `number` is allowed because the client stringifies every query value before it
|
|
2963
2856
|
// goes on the URL (see createValClient), so a numeric param round-trips fine.
|
|
2964
2857
|
|
|
2965
|
-
function _typeof(o) {
|
|
2966
|
-
"@babel/helpers - typeof";
|
|
2967
|
-
|
|
2968
|
-
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
|
|
2969
|
-
return typeof o;
|
|
2970
|
-
} : function (o) {
|
|
2971
|
-
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
2972
|
-
}, _typeof(o);
|
|
2973
|
-
}
|
|
2974
|
-
|
|
2975
2858
|
function getErrorMessageFromUnknownJson(json, fallback) {
|
|
2976
2859
|
if (_typeof(json) === "object" && json !== null && "message" in json && typeof json.message === "string") {
|
|
2977
2860
|
return json.message;
|
|
@@ -3651,10 +3534,6 @@ function newestCommitSha(commits) {
|
|
|
3651
3534
|
return (_newest$commitSha = (_newest = newest) === null || _newest === void 0 ? void 0 : _newest.commitSha) !== null && _newest$commitSha !== void 0 ? _newest$commitSha : null;
|
|
3652
3535
|
}
|
|
3653
3536
|
|
|
3654
|
-
var VAL_CONFIG_SESSION_STORAGE_KEY = "val-config";
|
|
3655
|
-
var VAL_THEME_SESSION_STORAGE_KEY = "val-theme";
|
|
3656
|
-
var VAL_AI_SESSION_STORAGE_KEY = "val-ai-session";
|
|
3657
|
-
|
|
3658
3537
|
var SharedValConfig = z.object({
|
|
3659
3538
|
contentHostUrl: z.string().optional(),
|
|
3660
3539
|
appHostUrl: z.string().optional(),
|
|
@@ -3682,170 +3561,6 @@ var ValDeployment = z.object({
|
|
|
3682
3561
|
createdAt: z.string()
|
|
3683
3562
|
});
|
|
3684
3563
|
|
|
3685
|
-
// Strictly speaking this should be in the next package but it's shared, because we want to use it in the ui package. We want to resolve that somehow
|
|
3686
|
-
function getNextAppRouterSitemapTree(srcFolder, paths) {
|
|
3687
|
-
var root = {
|
|
3688
|
-
type: "node",
|
|
3689
|
-
name: "/",
|
|
3690
|
-
children: []
|
|
3691
|
-
};
|
|
3692
|
-
var _iterator = _createForOfIteratorHelper(paths),
|
|
3693
|
-
_step;
|
|
3694
|
-
try {
|
|
3695
|
-
var _loop = function _loop() {
|
|
3696
|
-
var path = _step.value;
|
|
3697
|
-
var urlPath = path.urlPath,
|
|
3698
|
-
moduleFilePathS = path.moduleFilePath;
|
|
3699
|
-
var moduleFilePath = moduleFilePathS;
|
|
3700
|
-
if (!urlPath.startsWith("/")) {
|
|
3701
|
-
console.error("urlPath must start with /: ".concat(urlPath));
|
|
3702
|
-
return 0; // continue
|
|
3703
|
-
}
|
|
3704
|
-
var pattern = getPatternFromModuleFilePath(moduleFilePath, srcFolder);
|
|
3705
|
-
if (urlPath === "/") {
|
|
3706
|
-
var fullPath = "/";
|
|
3707
|
-
root.pattern = getPatternFromModuleFilePath(moduleFilePath, srcFolder);
|
|
3708
|
-
root.page = {
|
|
3709
|
-
fullPath: fullPath
|
|
3710
|
-
};
|
|
3711
|
-
root.sourcePath = Internal.joinModuleFilePathAndModulePath(moduleFilePath, "\"".concat(fullPath, "\""));
|
|
3712
|
-
root.moduleFilePath = moduleFilePath;
|
|
3713
|
-
return 0; // continue
|
|
3714
|
-
}
|
|
3715
|
-
var pathParts = urlPath.split("/").slice(1).filter(Boolean);
|
|
3716
|
-
var currentNode = root;
|
|
3717
|
-
pathParts.forEach(function (part, index) {
|
|
3718
|
-
var hasChildren = index < pathParts.length - 1;
|
|
3719
|
-
var isLast = index === pathParts.length - 1;
|
|
3720
|
-
var fullPath = "/" + pathParts.slice(0, index + 1).join("/");
|
|
3721
|
-
var sourcePath = Internal.joinModuleFilePathAndModulePath(moduleFilePath, "\"".concat(fullPath, "\""));
|
|
3722
|
-
var node = hasChildren ? _objectSpread2(_objectSpread2({
|
|
3723
|
-
type: "node",
|
|
3724
|
-
name: part,
|
|
3725
|
-
pattern: pattern,
|
|
3726
|
-
moduleFilePath: moduleFilePath
|
|
3727
|
-
}, isLast ? {
|
|
3728
|
-
page: {
|
|
3729
|
-
fullPath: fullPath
|
|
3730
|
-
},
|
|
3731
|
-
sourcePath: sourcePath
|
|
3732
|
-
} : {}), {}, {
|
|
3733
|
-
children: []
|
|
3734
|
-
}) : {
|
|
3735
|
-
type: "leaf",
|
|
3736
|
-
name: part,
|
|
3737
|
-
pattern: pattern,
|
|
3738
|
-
fullPath: fullPath,
|
|
3739
|
-
moduleFilePath: moduleFilePath,
|
|
3740
|
-
sourcePath: sourcePath,
|
|
3741
|
-
children: []
|
|
3742
|
-
};
|
|
3743
|
-
var existingNodeIndex = currentNode.children.findIndex(function (node) {
|
|
3744
|
-
return node.name === part;
|
|
3745
|
-
});
|
|
3746
|
-
if (existingNodeIndex === -1) {
|
|
3747
|
-
currentNode.children.push(node);
|
|
3748
|
-
if (currentNode.type === "node") {
|
|
3749
|
-
currentNode.isLinear = true;
|
|
3750
|
-
if (currentNode.children.length > 1) {
|
|
3751
|
-
delete currentNode.isLinear;
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
currentNode = node;
|
|
3755
|
-
} else {
|
|
3756
|
-
var existingNode = currentNode.children[existingNodeIndex];
|
|
3757
|
-
if (existingNode.type === "leaf" && hasChildren) {
|
|
3758
|
-
var _sourcePath = existingNode.sourcePath;
|
|
3759
|
-
// convert leaf to node
|
|
3760
|
-
currentNode.children[existingNodeIndex] = {
|
|
3761
|
-
type: "node",
|
|
3762
|
-
name: part,
|
|
3763
|
-
pattern: pattern,
|
|
3764
|
-
page: {
|
|
3765
|
-
fullPath: fullPath
|
|
3766
|
-
},
|
|
3767
|
-
sourcePath: _sourcePath,
|
|
3768
|
-
children: existingNode.children,
|
|
3769
|
-
moduleFilePath: moduleFilePath
|
|
3770
|
-
};
|
|
3771
|
-
currentNode = currentNode.children[existingNodeIndex];
|
|
3772
|
-
} else if (existingNode.type === "node" && isLast) {
|
|
3773
|
-
// add page, source path and module file path
|
|
3774
|
-
existingNode.page = {
|
|
3775
|
-
fullPath: fullPath
|
|
3776
|
-
};
|
|
3777
|
-
existingNode.sourcePath = sourcePath;
|
|
3778
|
-
existingNode.moduleFilePath = moduleFilePath;
|
|
3779
|
-
existingNode.pattern = pattern;
|
|
3780
|
-
} else {
|
|
3781
|
-
currentNode = currentNode.children[existingNodeIndex];
|
|
3782
|
-
}
|
|
3783
|
-
}
|
|
3784
|
-
});
|
|
3785
|
-
},
|
|
3786
|
-
_ret;
|
|
3787
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
3788
|
-
_ret = _loop();
|
|
3789
|
-
if (_ret === 0) continue;
|
|
3790
|
-
}
|
|
3791
|
-
} catch (err) {
|
|
3792
|
-
_iterator.e(err);
|
|
3793
|
-
} finally {
|
|
3794
|
-
_iterator.f();
|
|
3795
|
-
}
|
|
3796
|
-
return root;
|
|
3797
|
-
}
|
|
3798
|
-
function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
|
|
3799
|
-
return moduleFilePath.replace(srcFolder, "")
|
|
3800
|
-
// TODO: hacky!
|
|
3801
|
-
// remove route groups
|
|
3802
|
-
.replace(/\/\([^/]+\)/g, "") // not sure we want to do this?
|
|
3803
|
-
.replace("/page.val.ts", "").replace("/page.val.js", "");
|
|
3804
|
-
}
|
|
3805
|
-
|
|
3806
|
-
function parseRoutePattern(pattern) {
|
|
3807
|
-
if (pattern === "" || pattern === "/") {
|
|
3808
|
-
return [];
|
|
3809
|
-
}
|
|
3810
|
-
var p = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
|
3811
|
-
return p.split("/").map(function (part) {
|
|
3812
|
-
var isOptionalParam = part.startsWith("[[") && part.endsWith("]]");
|
|
3813
|
-
var isNonOptionalParam = part.startsWith("[") && part.endsWith("]");
|
|
3814
|
-
var nameStart = isOptionalParam ? 2 : isNonOptionalParam ? 1 : 0;
|
|
3815
|
-
var nameEnd = part.indexOf("]", nameStart);
|
|
3816
|
-
var isArrayParam = part.slice(nameStart, nameStart + 3) === "...";
|
|
3817
|
-
var paramName = part.slice(nameStart + (isArrayParam ? 3 : 0), nameEnd);
|
|
3818
|
-
if (isOptionalParam || isNonOptionalParam) {
|
|
3819
|
-
if (isArrayParam) {
|
|
3820
|
-
return {
|
|
3821
|
-
type: "array-param",
|
|
3822
|
-
paramName: paramName,
|
|
3823
|
-
optional: isOptionalParam
|
|
3824
|
-
};
|
|
3825
|
-
} else {
|
|
3826
|
-
return {
|
|
3827
|
-
type: "string-param",
|
|
3828
|
-
paramName: paramName,
|
|
3829
|
-
optional: isOptionalParam
|
|
3830
|
-
};
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
return {
|
|
3834
|
-
type: "literal",
|
|
3835
|
-
name: part
|
|
3836
|
-
};
|
|
3837
|
-
});
|
|
3838
|
-
}
|
|
3839
|
-
|
|
3840
|
-
function getNextAppRouterSourceFolder(moduleFilePath) {
|
|
3841
|
-
if (moduleFilePath.startsWith("/app")) {
|
|
3842
|
-
return "/app";
|
|
3843
|
-
} else if (moduleFilePath.startsWith("/src/app")) {
|
|
3844
|
-
return "/src/app";
|
|
3845
|
-
}
|
|
3846
|
-
return null;
|
|
3847
|
-
}
|
|
3848
|
-
|
|
3849
3564
|
/**
|
|
3850
3565
|
* Shared route validation utilities
|
|
3851
3566
|
*/
|
|
@@ -4327,84 +4042,6 @@ function resolveSchemaSourceFixes(validationErrors, snapshot) {
|
|
|
4327
4042
|
return result;
|
|
4328
4043
|
}
|
|
4329
4044
|
|
|
4330
|
-
/**
|
|
4331
|
-
* The protocol between the studio's canvas and the page inside it.
|
|
4332
|
-
*
|
|
4333
|
-
* The canvas shows the running site in a frame. That frame is a different
|
|
4334
|
-
* document — the customer's app, rendered by their components — so the studio
|
|
4335
|
-
* cannot reach into it, and it cannot reach out. Everything they say to each
|
|
4336
|
-
* other goes through `postMessage`, and this is the vocabulary.
|
|
4337
|
-
*
|
|
4338
|
-
* It lives in `shared` because both halves have to agree on it: the page half
|
|
4339
|
-
* ships in `@valbuild/next`, the studio half in `@valbuild/ui`, and a message
|
|
4340
|
-
* shape defined twice is a message shape that will differ once.
|
|
4341
|
-
*
|
|
4342
|
-
* ## Why a query parameter turns it on
|
|
4343
|
-
*
|
|
4344
|
-
* The frame has to render the page as Val sees it — draft content, with
|
|
4345
|
-
* `data-val-path` on every element Val tracks — while *not* showing Val's own
|
|
4346
|
-
* overlay, because the studio is already the UI. Nothing about the cookies can
|
|
4347
|
-
* express that: they are per-origin, and the studio and the page share an
|
|
4348
|
-
* origin, so a cookie would change the customer's own tabs too. A parameter on
|
|
4349
|
-
* the frame's URL is scoped to exactly the one document that needs it.
|
|
4350
|
-
*/
|
|
4351
|
-
|
|
4352
|
-
/** Marks a page load as the studio's canvas frame. */
|
|
4353
|
-
var VAL_CANVAS_PARAM = "val_canvas";
|
|
4354
|
-
|
|
4355
|
-
/**
|
|
4356
|
-
* Every message is tagged with this, and the tag is checked on receipt.
|
|
4357
|
-
*
|
|
4358
|
-
* A window receives messages from anything that has a handle on it —
|
|
4359
|
-
* extensions, embeds, other frames — and `event.data` is whatever they chose to
|
|
4360
|
-
* send. Checking a discriminator before reading anything else is what keeps a
|
|
4361
|
-
* stray message from being read as a canvas instruction.
|
|
4362
|
-
*/
|
|
4363
|
-
var VAL_CANVAS_MESSAGE = "val-canvas";
|
|
4364
|
-
|
|
4365
|
-
/** One element on the page that Val has a path for. */
|
|
4366
|
-
|
|
4367
|
-
/** What the page tells the studio. */
|
|
4368
|
-
|
|
4369
|
-
/** What the studio tells the page. */
|
|
4370
|
-
|
|
4371
|
-
/** Narrow an unknown `event.data` to a message from the page. */
|
|
4372
|
-
function isValCanvasPageMessage(data) {
|
|
4373
|
-
if (_typeof(data) !== "object" || data === null) return false;
|
|
4374
|
-
var message = data;
|
|
4375
|
-
if (message.val !== VAL_CANVAS_MESSAGE) return false;
|
|
4376
|
-
return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing";
|
|
4377
|
-
}
|
|
4378
|
-
|
|
4379
|
-
/** Narrow an unknown `event.data` to a message from the studio. */
|
|
4380
|
-
function isValCanvasStudioMessage(data) {
|
|
4381
|
-
if (_typeof(data) !== "object" || data === null) return false;
|
|
4382
|
-
var message = data;
|
|
4383
|
-
if (message.val !== VAL_CANVAS_MESSAGE) return false;
|
|
4384
|
-
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
|
|
4385
|
-
}
|
|
4386
|
-
|
|
4387
|
-
/**
|
|
4388
|
-
* Whether this document was loaded as the studio's canvas frame.
|
|
4389
|
-
*
|
|
4390
|
-
* Reads the location directly rather than taking it as an argument, because
|
|
4391
|
-
* every caller wants the answer for the document it is running in, and passing
|
|
4392
|
-
* it around is how one of them ends up asking about the wrong one.
|
|
4393
|
-
*/
|
|
4394
|
-
function isValCanvasFrame(search) {
|
|
4395
|
-
try {
|
|
4396
|
-
return new URLSearchParams(search).get(VAL_CANVAS_PARAM) !== null;
|
|
4397
|
-
} catch (_unused) {
|
|
4398
|
-
return false;
|
|
4399
|
-
}
|
|
4400
|
-
}
|
|
4401
|
-
|
|
4402
|
-
/** Add the canvas marker to a page URL. */
|
|
4403
|
-
function withValCanvasParam(url) {
|
|
4404
|
-
var separator = url.includes("?") ? "&" : "?";
|
|
4405
|
-
return "".concat(url).concat(separator).concat(VAL_CANVAS_PARAM, "=1");
|
|
4406
|
-
}
|
|
4407
|
-
|
|
4408
4045
|
/**
|
|
4409
4046
|
* Local `yyyy-MM-dd`, which is the one thing this module used `date-fns` for.
|
|
4410
4047
|
*
|
|
@@ -4666,13 +4303,21 @@ function checkGallerySchema(moduleSchema) {
|
|
|
4666
4303
|
};
|
|
4667
4304
|
}
|
|
4668
4305
|
function buildImageGalleryPatch(args, moduleSchema) {
|
|
4306
|
+
var _args$metadata$alt;
|
|
4669
4307
|
var wrong = checkGallerySchema(moduleSchema);
|
|
4670
4308
|
if (wrong) return wrong;
|
|
4671
4309
|
var remote = isRemoteSchema(moduleSchema);
|
|
4672
4310
|
var patch = [{
|
|
4673
4311
|
op: "add",
|
|
4674
4312
|
path: [args.filePath],
|
|
4675
|
-
value: _objectSpread2({}, args.metadata)
|
|
4313
|
+
value: _objectSpread2(_objectSpread2({}, args.metadata), {}, {
|
|
4314
|
+
// `alt` is a REQUIRED field of a gallery entry (`s.images()` gives it a
|
|
4315
|
+
// nullable string schema), so an entry written without one is invalid
|
|
4316
|
+
// the moment it lands — and the caller that had no alt to give is the
|
|
4317
|
+
// common case, not the odd one. Null is what the Studio's own upload
|
|
4318
|
+
// writes there.
|
|
4319
|
+
alt: (_args$metadata$alt = args.metadata.alt) !== null && _args$metadata$alt !== void 0 ? _args$metadata$alt : null
|
|
4320
|
+
})
|
|
4676
4321
|
}, {
|
|
4677
4322
|
op: "file",
|
|
4678
4323
|
path: [args.filePath],
|
|
@@ -5164,4 +4809,702 @@ function filterBlockingValidationErrors(validationErrors, schemas, sources) {
|
|
|
5164
4809
|
return partitionValidationErrors(resolved).surfaced;
|
|
5165
4810
|
}
|
|
5166
4811
|
|
|
5167
|
-
|
|
4812
|
+
/**
|
|
4813
|
+
* Every decision `encode` makes, with none of the pixels.
|
|
4814
|
+
*
|
|
4815
|
+
* The re-encode itself happens in two places that share nothing: the Studio
|
|
4816
|
+
* runs it on a `<canvas>` before the upload leaves the browser
|
|
4817
|
+
* (`packages/ui/spa/utils/encodeImage.ts`), and the MCP image tool runs it on
|
|
4818
|
+
* `sharp` in the server process (`@valbuild/mcp`). What must NOT differ between
|
|
4819
|
+
* them is what `s.image({ encode })` means — which images are converted, how
|
|
4820
|
+
* far they are scaled, and when the original wins anyway. So the decisions live
|
|
4821
|
+
* here, once, and each side supplies only its own encoder.
|
|
4822
|
+
*
|
|
4823
|
+
* See `architecture/media.md`.
|
|
4824
|
+
*/
|
|
4825
|
+
|
|
4826
|
+
var ENCODE_DEFAULTS = {
|
|
4827
|
+
quality: 0.8,
|
|
4828
|
+
maxWidth: 2560,
|
|
4829
|
+
maxHeight: 2560
|
|
4830
|
+
};
|
|
4831
|
+
var ENCODE_MIME_TYPE_OF = {
|
|
4832
|
+
webp: "image/webp"
|
|
4833
|
+
};
|
|
4834
|
+
|
|
4835
|
+
/**
|
|
4836
|
+
* Formats an encoder cannot round-trip without losing something: SVG is vector,
|
|
4837
|
+
* an animated GIF keeps only its first frame, and AVIF is already smaller than
|
|
4838
|
+
* anything we would produce.
|
|
4839
|
+
*/
|
|
4840
|
+
var NEVER_ENCODED = ["image/svg+xml", "image/gif", "image/avif"];
|
|
4841
|
+
|
|
4842
|
+
/** A dimension bound the encoder can act on, else the default. */
|
|
4843
|
+
function usableBound(value, fallback) {
|
|
4844
|
+
return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4847
|
+
/**
|
|
4848
|
+
* The schema's own option first, then the gallery it references, then off.
|
|
4849
|
+
*
|
|
4850
|
+
* Mirrors how `ImageField` resolves `accept` and `directory`. A gallery-backed
|
|
4851
|
+
* field (`s.image(galleryVal)`) serializes with EMPTY options, so without the
|
|
4852
|
+
* gallery fallback it could never inherit what the gallery asked for — and
|
|
4853
|
+
* `s.image(galleryVal, { encode: false })` is how such a field opts back out.
|
|
4854
|
+
*
|
|
4855
|
+
* Nonsense numbers fall back to the defaults rather than through. `fitWithin`
|
|
4856
|
+
* reads a bound of 0 (or NaN, or a negative) as "everything already fits", so
|
|
4857
|
+
* an unusable `maxWidth` would otherwise SILENTLY disable the downscale it was
|
|
4858
|
+
* asking for, and a `quality` outside 0–1 is ignored by `canvas.toBlob` just as
|
|
4859
|
+
* quietly. Falling back is deliberately not throwing: a schema is evaluated by
|
|
4860
|
+
* the CLI's `node:vm` sandbox and by the Next server, so a typo'd quality would
|
|
4861
|
+
* take a whole project down over an upload-time optimisation.
|
|
4862
|
+
*/
|
|
4863
|
+
function resolveEncodeSettings(fieldOption, galleryOption) {
|
|
4864
|
+
var option = fieldOption !== undefined ? fieldOption : galleryOption;
|
|
4865
|
+
if (!option) {
|
|
4866
|
+
return null;
|
|
4867
|
+
}
|
|
4868
|
+
var quality = option.quality;
|
|
4869
|
+
return {
|
|
4870
|
+
type: option.type,
|
|
4871
|
+
quality: quality !== undefined && Number.isFinite(quality) && quality > 0 ? Math.min(quality, 1) : ENCODE_DEFAULTS.quality,
|
|
4872
|
+
maxWidth: usableBound(option.maxWidth, ENCODE_DEFAULTS.maxWidth),
|
|
4873
|
+
maxHeight: usableBound(option.maxHeight, ENCODE_DEFAULTS.maxHeight)
|
|
4874
|
+
};
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4877
|
+
/**
|
|
4878
|
+
* Dimensions scaled to fit inside the box, or null when the image already does.
|
|
4879
|
+
*
|
|
4880
|
+
* Null is the signal for "no downscale was needed", which the size comparison
|
|
4881
|
+
* in {@link chooseEncoded} turns on.
|
|
4882
|
+
*/
|
|
4883
|
+
function fitWithin(width, height, maxWidth, maxHeight) {
|
|
4884
|
+
if (!(width > 0) || !(height > 0) || !(maxWidth > 0) || !(maxHeight > 0) || width <= maxWidth && height <= maxHeight) {
|
|
4885
|
+
return null;
|
|
4886
|
+
}
|
|
4887
|
+
var scale = Math.min(maxWidth / width, maxHeight / height);
|
|
4888
|
+
return {
|
|
4889
|
+
// At least one pixel: a very wide, very short image would otherwise round
|
|
4890
|
+
// its height to zero, and a zero-sized canvas cannot be encoded.
|
|
4891
|
+
width: Math.max(1, Math.round(width * scale)),
|
|
4892
|
+
height: Math.max(1, Math.round(height * scale))
|
|
4893
|
+
};
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4896
|
+
/**
|
|
4897
|
+
* Whether a schema that declares `accept` would take this mime type.
|
|
4898
|
+
*
|
|
4899
|
+
* The same rules as `ImageSchema.executeValidate`, which validates the STORED
|
|
4900
|
+
* mimeType against `accept` — so converting to a type `accept` forbids would
|
|
4901
|
+
* upload a file the schema reports as invalid the moment it lands. Delegated to
|
|
4902
|
+
* `@valbuild/core` rather than re-spelled, because a second reading of one
|
|
4903
|
+
* `accept` string is how a converted image passes here and fails there.
|
|
4904
|
+
*/
|
|
4905
|
+
function isMimeTypeAccepted(mimeType, accept) {
|
|
4906
|
+
if (!accept) {
|
|
4907
|
+
return true;
|
|
4908
|
+
}
|
|
4909
|
+
return Internal.mimeTypeMatchesAccept(mimeType, accept);
|
|
4910
|
+
}
|
|
4911
|
+
|
|
4912
|
+
/** Formats left alone: the lossy list, plus the target type when it already fits. */
|
|
4913
|
+
function isSkippedSource(sourceMimeType, targetMimeType, needsDownscale) {
|
|
4914
|
+
if (NEVER_ENCODED.includes(sourceMimeType)) {
|
|
4915
|
+
return true;
|
|
4916
|
+
}
|
|
4917
|
+
return sourceMimeType === targetMimeType && !needsDownscale;
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
/**
|
|
4921
|
+
* Whether to upload what the encoder produced, or the file that was picked.
|
|
4922
|
+
*
|
|
4923
|
+
* Two ways the encoded bytes lose. They are not actually the type we asked for
|
|
4924
|
+
* — `canvas.toBlob` answers an unsupported type with a PNG rather than with
|
|
4925
|
+
* null, so "not null" is not the same question as "is a webp". Or they are
|
|
4926
|
+
* simply bigger, which happens for small and flat images: measured, an 8x8
|
|
4927
|
+
* solid PNG of 74 bytes becomes a 548 byte webp. A downscale overrides that,
|
|
4928
|
+
* since the original is then the wrong size whatever it weighs.
|
|
4929
|
+
*/
|
|
4930
|
+
function chooseEncoded(input) {
|
|
4931
|
+
if (input.encodedSize === null || input.encodedType !== input.targetMimeType) {
|
|
4932
|
+
return false;
|
|
4933
|
+
}
|
|
4934
|
+
if (input.needsDownscale) {
|
|
4935
|
+
return true;
|
|
4936
|
+
}
|
|
4937
|
+
return input.encodedSize < input.originalSize;
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
/**
|
|
4941
|
+
* `photo.png` -> `photo.webp`. The extension is re-derived downstream too, but
|
|
4942
|
+
* the name should not lie.
|
|
4943
|
+
*/
|
|
4944
|
+
function withExtension(filename, ext) {
|
|
4945
|
+
var base = filename.split(".").slice(0, -1).join(".") || filename;
|
|
4946
|
+
return "".concat(base, ".").concat(ext);
|
|
4947
|
+
}
|
|
4948
|
+
|
|
4949
|
+
/**
|
|
4950
|
+
* The one way to build a child's source path.
|
|
4951
|
+
*
|
|
4952
|
+
* There were five hand-rolled versions of this, three of them subtly different
|
|
4953
|
+
* and two of them wrong, and the wrong ones were wrong in a way that hid: a
|
|
4954
|
+
* module path segment has to be JSON-quoted if it is a string and bare if it is
|
|
4955
|
+
* an array index, and both broken copies appended the raw key at the module
|
|
4956
|
+
* root. An unquoted key parses fine as long as it contains no `.` — so every
|
|
4957
|
+
* object field and every ordinary record key still resolved, and only a key with
|
|
4958
|
+
* a dot in it broke. That is exactly what a media gallery is keyed by, so
|
|
4959
|
+
* clicking a file in the Media panel navigated to
|
|
4960
|
+
* `?p=/public/val/red-8x8_bfbd0.png`, which `splitModulePath` reads as the two
|
|
4961
|
+
* segments `/public/val/red-8x8_bfbd0` and `png`, and the studio said the module
|
|
4962
|
+
* was not found.
|
|
4963
|
+
*
|
|
4964
|
+
* So: one function, and never a template string. `Internal.createValPathOfItem`
|
|
4965
|
+
* is the rule itself — `JSON.stringify` quotes a string and leaves a number
|
|
4966
|
+
* bare, which is precisely the module-path grammar — and everything here defers
|
|
4967
|
+
* to it rather than restating it.
|
|
4968
|
+
*/
|
|
4969
|
+
function sourcePathOfChild(parent, key) {
|
|
4970
|
+
/**
|
|
4971
|
+
* A parent that is already `…?p=` gets the key with no separator.
|
|
4972
|
+
*
|
|
4973
|
+
* The search index walks from `moduleFilePath + "?p="` rather than from the
|
|
4974
|
+
* module file path, so that every path it produces has a module path — and
|
|
4975
|
+
* `createValPathOfItem` would see the separator and join with a `.`, giving
|
|
4976
|
+
* `?p=."title"`.
|
|
4977
|
+
*/
|
|
4978
|
+
if (parent.endsWith(Internal.ModuleFilePathSep)) {
|
|
4979
|
+
return "".concat(parent).concat(JSON.stringify(key));
|
|
4980
|
+
}
|
|
4981
|
+
var path = Internal.createValPathOfItem(parent, key);
|
|
4982
|
+
if (path === undefined) {
|
|
4983
|
+
// Only when the parent is empty, which callers cannot produce: every walk
|
|
4984
|
+
// starts from a module file path. Thrown rather than skipped so a future
|
|
4985
|
+
// change that breaks that assumption is loud.
|
|
4986
|
+
throw new Error("Could not build a source path for '".concat(key, "' under ").concat(parent));
|
|
4987
|
+
}
|
|
4988
|
+
return path;
|
|
4989
|
+
}
|
|
4990
|
+
|
|
4991
|
+
/**
|
|
4992
|
+
* The same thing, for callers holding the module file path and module path apart.
|
|
4993
|
+
*
|
|
4994
|
+
* `ModulePath` is empty at the module root, which is the case both broken copies
|
|
4995
|
+
* got wrong: there is no separator in the path yet, so the key is the first
|
|
4996
|
+
* segment and still has to be quoted.
|
|
4997
|
+
*/
|
|
4998
|
+
function concatModulePath(moduleFilePath, modulePath, key) {
|
|
4999
|
+
return sourcePathOfChild(modulePath ? Internal.joinModuleFilePathAndModulePath(moduleFilePath, modulePath) : moduleFilePath, key);
|
|
5000
|
+
}
|
|
5001
|
+
|
|
5002
|
+
/**
|
|
5003
|
+
* Traverses a schema and source pair, calling a callback for each leaf node.
|
|
5004
|
+
* Handles all schema types including primitives, objects, arrays, records, unions, richtext, files, images, etc.
|
|
5005
|
+
*
|
|
5006
|
+
* @param source - The JSON source data
|
|
5007
|
+
* @param schema - The serialized schema
|
|
5008
|
+
* @param path - The current source path
|
|
5009
|
+
* @param callback - Function called for each traversable node with { source, schema, path }
|
|
5010
|
+
*/
|
|
5011
|
+
function traverseSchemaSource(source, schema, path, callback) {
|
|
5012
|
+
if (!(schema !== null && schema !== void 0 && schema.type)) {
|
|
5013
|
+
throw new Error("Schema not found for " + path);
|
|
5014
|
+
}
|
|
5015
|
+
if (source === null) {
|
|
5016
|
+
return;
|
|
5017
|
+
}
|
|
5018
|
+
|
|
5019
|
+
// An un-loaded `.jsonValues()` entry is an opaque `{_type:"json"}` marker, not
|
|
5020
|
+
// the value its schema describes. Skip it — its content is traversed once
|
|
5021
|
+
// loaded, since the sync engine substitutes it in place of the marker.
|
|
5022
|
+
//
|
|
5023
|
+
// Without this the marker falls through to whichever branch its ITEM schema
|
|
5024
|
+
// selects: an object/record item walks the marker's OWN keys, so an entry
|
|
5025
|
+
// schema with a field named `_type` or `patch_id` would index the marker's
|
|
5026
|
+
// internals as if they were content.
|
|
5027
|
+
if (Internal.isJson(source)) {
|
|
5028
|
+
return;
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
// Handle primitives
|
|
5032
|
+
if (schema.type === "string") {
|
|
5033
|
+
if (typeof source === "string") {
|
|
5034
|
+
callback({
|
|
5035
|
+
source: source,
|
|
5036
|
+
schema: schema,
|
|
5037
|
+
path: path
|
|
5038
|
+
});
|
|
5039
|
+
}
|
|
5040
|
+
return;
|
|
5041
|
+
}
|
|
5042
|
+
if (schema.type === "number") {
|
|
5043
|
+
if (typeof source === "number") {
|
|
5044
|
+
callback({
|
|
5045
|
+
source: source,
|
|
5046
|
+
schema: schema,
|
|
5047
|
+
path: path
|
|
5048
|
+
});
|
|
5049
|
+
}
|
|
5050
|
+
return;
|
|
5051
|
+
}
|
|
5052
|
+
if (schema.type === "boolean") {
|
|
5053
|
+
if (typeof source === "boolean") {
|
|
5054
|
+
callback({
|
|
5055
|
+
source: source,
|
|
5056
|
+
schema: schema,
|
|
5057
|
+
path: path
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
5060
|
+
return;
|
|
5061
|
+
}
|
|
5062
|
+
|
|
5063
|
+
// Handle richtext - flatten and call callback
|
|
5064
|
+
if (schema.type === "richtext") {
|
|
5065
|
+
callback({
|
|
5066
|
+
source: source,
|
|
5067
|
+
schema: schema,
|
|
5068
|
+
path: path
|
|
5069
|
+
});
|
|
5070
|
+
return;
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5073
|
+
// Handle file/image
|
|
5074
|
+
if (schema.type === "file" || schema.type === "image") {
|
|
5075
|
+
if (source && _typeof(source) === "object" && "path" in source && typeof source.path === "string") {
|
|
5076
|
+
callback({
|
|
5077
|
+
source: source,
|
|
5078
|
+
schema: schema,
|
|
5079
|
+
path: path
|
|
5080
|
+
});
|
|
5081
|
+
}
|
|
5082
|
+
return;
|
|
5083
|
+
}
|
|
5084
|
+
|
|
5085
|
+
// Handle date
|
|
5086
|
+
if (schema.type === "date") {
|
|
5087
|
+
if (typeof source === "string") {
|
|
5088
|
+
callback({
|
|
5089
|
+
source: source,
|
|
5090
|
+
schema: schema,
|
|
5091
|
+
path: path
|
|
5092
|
+
});
|
|
5093
|
+
}
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
5096
|
+
|
|
5097
|
+
// Handle dateTime
|
|
5098
|
+
if (schema.type === "dateTime") {
|
|
5099
|
+
if (typeof source === "string") {
|
|
5100
|
+
callback({
|
|
5101
|
+
source: source,
|
|
5102
|
+
schema: schema,
|
|
5103
|
+
path: path
|
|
5104
|
+
});
|
|
5105
|
+
}
|
|
5106
|
+
return;
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
// Handle color
|
|
5110
|
+
if (schema.type === "color") {
|
|
5111
|
+
if (typeof source === "string") {
|
|
5112
|
+
callback({
|
|
5113
|
+
source: source,
|
|
5114
|
+
schema: schema,
|
|
5115
|
+
path: path
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
return;
|
|
5119
|
+
}
|
|
5120
|
+
|
|
5121
|
+
// Handle code
|
|
5122
|
+
if (schema.type === "code") {
|
|
5123
|
+
if (typeof source === "string") {
|
|
5124
|
+
callback({
|
|
5125
|
+
source: source,
|
|
5126
|
+
schema: schema,
|
|
5127
|
+
path: path
|
|
5128
|
+
});
|
|
5129
|
+
}
|
|
5130
|
+
return;
|
|
5131
|
+
}
|
|
5132
|
+
|
|
5133
|
+
// Handle keyOf
|
|
5134
|
+
if (schema.type === "keyOf") {
|
|
5135
|
+
if (typeof source === "string" || typeof source === "number") {
|
|
5136
|
+
callback({
|
|
5137
|
+
source: source,
|
|
5138
|
+
schema: schema,
|
|
5139
|
+
path: path
|
|
5140
|
+
});
|
|
5141
|
+
}
|
|
5142
|
+
return;
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
// Handle literal
|
|
5146
|
+
if (schema.type === "literal") {
|
|
5147
|
+
callback({
|
|
5148
|
+
source: source,
|
|
5149
|
+
schema: schema,
|
|
5150
|
+
path: path
|
|
5151
|
+
});
|
|
5152
|
+
return;
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
// Handle route
|
|
5156
|
+
if (schema.type === "route") {
|
|
5157
|
+
if (typeof source === "string") {
|
|
5158
|
+
callback({
|
|
5159
|
+
source: source,
|
|
5160
|
+
schema: schema,
|
|
5161
|
+
path: path
|
|
5162
|
+
});
|
|
5163
|
+
}
|
|
5164
|
+
return;
|
|
5165
|
+
}
|
|
5166
|
+
|
|
5167
|
+
// Handle array
|
|
5168
|
+
if (schema.type === "array") {
|
|
5169
|
+
if (!Array.isArray(source)) {
|
|
5170
|
+
return;
|
|
5171
|
+
}
|
|
5172
|
+
if (!schema.item) {
|
|
5173
|
+
throw new Error("Schema (" + schema.type + ") item not found for " + path);
|
|
5174
|
+
}
|
|
5175
|
+
for (var i = 0; i < source.length; i++) {
|
|
5176
|
+
var subPath = sourcePathOfChild(path, i);
|
|
5177
|
+
traverseSchemaSource(source[i], schema.item, subPath, callback);
|
|
5178
|
+
}
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5182
|
+
// Handle object, record and settings
|
|
5183
|
+
if (schema.type === "object" || schema.type === "record" ||
|
|
5184
|
+
// Settings walks like an object, and starting from the SOURCE is what makes
|
|
5185
|
+
// its optional keys work: an absent section has nothing to traverse.
|
|
5186
|
+
schema.type === "settings") {
|
|
5187
|
+
if (_typeof(source) !== "object" || source === null || Array.isArray(source)) {
|
|
5188
|
+
return;
|
|
5189
|
+
}
|
|
5190
|
+
for (var key in source) {
|
|
5191
|
+
var _schema$items;
|
|
5192
|
+
var subSchema = schema.type === "record" ? schema.item : (_schema$items = schema.items) === null || _schema$items === void 0 ? void 0 : _schema$items[key];
|
|
5193
|
+
if (!subSchema) {
|
|
5194
|
+
continue;
|
|
5195
|
+
}
|
|
5196
|
+
var _subPath = sourcePathOfChild(path, key);
|
|
5197
|
+
traverseSchemaSource(source[key], subSchema, _subPath, callback);
|
|
5198
|
+
}
|
|
5199
|
+
return;
|
|
5200
|
+
}
|
|
5201
|
+
|
|
5202
|
+
// Handle union
|
|
5203
|
+
if (schema.type === "union") {
|
|
5204
|
+
var schemaKey = schema.key;
|
|
5205
|
+
if (typeof schemaKey === "string") {
|
|
5206
|
+
// Tagged union - find matching sub-schema
|
|
5207
|
+
if (source && _typeof(source) === "object" && !Array.isArray(source) && schemaKey in source) {
|
|
5208
|
+
var itemKey = source[schemaKey];
|
|
5209
|
+
if (typeof itemKey === "string") {
|
|
5210
|
+
var schemaOfItem = schema.items.filter(function (item) {
|
|
5211
|
+
return item.type === "object";
|
|
5212
|
+
}).find(function (item) {
|
|
5213
|
+
var itemKeySchema = item.items[schemaKey];
|
|
5214
|
+
if ((itemKeySchema === null || itemKeySchema === void 0 ? void 0 : itemKeySchema.type) === "literal") {
|
|
5215
|
+
return itemKeySchema.value === itemKey;
|
|
5216
|
+
}
|
|
5217
|
+
return false;
|
|
5218
|
+
});
|
|
5219
|
+
if (schemaOfItem) {
|
|
5220
|
+
traverseSchemaSource(source, schemaOfItem, path, callback);
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
} else {
|
|
5225
|
+
// Literal union - treat as primitive
|
|
5226
|
+
if (typeof source === "string") {
|
|
5227
|
+
callback({
|
|
5228
|
+
source: source,
|
|
5229
|
+
schema: schema,
|
|
5230
|
+
path: path
|
|
5231
|
+
});
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
return;
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
// Exhaustive check
|
|
5238
|
+
var exhaustiveCheck = schema;
|
|
5239
|
+
throw new Error("Unsupported schema type: " + JSON.stringify(exhaustiveCheck));
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
/**
|
|
5243
|
+
* Flattens richtext source to a plain string, extracting only text content.
|
|
5244
|
+
* Used for indexing richtext content in search.
|
|
5245
|
+
*/
|
|
5246
|
+
function flattenRichText(source) {
|
|
5247
|
+
var res = "";
|
|
5248
|
+
function rec(child) {
|
|
5249
|
+
if (typeof child === "string") {
|
|
5250
|
+
res += child;
|
|
5251
|
+
} else {
|
|
5252
|
+
if (child && _typeof(child) === "object" && "children" in child && Array.isArray(child.children)) {
|
|
5253
|
+
var _iterator = _createForOfIteratorHelper(child.children),
|
|
5254
|
+
_step;
|
|
5255
|
+
try {
|
|
5256
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
5257
|
+
var c = _step.value;
|
|
5258
|
+
rec(c);
|
|
5259
|
+
}
|
|
5260
|
+
} catch (err) {
|
|
5261
|
+
_iterator.e(err);
|
|
5262
|
+
} finally {
|
|
5263
|
+
_iterator.f();
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
5268
|
+
rec({
|
|
5269
|
+
children: source
|
|
5270
|
+
});
|
|
5271
|
+
return res;
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
/**
|
|
5275
|
+
* Resolves a file ref (local path or remote URL) to a clean local-style path.
|
|
5276
|
+
*/
|
|
5277
|
+
function cleanRefPath(ref) {
|
|
5278
|
+
var remoteRefRes = Internal.remote.splitRemoteRef(ref);
|
|
5279
|
+
return remoteRefRes.status === "success" ? "/".concat(remoteRefRes.filePath) : ref;
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5282
|
+
/**
|
|
5283
|
+
* Extract a human-readable filename from a file ref (local path or remote URL).
|
|
5284
|
+
* Handles both remote refs (via splitRemoteRef) and plain `/public/...` paths.
|
|
5285
|
+
*/
|
|
5286
|
+
function getFilenameFromRef(ref) {
|
|
5287
|
+
var cleanPath = cleanRefPath(ref);
|
|
5288
|
+
return cleanPath.split("/").pop() || cleanPath;
|
|
5289
|
+
}
|
|
5290
|
+
|
|
5291
|
+
/**
|
|
5292
|
+
* Parse a file ref into its constituent parts: a clean path, filename, and
|
|
5293
|
+
* folder (the path relative to `/public`).
|
|
5294
|
+
*
|
|
5295
|
+
* The `folder` strips the standard `/public` prefix so it shows a
|
|
5296
|
+
* concise location like `/images` instead of `/public/images`.
|
|
5297
|
+
*/
|
|
5298
|
+
function getRefParts(ref) {
|
|
5299
|
+
var cleanPath = cleanRefPath(ref);
|
|
5300
|
+
var filename = cleanPath.split("/").pop() || cleanPath;
|
|
5301
|
+
// Only strip a leading "/public" path segment: "/publicity/a.png" must keep its folder
|
|
5302
|
+
var folder = cleanPath.replace(/^\/public(?=\/|$)/, "").replace(/\/[^/]+$/, "") || "/";
|
|
5303
|
+
return {
|
|
5304
|
+
cleanPath: cleanPath,
|
|
5305
|
+
filename: filename,
|
|
5306
|
+
folder: folder
|
|
5307
|
+
};
|
|
5308
|
+
}
|
|
5309
|
+
|
|
5310
|
+
/**
|
|
5311
|
+
* The search index and the labels to render for its hits.
|
|
5312
|
+
*
|
|
5313
|
+
* Kept as a plain module rather than living inside a worker so it can be tested:
|
|
5314
|
+
* a worker entry runs `self.onmessage` on import, which no test environment here
|
|
5315
|
+
* provides.
|
|
5316
|
+
*
|
|
5317
|
+
* In `@valbuild/shared` rather than the Studio because there are now two
|
|
5318
|
+
* realms searching the same content: the Studio's `SearchStore`, which keeps an
|
|
5319
|
+
* index alive in a worker and re-indexes a module at a time, and the MCP
|
|
5320
|
+
* `search_content` tool, which builds one per call and throws it away. What
|
|
5321
|
+
* counts as a document, and what a hit is labelled, must not differ between
|
|
5322
|
+
* them — an agent and an editor searching the same project should find the same
|
|
5323
|
+
* things.
|
|
5324
|
+
*/
|
|
5325
|
+
|
|
5326
|
+
/** An index with nothing in it. Fill it with {@link indexModule}. */
|
|
5327
|
+
function createSearchIndex() {
|
|
5328
|
+
return {
|
|
5329
|
+
index: new FlexSearch.Index({
|
|
5330
|
+
tokenize: "forward"
|
|
5331
|
+
}),
|
|
5332
|
+
pathToLabel: new Map(),
|
|
5333
|
+
docsByModule: new Map()
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
5336
|
+
|
|
5337
|
+
/**
|
|
5338
|
+
* Index one module, replacing whatever was previously indexed for it.
|
|
5339
|
+
*
|
|
5340
|
+
* Remove-then-add rather than `update`, because the set of paths in a module is
|
|
5341
|
+
* not stable: deleting an array item or a record entry means documents that must
|
|
5342
|
+
* disappear, and `update` only revises ids it is given.
|
|
5343
|
+
*
|
|
5344
|
+
* `.jsonValues()` entries are indexed only once their content is loaded — an
|
|
5345
|
+
* un-loaded entry is an opaque marker that {@link traverseSchemaSource} skips, so
|
|
5346
|
+
* the index is naturally PARTIAL and grows as batches land. That is why the search
|
|
5347
|
+
* UI loads the entries on the first query and re-indexes (debounced) as they
|
|
5348
|
+
* arrive, rather than pretending an empty result set is an answer.
|
|
5349
|
+
*/
|
|
5350
|
+
function indexModule(searchIndex, moduleFilePath, source, schema) {
|
|
5351
|
+
var index = searchIndex.index,
|
|
5352
|
+
pathToLabel = searchIndex.pathToLabel,
|
|
5353
|
+
docsByModule = searchIndex.docsByModule;
|
|
5354
|
+
removeModule(searchIndex, moduleFilePath);
|
|
5355
|
+
var docs = new Set();
|
|
5356
|
+
docsByModule.set(moduleFilePath, docs);
|
|
5357
|
+
var path = moduleFilePath + "?p=";
|
|
5358
|
+
traverseSchemaSource(source, schema, path, function (_ref) {
|
|
5359
|
+
var source = _ref.source,
|
|
5360
|
+
schema = _ref.schema,
|
|
5361
|
+
path = _ref.path;
|
|
5362
|
+
if (source === null) {
|
|
5363
|
+
return;
|
|
5364
|
+
}
|
|
5365
|
+
var searchText = "";
|
|
5366
|
+
var label = "";
|
|
5367
|
+
|
|
5368
|
+
// Handle primitives
|
|
5369
|
+
if (schema.type === "string" || schema.type === "number" || schema.type === "boolean" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route") {
|
|
5370
|
+
var _source$toString, _source$toString2;
|
|
5371
|
+
searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
|
|
5372
|
+
label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
|
|
5373
|
+
} else if (schema.type === "literal") {
|
|
5374
|
+
searchText = schema.value.toString();
|
|
5375
|
+
label = schema.value.toString();
|
|
5376
|
+
}
|
|
5377
|
+
// Handle richtext - flatten to get text content
|
|
5378
|
+
else if (schema.type === "richtext") {
|
|
5379
|
+
searchText = flattenRichText(source);
|
|
5380
|
+
// Use first 50 chars as label
|
|
5381
|
+
label = searchText.substring(0, 50) || "richtext";
|
|
5382
|
+
}
|
|
5383
|
+
// Handle file/image
|
|
5384
|
+
else if (schema.type === "file" || schema.type === "image") {
|
|
5385
|
+
if (source !== null && _typeof(source) === "object" && "path" in source && typeof source.path === "string") {
|
|
5386
|
+
// The label is the bare filename - the folder is shown separately in
|
|
5387
|
+
// the UI - but both are searchable.
|
|
5388
|
+
var _getRefParts = getRefParts(source.path),
|
|
5389
|
+
filename = _getRefParts.filename,
|
|
5390
|
+
folder = _getRefParts.folder;
|
|
5391
|
+
var alt = "alt" in source ? source.alt : "";
|
|
5392
|
+
searchText = filename + " " + folder + " " + alt;
|
|
5393
|
+
label = filename;
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
|
|
5397
|
+
// Add to index if we have search text
|
|
5398
|
+
if (searchText) {
|
|
5399
|
+
var _Internal$splitModule = Internal.splitModuleFilePathAndModulePath(path),
|
|
5400
|
+
_Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
|
|
5401
|
+
modulePath = _Internal$splitModule2[1];
|
|
5402
|
+
var cleanPath = fastRemoveNonWordChars(modulePath) + " ";
|
|
5403
|
+
index.add(path, cleanPath + " " + path + " " + searchText);
|
|
5404
|
+
pathToLabel.set(path, label);
|
|
5405
|
+
docs.add(path);
|
|
5406
|
+
}
|
|
5407
|
+
});
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
/**
|
|
5411
|
+
* Drop everything indexed for one module.
|
|
5412
|
+
*
|
|
5413
|
+
* Also used for a module that has gone away entirely — otherwise its documents
|
|
5414
|
+
* stay searchable and a hit navigates to a path that no longer exists.
|
|
5415
|
+
*/
|
|
5416
|
+
function removeModule(searchIndex, moduleFilePath) {
|
|
5417
|
+
var index = searchIndex.index,
|
|
5418
|
+
pathToLabel = searchIndex.pathToLabel,
|
|
5419
|
+
docsByModule = searchIndex.docsByModule;
|
|
5420
|
+
var existing = docsByModule.get(moduleFilePath);
|
|
5421
|
+
if (existing === undefined) return;
|
|
5422
|
+
var _iterator = _createForOfIteratorHelper(existing),
|
|
5423
|
+
_step;
|
|
5424
|
+
try {
|
|
5425
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
5426
|
+
var id = _step.value;
|
|
5427
|
+
index.remove(id);
|
|
5428
|
+
pathToLabel["delete"](id);
|
|
5429
|
+
}
|
|
5430
|
+
} catch (err) {
|
|
5431
|
+
_iterator.e(err);
|
|
5432
|
+
} finally {
|
|
5433
|
+
_iterator.f();
|
|
5434
|
+
}
|
|
5435
|
+
docsByModule["delete"](moduleFilePath);
|
|
5436
|
+
}
|
|
5437
|
+
|
|
5438
|
+
/**
|
|
5439
|
+
* Index every module, from scratch.
|
|
5440
|
+
*
|
|
5441
|
+
* Now a loop over {@link indexModule} rather than its own walk, so the full
|
|
5442
|
+
* build and the incremental one cannot drift in what they consider a document.
|
|
5443
|
+
*/
|
|
5444
|
+
function buildSearchIndex(modules) {
|
|
5445
|
+
var searchIndex = createSearchIndex();
|
|
5446
|
+
for (var moduleFilePathS in modules) {
|
|
5447
|
+
var moduleFilePath = moduleFilePathS;
|
|
5448
|
+
var _modules$moduleFilePa = modules[moduleFilePath],
|
|
5449
|
+
source = _modules$moduleFilePa.source,
|
|
5450
|
+
schema = _modules$moduleFilePa.schema;
|
|
5451
|
+
indexModule(searchIndex, moduleFilePath, source, schema);
|
|
5452
|
+
}
|
|
5453
|
+
return searchIndex;
|
|
5454
|
+
}
|
|
5455
|
+
var NON_CHARS = new Set(["/", "-", "_", ".", ":", "?", "&", "=", "@", '"', "'"]);
|
|
5456
|
+
function fastRemoveNonWordChars(str) {
|
|
5457
|
+
var result = "";
|
|
5458
|
+
for (var i = 0; i < str.length; i++) {
|
|
5459
|
+
var _char = str[i];
|
|
5460
|
+
if (!NON_CHARS.has(_char)) {
|
|
5461
|
+
result += _char;
|
|
5462
|
+
} else {
|
|
5463
|
+
result += " ";
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
return result;
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
/**
|
|
5470
|
+
* How many hits are counted before `total` stops being a count.
|
|
5471
|
+
*
|
|
5472
|
+
* FlexSearch stops as soon as it has the number of ids it was asked for, so a
|
|
5473
|
+
* `total` taken from a page-sized search is just the page size again — which
|
|
5474
|
+
* reads as "that is all there is" to anyone who did not write it. Counting is
|
|
5475
|
+
* asking for more ids than the page needs, so it costs an array of ids and no
|
|
5476
|
+
* more; this bound is where that stops being free, and `totalIsLowerBound` says
|
|
5477
|
+
* it was reached rather than letting the number quietly lie.
|
|
5478
|
+
*/
|
|
5479
|
+
var MAX_COUNTED_RESULTS = 10000;
|
|
5480
|
+
function performSearch(searchIndex, query) {
|
|
5481
|
+
var limit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 50;
|
|
5482
|
+
var offset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
|
|
5483
|
+
if (searchIndex === null || !query.trim()) {
|
|
5484
|
+
return {
|
|
5485
|
+
results: [],
|
|
5486
|
+
total: 0,
|
|
5487
|
+
totalIsLowerBound: false
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
var index = searchIndex.index,
|
|
5491
|
+
pathToLabel = searchIndex.pathToLabel;
|
|
5492
|
+
var counted = Math.max(offset + limit, MAX_COUNTED_RESULTS);
|
|
5493
|
+
var searchResults = index.search(query, {
|
|
5494
|
+
limit: counted
|
|
5495
|
+
});
|
|
5496
|
+
var total = searchResults.length;
|
|
5497
|
+
var paged = searchResults.slice(offset, offset + limit);
|
|
5498
|
+
return {
|
|
5499
|
+
results: paged.map(function (id) {
|
|
5500
|
+
return {
|
|
5501
|
+
path: id,
|
|
5502
|
+
label: pathToLabel.get(id) || id
|
|
5503
|
+
};
|
|
5504
|
+
}),
|
|
5505
|
+
total: total,
|
|
5506
|
+
totalIsLowerBound: total >= counted
|
|
5507
|
+
};
|
|
5508
|
+
}
|
|
5509
|
+
|
|
5510
|
+
export { Api, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchGroup, PatchId, PatchJSON, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedUnionSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, ValCommit, ValDeployment, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, checkCompatibility, chooseEncoded, concatModulePath, createRegExpFromPattern, createSearchIndex, createValClient, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isMimeTypeAccepted, isRemoteSchema, isSkippedSource, levenshtein, newestCommitSha, partitionValidationErrors, performSearch, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, sourcePathOfChild, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
|