@valbuild/server 0.132.1 → 0.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -0
- package/dist/declarations/src/ValOps.d.ts +50 -1
- package/dist/declarations/src/ValOpsHttp.d.ts +83 -6
- package/dist/declarations/src/ValRouter.d.ts +37 -8
- package/dist/declarations/src/ValServer.d.ts +11 -2
- package/dist/declarations/src/fixHandlers.d.ts +8 -0
- package/dist/declarations/src/history/types.d.ts +4 -2
- package/dist/valbuild-server.cjs.dev.js +414 -36
- package/dist/valbuild-server.cjs.prod.js +414 -36
- package/dist/valbuild-server.esm.js +414 -36
- package/package.json +4 -4
|
@@ -4347,7 +4347,25 @@ class ValOps {
|
|
|
4347
4347
|
};
|
|
4348
4348
|
};
|
|
4349
4349
|
const patchedJsonEntries = {};
|
|
4350
|
-
|
|
4350
|
+
/*
|
|
4351
|
+
* Patching the FILE TEXT, for a store that has somewhere to put it.
|
|
4352
|
+
*
|
|
4353
|
+
* Where there is nowhere -- an http project with no repository -- the
|
|
4354
|
+
* patches are still all applied, because they are: `getSources(analysis)`
|
|
4355
|
+
* below applies them to Source, and that is what the commit records and
|
|
4356
|
+
* what every reader of this project sees. What is skipped is rendering
|
|
4357
|
+
* that data back out as `.val.ts` and `*.val.json`, which would be a
|
|
4358
|
+
* mirror with no repository to mirror into -- and which cannot even be
|
|
4359
|
+
* attempted, because producing it starts by READING the current text from
|
|
4360
|
+
* the repository that is not there.
|
|
4361
|
+
*/
|
|
4362
|
+
const allResults = await Promise.all(Object.entries(patchesByModule).map(([path, patches]) => this.mirrorsSourceFiles ? applySourceFilePatches(path, patches) : Promise.resolve({
|
|
4363
|
+
path: path,
|
|
4364
|
+
appliedPatches: patches.map(p => p.patchId),
|
|
4365
|
+
result: null,
|
|
4366
|
+
extraFiles: {},
|
|
4367
|
+
jsonEntries: {}
|
|
4368
|
+
})));
|
|
4351
4369
|
let hasErrors = false;
|
|
4352
4370
|
const sourceFilePatchErrors = {};
|
|
4353
4371
|
const appliedPatches = {};
|
|
@@ -4522,6 +4540,46 @@ class ValOps {
|
|
|
4522
4540
|
});
|
|
4523
4541
|
}
|
|
4524
4542
|
|
|
4543
|
+
/**
|
|
4544
|
+
* Why a publish cannot happen here, or `null` when one can.
|
|
4545
|
+
*
|
|
4546
|
+
* Named rather than thrown, and asked BEFORE the click: the Studio shows the
|
|
4547
|
+
* reason and disables the action, instead of letting someone write a commit
|
|
4548
|
+
* message and then meeting a failure from four layers down.
|
|
4549
|
+
*
|
|
4550
|
+
* `no-base` is the only code so far and means what it says: there is nowhere
|
|
4551
|
+
* for this publish's commit to be based. Note which way round that is --
|
|
4552
|
+
* a project whose content service is the store of record always HAS a base
|
|
4553
|
+
* (the service's own chain, which mints its own shas), so the refusal is not
|
|
4554
|
+
* about missing git. It is about a deployment that cannot do what its
|
|
4555
|
+
* project requires.
|
|
4556
|
+
*/
|
|
4557
|
+
publishRefusal() {
|
|
4558
|
+
return null;
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4561
|
+
/**
|
|
4562
|
+
* Whether a commit here produces `.val.ts` TEXT as well as data.
|
|
4563
|
+
*
|
|
4564
|
+
* True everywhere there is somewhere to put it: a working tree in `fs` mode,
|
|
4565
|
+
* a host holding its own source in memory mode, a git repository in `http`
|
|
4566
|
+
* mode. False for an `http` project whose content service is the store of
|
|
4567
|
+
* record and which has no repository attached -- see `git` on
|
|
4568
|
+
* {@link ValApiOptions}.
|
|
4569
|
+
*
|
|
4570
|
+
* WHAT IS NOT AFFECTED, and it is the part worth being sure of:
|
|
4571
|
+
* `moduleVersions` -- what each changed module IS after the commit, with its
|
|
4572
|
+
* schema -- comes from `getSources(analysis)`, which applies the ops to
|
|
4573
|
+
* Source in the stores. It does not go near the file text. So a commit with
|
|
4574
|
+
* no mirror still records everything history and a later `connect-github`
|
|
4575
|
+
* fold need; what it does not record is a rendering of that data as code.
|
|
4576
|
+
*
|
|
4577
|
+
* WHAT IS: the ops are no longer applied to the file text as well, so a
|
|
4578
|
+
* patch that would not fit the `.val.ts` is not reported here. That check
|
|
4579
|
+
* only ever existed for the text being produced, and there is none.
|
|
4580
|
+
*/
|
|
4581
|
+
mirrorsSourceFiles = true;
|
|
4582
|
+
|
|
4525
4583
|
/**
|
|
4526
4584
|
* Take the `.val.ts` text a commit produced as the new committed source.
|
|
4527
4585
|
*
|
|
@@ -4686,6 +4744,16 @@ function formatPatchSourceError(error) {
|
|
|
4686
4744
|
return "Unknown patch source error: " + JSON.stringify(_exhaustiveCheck);
|
|
4687
4745
|
}
|
|
4688
4746
|
}
|
|
4747
|
+
|
|
4748
|
+
/**
|
|
4749
|
+
* Why a publish is refused, in a form a person can be shown.
|
|
4750
|
+
*
|
|
4751
|
+
* `code` is for the Studio to branch on and `message` is what it says. Both,
|
|
4752
|
+
* rather than a code and a lookup table on the client: the server knows what
|
|
4753
|
+
* is actually missing -- which branch, which commit -- and a client-side table
|
|
4754
|
+
* could only ever say the generic version.
|
|
4755
|
+
*/
|
|
4756
|
+
|
|
4689
4757
|
/**
|
|
4690
4758
|
* The patches a json entry render should apply, out of the whole chain.
|
|
4691
4759
|
*
|
|
@@ -7525,8 +7593,14 @@ const GetApplicablePatches = z.z.object({
|
|
|
7525
7593
|
})),
|
|
7526
7594
|
commits: z.z.array(z.z.object({
|
|
7527
7595
|
commitSha: z.z.string(),
|
|
7528
|
-
|
|
7529
|
-
|
|
7596
|
+
/*
|
|
7597
|
+
* Nullable since the content service mints its own commit shas: a
|
|
7598
|
+
* root commit has no parent, and a publisher that did not say where
|
|
7599
|
+
* it was has no client sha. Parsed rather than trusted, so a service
|
|
7600
|
+
* that sends null gets null here instead of failing the whole poll.
|
|
7601
|
+
*/
|
|
7602
|
+
clientCommitSha: z.z.string().nullable(),
|
|
7603
|
+
parentCommitSha: z.z.string().nullable(),
|
|
7530
7604
|
commitMessage: z.z.string().nullable(),
|
|
7531
7605
|
branch: z.z.string(),
|
|
7532
7606
|
creator: z.z.string(),
|
|
@@ -7542,7 +7616,21 @@ const GetApplicablePatches = z.z.object({
|
|
|
7542
7616
|
// `ValDeployment`: this is the only source for a deployment that Val
|
|
7543
7617
|
// did not publish, and zod would otherwise strip it here.
|
|
7544
7618
|
commitMessage: z.z.string().nullable().optional()
|
|
7545
|
-
})).optional()
|
|
7619
|
+
})).optional(),
|
|
7620
|
+
/**
|
|
7621
|
+
* What the project EXPECTS of whoever publishes it.
|
|
7622
|
+
*
|
|
7623
|
+
* Optional because a content service that predates it sends nothing, and
|
|
7624
|
+
* absent means "not reported" -- never "managed". The difference decides
|
|
7625
|
+
* whether a publish is refused, so guessing either way would be worse than
|
|
7626
|
+
* not checking: guessing `managed` would let a deployment publish a
|
|
7627
|
+
* connected project it cannot mirror, and guessing `connected` would refuse
|
|
7628
|
+
* every publish against an older service.
|
|
7629
|
+
*/
|
|
7630
|
+
project: z.z.object({
|
|
7631
|
+
sourceMode: z.z.union([z.z.literal("managed"), z.z.literal("connected")]),
|
|
7632
|
+
branch: z.z.string()
|
|
7633
|
+
}).optional()
|
|
7546
7634
|
});
|
|
7547
7635
|
const FilesResponse = z.z.object({
|
|
7548
7636
|
files: z.z.array(z.z.union([z.z.object({
|
|
@@ -7615,8 +7703,10 @@ const CommitResponse = z.z.object({
|
|
|
7615
7703
|
// commit changed nothing", which is indistinguishable from a real answer.
|
|
7616
7704
|
const HistoricalCommitResponse = z.z.object({
|
|
7617
7705
|
commitSha: z.z.string(),
|
|
7618
|
-
|
|
7619
|
-
|
|
7706
|
+
/** `null` for a root commit; see the note on the applicable-patches schema. */
|
|
7707
|
+
parentCommitSha: z.z.string().nullable(),
|
|
7708
|
+
/** `null` when the publisher did not say where it was. */
|
|
7709
|
+
clientCommitSha: z.z.string().nullable(),
|
|
7620
7710
|
branch: z.z.string(),
|
|
7621
7711
|
createdBranch: z.z.string().nullable(),
|
|
7622
7712
|
creator: z.z.string().nullable(),
|
|
@@ -7634,8 +7724,8 @@ const CommitPatchesResponse = z.z.object({
|
|
|
7634
7724
|
commitSha: z.z.string(),
|
|
7635
7725
|
commit: z.z.object({
|
|
7636
7726
|
commitSha: z.z.string(),
|
|
7637
|
-
parentCommitSha: z.z.string(),
|
|
7638
|
-
clientCommitSha: z.z.string(),
|
|
7727
|
+
parentCommitSha: z.z.string().nullable(),
|
|
7728
|
+
clientCommitSha: z.z.string().nullable(),
|
|
7639
7729
|
branch: z.z.string(),
|
|
7640
7730
|
createdBranch: z.z.string().nullable(),
|
|
7641
7731
|
creator: z.z.string().nullable(),
|
|
@@ -7735,9 +7825,45 @@ class ValOpsHttp extends ValOps {
|
|
|
7735
7825
|
patchesAreLocal = false;
|
|
7736
7826
|
/** See {@link ValOps.requiresAuth}. */
|
|
7737
7827
|
requiresAuth = true;
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7828
|
+
/**
|
|
7829
|
+
* A commit mirrors into `.val.ts` only when there is a repository.
|
|
7830
|
+
*
|
|
7831
|
+
* See {@link ValOps.mirrorsSourceFiles}. Set in the constructor rather than
|
|
7832
|
+
* as an initialiser because it depends on `git`, and a class field
|
|
7833
|
+
* initialiser runs before the constructor body has assigned it.
|
|
7834
|
+
*/
|
|
7835
|
+
mirrorsSourceFiles;
|
|
7836
|
+
/**
|
|
7837
|
+
* What the content service last said this project expects of its publisher.
|
|
7838
|
+
*
|
|
7839
|
+
* `null` until something has asked it, which in practice is the first poll.
|
|
7840
|
+
* It is remembered rather than asked for on demand because the answer is
|
|
7841
|
+
* only wanted on the publish path, and that path already fetches the
|
|
7842
|
+
* patches it is publishing -- so a dedicated request would be a second round
|
|
7843
|
+
* trip for two fields that just arrived.
|
|
7844
|
+
*
|
|
7845
|
+
* It can be one poll out of date, and that is the right amount: the thing it
|
|
7846
|
+
* changes is whether this deployment can mirror commits into a repository,
|
|
7847
|
+
* which changes when a project CONNECTS one -- and a project that has just
|
|
7848
|
+
* connected is one whose builds are about to be replaced anyway.
|
|
7849
|
+
*/
|
|
7850
|
+
projectExpectation = null;
|
|
7851
|
+
constructor(contentUrl, project,
|
|
7852
|
+
/**
|
|
7853
|
+
* The repository this project's commits are mirrored into, or `null`.
|
|
7854
|
+
*
|
|
7855
|
+
* `null` is a project whose content service is the store of record: it
|
|
7856
|
+
* mints its own commit shas, and it knows this project's branch from the
|
|
7857
|
+
* project itself. Every request below that would have carried a branch and
|
|
7858
|
+
* a commit omits them instead, and the service answers from the project's
|
|
7859
|
+
* own chain -- which is where those answers always came from.
|
|
7860
|
+
*
|
|
7861
|
+
* It is NOT a degraded mode. The one thing that genuinely needs a
|
|
7862
|
+
* repository is producing the `.val.ts` text a commit mirrors, and a
|
|
7863
|
+
* project with no repository has nothing to mirror into. See `git` on
|
|
7864
|
+
* {@link ValApiOptions}.
|
|
7865
|
+
*/
|
|
7866
|
+
git,
|
|
7741
7867
|
/**
|
|
7742
7868
|
* An api key (how the app itself authenticates) or a personal access token
|
|
7743
7869
|
* (how the CLI authenticates after `val login`). Same two shapes as
|
|
@@ -7747,14 +7873,49 @@ class ValOpsHttp extends ValOps {
|
|
|
7747
7873
|
super(valModules, options);
|
|
7748
7874
|
this.contentUrl = contentUrl;
|
|
7749
7875
|
this.project = project;
|
|
7750
|
-
this.
|
|
7751
|
-
this.branch = branch;
|
|
7876
|
+
this.git = git;
|
|
7752
7877
|
this.authHeaders = "pat" in auth ? {
|
|
7753
7878
|
"x-val-pat": auth.pat
|
|
7754
7879
|
} : {
|
|
7755
7880
|
Authorization: `Bearer ${auth.apiKey}`
|
|
7756
7881
|
};
|
|
7757
7882
|
this.root = (options === null || options === void 0 ? void 0 : options.root) ?? "";
|
|
7883
|
+
this.mirrorsSourceFiles = git !== null;
|
|
7884
|
+
}
|
|
7885
|
+
/**
|
|
7886
|
+
* A deployment that cannot mirror a project which expects to be mirrored.
|
|
7887
|
+
*
|
|
7888
|
+
* This is the one shape of "no base" that exists, and it is not the one it
|
|
7889
|
+
* sounds like. A project with no repository is fine: the content service is
|
|
7890
|
+
* the store of record for it and mints its own commit shas, so there is
|
|
7891
|
+
* always somewhere for the commit to go. What is refused is the mismatch --
|
|
7892
|
+
* a project whose commits are mirrored into a repository, being published by
|
|
7893
|
+
* a build that was made before it had one and so has no commit to produce
|
|
7894
|
+
* that mirror against.
|
|
7895
|
+
*
|
|
7896
|
+
* It is a REAL state rather than a defensive check: it is exactly what a
|
|
7897
|
+
* deployment looks like between a project connecting a repository and its
|
|
7898
|
+
* next build going out. Left unchecked, such a publish writes the data and
|
|
7899
|
+
* silently fails to mirror it, and the repository quietly falls behind the
|
|
7900
|
+
* content nobody is told about.
|
|
7901
|
+
*
|
|
7902
|
+
* `null` when the service did not say (see `project` on the response
|
|
7903
|
+
* schema): an older content service is not evidence of anything, and
|
|
7904
|
+
* refusing every publish against one would be a worse failure than not
|
|
7905
|
+
* checking.
|
|
7906
|
+
*/
|
|
7907
|
+
publishRefusal() {
|
|
7908
|
+
var _this$projectExpectat;
|
|
7909
|
+
if (this.git !== null) {
|
|
7910
|
+
return null;
|
|
7911
|
+
}
|
|
7912
|
+
if (((_this$projectExpectat = this.projectExpectation) === null || _this$projectExpectat === void 0 ? void 0 : _this$projectExpectat.sourceMode) !== "connected") {
|
|
7913
|
+
return null;
|
|
7914
|
+
}
|
|
7915
|
+
return {
|
|
7916
|
+
code: "no-base",
|
|
7917
|
+
message: `This project mirrors its content into a git repository (branch ` + `'${this.projectExpectation.branch}'), but this deployment was not ` + "built from one, so it does not know which commit to write that " + "mirror against. Publishing would save the content and silently " + "leave the repository behind. Deploy this project again from its " + "repository, and publishing will work from that build on."
|
|
7918
|
+
};
|
|
7758
7919
|
}
|
|
7759
7920
|
async onInit() {
|
|
7760
7921
|
// TODO: unused for now. Implement or remove
|
|
@@ -7945,16 +8106,31 @@ class ValOpsHttp extends ValOps {
|
|
|
7945
8106
|
* decided against.
|
|
7946
8107
|
*/
|
|
7947
8108
|
headCommitSha: internal.newestCommitSha(allPatchData.commits) ?? undefined,
|
|
7948
|
-
|
|
8109
|
+
/*
|
|
8110
|
+
* Spread: a project with no repository has no such commit, and saying
|
|
8111
|
+
* so by leaving the key out is different from sending an empty string
|
|
8112
|
+
* the Studio would try to show.
|
|
8113
|
+
*/
|
|
8114
|
+
...(this.git ? {
|
|
8115
|
+
commitSha: this.git.commit
|
|
8116
|
+
} : {})
|
|
7949
8117
|
};
|
|
7950
8118
|
}
|
|
7951
8119
|
async getWebSocketNonce(profileId) {
|
|
7952
8120
|
return fetch(`${this.contentUrl}/v1/${this.project}/websocket/nonces`, {
|
|
7953
8121
|
method: "POST",
|
|
7954
8122
|
body: JSON.stringify({
|
|
7955
|
-
branch: this.branch,
|
|
7956
8123
|
profileId,
|
|
7957
|
-
|
|
8124
|
+
/*
|
|
8125
|
+
* Omitted when there is no repository, like every other request here.
|
|
8126
|
+
* The content service knows this project's branch -- it is a column on
|
|
8127
|
+
* the project -- and a nonce is scoped to the project and the person,
|
|
8128
|
+
* not to a position in a chain.
|
|
8129
|
+
*/
|
|
8130
|
+
...(this.git ? {
|
|
8131
|
+
branch: this.git.branch,
|
|
8132
|
+
commitSha: this.git.commit
|
|
8133
|
+
} : {})
|
|
7958
8134
|
}),
|
|
7959
8135
|
headers: {
|
|
7960
8136
|
...this.authHeaders,
|
|
@@ -8102,8 +8278,20 @@ class ValOpsHttp extends ValOps {
|
|
|
8102
8278
|
}
|
|
8103
8279
|
async fetchPatchesInternal(filters) {
|
|
8104
8280
|
const params = [];
|
|
8105
|
-
|
|
8106
|
-
|
|
8281
|
+
/*
|
|
8282
|
+
* A position in the chain, WHEN THIS BUILD HAS ONE.
|
|
8283
|
+
*
|
|
8284
|
+
* `commit` tells the content service where this deployment sits, so it can
|
|
8285
|
+
* answer with the commits at or after that point. A build with no
|
|
8286
|
+
* repository has no such commit baked into it, and asking the service to
|
|
8287
|
+
* resolve the position itself is strictly better than inventing one: it
|
|
8288
|
+
* holds the chain, and for a project it is the only publisher of, the
|
|
8289
|
+
* position IS the head.
|
|
8290
|
+
*/
|
|
8291
|
+
if (this.git) {
|
|
8292
|
+
params.push(["branch", this.git.branch]);
|
|
8293
|
+
params.push(["commit", this.git.commit]);
|
|
8294
|
+
}
|
|
8107
8295
|
if (filters.patchIds) {
|
|
8108
8296
|
for (const patchId of filters.patchIds) {
|
|
8109
8297
|
params.push(["patch_id", patchId]);
|
|
@@ -8124,6 +8312,17 @@ class ValOpsHttp extends ValOps {
|
|
|
8124
8312
|
if (parsed.success) {
|
|
8125
8313
|
const errors = [];
|
|
8126
8314
|
const data = parsed.data;
|
|
8315
|
+
/*
|
|
8316
|
+
* What the project expects of its publisher, remembered.
|
|
8317
|
+
*
|
|
8318
|
+
* Recorded here rather than returned because every caller of this
|
|
8319
|
+
* already has what it needs and only the publish path asks the
|
|
8320
|
+
* question -- and that path calls this first. See
|
|
8321
|
+
* {@link publishRefusal}.
|
|
8322
|
+
*/
|
|
8323
|
+
if (data.project) {
|
|
8324
|
+
this.projectExpectation = data.project;
|
|
8325
|
+
}
|
|
8127
8326
|
for (const patchesRes of data.patches) {
|
|
8128
8327
|
var _patchesRes$applied;
|
|
8129
8328
|
patches.push({
|
|
@@ -8344,7 +8543,7 @@ class ValOpsHttp extends ValOps {
|
|
|
8344
8543
|
* `saveSourceFilePatch`): groups are per branch, so a request without one
|
|
8345
8544
|
* is not merely under-specified, it is rejected.
|
|
8346
8545
|
*/
|
|
8347
|
-
const params = new URLSearchParams([["branch", this.branch]]);
|
|
8546
|
+
const params = new URLSearchParams(this.git ? [["branch", this.git.branch]] : []);
|
|
8348
8547
|
const res = await fetch(`${this.contentUrl}/v1/${this.project}/patch-groups?${params}`, {
|
|
8349
8548
|
headers: this.authHeaders
|
|
8350
8549
|
});
|
|
@@ -8496,8 +8695,10 @@ class ValOpsHttp extends ValOps {
|
|
|
8496
8695
|
patchId,
|
|
8497
8696
|
parentPatchId: parentRef.type === "patch" ? parentRef.patchId : null,
|
|
8498
8697
|
baseSha,
|
|
8499
|
-
|
|
8500
|
-
|
|
8698
|
+
...(this.git ? {
|
|
8699
|
+
commit: this.git.commit,
|
|
8700
|
+
branch: this.git.branch
|
|
8701
|
+
} : {}),
|
|
8501
8702
|
coreVersion: core.Internal.VERSION.core,
|
|
8502
8703
|
/*
|
|
8503
8704
|
* Group membership in the SAME request as the patch.
|
|
@@ -8663,11 +8864,28 @@ class ValOpsHttp extends ValOps {
|
|
|
8663
8864
|
});
|
|
8664
8865
|
}
|
|
8665
8866
|
async getSourceFile(path) {
|
|
8867
|
+
/*
|
|
8868
|
+
* There is no file to read without a repository to read it from.
|
|
8869
|
+
*
|
|
8870
|
+
* Reachable only through the CLI's debug snapshot now: the publish path
|
|
8871
|
+
* does not call this for such a project at all -- see
|
|
8872
|
+
* {@link ValOps.mirrorsSourceFiles} -- because there is nothing for it to
|
|
8873
|
+
* produce. It is an error rather than an empty string because an empty
|
|
8874
|
+
* `.val.ts` would be patched successfully and committed as a module that
|
|
8875
|
+
* had lost all its content.
|
|
8876
|
+
*/
|
|
8877
|
+
if (!this.git) {
|
|
8878
|
+
return {
|
|
8879
|
+
error: {
|
|
8880
|
+
message: `Cannot read the source of ${path}: this project has no ` + "repository, so there is no `.val.ts` to read. Its content lives " + "in Val's content service, which is the store of record for it."
|
|
8881
|
+
}
|
|
8882
|
+
};
|
|
8883
|
+
}
|
|
8666
8884
|
const filesRes = await this.getHttpFiles([{
|
|
8667
8885
|
filePath: path,
|
|
8668
8886
|
location: "repo",
|
|
8669
8887
|
root: this.root,
|
|
8670
|
-
commitSha: this.
|
|
8888
|
+
commitSha: this.git.commit
|
|
8671
8889
|
}]);
|
|
8672
8890
|
if (filesRes.error) {
|
|
8673
8891
|
return filesRes;
|
|
@@ -8687,11 +8905,22 @@ class ValOpsHttp extends ValOps {
|
|
|
8687
8905
|
async getBinaryFile(filePath) {
|
|
8688
8906
|
// We could also just get this from public/ on the running server. Current approach feels more clean, but will be slower / puts more server load... We might want to change this
|
|
8689
8907
|
const requestFiles = [];
|
|
8908
|
+
if (!this.git) {
|
|
8909
|
+
/*
|
|
8910
|
+
* A published binary lives in the repository, and there is none.
|
|
8911
|
+
*
|
|
8912
|
+
* `null` is this method's existing "not found", which is what a caller
|
|
8913
|
+
* already handles: the Studio falls back to the patch's own copy, which
|
|
8914
|
+
* is where a managed project's files stay. See the note on local files
|
|
8915
|
+
* in the content service's commit handler.
|
|
8916
|
+
*/
|
|
8917
|
+
return null;
|
|
8918
|
+
}
|
|
8690
8919
|
requestFiles.push({
|
|
8691
8920
|
filePath: filePath,
|
|
8692
8921
|
location: "repo",
|
|
8693
8922
|
root: this.root,
|
|
8694
|
-
commitSha: this.
|
|
8923
|
+
commitSha: this.git.commit
|
|
8695
8924
|
});
|
|
8696
8925
|
const filesRes = await this.getHttpFiles(requestFiles);
|
|
8697
8926
|
if (filesRes.error) {
|
|
@@ -8870,7 +9099,6 @@ class ValOpsHttp extends ValOps {
|
|
|
8870
9099
|
patchGroupId) {
|
|
8871
9100
|
try {
|
|
8872
9101
|
var _res$headers$get3;
|
|
8873
|
-
const existingBranch = this.branch;
|
|
8874
9102
|
const res = await fetch(`${this.contentUrl}/v1/${this.project}/commit`, {
|
|
8875
9103
|
method: "POST",
|
|
8876
9104
|
headers: {
|
|
@@ -8914,13 +9142,29 @@ class ValOpsHttp extends ValOps {
|
|
|
8914
9142
|
* moved on.
|
|
8915
9143
|
*/
|
|
8916
9144
|
modules: prepared.moduleVersions,
|
|
8917
|
-
|
|
9145
|
+
/*
|
|
9146
|
+
* WHERE THIS BUILD THOUGHT IT WAS -- and only for a project with a
|
|
9147
|
+
* repository, which is the only thing that still reads it.
|
|
9148
|
+
*
|
|
9149
|
+
* The content service mints its own commit shas, so this is no
|
|
9150
|
+
* longer the parent, and it never was the concurrency check: that is
|
|
9151
|
+
* `expectedHeadCommitSha` against `newestCommitSha(patches.commits)`,
|
|
9152
|
+
* which `/save` runs before it gets here. What is left is the git
|
|
9153
|
+
* fast-forward check, which needs to know the commit this build read
|
|
9154
|
+
* the branch at to tell whether the branch has moved under it.
|
|
9155
|
+
*
|
|
9156
|
+
* The branch goes with it. The project's branch is a column on the
|
|
9157
|
+
* project, so a build asserting one could only ever disagree with
|
|
9158
|
+
* the project it is publishing to.
|
|
9159
|
+
*/
|
|
9160
|
+
...(this.git ? {
|
|
9161
|
+
commit: this.git.commit
|
|
9162
|
+
} : {}),
|
|
8918
9163
|
root: this.root,
|
|
8919
9164
|
filesDirectory,
|
|
8920
9165
|
baseSha: await this.getBaseSha(),
|
|
8921
9166
|
committer,
|
|
8922
9167
|
message,
|
|
8923
|
-
existingBranch,
|
|
8924
9168
|
newBranch,
|
|
8925
9169
|
...(patchGroupId !== undefined ? {
|
|
8926
9170
|
patchGroupId
|
|
@@ -10682,13 +10926,42 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10682
10926
|
if (!maybeApiKey || !maybeValSecret) {
|
|
10683
10927
|
throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode" + because);
|
|
10684
10928
|
}
|
|
10929
|
+
/*
|
|
10930
|
+
* A COMMIT IS NOT WHAT PUTS AN APP IN HTTP MODE. Credentials are.
|
|
10931
|
+
*
|
|
10932
|
+
* Both of these used to be required here, and the requirement was a
|
|
10933
|
+
* repository disguised as a configuration check: a deployment with no
|
|
10934
|
+
* commit to name -- one whose content service owns its content, which is
|
|
10935
|
+
* now the normal case -- threw at boot, or, worse, never reached this
|
|
10936
|
+
* branch at all and fell through to `fs` mode, looking for a working tree
|
|
10937
|
+
* that was not there.
|
|
10938
|
+
*
|
|
10939
|
+
* Absent is a project with no repository to mirror commits into. The
|
|
10940
|
+
* content service mints its own commit shas and is the store of record for
|
|
10941
|
+
* content, so there is nothing missing: see `git` on {@link ValApiOptions}
|
|
10942
|
+
* for what a commit is still FOR where there is one.
|
|
10943
|
+
*
|
|
10944
|
+
* Taken together or not at all. A commit without a branch names a point
|
|
10945
|
+
* with no line of work to publish to, and a branch without a commit names
|
|
10946
|
+
* a line with no position in it; either alone would be a half-configured
|
|
10947
|
+
* repository that fails later, at a publish, rather than here.
|
|
10948
|
+
*
|
|
10949
|
+
* ONE NAME, wherever it comes from. `opts` is the bindings'
|
|
10950
|
+
* `{ versions, ...config }`, so `opts.gitCommit` IS `val.config.ts`'s
|
|
10951
|
+
* `gitCommit` -- there is nothing to map and no second place to look.
|
|
10952
|
+
*
|
|
10953
|
+
* It used to be a nested `git: { commit, branch }` here and two flat keys
|
|
10954
|
+
* in `ValConfig`, and the two never met: nothing mapped one onto the
|
|
10955
|
+
* other, so a project that set the documented config keys -- which is
|
|
10956
|
+
* what every Vercel deployment does, from `VERCEL_GIT_COMMIT_SHA` --
|
|
10957
|
+
* resolved to no repository at all. Nothing failed and nothing was
|
|
10958
|
+
* logged; the commit was simply never sent, and every patch it saved
|
|
10959
|
+
* recorded none.
|
|
10960
|
+
*/
|
|
10685
10961
|
const maybeGitCommit = opts.gitCommit || process.env.VAL_GIT_COMMIT;
|
|
10686
|
-
if (!maybeGitCommit) {
|
|
10687
|
-
throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode" + because);
|
|
10688
|
-
}
|
|
10689
10962
|
const maybeGitBranch = opts.gitBranch || process.env.VAL_GIT_BRANCH;
|
|
10690
|
-
if (
|
|
10691
|
-
throw new Error("
|
|
10963
|
+
if (!!maybeGitCommit !== !!maybeGitBranch) {
|
|
10964
|
+
throw new Error(`Val is configured with a git ${maybeGitCommit ? "commit" : "branch"} ` + `but no ${maybeGitCommit ? "branch" : "commit"}. Set both ` + "(`gitCommit` and `gitBranch` in val.config.ts, or VAL_GIT_COMMIT " + "and VAL_GIT_BRANCH) for a project whose content is mirrored into " + "a repository, or neither for one whose content service is the " + "store of record." + because);
|
|
10692
10965
|
}
|
|
10693
10966
|
if (!maybeValProject) {
|
|
10694
10967
|
throw new Error("Proxy mode does not work unless the 'project' option in val.config is defined or the VAL_PROJECT env var is set." + because);
|
|
@@ -10706,8 +10979,19 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10706
10979
|
route,
|
|
10707
10980
|
apiKey: maybeApiKey,
|
|
10708
10981
|
valSecret: maybeValSecret,
|
|
10709
|
-
|
|
10710
|
-
|
|
10982
|
+
/*
|
|
10983
|
+
* Spread, so a project with no repository has no `git` key at all rather
|
|
10984
|
+
* than one holding undefined. `ValOpsHttp` asks `git === null` to decide
|
|
10985
|
+
* whether to send a branch and a commit with every request, and a key
|
|
10986
|
+
* that is present-but-undefined is one more thing for that check to get
|
|
10987
|
+
* wrong.
|
|
10988
|
+
*/
|
|
10989
|
+
...(maybeGitCommit && maybeGitBranch ? {
|
|
10990
|
+
git: {
|
|
10991
|
+
commit: maybeGitCommit,
|
|
10992
|
+
branch: maybeGitBranch
|
|
10993
|
+
}
|
|
10994
|
+
} : {}),
|
|
10711
10995
|
root: opts.root,
|
|
10712
10996
|
project: maybeValProject,
|
|
10713
10997
|
valEnableRedirectUrl,
|
|
@@ -10770,7 +11054,7 @@ function createValOps(valModules, options) {
|
|
|
10770
11054
|
});
|
|
10771
11055
|
}
|
|
10772
11056
|
if (options.mode === "http") {
|
|
10773
|
-
return new ValOpsHttp(options.valContentUrl, options.project, options.
|
|
11057
|
+
return new ValOpsHttp(options.valContentUrl, options.project, options.git ?? null, {
|
|
10774
11058
|
apiKey: options.apiKey
|
|
10775
11059
|
}, valModules, {
|
|
10776
11060
|
formatter: options.formatter,
|
|
@@ -10976,6 +11260,7 @@ async function resolveRemoteFileAuth(options) {
|
|
|
10976
11260
|
/** What a publish reports back, in either shape. */
|
|
10977
11261
|
|
|
10978
11262
|
const ValServer = (valModules, options, callbacks) => {
|
|
11263
|
+
var _options$git;
|
|
10979
11264
|
const AIContentBlock = z.z.union([z.z.object({
|
|
10980
11265
|
type: z.z.literal("text"),
|
|
10981
11266
|
text: z.z.string()
|
|
@@ -11007,7 +11292,7 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
11007
11292
|
url.searchParams.set("state", token);
|
|
11008
11293
|
return url.toString();
|
|
11009
11294
|
};
|
|
11010
|
-
const commit = options.mode === "http" ? options.commit : undefined;
|
|
11295
|
+
const commit = options.mode === "http" ? (_options$git = options.git) === null || _options$git === void 0 ? void 0 : _options$git.commit : undefined;
|
|
11011
11296
|
const getAppErrorUrl = error => {
|
|
11012
11297
|
if (!options.project) {
|
|
11013
11298
|
throw new Error("Project is not set");
|
|
@@ -11604,12 +11889,24 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
11604
11889
|
* start against a server that was working.
|
|
11605
11890
|
*/
|
|
11606
11891
|
const mode = serverOps.patchesAreLocal ? "fs" : "http";
|
|
11892
|
+
/*
|
|
11893
|
+
* Why publishing is unavailable, so the Studio can say so.
|
|
11894
|
+
*
|
|
11895
|
+
* Spread rather than set to null, so a server that can publish sends
|
|
11896
|
+
* no such key at all: the Studio's "can I publish" is then the
|
|
11897
|
+
* presence of the field, and there is no null to mistake for a reason
|
|
11898
|
+
* it failed to compute.
|
|
11899
|
+
*/
|
|
11900
|
+
const publishRefusal = serverOps.publishRefusal();
|
|
11607
11901
|
return {
|
|
11608
11902
|
status: 200,
|
|
11609
11903
|
json: {
|
|
11610
11904
|
...currentStat,
|
|
11611
11905
|
profileId: profileId ?? null,
|
|
11612
11906
|
mode,
|
|
11907
|
+
...(publishRefusal ? {
|
|
11908
|
+
publishRefusal
|
|
11909
|
+
} : {}),
|
|
11613
11910
|
config: options.config
|
|
11614
11911
|
}
|
|
11615
11912
|
};
|
|
@@ -12798,6 +13095,29 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
12798
13095
|
patchIds,
|
|
12799
13096
|
excludePatchOps: false
|
|
12800
13097
|
});
|
|
13098
|
+
/*
|
|
13099
|
+
* Can this deployment publish this project AT ALL?
|
|
13100
|
+
*
|
|
13101
|
+
* After the fetch above, deliberately: that call is what tells the
|
|
13102
|
+
* data layer what the project expects, and asking before it would be
|
|
13103
|
+
* asking a question nothing has answered yet. It is still before
|
|
13104
|
+
* anything is written, which is the part that matters.
|
|
13105
|
+
*
|
|
13106
|
+
* The Studio already knows -- `/stat` carries the same refusal, so the
|
|
13107
|
+
* action is disabled with the reason shown. This is the second half of
|
|
13108
|
+
* that: a stat can be stale by a poll, and nothing may reach `prepare`
|
|
13109
|
+
* on a project it cannot mirror.
|
|
13110
|
+
*/
|
|
13111
|
+
const refusal = serverOps.publishRefusal();
|
|
13112
|
+
if (refusal) {
|
|
13113
|
+
return {
|
|
13114
|
+
status: 409,
|
|
13115
|
+
json: {
|
|
13116
|
+
message: refusal.message,
|
|
13117
|
+
publishRefusal: refusal
|
|
13118
|
+
}
|
|
13119
|
+
};
|
|
13120
|
+
}
|
|
12801
13121
|
/*
|
|
12802
13122
|
* Exactly the patches this request consumes, and the ONLY ones it may
|
|
12803
13123
|
* delete afterwards.
|
|
@@ -16027,6 +16347,44 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
16027
16347
|
fixes: undefined
|
|
16028
16348
|
});
|
|
16029
16349
|
}
|
|
16350
|
+
} else if (fix === "view:check-module") {
|
|
16351
|
+
// The schema is the authority: it names the module, and there is exactly
|
|
16352
|
+
// one valid pointer. So this is written rather than reported — nobody has
|
|
16353
|
+
// a decision to make here.
|
|
16354
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
|
|
16355
|
+
if (moduleSource === undefined || moduleSchema === undefined) {
|
|
16356
|
+
remainingErrors.push({
|
|
16357
|
+
...validationError,
|
|
16358
|
+
message: "Unexpected error while checking a view (no module source or schema)",
|
|
16359
|
+
fixes: undefined
|
|
16360
|
+
});
|
|
16361
|
+
continue;
|
|
16362
|
+
}
|
|
16363
|
+
const {
|
|
16364
|
+
schema: schemaAtPath
|
|
16365
|
+
} = core.Internal.resolvePath(modulePath, moduleSource, moduleSchema);
|
|
16366
|
+
if (schemaAtPath.type !== "view") {
|
|
16367
|
+
remainingErrors.push({
|
|
16368
|
+
...validationError,
|
|
16369
|
+
message: `Could not fix view: schema at ${sourcePath} is '${schemaAtPath.type}', not a view`,
|
|
16370
|
+
fixes: undefined
|
|
16371
|
+
});
|
|
16372
|
+
continue;
|
|
16373
|
+
}
|
|
16374
|
+
if (apply) {
|
|
16375
|
+
patch$1.push({
|
|
16376
|
+
op: "replace",
|
|
16377
|
+
path: core.Internal.createPatchPath(modulePath),
|
|
16378
|
+
value: {
|
|
16379
|
+
view: schemaAtPath.moduleFilePath
|
|
16380
|
+
}
|
|
16381
|
+
});
|
|
16382
|
+
} else {
|
|
16383
|
+
remainingErrors.push({
|
|
16384
|
+
...validationError,
|
|
16385
|
+
message: `This view points at the wrong module. Expected '${schemaAtPath.moduleFilePath}'.`
|
|
16386
|
+
});
|
|
16387
|
+
}
|
|
16030
16388
|
}
|
|
16031
16389
|
}
|
|
16032
16390
|
if (!validationError.fixes || validationError.fixes.length === 0) {
|
|
@@ -16770,6 +17128,25 @@ async function handleJsonValuesExtractEntry(ctx) {
|
|
|
16770
17128
|
* `fixableErrorMessage` rather than a plain error, because the error IS fixable
|
|
16771
17129
|
* — just not by this command.
|
|
16772
17130
|
*/
|
|
17131
|
+
/**
|
|
17132
|
+
* A view pointer that names a module its schema does not.
|
|
17133
|
+
*
|
|
17134
|
+
* Nothing to look up and nothing to ask: the schema names the module, so the
|
|
17135
|
+
* one correct value is already known. `createFixPatch` writes it — this handler
|
|
17136
|
+
* exists to send it there, and to say what `--fix` would do when it is off.
|
|
17137
|
+
*/
|
|
17138
|
+
async function handleViewCheckModule(ctx) {
|
|
17139
|
+
if (!ctx.fix) {
|
|
17140
|
+
return {
|
|
17141
|
+
success: true,
|
|
17142
|
+
fixableErrorMessage: `${ctx.validationError.message}. ` + "Run 'val validate --fix' to point it at the module its schema names."
|
|
17143
|
+
};
|
|
17144
|
+
}
|
|
17145
|
+
return {
|
|
17146
|
+
success: true,
|
|
17147
|
+
shouldApplyPatch: true
|
|
17148
|
+
};
|
|
17149
|
+
}
|
|
16773
17150
|
async function handleExternalUpload() {
|
|
16774
17151
|
return {
|
|
16775
17152
|
success: true,
|
|
@@ -16801,7 +17178,8 @@ const currentFixHandlers = {
|
|
|
16801
17178
|
"images:check-all-files": handleCheckAllFiles,
|
|
16802
17179
|
"files:check-all-files": handleCheckAllFiles,
|
|
16803
17180
|
"jsonValues:extract-entry": handleJsonValuesExtractEntry,
|
|
16804
|
-
"external:upload": handleExternalUpload
|
|
17181
|
+
"external:upload": handleExternalUpload,
|
|
17182
|
+
"view:check-module": handleViewCheckModule
|
|
16805
17183
|
};
|
|
16806
17184
|
const deprecatedFixHandlers = {
|
|
16807
17185
|
"image:replace-metadata": handleFileMetadata
|