@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
|
@@ -3593,6 +3593,12 @@ function findNestedJsonValuesModuleErrors(schemas) {
|
|
|
3593
3593
|
}
|
|
3594
3594
|
return errors;
|
|
3595
3595
|
}
|
|
3596
|
+
|
|
3597
|
+
/**
|
|
3598
|
+
* What a save told this instance a `.jsonValues()` entry holds: its content --
|
|
3599
|
+
* JSON `null` included -- or that it was deleted. See `adoptedJsonEntries`.
|
|
3600
|
+
*/
|
|
3601
|
+
|
|
3596
3602
|
// #region ValOps
|
|
3597
3603
|
class ValOps {
|
|
3598
3604
|
/** Sources from val modules, immutable (without patches or anything) */
|
|
@@ -3630,6 +3636,13 @@ class ValOps {
|
|
|
3630
3636
|
* {@link siteSourcesUnderSnapshot}, and only where there is a snapshot.
|
|
3631
3637
|
*/
|
|
3632
3638
|
siteSourcesBeforeSnapshot = null;
|
|
3639
|
+
/**
|
|
3640
|
+
* The same modules as the bundle evaluated them, imports and all: where a
|
|
3641
|
+
* `.jsonValues()` entry the snapshot did not save is read from. Not
|
|
3642
|
+
* {@link siteSourcesBeforeSnapshot}, whose markers may have come as JSON
|
|
3643
|
+
* from a content version, with no import behind them.
|
|
3644
|
+
*/
|
|
3645
|
+
bundleSourcesBeforeSnapshot = null;
|
|
3633
3646
|
/**
|
|
3634
3647
|
* What a save has told us each `.jsonValues()` entry now holds.
|
|
3635
3648
|
*
|
|
@@ -3641,7 +3654,9 @@ class ValOps {
|
|
|
3641
3654
|
* unlike a module source there is nothing to re-extract, because the memo was
|
|
3642
3655
|
* never holding the content in the first place.
|
|
3643
3656
|
*
|
|
3644
|
-
* `
|
|
3657
|
+
* `deleted` for an entry the commit deleted: tagged, not `null`, because an
|
|
3658
|
+
* entry's content may itself be JSON `null` (a nullable item), and a saved
|
|
3659
|
+
* `null` read back as "deleted" would report the entry missing.
|
|
3645
3660
|
*
|
|
3646
3661
|
* Never cleared: it describes what is on disk. A host rebuild makes a new
|
|
3647
3662
|
* instance, which is the right reset. Bounded by the project's entry count,
|
|
@@ -3660,6 +3675,16 @@ class ValOps {
|
|
|
3660
3675
|
this.modulesErrors = null;
|
|
3661
3676
|
this.shaEntries = null;
|
|
3662
3677
|
this.shaModuleErrors = null;
|
|
3678
|
+
for (const [moduleFilePath, entries] of (options === null || options === void 0 ? void 0 : options.snapshotJsonEntries) ?? []) {
|
|
3679
|
+
const adopted = new Map();
|
|
3680
|
+
for (const [entryKey, content] of entries) {
|
|
3681
|
+
adopted.set(entryKey, {
|
|
3682
|
+
kind: "content",
|
|
3683
|
+
content
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
this.adoptedJsonEntries.set(moduleFilePath, adopted);
|
|
3687
|
+
}
|
|
3663
3688
|
}
|
|
3664
3689
|
|
|
3665
3690
|
// #region stat
|
|
@@ -3676,7 +3701,7 @@ class ValOps {
|
|
|
3676
3701
|
// #region initTree
|
|
3677
3702
|
async initSources() {
|
|
3678
3703
|
if (this.baseSha === null || this.sourcesSha === null || this.configSha === null || this.schemaSha === null || this.sources === null || this.schemas === null || this.modulesErrors === null) {
|
|
3679
|
-
var _this$options;
|
|
3704
|
+
var _this$options, _this$options2;
|
|
3680
3705
|
const extracted = await core.extractValModules(this.valModules);
|
|
3681
3706
|
const moduleErrors = extracted.moduleErrors.concat(findNestedJsonValuesModuleErrors(extracted.schemas));
|
|
3682
3707
|
this.sources = extracted.sources;
|
|
@@ -3694,16 +3719,45 @@ class ValOps {
|
|
|
3694
3719
|
* being served, and a base SHA that still described the bundle would
|
|
3695
3720
|
* say the snapshot's content was the bundle's.
|
|
3696
3721
|
*/
|
|
3697
|
-
|
|
3722
|
+
const siteLayer = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.siteSnapshotSources;
|
|
3723
|
+
const snapshotLayer = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.snapshotSources;
|
|
3724
|
+
/*
|
|
3725
|
+
* The bundle's own Source for every module either layer replaces: a
|
|
3726
|
+
* replaced `.jsonValues()` module's markers came as JSON, and an entry
|
|
3727
|
+
* neither layer saved is read through the bundle's marker, which still
|
|
3728
|
+
* has its `import()`.
|
|
3729
|
+
*/
|
|
3730
|
+
if (siteLayer || snapshotLayer) {
|
|
3731
|
+
const bundle = {};
|
|
3732
|
+
for (const moduleFilePath of Object.keys({
|
|
3733
|
+
...siteLayer,
|
|
3734
|
+
...snapshotLayer
|
|
3735
|
+
})) {
|
|
3736
|
+
const path = moduleFilePath;
|
|
3737
|
+
const bundled = extracted.sources[path];
|
|
3738
|
+
if (bundled !== undefined) {
|
|
3739
|
+
bundle[path] = bundled;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
this.bundleSourcesBeforeSnapshot = bundle;
|
|
3743
|
+
}
|
|
3744
|
+
// What a proposal is compared against: the site, only where there is a proposal.
|
|
3745
|
+
if (snapshotLayer) {
|
|
3698
3746
|
const site = {};
|
|
3699
|
-
for (const moduleFilePath of Object.keys(
|
|
3700
|
-
const
|
|
3747
|
+
for (const moduleFilePath of Object.keys(snapshotLayer)) {
|
|
3748
|
+
const path = moduleFilePath;
|
|
3749
|
+
const source = (siteLayer === null || siteLayer === void 0 ? void 0 : siteLayer[path]) ?? extracted.sources[path];
|
|
3701
3750
|
if (source !== undefined) {
|
|
3702
|
-
site[
|
|
3751
|
+
site[path] = source;
|
|
3703
3752
|
}
|
|
3704
3753
|
}
|
|
3705
3754
|
this.siteSourcesBeforeSnapshot = site;
|
|
3706
|
-
|
|
3755
|
+
}
|
|
3756
|
+
if (siteLayer || snapshotLayer) {
|
|
3757
|
+
this.promoteCommittedSources({
|
|
3758
|
+
...siteLayer,
|
|
3759
|
+
...snapshotLayer
|
|
3760
|
+
});
|
|
3707
3761
|
}
|
|
3708
3762
|
return {
|
|
3709
3763
|
baseSha: this.baseSha,
|
|
@@ -3733,8 +3787,8 @@ class ValOps {
|
|
|
3733
3787
|
* both, so it is not sent. `null` anywhere that is not a proposal.
|
|
3734
3788
|
*/
|
|
3735
3789
|
async siteSourcesUnderSnapshot() {
|
|
3736
|
-
var _this$
|
|
3737
|
-
if (!((_this$
|
|
3790
|
+
var _this$options3;
|
|
3791
|
+
if (!((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.snapshotSources)) return null;
|
|
3738
3792
|
await this.initSources();
|
|
3739
3793
|
return this.siteSourcesBeforeSnapshot;
|
|
3740
3794
|
}
|
|
@@ -3801,7 +3855,13 @@ class ValOps {
|
|
|
3801
3855
|
}
|
|
3802
3856
|
const adopted = this.adoptedJsonEntries.get(moduleFilePath) ?? new Map();
|
|
3803
3857
|
for (const [entryKey, content] of Object.entries(entries)) {
|
|
3804
|
-
|
|
3858
|
+
// A commit's `null` is a deleted entry: see `patchedJsonEntries`.
|
|
3859
|
+
adopted.set(entryKey, content === null ? {
|
|
3860
|
+
kind: "deleted"
|
|
3861
|
+
} : {
|
|
3862
|
+
kind: "content",
|
|
3863
|
+
content
|
|
3864
|
+
});
|
|
3805
3865
|
}
|
|
3806
3866
|
this.adoptedJsonEntries.set(moduleFilePath, adopted);
|
|
3807
3867
|
}
|
|
@@ -4035,6 +4095,7 @@ class ValOps {
|
|
|
4035
4095
|
}
|
|
4036
4096
|
}
|
|
4037
4097
|
const loaded = await Promise.all([...requestedKeys, ...sourceKeys].map(async entryKey => {
|
|
4098
|
+
var _this$adoptedJsonEntr, _this$bundleSourcesBe;
|
|
4038
4099
|
const marker = record[entryKey];
|
|
4039
4100
|
if (marker !== undefined && !core.Internal.isJson(marker)) {
|
|
4040
4101
|
// Not a jsonValues entry — return the inlined value as-is (defensive).
|
|
@@ -4069,15 +4130,20 @@ class ValOps {
|
|
|
4069
4130
|
* The BASELINE only. Pending patches replay over it below exactly as
|
|
4070
4131
|
* they do over the thunk's answer.
|
|
4071
4132
|
*/
|
|
4072
|
-
const adopted = this.adoptedJsonEntries.get(moduleFilePath);
|
|
4073
|
-
if (adopted !== undefined
|
|
4074
|
-
const content = adopted.get(entryKey);
|
|
4133
|
+
const adopted = (_this$adoptedJsonEntr = this.adoptedJsonEntries.get(moduleFilePath)) === null || _this$adoptedJsonEntr === void 0 ? void 0 : _this$adoptedJsonEntr.get(entryKey);
|
|
4134
|
+
if (adopted !== undefined) {
|
|
4075
4135
|
return {
|
|
4076
4136
|
entryKey,
|
|
4077
|
-
baseContent:
|
|
4137
|
+
baseContent: adopted.kind === "deleted" ? undefined : adopted.content
|
|
4078
4138
|
};
|
|
4079
4139
|
}
|
|
4080
|
-
|
|
4140
|
+
/*
|
|
4141
|
+
* A snapshot's marker came as JSON, with no import behind it: the
|
|
4142
|
+
* entry the proposal did not save is the build's, behind the marker
|
|
4143
|
+
* the module evaluated to.
|
|
4144
|
+
*/
|
|
4145
|
+
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];
|
|
4146
|
+
const thunk = core.Internal.getJsonImport(marker) ?? (core.Internal.isJson(siteMarker) ? core.Internal.getJsonImport(siteMarker) : undefined);
|
|
4081
4147
|
if (!thunk) {
|
|
4082
4148
|
return {
|
|
4083
4149
|
entryKey,
|
|
@@ -5335,9 +5401,9 @@ class ValOps {
|
|
|
5335
5401
|
// https://github.com/microsoft/TypeScript/issues/36174
|
|
5336
5402
|
let sourceFileText = null;
|
|
5337
5403
|
if (tsChanged) {
|
|
5338
|
-
var _this$
|
|
5404
|
+
var _this$options4;
|
|
5339
5405
|
sourceFileText = unescape(tsSourceFile.getText(tsSourceFile).replace(/\\u/g, "%u"));
|
|
5340
|
-
if ((_this$
|
|
5406
|
+
if ((_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.formatter) {
|
|
5341
5407
|
try {
|
|
5342
5408
|
sourceFileText = await this.options.formatter(sourceFileText, path);
|
|
5343
5409
|
} catch (err) {
|
|
@@ -5349,13 +5415,13 @@ class ValOps {
|
|
|
5349
5415
|
}
|
|
5350
5416
|
const extraFiles = {};
|
|
5351
5417
|
for (const [jsonPath, content] of Array.from(jsonEntryContents)) {
|
|
5352
|
-
var _this$
|
|
5418
|
+
var _this$options5;
|
|
5353
5419
|
if (content === null) {
|
|
5354
5420
|
extraFiles[jsonPath] = null;
|
|
5355
5421
|
continue;
|
|
5356
5422
|
}
|
|
5357
5423
|
let jsonText = JSON.stringify(content, null, 2);
|
|
5358
|
-
if ((_this$
|
|
5424
|
+
if ((_this$options5 = this.options) !== null && _this$options5 !== void 0 && _this$options5.formatter) {
|
|
5359
5425
|
try {
|
|
5360
5426
|
jsonText = await this.options.formatter(jsonText, jsonPath);
|
|
5361
5427
|
} catch (err) {
|
|
@@ -8974,6 +9040,182 @@ function proposalFromEnv(env) {
|
|
|
8974
9040
|
};
|
|
8975
9041
|
}
|
|
8976
9042
|
|
|
9043
|
+
/**
|
|
9044
|
+
* A content version: the site's content, served by a build that bundles an
|
|
9045
|
+
* older one. valbuild/home `docs/hosted-publishing.md`, step 2.
|
|
9046
|
+
*
|
|
9047
|
+
* The platform hands over only what differs from the bundle -- the modules
|
|
9048
|
+
* whose Source changed and the Val files whose text did -- the way it hands a
|
|
9049
|
+
* proposal's address its saved content, and it is read the same way: the
|
|
9050
|
+
* snapshot source provider, over the bundle. What differs is the position: a
|
|
9051
|
+
* version names the publish that made it (`job`, or its `commit`), and that is
|
|
9052
|
+
* where this server says it sits, never at the build it runs in.
|
|
9053
|
+
*/
|
|
9054
|
+
|
|
9055
|
+
/**
|
|
9056
|
+
* The content version a platform hands over in its runtime env
|
|
9057
|
+
* (`VAL_CONTENT`), or `undefined` when it hands over none.
|
|
9058
|
+
*
|
|
9059
|
+
* Throws on one it cannot read rather than serving the bundle as though it
|
|
9060
|
+
* were the version: the site would show content older than what was
|
|
9061
|
+
* published, and nothing would say so.
|
|
9062
|
+
*
|
|
9063
|
+
* A version too large for one variable names others under `refs`: a module's
|
|
9064
|
+
* Source (JSON) or a file's text, each in a variable of its own --
|
|
9065
|
+
* `{ "refs": { "modules": { "/a.val.ts": "env://VAL_CONTENT_1" } } }`. Flat:
|
|
9066
|
+
* a referenced variable holds the value itself and is never read for further
|
|
9067
|
+
* references. Only `refs` is resolved, never a string inside Source, and only
|
|
9068
|
+
* `VAL_CONTENT_<n>` is ever read: Source is text editors write, and a
|
|
9069
|
+
* reference resolved in there would read any variable the isolate holds,
|
|
9070
|
+
* `VAL_SECRET` included, into a page. The scheme leaves room for another
|
|
9071
|
+
* place to read from; one this does not know is refused, not skipped.
|
|
9072
|
+
*/
|
|
9073
|
+
function contentFromEnv(env) {
|
|
9074
|
+
if (!env.VAL_CONTENT) {
|
|
9075
|
+
return undefined;
|
|
9076
|
+
}
|
|
9077
|
+
let parsed;
|
|
9078
|
+
try {
|
|
9079
|
+
parsed = JSON.parse(env.VAL_CONTENT);
|
|
9080
|
+
} catch (err) {
|
|
9081
|
+
throw new Error(`Val: VAL_CONTENT is not JSON (${err instanceof Error ? err.message : String(err)})`, {
|
|
9082
|
+
cause: err
|
|
9083
|
+
});
|
|
9084
|
+
}
|
|
9085
|
+
if (!isObject$1(parsed) || Array.isArray(parsed)) {
|
|
9086
|
+
throw new Error("Val: VAL_CONTENT is not an object");
|
|
9087
|
+
}
|
|
9088
|
+
const id = parsed["id"];
|
|
9089
|
+
if (typeof id !== "string" || id === "") {
|
|
9090
|
+
throw new Error("Val: VAL_CONTENT has no id");
|
|
9091
|
+
}
|
|
9092
|
+
const job = parsed["job"] ?? null;
|
|
9093
|
+
const commit = parsed["commit"] ?? null;
|
|
9094
|
+
if (job !== null && typeof job !== "string") {
|
|
9095
|
+
throw new Error(`Val: content version ${id}: job must be a string or null`);
|
|
9096
|
+
}
|
|
9097
|
+
if (commit !== null && typeof commit !== "string") {
|
|
9098
|
+
throw new Error(`Val: content version ${id}: commit must be a string or null`);
|
|
9099
|
+
}
|
|
9100
|
+
const modulesIn = parsed["modules"] ?? {};
|
|
9101
|
+
const filesIn = parsed["files"] ?? {};
|
|
9102
|
+
if (!isObject$1(modulesIn) || Array.isArray(modulesIn) || !isObject$1(filesIn) || Array.isArray(filesIn)) {
|
|
9103
|
+
throw new Error(`Val: content version ${id}: modules and files must be objects`);
|
|
9104
|
+
}
|
|
9105
|
+
const files = {};
|
|
9106
|
+
for (const [path, text] of Object.entries(filesIn)) {
|
|
9107
|
+
if (typeof text === "string") {
|
|
9108
|
+
files[path] = text;
|
|
9109
|
+
}
|
|
9110
|
+
}
|
|
9111
|
+
const modules = {
|
|
9112
|
+
...modulesIn
|
|
9113
|
+
};
|
|
9114
|
+
const refs = parsed["refs"] ?? {};
|
|
9115
|
+
if (!isObject$1(refs) || Array.isArray(refs)) {
|
|
9116
|
+
throw new Error(`Val: content version ${id}: refs must be an object`);
|
|
9117
|
+
}
|
|
9118
|
+
const read = (kind, path, ref) => {
|
|
9119
|
+
const where = `Val: content version ${id}: refs.${kind}["${path}"]`;
|
|
9120
|
+
if (typeof ref !== "string") {
|
|
9121
|
+
throw new Error(`${where} must be a string`);
|
|
9122
|
+
}
|
|
9123
|
+
if (!ref.startsWith(ENV_REF)) {
|
|
9124
|
+
throw new Error(`${where} is ${ref}: only ${ENV_REF}VAL_CONTENT_<n> is read`);
|
|
9125
|
+
}
|
|
9126
|
+
const name = ref.slice(ENV_REF.length);
|
|
9127
|
+
if (!CONTENT_VARIABLE.test(name)) {
|
|
9128
|
+
throw new Error(`${where} names ${name}: only VAL_CONTENT_<n> is read`);
|
|
9129
|
+
}
|
|
9130
|
+
const value = env[name];
|
|
9131
|
+
if (value === undefined) {
|
|
9132
|
+
throw new Error(`${where} names ${name}, which is not set`);
|
|
9133
|
+
}
|
|
9134
|
+
return {
|
|
9135
|
+
where,
|
|
9136
|
+
name,
|
|
9137
|
+
value
|
|
9138
|
+
};
|
|
9139
|
+
};
|
|
9140
|
+
const refModules = refs["modules"] ?? {};
|
|
9141
|
+
const refFiles = refs["files"] ?? {};
|
|
9142
|
+
if (!isObject$1(refModules) || Array.isArray(refModules) || !isObject$1(refFiles) || Array.isArray(refFiles)) {
|
|
9143
|
+
throw new Error(`Val: content version ${id}: refs.modules and refs.files must be objects`);
|
|
9144
|
+
}
|
|
9145
|
+
for (const [path, ref] of Object.entries(refModules)) {
|
|
9146
|
+
const {
|
|
9147
|
+
where,
|
|
9148
|
+
name,
|
|
9149
|
+
value
|
|
9150
|
+
} = read("modules", path, ref);
|
|
9151
|
+
if (Object.prototype.hasOwnProperty.call(modules, path)) {
|
|
9152
|
+
throw new Error(`${where}: ${path} is also in modules`);
|
|
9153
|
+
}
|
|
9154
|
+
try {
|
|
9155
|
+
modules[path] = JSON.parse(value);
|
|
9156
|
+
} catch (err) {
|
|
9157
|
+
throw new Error(`${where}: ${name} is not JSON (${err instanceof Error ? err.message : String(err)})`, {
|
|
9158
|
+
cause: err
|
|
9159
|
+
});
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
for (const [path, ref] of Object.entries(refFiles)) {
|
|
9163
|
+
const {
|
|
9164
|
+
where,
|
|
9165
|
+
value
|
|
9166
|
+
} = read("files", path, ref);
|
|
9167
|
+
// The handed object, not `files`: an inline value that is not text was
|
|
9168
|
+
// dropped from `files`, and is still a second naming of the path.
|
|
9169
|
+
if (Object.prototype.hasOwnProperty.call(filesIn, path)) {
|
|
9170
|
+
throw new Error(`${where}: ${path} is also in files`);
|
|
9171
|
+
}
|
|
9172
|
+
files[path] = value;
|
|
9173
|
+
}
|
|
9174
|
+
if (job === null && commit === null) {
|
|
9175
|
+
// Placed nowhere, Val would place it at the build it runs in -- the one
|
|
9176
|
+
// position a version must never take: its own changes would be replayed
|
|
9177
|
+
// over a snapshot that already holds them.
|
|
9178
|
+
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`);
|
|
9179
|
+
}
|
|
9180
|
+
return {
|
|
9181
|
+
id,
|
|
9182
|
+
job,
|
|
9183
|
+
commit,
|
|
9184
|
+
modules,
|
|
9185
|
+
files
|
|
9186
|
+
};
|
|
9187
|
+
}
|
|
9188
|
+
|
|
9189
|
+
/** The one scheme a content version's `refs` may use, for now. */
|
|
9190
|
+
const ENV_REF = "env://";
|
|
9191
|
+
/** The only variables a reference may name. */
|
|
9192
|
+
const CONTENT_VARIABLE = /^VAL_CONTENT_[0-9]+$/;
|
|
9193
|
+
|
|
9194
|
+
/**
|
|
9195
|
+
* What a server or reader renders over the bundle: a proposal's saves over the
|
|
9196
|
+
* content version the site serves, either alone, or nothing.
|
|
9197
|
+
*
|
|
9198
|
+
* Layered rather than chosen. At a proposal's address the platform hands both
|
|
9199
|
+
* when the base the proposal was opened from serves content its build does not
|
|
9200
|
+
* bundle (a content-only publish), and the proposal saves only the modules it
|
|
9201
|
+
* changed: choosing the proposal alone would show every other module as the
|
|
9202
|
+
* build bundles it -- older than the site it was opened from.
|
|
9203
|
+
*/
|
|
9204
|
+
function layeredSnapshot(proposal, contentVersion) {
|
|
9205
|
+
if (proposal === undefined) return contentVersion;
|
|
9206
|
+
if (contentVersion === undefined) return proposal;
|
|
9207
|
+
return {
|
|
9208
|
+
modules: {
|
|
9209
|
+
...contentVersion.modules,
|
|
9210
|
+
...proposal.modules
|
|
9211
|
+
},
|
|
9212
|
+
files: {
|
|
9213
|
+
...contentVersion.files,
|
|
9214
|
+
...proposal.files
|
|
9215
|
+
}
|
|
9216
|
+
};
|
|
9217
|
+
}
|
|
9218
|
+
|
|
8977
9219
|
/**
|
|
8978
9220
|
* The proposal's saved Source, as Source: every module whose value is JSON.
|
|
8979
9221
|
* One place, so the server and the readers that render without it agree on
|
|
@@ -8988,6 +9230,116 @@ function proposalSnapshot(proposal) {
|
|
|
8988
9230
|
}
|
|
8989
9231
|
return snapshot;
|
|
8990
9232
|
}
|
|
9233
|
+
|
|
9234
|
+
/**
|
|
9235
|
+
* The `.jsonValues()` entries the proposal saved, by module and key.
|
|
9236
|
+
*
|
|
9237
|
+
* An entry's content is not in its module's Source -- that holds a marker per
|
|
9238
|
+
* entry -- but in the entry's own `*.val.json`, and a save writes that file:
|
|
9239
|
+
* it reaches the proposal's address as saved TEXT ({@link ValProposal.files}),
|
|
9240
|
+
* not as Source. So the snapshot alone shows the build's entries. This is the
|
|
9241
|
+
* other half: each saved `*.val.json`, read as the entry its module names.
|
|
9242
|
+
*
|
|
9243
|
+
* Which entry a file is comes from the module's `.val.ts`, whose
|
|
9244
|
+
* `c.json(() => import("..."))` thunks name the files -- the saved text if
|
|
9245
|
+
* the save rewrote it, else the build's own (`projectSource`). An entry whose
|
|
9246
|
+
* module cannot be read that way is left to the build, as before: this adds
|
|
9247
|
+
* what it can find and never fails the address.
|
|
9248
|
+
*/
|
|
9249
|
+
function proposalJsonEntries(proposal, projectSource) {
|
|
9250
|
+
const found = new Map();
|
|
9251
|
+
const savedJson = Object.keys(proposal.files).filter(path => path.endsWith(".val.json"));
|
|
9252
|
+
if (savedJson.length === 0) return found;
|
|
9253
|
+
const savedJsonPaths = new Set(savedJson);
|
|
9254
|
+
for (const [moduleFilePath, source] of Object.entries(proposal.modules)) {
|
|
9255
|
+
if (!holdsJsonMarkers(source)) continue;
|
|
9256
|
+
const relative = moduleFilePath.replace(/^\//, "");
|
|
9257
|
+
const text = proposal.files[relative] ?? (projectSource === null || projectSource === void 0 ? void 0 : projectSource[relative]);
|
|
9258
|
+
if (text === undefined) continue;
|
|
9259
|
+
const entries = jsonEntryPaths(relative, text);
|
|
9260
|
+
if (entries === null) continue;
|
|
9261
|
+
const byKey = new Map();
|
|
9262
|
+
for (const [key, importPath] of entries) {
|
|
9263
|
+
const jsonPath = resolveExistingJsonPath(moduleFilePath, importPath).replace(/^\//, "");
|
|
9264
|
+
if (!savedJsonPaths.has(jsonPath)) continue;
|
|
9265
|
+
const content = parseJson(proposal.files[jsonPath]);
|
|
9266
|
+
if (content !== undefined) byKey.set(key, content);
|
|
9267
|
+
}
|
|
9268
|
+
if (byKey.size > 0) found.set(moduleFilePath, byKey);
|
|
9269
|
+
}
|
|
9270
|
+
return found;
|
|
9271
|
+
}
|
|
9272
|
+
|
|
9273
|
+
/**
|
|
9274
|
+
* The snapshot as a page reads it: {@link proposalSnapshot}, with each saved
|
|
9275
|
+
* `.jsonValues()` entry's content in place of its marker -- the shape a draft
|
|
9276
|
+
* already has, where the entries it edits hold content and the rest are
|
|
9277
|
+
* markers the readers resolve from the build. See {@link proposalJsonEntries}.
|
|
9278
|
+
*
|
|
9279
|
+
* For the readers only. Val's own server keeps the markers
|
|
9280
|
+
* ({@link proposalSnapshot}): its patching works on them, and it takes the
|
|
9281
|
+
* saved entries separately.
|
|
9282
|
+
*/
|
|
9283
|
+
function proposalSnapshotForReaders(proposal, projectSource) {
|
|
9284
|
+
const snapshot = proposalSnapshot(proposal);
|
|
9285
|
+
for (const [moduleFilePath, entries] of proposalJsonEntries(proposal, projectSource)) {
|
|
9286
|
+
const source = snapshot[moduleFilePath];
|
|
9287
|
+
if (!isObject$1(source)) continue;
|
|
9288
|
+
const filled = {};
|
|
9289
|
+
for (const [key, value] of Object.entries(source)) {
|
|
9290
|
+
if (isJsonValue(value)) filled[key] = value;
|
|
9291
|
+
}
|
|
9292
|
+
for (const [key, content] of entries) {
|
|
9293
|
+
// Own keys only: an entry may be named `toString`.
|
|
9294
|
+
if (Object.prototype.hasOwnProperty.call(filled, key)) {
|
|
9295
|
+
filled[key] = content;
|
|
9296
|
+
}
|
|
9297
|
+
}
|
|
9298
|
+
snapshot[moduleFilePath] = filled;
|
|
9299
|
+
}
|
|
9300
|
+
return snapshot;
|
|
9301
|
+
}
|
|
9302
|
+
|
|
9303
|
+
/** A record of `.jsonValues()` markers (`{ _type: "json" }`), at least one. */
|
|
9304
|
+
function holdsJsonMarkers(source) {
|
|
9305
|
+
return isObject$1(source) && Object.values(source).some(value => isObject$1(value) && value["_type"] === "json");
|
|
9306
|
+
}
|
|
9307
|
+
|
|
9308
|
+
/** Each entry key's import path, from a `.jsonValues()` module's text. */
|
|
9309
|
+
function jsonEntryPaths(fileName, text) {
|
|
9310
|
+
try {
|
|
9311
|
+
const analysis = analyzeValModule(ts__default["default"].createSourceFile(fileName, text, ts__default["default"].ScriptTarget.ES2015, true));
|
|
9312
|
+
if (fp.result.isErr(analysis)) return null;
|
|
9313
|
+
const paths = new Map();
|
|
9314
|
+
for (const [key, entry] of analyzeJsonValuesEntries(analysis.value.source)) {
|
|
9315
|
+
paths.set(key, entry.importPath);
|
|
9316
|
+
}
|
|
9317
|
+
return paths;
|
|
9318
|
+
} catch {
|
|
9319
|
+
// Not a module this can read: its entries stay the build's.
|
|
9320
|
+
return null;
|
|
9321
|
+
}
|
|
9322
|
+
}
|
|
9323
|
+
function parseJson(text) {
|
|
9324
|
+
if (text === undefined) return undefined;
|
|
9325
|
+
try {
|
|
9326
|
+
const value = JSON.parse(text);
|
|
9327
|
+
return isMutableJsonValue(value) ? value : undefined;
|
|
9328
|
+
} catch {
|
|
9329
|
+
return undefined;
|
|
9330
|
+
}
|
|
9331
|
+
}
|
|
9332
|
+
|
|
9333
|
+
/** {@link isJsonValue}, for the mutable JSON a server patches. */
|
|
9334
|
+
function isMutableJsonValue(value) {
|
|
9335
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
9336
|
+
return true;
|
|
9337
|
+
}
|
|
9338
|
+
if (Array.isArray(value)) {
|
|
9339
|
+
return value.every(isMutableJsonValue);
|
|
9340
|
+
}
|
|
9341
|
+
return isObject$1(value) && Object.values(value).every(isMutableJsonValue);
|
|
9342
|
+
}
|
|
8991
9343
|
function isObject$1(value) {
|
|
8992
9344
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8993
9345
|
}
|
|
@@ -9358,6 +9710,8 @@ class ValOpsHttp extends ValOps {
|
|
|
9358
9710
|
publishBuild;
|
|
9359
9711
|
/** See `proposal` on {@link ValApiOptions}; `null` on the site. */
|
|
9360
9712
|
proposal;
|
|
9713
|
+
/** See `contentVersion` on {@link ValApiOptions}; `null` at a proposal's address. */
|
|
9714
|
+
contentVersion;
|
|
9361
9715
|
|
|
9362
9716
|
/** Did the host hand over the running build's source? See `projectSource`. */
|
|
9363
9717
|
embedsSource() {
|
|
@@ -9430,9 +9784,22 @@ class ValOpsHttp extends ValOps {
|
|
|
9430
9784
|
* getSettings / uploadRemoteFile / getPresignedAuthNonce.
|
|
9431
9785
|
*/
|
|
9432
9786
|
auth, valModules, options) {
|
|
9433
|
-
|
|
9787
|
+
/*
|
|
9788
|
+
* What replaces the bundle's Source: the content version the site serves,
|
|
9789
|
+
* and a proposal's saves over it at its address. Two layers rather than
|
|
9790
|
+
* one, because the version is the SITE -- what a proposal is compared
|
|
9791
|
+
* against -- and the proposal is what is being proposed.
|
|
9792
|
+
*/
|
|
9793
|
+
const snapshot = layeredSnapshot(options === null || options === void 0 ? void 0 : options.proposal, options === null || options === void 0 ? void 0 : options.contentVersion);
|
|
9794
|
+
super(valModules, options && snapshot ? {
|
|
9434
9795
|
...options,
|
|
9435
|
-
|
|
9796
|
+
...(options.proposal !== undefined ? {
|
|
9797
|
+
snapshotSources: proposalSnapshot(options.proposal)
|
|
9798
|
+
} : {}),
|
|
9799
|
+
...(options.contentVersion !== undefined ? {
|
|
9800
|
+
siteSnapshotSources: proposalSnapshot(options.contentVersion)
|
|
9801
|
+
} : {}),
|
|
9802
|
+
snapshotJsonEntries: proposalJsonEntries(snapshot, options.projectSource)
|
|
9436
9803
|
} : options);
|
|
9437
9804
|
this.contentUrl = contentUrl;
|
|
9438
9805
|
this.project = project;
|
|
@@ -9444,16 +9811,25 @@ class ValOpsHttp extends ValOps {
|
|
|
9444
9811
|
};
|
|
9445
9812
|
this.root = (options === null || options === void 0 ? void 0 : options.root) ?? "";
|
|
9446
9813
|
this.proposal = (options === null || options === void 0 ? void 0 : options.proposal) ?? null;
|
|
9814
|
+
this.contentVersion = this.proposal === null ? (options === null || options === void 0 ? void 0 : options.contentVersion) ?? null : null;
|
|
9447
9815
|
/*
|
|
9448
9816
|
* In a proposal, the text its saves wrote over the text the build was made
|
|
9449
9817
|
* from: the next save patches the last one's `.val.ts`, not the base's.
|
|
9818
|
+
* The same for a content version: the next publish patches what the last
|
|
9819
|
+
* one published, not what the build bundles.
|
|
9450
9820
|
*/
|
|
9451
|
-
this.projectSource =
|
|
9821
|
+
this.projectSource = snapshot !== undefined ? {
|
|
9452
9822
|
...((options === null || options === void 0 ? void 0 : options.projectSource) ?? {}),
|
|
9453
|
-
...
|
|
9823
|
+
...snapshot.files
|
|
9454
9824
|
} : (options === null || options === void 0 ? void 0 : options.projectSource) ?? null;
|
|
9455
9825
|
this.publishJob = (options === null || options === void 0 ? void 0 : options.publishJob) ?? null;
|
|
9456
|
-
|
|
9826
|
+
/*
|
|
9827
|
+
* Not with a content version: the content service places a build by the
|
|
9828
|
+
* build first (`getApplicablePatchesAndCommits`), and this build bundles an
|
|
9829
|
+
* older version than it serves -- placed there, the version's own changes
|
|
9830
|
+
* would be replayed over a snapshot that already holds them.
|
|
9831
|
+
*/
|
|
9832
|
+
this.publishBuild = this.contentVersion === null ? (options === null || options === void 0 ? void 0 : options.publishBuild) ?? null : null;
|
|
9457
9833
|
this.mirrorsSourceFiles = git !== null || this.projectSource !== null;
|
|
9458
9834
|
}
|
|
9459
9835
|
/**
|
|
@@ -10127,6 +10503,22 @@ class ValOpsHttp extends ValOps {
|
|
|
10127
10503
|
} : {})
|
|
10128
10504
|
};
|
|
10129
10505
|
}
|
|
10506
|
+
/*
|
|
10507
|
+
* A content version sits where the publish that made it does: its job,
|
|
10508
|
+
* which content places exactly, or its commit, once nothing else says.
|
|
10509
|
+
*/
|
|
10510
|
+
if (this.contentVersion !== null) {
|
|
10511
|
+
if (this.contentVersion.job !== null) {
|
|
10512
|
+
return {
|
|
10513
|
+
job: this.contentVersion.job
|
|
10514
|
+
};
|
|
10515
|
+
}
|
|
10516
|
+
if (this.contentVersion.commit !== null) {
|
|
10517
|
+
return {
|
|
10518
|
+
commit: this.contentVersion.commit
|
|
10519
|
+
};
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10130
10522
|
if (this.git) {
|
|
10131
10523
|
return {
|
|
10132
10524
|
branch: this.git.branch,
|
|
@@ -13387,6 +13779,9 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
13387
13779
|
...(opts.proposal !== undefined ? {
|
|
13388
13780
|
proposal: opts.proposal
|
|
13389
13781
|
} : {}),
|
|
13782
|
+
...(opts.contentVersion !== undefined ? {
|
|
13783
|
+
contentVersion: opts.contentVersion
|
|
13784
|
+
} : {}),
|
|
13390
13785
|
project: maybeValProject,
|
|
13391
13786
|
valEnableRedirectUrl,
|
|
13392
13787
|
valDisableRedirectUrl,
|
|
@@ -13465,6 +13860,9 @@ function createValOps(valModules, options) {
|
|
|
13465
13860
|
} : {}),
|
|
13466
13861
|
...(options.proposal !== undefined ? {
|
|
13467
13862
|
proposal: options.proposal
|
|
13863
|
+
} : {}),
|
|
13864
|
+
...(options.contentVersion !== undefined ? {
|
|
13865
|
+
contentVersion: options.contentVersion
|
|
13468
13866
|
} : {})
|
|
13469
13867
|
});
|
|
13470
13868
|
}
|
|
@@ -23961,6 +24359,7 @@ exports.awaitValLoginConfirmation = awaitValLoginConfirmation;
|
|
|
23961
24359
|
exports.checkRemoteRef = checkRemoteRef;
|
|
23962
24360
|
exports.classifyJsonValuesOp = classifyJsonValuesOp;
|
|
23963
24361
|
exports.compareWithCapturedReport = compareWithCapturedReport;
|
|
24362
|
+
exports.contentFromEnv = contentFromEnv;
|
|
23964
24363
|
exports.createDefaultValFSHost = createDefaultValFSHost;
|
|
23965
24364
|
exports.createFixPatch = createFixPatch;
|
|
23966
24365
|
exports.createJsonEntryPathMap = createJsonEntryPathMap;
|
|
@@ -24016,6 +24415,7 @@ exports.handleVideosetMetadata = handleVideosetMetadata;
|
|
|
24016
24415
|
exports.handleVideosetUploadRemote = handleVideosetUploadRemote;
|
|
24017
24416
|
exports.initHandlerOptions = initHandlerOptions;
|
|
24018
24417
|
exports.isExternalResult = isExternalResult;
|
|
24418
|
+
exports.layeredSnapshot = layeredSnapshot;
|
|
24019
24419
|
exports.loadValModules = loadValModules;
|
|
24020
24420
|
exports.ok = ok;
|
|
24021
24421
|
exports.parsePersonalAccessTokenFile = parsePersonalAccessTokenFile;
|
|
@@ -24024,6 +24424,7 @@ exports.persistPersonalAccessToken = persistPersonalAccessToken;
|
|
|
24024
24424
|
exports.planJsonValuesEntryExtraction = planJsonValuesEntryExtraction;
|
|
24025
24425
|
exports.proposalFromEnv = proposalFromEnv;
|
|
24026
24426
|
exports.proposalSnapshot = proposalSnapshot;
|
|
24427
|
+
exports.proposalSnapshotForReaders = proposalSnapshotForReaders;
|
|
24027
24428
|
exports.readCapturedReport = readCapturedReport;
|
|
24028
24429
|
exports.readPatchStore = readPatchStore;
|
|
24029
24430
|
exports.rebaseContentOp = rebaseContentOp;
|