@valbuild/server 0.144.0 → 0.144.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 +24 -0
- package/dist/declarations/src/ValOps.d.ts +25 -1
- package/dist/declarations/src/ValOpsHttp.d.ts +5 -1
- package/dist/declarations/src/ValRouter.d.ts +9 -1
- package/dist/declarations/src/ValServer.d.ts +3 -1
- package/dist/declarations/src/index.d.ts +2 -2
- package/dist/declarations/src/proposal.d.ts +81 -0
- package/dist/valbuild-server.cjs.dev.js +425 -24
- package/dist/valbuild-server.cjs.prod.js +425 -24
- package/dist/valbuild-server.esm.js +423 -25
- package/package.json +4 -4
|
@@ -3556,6 +3556,12 @@ function findNestedJsonValuesModuleErrors(schemas) {
|
|
|
3556
3556
|
}
|
|
3557
3557
|
return errors;
|
|
3558
3558
|
}
|
|
3559
|
+
|
|
3560
|
+
/**
|
|
3561
|
+
* What a save told this instance a `.jsonValues()` entry holds: its content --
|
|
3562
|
+
* JSON `null` included -- or that it was deleted. See `adoptedJsonEntries`.
|
|
3563
|
+
*/
|
|
3564
|
+
|
|
3559
3565
|
// #region ValOps
|
|
3560
3566
|
class ValOps {
|
|
3561
3567
|
/** Sources from val modules, immutable (without patches or anything) */
|
|
@@ -3593,6 +3599,13 @@ class ValOps {
|
|
|
3593
3599
|
* {@link siteSourcesUnderSnapshot}, and only where there is a snapshot.
|
|
3594
3600
|
*/
|
|
3595
3601
|
siteSourcesBeforeSnapshot = null;
|
|
3602
|
+
/**
|
|
3603
|
+
* The same modules as the bundle evaluated them, imports and all: where a
|
|
3604
|
+
* `.jsonValues()` entry the snapshot did not save is read from. Not
|
|
3605
|
+
* {@link siteSourcesBeforeSnapshot}, whose markers may have come as JSON
|
|
3606
|
+
* from a content version, with no import behind them.
|
|
3607
|
+
*/
|
|
3608
|
+
bundleSourcesBeforeSnapshot = null;
|
|
3596
3609
|
/**
|
|
3597
3610
|
* What a save has told us each `.jsonValues()` entry now holds.
|
|
3598
3611
|
*
|
|
@@ -3604,7 +3617,9 @@ class ValOps {
|
|
|
3604
3617
|
* unlike a module source there is nothing to re-extract, because the memo was
|
|
3605
3618
|
* never holding the content in the first place.
|
|
3606
3619
|
*
|
|
3607
|
-
* `
|
|
3620
|
+
* `deleted` for an entry the commit deleted: tagged, not `null`, because an
|
|
3621
|
+
* entry's content may itself be JSON `null` (a nullable item), and a saved
|
|
3622
|
+
* `null` read back as "deleted" would report the entry missing.
|
|
3608
3623
|
*
|
|
3609
3624
|
* Never cleared: it describes what is on disk. A host rebuild makes a new
|
|
3610
3625
|
* instance, which is the right reset. Bounded by the project's entry count,
|
|
@@ -3623,6 +3638,16 @@ class ValOps {
|
|
|
3623
3638
|
this.modulesErrors = null;
|
|
3624
3639
|
this.shaEntries = null;
|
|
3625
3640
|
this.shaModuleErrors = null;
|
|
3641
|
+
for (const [moduleFilePath, entries] of (options === null || options === void 0 ? void 0 : options.snapshotJsonEntries) ?? []) {
|
|
3642
|
+
const adopted = new Map();
|
|
3643
|
+
for (const [entryKey, content] of entries) {
|
|
3644
|
+
adopted.set(entryKey, {
|
|
3645
|
+
kind: "content",
|
|
3646
|
+
content
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
this.adoptedJsonEntries.set(moduleFilePath, adopted);
|
|
3650
|
+
}
|
|
3626
3651
|
}
|
|
3627
3652
|
|
|
3628
3653
|
// #region stat
|
|
@@ -3639,7 +3664,7 @@ class ValOps {
|
|
|
3639
3664
|
// #region initTree
|
|
3640
3665
|
async initSources() {
|
|
3641
3666
|
if (this.baseSha === null || this.sourcesSha === null || this.configSha === null || this.schemaSha === null || this.sources === null || this.schemas === null || this.modulesErrors === null) {
|
|
3642
|
-
var _this$options;
|
|
3667
|
+
var _this$options, _this$options2;
|
|
3643
3668
|
const extracted = await extractValModules(this.valModules);
|
|
3644
3669
|
const moduleErrors = extracted.moduleErrors.concat(findNestedJsonValuesModuleErrors(extracted.schemas));
|
|
3645
3670
|
this.sources = extracted.sources;
|
|
@@ -3657,16 +3682,45 @@ class ValOps {
|
|
|
3657
3682
|
* being served, and a base SHA that still described the bundle would
|
|
3658
3683
|
* say the snapshot's content was the bundle's.
|
|
3659
3684
|
*/
|
|
3660
|
-
|
|
3685
|
+
const siteLayer = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.siteSnapshotSources;
|
|
3686
|
+
const snapshotLayer = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.snapshotSources;
|
|
3687
|
+
/*
|
|
3688
|
+
* The bundle's own Source for every module either layer replaces: a
|
|
3689
|
+
* replaced `.jsonValues()` module's markers came as JSON, and an entry
|
|
3690
|
+
* neither layer saved is read through the bundle's marker, which still
|
|
3691
|
+
* has its `import()`.
|
|
3692
|
+
*/
|
|
3693
|
+
if (siteLayer || snapshotLayer) {
|
|
3694
|
+
const bundle = {};
|
|
3695
|
+
for (const moduleFilePath of Object.keys({
|
|
3696
|
+
...siteLayer,
|
|
3697
|
+
...snapshotLayer
|
|
3698
|
+
})) {
|
|
3699
|
+
const path = moduleFilePath;
|
|
3700
|
+
const bundled = extracted.sources[path];
|
|
3701
|
+
if (bundled !== undefined) {
|
|
3702
|
+
bundle[path] = bundled;
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
this.bundleSourcesBeforeSnapshot = bundle;
|
|
3706
|
+
}
|
|
3707
|
+
// What a proposal is compared against: the site, only where there is a proposal.
|
|
3708
|
+
if (snapshotLayer) {
|
|
3661
3709
|
const site = {};
|
|
3662
|
-
for (const moduleFilePath of Object.keys(
|
|
3663
|
-
const
|
|
3710
|
+
for (const moduleFilePath of Object.keys(snapshotLayer)) {
|
|
3711
|
+
const path = moduleFilePath;
|
|
3712
|
+
const source = (siteLayer === null || siteLayer === void 0 ? void 0 : siteLayer[path]) ?? extracted.sources[path];
|
|
3664
3713
|
if (source !== undefined) {
|
|
3665
|
-
site[
|
|
3714
|
+
site[path] = source;
|
|
3666
3715
|
}
|
|
3667
3716
|
}
|
|
3668
3717
|
this.siteSourcesBeforeSnapshot = site;
|
|
3669
|
-
|
|
3718
|
+
}
|
|
3719
|
+
if (siteLayer || snapshotLayer) {
|
|
3720
|
+
this.promoteCommittedSources({
|
|
3721
|
+
...siteLayer,
|
|
3722
|
+
...snapshotLayer
|
|
3723
|
+
});
|
|
3670
3724
|
}
|
|
3671
3725
|
return {
|
|
3672
3726
|
baseSha: this.baseSha,
|
|
@@ -3696,8 +3750,8 @@ class ValOps {
|
|
|
3696
3750
|
* both, so it is not sent. `null` anywhere that is not a proposal.
|
|
3697
3751
|
*/
|
|
3698
3752
|
async siteSourcesUnderSnapshot() {
|
|
3699
|
-
var _this$
|
|
3700
|
-
if (!((_this$
|
|
3753
|
+
var _this$options3;
|
|
3754
|
+
if (!((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.snapshotSources)) return null;
|
|
3701
3755
|
await this.initSources();
|
|
3702
3756
|
return this.siteSourcesBeforeSnapshot;
|
|
3703
3757
|
}
|
|
@@ -3764,7 +3818,13 @@ class ValOps {
|
|
|
3764
3818
|
}
|
|
3765
3819
|
const adopted = this.adoptedJsonEntries.get(moduleFilePath) ?? new Map();
|
|
3766
3820
|
for (const [entryKey, content] of Object.entries(entries)) {
|
|
3767
|
-
|
|
3821
|
+
// A commit's `null` is a deleted entry: see `patchedJsonEntries`.
|
|
3822
|
+
adopted.set(entryKey, content === null ? {
|
|
3823
|
+
kind: "deleted"
|
|
3824
|
+
} : {
|
|
3825
|
+
kind: "content",
|
|
3826
|
+
content
|
|
3827
|
+
});
|
|
3768
3828
|
}
|
|
3769
3829
|
this.adoptedJsonEntries.set(moduleFilePath, adopted);
|
|
3770
3830
|
}
|
|
@@ -3998,6 +4058,7 @@ class ValOps {
|
|
|
3998
4058
|
}
|
|
3999
4059
|
}
|
|
4000
4060
|
const loaded = await Promise.all([...requestedKeys, ...sourceKeys].map(async entryKey => {
|
|
4061
|
+
var _this$adoptedJsonEntr, _this$bundleSourcesBe;
|
|
4001
4062
|
const marker = record[entryKey];
|
|
4002
4063
|
if (marker !== undefined && !Internal.isJson(marker)) {
|
|
4003
4064
|
// Not a jsonValues entry — return the inlined value as-is (defensive).
|
|
@@ -4032,15 +4093,20 @@ class ValOps {
|
|
|
4032
4093
|
* The BASELINE only. Pending patches replay over it below exactly as
|
|
4033
4094
|
* they do over the thunk's answer.
|
|
4034
4095
|
*/
|
|
4035
|
-
const adopted = this.adoptedJsonEntries.get(moduleFilePath);
|
|
4036
|
-
if (adopted !== undefined
|
|
4037
|
-
const content = adopted.get(entryKey);
|
|
4096
|
+
const adopted = (_this$adoptedJsonEntr = this.adoptedJsonEntries.get(moduleFilePath)) === null || _this$adoptedJsonEntr === void 0 ? void 0 : _this$adoptedJsonEntr.get(entryKey);
|
|
4097
|
+
if (adopted !== undefined) {
|
|
4038
4098
|
return {
|
|
4039
4099
|
entryKey,
|
|
4040
|
-
baseContent:
|
|
4100
|
+
baseContent: adopted.kind === "deleted" ? undefined : adopted.content
|
|
4041
4101
|
};
|
|
4042
4102
|
}
|
|
4043
|
-
|
|
4103
|
+
/*
|
|
4104
|
+
* A snapshot's marker came as JSON, with no import behind it: the
|
|
4105
|
+
* entry the proposal did not save is the build's, behind the marker
|
|
4106
|
+
* the module evaluated to.
|
|
4107
|
+
*/
|
|
4108
|
+
const siteMarker = (_this$bundleSourcesBe = this.bundleSourcesBeforeSnapshot) === null || _this$bundleSourcesBe === void 0 || (_this$bundleSourcesBe = _this$bundleSourcesBe[moduleFilePath]) === null || _this$bundleSourcesBe === void 0 ? void 0 : _this$bundleSourcesBe[entryKey];
|
|
4109
|
+
const thunk = Internal.getJsonImport(marker) ?? (Internal.isJson(siteMarker) ? Internal.getJsonImport(siteMarker) : undefined);
|
|
4044
4110
|
if (!thunk) {
|
|
4045
4111
|
return {
|
|
4046
4112
|
entryKey,
|
|
@@ -5298,9 +5364,9 @@ class ValOps {
|
|
|
5298
5364
|
// https://github.com/microsoft/TypeScript/issues/36174
|
|
5299
5365
|
let sourceFileText = null;
|
|
5300
5366
|
if (tsChanged) {
|
|
5301
|
-
var _this$
|
|
5367
|
+
var _this$options4;
|
|
5302
5368
|
sourceFileText = unescape(tsSourceFile.getText(tsSourceFile).replace(/\\u/g, "%u"));
|
|
5303
|
-
if ((_this$
|
|
5369
|
+
if ((_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.formatter) {
|
|
5304
5370
|
try {
|
|
5305
5371
|
sourceFileText = await this.options.formatter(sourceFileText, path);
|
|
5306
5372
|
} catch (err) {
|
|
@@ -5312,13 +5378,13 @@ class ValOps {
|
|
|
5312
5378
|
}
|
|
5313
5379
|
const extraFiles = {};
|
|
5314
5380
|
for (const [jsonPath, content] of Array.from(jsonEntryContents)) {
|
|
5315
|
-
var _this$
|
|
5381
|
+
var _this$options5;
|
|
5316
5382
|
if (content === null) {
|
|
5317
5383
|
extraFiles[jsonPath] = null;
|
|
5318
5384
|
continue;
|
|
5319
5385
|
}
|
|
5320
5386
|
let jsonText = JSON.stringify(content, null, 2);
|
|
5321
|
-
if ((_this$
|
|
5387
|
+
if ((_this$options5 = this.options) !== null && _this$options5 !== void 0 && _this$options5.formatter) {
|
|
5322
5388
|
try {
|
|
5323
5389
|
jsonText = await this.options.formatter(jsonText, jsonPath);
|
|
5324
5390
|
} catch (err) {
|
|
@@ -8937,6 +9003,182 @@ function proposalFromEnv(env) {
|
|
|
8937
9003
|
};
|
|
8938
9004
|
}
|
|
8939
9005
|
|
|
9006
|
+
/**
|
|
9007
|
+
* A content version: the site's content, served by a build that bundles an
|
|
9008
|
+
* older one. valbuild/home `docs/hosted-publishing.md`, step 2.
|
|
9009
|
+
*
|
|
9010
|
+
* The platform hands over only what differs from the bundle -- the modules
|
|
9011
|
+
* whose Source changed and the Val files whose text did -- the way it hands a
|
|
9012
|
+
* proposal's address its saved content, and it is read the same way: the
|
|
9013
|
+
* snapshot source provider, over the bundle. What differs is the position: a
|
|
9014
|
+
* version names the publish that made it (`job`, or its `commit`), and that is
|
|
9015
|
+
* where this server says it sits, never at the build it runs in.
|
|
9016
|
+
*/
|
|
9017
|
+
|
|
9018
|
+
/**
|
|
9019
|
+
* The content version a platform hands over in its runtime env
|
|
9020
|
+
* (`VAL_CONTENT`), or `undefined` when it hands over none.
|
|
9021
|
+
*
|
|
9022
|
+
* Throws on one it cannot read rather than serving the bundle as though it
|
|
9023
|
+
* were the version: the site would show content older than what was
|
|
9024
|
+
* published, and nothing would say so.
|
|
9025
|
+
*
|
|
9026
|
+
* A version too large for one variable names others under `refs`: a module's
|
|
9027
|
+
* Source (JSON) or a file's text, each in a variable of its own --
|
|
9028
|
+
* `{ "refs": { "modules": { "/a.val.ts": "env://VAL_CONTENT_1" } } }`. Flat:
|
|
9029
|
+
* a referenced variable holds the value itself and is never read for further
|
|
9030
|
+
* references. Only `refs` is resolved, never a string inside Source, and only
|
|
9031
|
+
* `VAL_CONTENT_<n>` is ever read: Source is text editors write, and a
|
|
9032
|
+
* reference resolved in there would read any variable the isolate holds,
|
|
9033
|
+
* `VAL_SECRET` included, into a page. The scheme leaves room for another
|
|
9034
|
+
* place to read from; one this does not know is refused, not skipped.
|
|
9035
|
+
*/
|
|
9036
|
+
function contentFromEnv(env) {
|
|
9037
|
+
if (!env.VAL_CONTENT) {
|
|
9038
|
+
return undefined;
|
|
9039
|
+
}
|
|
9040
|
+
let parsed;
|
|
9041
|
+
try {
|
|
9042
|
+
parsed = JSON.parse(env.VAL_CONTENT);
|
|
9043
|
+
} catch (err) {
|
|
9044
|
+
throw new Error(`Val: VAL_CONTENT is not JSON (${err instanceof Error ? err.message : String(err)})`, {
|
|
9045
|
+
cause: err
|
|
9046
|
+
});
|
|
9047
|
+
}
|
|
9048
|
+
if (!isObject$1(parsed) || Array.isArray(parsed)) {
|
|
9049
|
+
throw new Error("Val: VAL_CONTENT is not an object");
|
|
9050
|
+
}
|
|
9051
|
+
const id = parsed["id"];
|
|
9052
|
+
if (typeof id !== "string" || id === "") {
|
|
9053
|
+
throw new Error("Val: VAL_CONTENT has no id");
|
|
9054
|
+
}
|
|
9055
|
+
const job = parsed["job"] ?? null;
|
|
9056
|
+
const commit = parsed["commit"] ?? null;
|
|
9057
|
+
if (job !== null && typeof job !== "string") {
|
|
9058
|
+
throw new Error(`Val: content version ${id}: job must be a string or null`);
|
|
9059
|
+
}
|
|
9060
|
+
if (commit !== null && typeof commit !== "string") {
|
|
9061
|
+
throw new Error(`Val: content version ${id}: commit must be a string or null`);
|
|
9062
|
+
}
|
|
9063
|
+
const modulesIn = parsed["modules"] ?? {};
|
|
9064
|
+
const filesIn = parsed["files"] ?? {};
|
|
9065
|
+
if (!isObject$1(modulesIn) || Array.isArray(modulesIn) || !isObject$1(filesIn) || Array.isArray(filesIn)) {
|
|
9066
|
+
throw new Error(`Val: content version ${id}: modules and files must be objects`);
|
|
9067
|
+
}
|
|
9068
|
+
const files = {};
|
|
9069
|
+
for (const [path, text] of Object.entries(filesIn)) {
|
|
9070
|
+
if (typeof text === "string") {
|
|
9071
|
+
files[path] = text;
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
9074
|
+
const modules = {
|
|
9075
|
+
...modulesIn
|
|
9076
|
+
};
|
|
9077
|
+
const refs = parsed["refs"] ?? {};
|
|
9078
|
+
if (!isObject$1(refs) || Array.isArray(refs)) {
|
|
9079
|
+
throw new Error(`Val: content version ${id}: refs must be an object`);
|
|
9080
|
+
}
|
|
9081
|
+
const read = (kind, path, ref) => {
|
|
9082
|
+
const where = `Val: content version ${id}: refs.${kind}["${path}"]`;
|
|
9083
|
+
if (typeof ref !== "string") {
|
|
9084
|
+
throw new Error(`${where} must be a string`);
|
|
9085
|
+
}
|
|
9086
|
+
if (!ref.startsWith(ENV_REF)) {
|
|
9087
|
+
throw new Error(`${where} is ${ref}: only ${ENV_REF}VAL_CONTENT_<n> is read`);
|
|
9088
|
+
}
|
|
9089
|
+
const name = ref.slice(ENV_REF.length);
|
|
9090
|
+
if (!CONTENT_VARIABLE.test(name)) {
|
|
9091
|
+
throw new Error(`${where} names ${name}: only VAL_CONTENT_<n> is read`);
|
|
9092
|
+
}
|
|
9093
|
+
const value = env[name];
|
|
9094
|
+
if (value === undefined) {
|
|
9095
|
+
throw new Error(`${where} names ${name}, which is not set`);
|
|
9096
|
+
}
|
|
9097
|
+
return {
|
|
9098
|
+
where,
|
|
9099
|
+
name,
|
|
9100
|
+
value
|
|
9101
|
+
};
|
|
9102
|
+
};
|
|
9103
|
+
const refModules = refs["modules"] ?? {};
|
|
9104
|
+
const refFiles = refs["files"] ?? {};
|
|
9105
|
+
if (!isObject$1(refModules) || Array.isArray(refModules) || !isObject$1(refFiles) || Array.isArray(refFiles)) {
|
|
9106
|
+
throw new Error(`Val: content version ${id}: refs.modules and refs.files must be objects`);
|
|
9107
|
+
}
|
|
9108
|
+
for (const [path, ref] of Object.entries(refModules)) {
|
|
9109
|
+
const {
|
|
9110
|
+
where,
|
|
9111
|
+
name,
|
|
9112
|
+
value
|
|
9113
|
+
} = read("modules", path, ref);
|
|
9114
|
+
if (Object.prototype.hasOwnProperty.call(modules, path)) {
|
|
9115
|
+
throw new Error(`${where}: ${path} is also in modules`);
|
|
9116
|
+
}
|
|
9117
|
+
try {
|
|
9118
|
+
modules[path] = JSON.parse(value);
|
|
9119
|
+
} catch (err) {
|
|
9120
|
+
throw new Error(`${where}: ${name} is not JSON (${err instanceof Error ? err.message : String(err)})`, {
|
|
9121
|
+
cause: err
|
|
9122
|
+
});
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
for (const [path, ref] of Object.entries(refFiles)) {
|
|
9126
|
+
const {
|
|
9127
|
+
where,
|
|
9128
|
+
value
|
|
9129
|
+
} = read("files", path, ref);
|
|
9130
|
+
// The handed object, not `files`: an inline value that is not text was
|
|
9131
|
+
// dropped from `files`, and is still a second naming of the path.
|
|
9132
|
+
if (Object.prototype.hasOwnProperty.call(filesIn, path)) {
|
|
9133
|
+
throw new Error(`${where}: ${path} is also in files`);
|
|
9134
|
+
}
|
|
9135
|
+
files[path] = value;
|
|
9136
|
+
}
|
|
9137
|
+
if (job === null && commit === null) {
|
|
9138
|
+
// Placed nowhere, Val would place it at the build it runs in -- the one
|
|
9139
|
+
// position a version must never take: its own changes would be replayed
|
|
9140
|
+
// over a snapshot that already holds them.
|
|
9141
|
+
throw new Error(`Val: content version ${id} must name a job or a commit, or it would be placed at the build it runs in`);
|
|
9142
|
+
}
|
|
9143
|
+
return {
|
|
9144
|
+
id,
|
|
9145
|
+
job,
|
|
9146
|
+
commit,
|
|
9147
|
+
modules,
|
|
9148
|
+
files
|
|
9149
|
+
};
|
|
9150
|
+
}
|
|
9151
|
+
|
|
9152
|
+
/** The one scheme a content version's `refs` may use, for now. */
|
|
9153
|
+
const ENV_REF = "env://";
|
|
9154
|
+
/** The only variables a reference may name. */
|
|
9155
|
+
const CONTENT_VARIABLE = /^VAL_CONTENT_[0-9]+$/;
|
|
9156
|
+
|
|
9157
|
+
/**
|
|
9158
|
+
* What a server or reader renders over the bundle: a proposal's saves over the
|
|
9159
|
+
* content version the site serves, either alone, or nothing.
|
|
9160
|
+
*
|
|
9161
|
+
* Layered rather than chosen. At a proposal's address the platform hands both
|
|
9162
|
+
* when the base the proposal was opened from serves content its build does not
|
|
9163
|
+
* bundle (a content-only publish), and the proposal saves only the modules it
|
|
9164
|
+
* changed: choosing the proposal alone would show every other module as the
|
|
9165
|
+
* build bundles it -- older than the site it was opened from.
|
|
9166
|
+
*/
|
|
9167
|
+
function layeredSnapshot(proposal, contentVersion) {
|
|
9168
|
+
if (proposal === undefined) return contentVersion;
|
|
9169
|
+
if (contentVersion === undefined) return proposal;
|
|
9170
|
+
return {
|
|
9171
|
+
modules: {
|
|
9172
|
+
...contentVersion.modules,
|
|
9173
|
+
...proposal.modules
|
|
9174
|
+
},
|
|
9175
|
+
files: {
|
|
9176
|
+
...contentVersion.files,
|
|
9177
|
+
...proposal.files
|
|
9178
|
+
}
|
|
9179
|
+
};
|
|
9180
|
+
}
|
|
9181
|
+
|
|
8940
9182
|
/**
|
|
8941
9183
|
* The proposal's saved Source, as Source: every module whose value is JSON.
|
|
8942
9184
|
* One place, so the server and the readers that render without it agree on
|
|
@@ -8951,6 +9193,116 @@ function proposalSnapshot(proposal) {
|
|
|
8951
9193
|
}
|
|
8952
9194
|
return snapshot;
|
|
8953
9195
|
}
|
|
9196
|
+
|
|
9197
|
+
/**
|
|
9198
|
+
* The `.jsonValues()` entries the proposal saved, by module and key.
|
|
9199
|
+
*
|
|
9200
|
+
* An entry's content is not in its module's Source -- that holds a marker per
|
|
9201
|
+
* entry -- but in the entry's own `*.val.json`, and a save writes that file:
|
|
9202
|
+
* it reaches the proposal's address as saved TEXT ({@link ValProposal.files}),
|
|
9203
|
+
* not as Source. So the snapshot alone shows the build's entries. This is the
|
|
9204
|
+
* other half: each saved `*.val.json`, read as the entry its module names.
|
|
9205
|
+
*
|
|
9206
|
+
* Which entry a file is comes from the module's `.val.ts`, whose
|
|
9207
|
+
* `c.json(() => import("..."))` thunks name the files -- the saved text if
|
|
9208
|
+
* the save rewrote it, else the build's own (`projectSource`). An entry whose
|
|
9209
|
+
* module cannot be read that way is left to the build, as before: this adds
|
|
9210
|
+
* what it can find and never fails the address.
|
|
9211
|
+
*/
|
|
9212
|
+
function proposalJsonEntries(proposal, projectSource) {
|
|
9213
|
+
const found = new Map();
|
|
9214
|
+
const savedJson = Object.keys(proposal.files).filter(path => path.endsWith(".val.json"));
|
|
9215
|
+
if (savedJson.length === 0) return found;
|
|
9216
|
+
const savedJsonPaths = new Set(savedJson);
|
|
9217
|
+
for (const [moduleFilePath, source] of Object.entries(proposal.modules)) {
|
|
9218
|
+
if (!holdsJsonMarkers(source)) continue;
|
|
9219
|
+
const relative = moduleFilePath.replace(/^\//, "");
|
|
9220
|
+
const text = proposal.files[relative] ?? (projectSource === null || projectSource === void 0 ? void 0 : projectSource[relative]);
|
|
9221
|
+
if (text === undefined) continue;
|
|
9222
|
+
const entries = jsonEntryPaths(relative, text);
|
|
9223
|
+
if (entries === null) continue;
|
|
9224
|
+
const byKey = new Map();
|
|
9225
|
+
for (const [key, importPath] of entries) {
|
|
9226
|
+
const jsonPath = resolveExistingJsonPath(moduleFilePath, importPath).replace(/^\//, "");
|
|
9227
|
+
if (!savedJsonPaths.has(jsonPath)) continue;
|
|
9228
|
+
const content = parseJson(proposal.files[jsonPath]);
|
|
9229
|
+
if (content !== undefined) byKey.set(key, content);
|
|
9230
|
+
}
|
|
9231
|
+
if (byKey.size > 0) found.set(moduleFilePath, byKey);
|
|
9232
|
+
}
|
|
9233
|
+
return found;
|
|
9234
|
+
}
|
|
9235
|
+
|
|
9236
|
+
/**
|
|
9237
|
+
* The snapshot as a page reads it: {@link proposalSnapshot}, with each saved
|
|
9238
|
+
* `.jsonValues()` entry's content in place of its marker -- the shape a draft
|
|
9239
|
+
* already has, where the entries it edits hold content and the rest are
|
|
9240
|
+
* markers the readers resolve from the build. See {@link proposalJsonEntries}.
|
|
9241
|
+
*
|
|
9242
|
+
* For the readers only. Val's own server keeps the markers
|
|
9243
|
+
* ({@link proposalSnapshot}): its patching works on them, and it takes the
|
|
9244
|
+
* saved entries separately.
|
|
9245
|
+
*/
|
|
9246
|
+
function proposalSnapshotForReaders(proposal, projectSource) {
|
|
9247
|
+
const snapshot = proposalSnapshot(proposal);
|
|
9248
|
+
for (const [moduleFilePath, entries] of proposalJsonEntries(proposal, projectSource)) {
|
|
9249
|
+
const source = snapshot[moduleFilePath];
|
|
9250
|
+
if (!isObject$1(source)) continue;
|
|
9251
|
+
const filled = {};
|
|
9252
|
+
for (const [key, value] of Object.entries(source)) {
|
|
9253
|
+
if (isJsonValue(value)) filled[key] = value;
|
|
9254
|
+
}
|
|
9255
|
+
for (const [key, content] of entries) {
|
|
9256
|
+
// Own keys only: an entry may be named `toString`.
|
|
9257
|
+
if (Object.prototype.hasOwnProperty.call(filled, key)) {
|
|
9258
|
+
filled[key] = content;
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
snapshot[moduleFilePath] = filled;
|
|
9262
|
+
}
|
|
9263
|
+
return snapshot;
|
|
9264
|
+
}
|
|
9265
|
+
|
|
9266
|
+
/** A record of `.jsonValues()` markers (`{ _type: "json" }`), at least one. */
|
|
9267
|
+
function holdsJsonMarkers(source) {
|
|
9268
|
+
return isObject$1(source) && Object.values(source).some(value => isObject$1(value) && value["_type"] === "json");
|
|
9269
|
+
}
|
|
9270
|
+
|
|
9271
|
+
/** Each entry key's import path, from a `.jsonValues()` module's text. */
|
|
9272
|
+
function jsonEntryPaths(fileName, text) {
|
|
9273
|
+
try {
|
|
9274
|
+
const analysis = analyzeValModule(ts.createSourceFile(fileName, text, ts.ScriptTarget.ES2015, true));
|
|
9275
|
+
if (result.isErr(analysis)) return null;
|
|
9276
|
+
const paths = new Map();
|
|
9277
|
+
for (const [key, entry] of analyzeJsonValuesEntries(analysis.value.source)) {
|
|
9278
|
+
paths.set(key, entry.importPath);
|
|
9279
|
+
}
|
|
9280
|
+
return paths;
|
|
9281
|
+
} catch {
|
|
9282
|
+
// Not a module this can read: its entries stay the build's.
|
|
9283
|
+
return null;
|
|
9284
|
+
}
|
|
9285
|
+
}
|
|
9286
|
+
function parseJson(text) {
|
|
9287
|
+
if (text === undefined) return undefined;
|
|
9288
|
+
try {
|
|
9289
|
+
const value = JSON.parse(text);
|
|
9290
|
+
return isMutableJsonValue(value) ? value : undefined;
|
|
9291
|
+
} catch {
|
|
9292
|
+
return undefined;
|
|
9293
|
+
}
|
|
9294
|
+
}
|
|
9295
|
+
|
|
9296
|
+
/** {@link isJsonValue}, for the mutable JSON a server patches. */
|
|
9297
|
+
function isMutableJsonValue(value) {
|
|
9298
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
9299
|
+
return true;
|
|
9300
|
+
}
|
|
9301
|
+
if (Array.isArray(value)) {
|
|
9302
|
+
return value.every(isMutableJsonValue);
|
|
9303
|
+
}
|
|
9304
|
+
return isObject$1(value) && Object.values(value).every(isMutableJsonValue);
|
|
9305
|
+
}
|
|
8954
9306
|
function isObject$1(value) {
|
|
8955
9307
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8956
9308
|
}
|
|
@@ -9321,6 +9673,8 @@ class ValOpsHttp extends ValOps {
|
|
|
9321
9673
|
publishBuild;
|
|
9322
9674
|
/** See `proposal` on {@link ValApiOptions}; `null` on the site. */
|
|
9323
9675
|
proposal;
|
|
9676
|
+
/** See `contentVersion` on {@link ValApiOptions}; `null` at a proposal's address. */
|
|
9677
|
+
contentVersion;
|
|
9324
9678
|
|
|
9325
9679
|
/** Did the host hand over the running build's source? See `projectSource`. */
|
|
9326
9680
|
embedsSource() {
|
|
@@ -9393,9 +9747,22 @@ class ValOpsHttp extends ValOps {
|
|
|
9393
9747
|
* getSettings / uploadRemoteFile / getPresignedAuthNonce.
|
|
9394
9748
|
*/
|
|
9395
9749
|
auth, valModules, options) {
|
|
9396
|
-
|
|
9750
|
+
/*
|
|
9751
|
+
* What replaces the bundle's Source: the content version the site serves,
|
|
9752
|
+
* and a proposal's saves over it at its address. Two layers rather than
|
|
9753
|
+
* one, because the version is the SITE -- what a proposal is compared
|
|
9754
|
+
* against -- and the proposal is what is being proposed.
|
|
9755
|
+
*/
|
|
9756
|
+
const snapshot = layeredSnapshot(options === null || options === void 0 ? void 0 : options.proposal, options === null || options === void 0 ? void 0 : options.contentVersion);
|
|
9757
|
+
super(valModules, options && snapshot ? {
|
|
9397
9758
|
...options,
|
|
9398
|
-
|
|
9759
|
+
...(options.proposal !== undefined ? {
|
|
9760
|
+
snapshotSources: proposalSnapshot(options.proposal)
|
|
9761
|
+
} : {}),
|
|
9762
|
+
...(options.contentVersion !== undefined ? {
|
|
9763
|
+
siteSnapshotSources: proposalSnapshot(options.contentVersion)
|
|
9764
|
+
} : {}),
|
|
9765
|
+
snapshotJsonEntries: proposalJsonEntries(snapshot, options.projectSource)
|
|
9399
9766
|
} : options);
|
|
9400
9767
|
this.contentUrl = contentUrl;
|
|
9401
9768
|
this.project = project;
|
|
@@ -9407,16 +9774,25 @@ class ValOpsHttp extends ValOps {
|
|
|
9407
9774
|
};
|
|
9408
9775
|
this.root = (options === null || options === void 0 ? void 0 : options.root) ?? "";
|
|
9409
9776
|
this.proposal = (options === null || options === void 0 ? void 0 : options.proposal) ?? null;
|
|
9777
|
+
this.contentVersion = this.proposal === null ? (options === null || options === void 0 ? void 0 : options.contentVersion) ?? null : null;
|
|
9410
9778
|
/*
|
|
9411
9779
|
* In a proposal, the text its saves wrote over the text the build was made
|
|
9412
9780
|
* from: the next save patches the last one's `.val.ts`, not the base's.
|
|
9781
|
+
* The same for a content version: the next publish patches what the last
|
|
9782
|
+
* one published, not what the build bundles.
|
|
9413
9783
|
*/
|
|
9414
|
-
this.projectSource =
|
|
9784
|
+
this.projectSource = snapshot !== undefined ? {
|
|
9415
9785
|
...((options === null || options === void 0 ? void 0 : options.projectSource) ?? {}),
|
|
9416
|
-
...
|
|
9786
|
+
...snapshot.files
|
|
9417
9787
|
} : (options === null || options === void 0 ? void 0 : options.projectSource) ?? null;
|
|
9418
9788
|
this.publishJob = (options === null || options === void 0 ? void 0 : options.publishJob) ?? null;
|
|
9419
|
-
|
|
9789
|
+
/*
|
|
9790
|
+
* Not with a content version: the content service places a build by the
|
|
9791
|
+
* build first (`getApplicablePatchesAndCommits`), and this build bundles an
|
|
9792
|
+
* older version than it serves -- placed there, the version's own changes
|
|
9793
|
+
* would be replayed over a snapshot that already holds them.
|
|
9794
|
+
*/
|
|
9795
|
+
this.publishBuild = this.contentVersion === null ? (options === null || options === void 0 ? void 0 : options.publishBuild) ?? null : null;
|
|
9420
9796
|
this.mirrorsSourceFiles = git !== null || this.projectSource !== null;
|
|
9421
9797
|
}
|
|
9422
9798
|
/**
|
|
@@ -10090,6 +10466,22 @@ class ValOpsHttp extends ValOps {
|
|
|
10090
10466
|
} : {})
|
|
10091
10467
|
};
|
|
10092
10468
|
}
|
|
10469
|
+
/*
|
|
10470
|
+
* A content version sits where the publish that made it does: its job,
|
|
10471
|
+
* which content places exactly, or its commit, once nothing else says.
|
|
10472
|
+
*/
|
|
10473
|
+
if (this.contentVersion !== null) {
|
|
10474
|
+
if (this.contentVersion.job !== null) {
|
|
10475
|
+
return {
|
|
10476
|
+
job: this.contentVersion.job
|
|
10477
|
+
};
|
|
10478
|
+
}
|
|
10479
|
+
if (this.contentVersion.commit !== null) {
|
|
10480
|
+
return {
|
|
10481
|
+
commit: this.contentVersion.commit
|
|
10482
|
+
};
|
|
10483
|
+
}
|
|
10484
|
+
}
|
|
10093
10485
|
if (this.git) {
|
|
10094
10486
|
return {
|
|
10095
10487
|
branch: this.git.branch,
|
|
@@ -13350,6 +13742,9 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
13350
13742
|
...(opts.proposal !== undefined ? {
|
|
13351
13743
|
proposal: opts.proposal
|
|
13352
13744
|
} : {}),
|
|
13745
|
+
...(opts.contentVersion !== undefined ? {
|
|
13746
|
+
contentVersion: opts.contentVersion
|
|
13747
|
+
} : {}),
|
|
13353
13748
|
project: maybeValProject,
|
|
13354
13749
|
valEnableRedirectUrl,
|
|
13355
13750
|
valDisableRedirectUrl,
|
|
@@ -13428,6 +13823,9 @@ function createValOps(valModules, options) {
|
|
|
13428
13823
|
} : {}),
|
|
13429
13824
|
...(options.proposal !== undefined ? {
|
|
13430
13825
|
proposal: options.proposal
|
|
13826
|
+
} : {}),
|
|
13827
|
+
...(options.contentVersion !== undefined ? {
|
|
13828
|
+
contentVersion: options.contentVersion
|
|
13431
13829
|
} : {})
|
|
13432
13830
|
});
|
|
13433
13831
|
}
|
|
@@ -23901,4 +24299,4 @@ function readCapturedReport(snapshotDir) {
|
|
|
23901
24299
|
return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
|
|
23902
24300
|
}
|
|
23903
24301
|
|
|
23904
|
-
export { APP_MODE_REQUIRES_REMOTE_FILES, DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, InMemoryPatchStore, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValOpsMemory, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createPrettierFormatter, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, extractVideoMetadata, extractVideoMetadataFromFile, filesOfVideo, filesOfVideosetEntry, findAndEvalValConfigFile, findDefaultExport, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, handleVideosetCheckAllFiles, handleVideosetCheckRemote, handleVideosetMetadata, handleVideosetUploadRemote, initHandlerOptions, isExternalResult, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, planJsonValuesEntryExtraction, proposalFromEnv, proposalSnapshot, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt, videosetEntryVideoSchema };
|
|
24302
|
+
export { APP_MODE_REQUIRES_REMOTE_FILES, DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, InMemoryPatchStore, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValOpsMemory, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, contentFromEnv, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createPrettierFormatter, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, extractVideoMetadata, extractVideoMetadataFromFile, filesOfVideo, filesOfVideosetEntry, findAndEvalValConfigFile, findDefaultExport, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, handleVideosetCheckAllFiles, handleVideosetCheckRemote, handleVideosetMetadata, handleVideosetUploadRemote, initHandlerOptions, isExternalResult, layeredSnapshot, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, planJsonValuesEntryExtraction, proposalFromEnv, proposalSnapshot, proposalSnapshotForReaders, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt, videosetEntryVideoSchema };
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./package.json": "./package.json"
|
|
17
17
|
},
|
|
18
18
|
"types": "dist/valbuild-server.cjs.d.ts",
|
|
19
|
-
"version": "0.144.
|
|
19
|
+
"version": "0.144.2",
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@prettier/sync": "^0.6.1",
|
|
22
22
|
"@types/jest": "^30.0.0",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
31
|
"zod": "^4.4.3",
|
|
32
32
|
"zod-validation-error": "^5.0.0",
|
|
33
|
-
"@valbuild/
|
|
34
|
-
"@valbuild/
|
|
35
|
-
"@valbuild/
|
|
33
|
+
"@valbuild/shared": "0.144.2",
|
|
34
|
+
"@valbuild/ui": "0.144.2",
|
|
35
|
+
"@valbuild/core": "0.144.0"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": "^20.19.0 || >=22"
|