@valbuild/server 0.147.0 → 0.148.1
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 +115 -0
- package/browser/dist/valbuild-server-browser.cjs.dev.js +2 -2
- package/browser/dist/valbuild-server-browser.cjs.prod.js +2 -2
- package/browser/dist/valbuild-server-browser.esm.js +2 -2
- package/dist/{ValRouterCore-7ffcc08c.cjs.prod.js → ValRouterCore-42772c21.cjs.prod.js} +980 -53
- package/dist/{ValRouterCore-f5004f8f.esm.js → ValRouterCore-7ca3dc68.esm.js} +972 -54
- package/dist/{ValRouterCore-3fd3d444.cjs.dev.js → ValRouterCore-9bdaaf89.cjs.dev.js} +980 -53
- package/dist/declarations/src/ValOps.d.ts +32 -11
- package/dist/declarations/src/ValOpsMemory.d.ts +4 -4
- package/dist/declarations/src/ValServer.d.ts +1 -2
- package/dist/declarations/src/checkRemoteRef.d.ts +40 -2
- package/dist/declarations/src/createFixPatch.d.ts +25 -3
- package/dist/declarations/src/fixFiles.d.ts +42 -0
- package/dist/declarations/src/fixHandlers.d.ts +14 -0
- package/dist/declarations/src/getSettings.d.ts +1 -0
- package/dist/declarations/src/index.d.ts +1 -0
- package/dist/declarations/src/platform.d.ts +16 -0
- package/dist/declarations/src/remoteFileCheck.d.ts +116 -0
- package/dist/declarations/src/remoteHostCheck.d.ts +101 -0
- package/dist/declarations/src/studioValidation.d.ts +84 -0
- package/dist/declarations/src/uploadRemoteFile.d.ts +1 -1
- package/dist/declarations/src/videosetFixes.d.ts +3 -2
- package/dist/{getJsonEntryAtCommit-df65d861.cjs.prod.js → getJsonEntryAtCommit-2e36e805.cjs.prod.js} +3 -4
- package/dist/{getJsonEntryAtCommit-1e08d724.esm.js → getJsonEntryAtCommit-40b6d9c1.esm.js} +3 -4
- package/dist/{getJsonEntryAtCommit-e96beb9e.cjs.dev.js → getJsonEntryAtCommit-64f67a50.cjs.dev.js} +3 -4
- package/dist/valbuild-server.cjs.dev.js +1352 -695
- package/dist/valbuild-server.cjs.prod.js +1352 -695
- package/dist/valbuild-server.esm.js +1352 -696
- package/package.json +4 -5
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Buffer } from "buffer";
|
|
2
1
|
import { MediaSource, FileMetadata, FileSource, ImageMetadata, ModuleFilePath, PatchId, Schema, SelectorSource, SerializedSchema, Source, SourcePath, ValConfig, ValModules, ValidationError } from "@valbuild/core";
|
|
3
2
|
import { result } from "@valbuild/core/fp";
|
|
4
3
|
import { JSONValue, ParentRef, Patch, PatchError } from "@valbuild/core/patch";
|
|
5
4
|
import type { HistoryError } from "./history/HistoryError.js";
|
|
6
5
|
import type { PlanRefusalErrorT } from "@valbuild/shared/internal";
|
|
7
6
|
import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
|
|
7
|
+
import { type RemoteFilesCheck, type RemoteNetwork } from "./remoteFileCheck.js";
|
|
8
8
|
import type { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js";
|
|
9
9
|
import { ParentPatchId } from "@valbuild/core";
|
|
10
10
|
import type { ReifiedPreview } from "@valbuild/core";
|
|
@@ -383,7 +383,14 @@ export declare abstract class ValOps {
|
|
|
383
383
|
* are absent. `/sources/~` overlays the two for exactly this reason.
|
|
384
384
|
*/
|
|
385
385
|
getSourcesWithPatchesApplied(analysis: PatchAnalysis & OrderedPatches): Promise<Awaited<ReturnType<ValOps["getSources"]>>>;
|
|
386
|
-
validateSources(schemas: Schemas, sources: Sources, patchesByModule?: PatchAnalysis["patchesByModule"]
|
|
386
|
+
validateSources(schemas: Schemas, sources: Sources, patchesByModule?: PatchAnalysis["patchesByModule"],
|
|
387
|
+
/**
|
|
388
|
+
* Validate only these modules. The snapshot the cross-module checks read
|
|
389
|
+
* (a keyOf's keys, a route, a set's directory) is still built from every
|
|
390
|
+
* module, which is what makes checking one module at a time give the same
|
|
391
|
+
* answer for it as checking them all. Wins over `patchesByModule`.
|
|
392
|
+
*/
|
|
393
|
+
onlyModules?: readonly ModuleFilePath[]): Promise<{
|
|
387
394
|
errors: Record<ModuleFilePath, {
|
|
388
395
|
invalidSource?: {
|
|
389
396
|
message: string;
|
|
@@ -393,7 +400,19 @@ export declare abstract class ValOps {
|
|
|
393
400
|
files: Record<SourcePath, FileSource>;
|
|
394
401
|
remoteFiles: Record<SourcePath, MediaSource>;
|
|
395
402
|
}>;
|
|
396
|
-
|
|
403
|
+
/**
|
|
404
|
+
* The remote values `validateSources` set aside, checked: is each ref
|
|
405
|
+
* sound, and does its host serve it. See `checkRemoteFiles`.
|
|
406
|
+
*
|
|
407
|
+
* `fileLastUpdatedByPatchId` names the refs whose bytes are still in a
|
|
408
|
+
* pending patch, which the host will not have until publish.
|
|
409
|
+
*/
|
|
410
|
+
validateRemoteFiles(schemas: Schemas, sources: Sources, remoteFiles: Record<SourcePath, MediaSource>, fileLastUpdatedByPatchId?: PatchAnalysis["fileLastUpdatedByPatchId"], network?: RemoteNetwork): Promise<RemoteFilesCheck>;
|
|
411
|
+
/**
|
|
412
|
+
* Is a remote file's bytes in a pending patch? `not-pending` when no patch
|
|
413
|
+
* wrote it; `missing` when one did and its bytes are not there.
|
|
414
|
+
*/
|
|
415
|
+
pendingBytesOf(fileLastUpdatedByPatchId: PatchAnalysis["fileLastUpdatedByPatchId"]): (ref: string) => Promise<"present" | "missing" | "not-pending">;
|
|
397
416
|
validateFiles(schemas: Schemas, sources: Sources, files: Record<SourcePath, FileSource>, fileLastUpdatedByPatchId?: PatchAnalysis["fileLastUpdatedByPatchId"]): Promise<Record<SourcePath, ValidationError[]>>;
|
|
398
417
|
/**
|
|
399
418
|
* Applies the pending patches to the source files so they can be committed.
|
|
@@ -641,13 +660,13 @@ export declare abstract class ValOps {
|
|
|
641
660
|
* gets this for free; a backend that can take bytes straight through should
|
|
642
661
|
* override it.
|
|
643
662
|
*/
|
|
644
|
-
saveBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, bytes:
|
|
663
|
+
saveBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, bytes: Uint8Array, mimeType: string, type: "file" | "image", metadata: MetadataOfType<"file" | "image"> | undefined): Promise<WithGenericError<{
|
|
645
664
|
patchId: PatchId;
|
|
646
665
|
filePath: string;
|
|
647
666
|
}>>;
|
|
648
|
-
abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<
|
|
667
|
+
abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<Uint8Array | null>;
|
|
649
668
|
protected abstract getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, remote: boolean): Promise<OpsMetadata<T>>;
|
|
650
|
-
abstract getBinaryFile(filePathOrRef: string): Promise<
|
|
669
|
+
abstract getBinaryFile(filePathOrRef: string): Promise<Uint8Array | null>;
|
|
651
670
|
/**
|
|
652
671
|
* A file's bytes as something a `Range` request can be answered from
|
|
653
672
|
* without reading the rest: a video is seeked in by many small ranges, and
|
|
@@ -710,7 +729,7 @@ export declare abstract class ValOps {
|
|
|
710
729
|
/** Which files the commit touched, and how. Names them; does not fetch them. */
|
|
711
730
|
abstract getCommitAffectedFiles(commitSha: string): Promise<result.Result<AffectedFile[], HistoryError>>;
|
|
712
731
|
/** One file's bytes as they were at one commit. */
|
|
713
|
-
abstract getFileAtCommit(commitSha: string, filePath: string, remote: boolean): Promise<result.Result<
|
|
732
|
+
abstract getFileAtCommit(commitSha: string, filePath: string, remote: boolean): Promise<result.Result<Uint8Array, HistoryError>>;
|
|
714
733
|
/**
|
|
715
734
|
* Where a module lives in the REPOSITORY, as `getFileAtCommit` wants it.
|
|
716
735
|
*
|
|
@@ -1019,13 +1038,15 @@ export type OrderedPatchesMetadata = {
|
|
|
1019
1038
|
networkError?: boolean;
|
|
1020
1039
|
};
|
|
1021
1040
|
export declare function getFieldsForType<T extends BinaryFileType>(type: T): (keyof MetadataOfType<T> & string)[];
|
|
1022
|
-
export declare function createMetadataFromBuffer<T extends BinaryFileType>(type: BinaryFileType, mimeType: string, buffer:
|
|
1041
|
+
export declare function createMetadataFromBuffer<T extends BinaryFileType>(type: BinaryFileType, mimeType: string, buffer: Uint8Array): OpsMetadata<T>;
|
|
1023
1042
|
export declare function guessMimeTypeFromPath(filePath: string): string | null;
|
|
1024
|
-
export declare function bufferFromDataUrl(dataUrl: string
|
|
1043
|
+
export declare function bufferFromDataUrl(dataUrl: string,
|
|
1044
|
+
/** Node passes `Buffer`'s, which is faster: this is a whole upload. */
|
|
1045
|
+
decodeBase64?: (base64: string) => Uint8Array): Uint8Array | undefined;
|
|
1025
1046
|
/** A file of known size whose bytes are read a range at a time. */
|
|
1026
1047
|
export type BinaryFileReader = {
|
|
1027
1048
|
readonly size: number;
|
|
1028
1049
|
/** Bytes `start` to `end`, both inclusive, as an HTTP range is. */
|
|
1029
|
-
read(start: number, end: number): Promise<
|
|
1050
|
+
read(start: number, end: number): Promise<Uint8Array>;
|
|
1030
1051
|
};
|
|
1031
|
-
export declare function bufferReader(buffer:
|
|
1052
|
+
export declare function bufferReader(buffer: Uint8Array): BinaryFileReader;
|
|
@@ -34,7 +34,7 @@ export type StoredFile = {
|
|
|
34
34
|
* has resolved that before it asks.
|
|
35
35
|
*/
|
|
36
36
|
filePath: string;
|
|
37
|
-
data:
|
|
37
|
+
data: Uint8Array;
|
|
38
38
|
metadata: MetadataOfType<"file" | "image"> | undefined;
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
@@ -294,9 +294,9 @@ export declare class ValOpsMemory extends ValOps {
|
|
|
294
294
|
patchId: PatchId;
|
|
295
295
|
filePath: string;
|
|
296
296
|
}>>;
|
|
297
|
-
getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, _remote?: boolean): Promise<
|
|
297
|
+
getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, _remote?: boolean): Promise<Uint8Array | null>;
|
|
298
298
|
protected getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, _remote?: boolean): Promise<OpsMetadata<T>>;
|
|
299
|
-
getBinaryFile(_filePathOrRef: string): Promise<
|
|
299
|
+
getBinaryFile(_filePathOrRef: string): Promise<Uint8Array | null>;
|
|
300
300
|
protected getBinaryFileMetadata<T extends "file" | "image">(_filePath: string, _type: T): Promise<OpsMetadata<T>>;
|
|
301
301
|
listCommits(): Promise<result.Result<CommitPage, HistoryError>>;
|
|
302
302
|
getCommitPatches(): Promise<result.Result<{
|
|
@@ -308,7 +308,7 @@ export declare class ValOpsMemory extends ValOps {
|
|
|
308
308
|
complete: boolean;
|
|
309
309
|
}, HistoryError>>;
|
|
310
310
|
getCommitAffectedFiles(): Promise<result.Result<AffectedFile[], HistoryError>>;
|
|
311
|
-
getFileAtCommit(): Promise<result.Result<
|
|
311
|
+
getFileAtCommit(): Promise<result.Result<Uint8Array, HistoryError>>;
|
|
312
312
|
gitPathOfModule(_moduleFilePath: ModuleFilePath): result.Result<string, HistoryError>;
|
|
313
313
|
}
|
|
314
314
|
/** Kept exported so a host can name the error shape it may get back. */
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Buffer } from "buffer";
|
|
2
1
|
import type { ValContentVersion, ValProposal } from "./proposal.js";
|
|
3
2
|
import { ValModules, ValConfig } from "@valbuild/core";
|
|
4
3
|
import { Api, ServerOf } from "@valbuild/shared/internal";
|
|
@@ -197,7 +196,7 @@ export declare const ENABLE_COOKIE_VALUE: {
|
|
|
197
196
|
readonly sameSite: "lax";
|
|
198
197
|
};
|
|
199
198
|
};
|
|
200
|
-
export declare function bufferToReadableStream(buffer:
|
|
199
|
+
export declare function bufferToReadableStream(buffer: Uint8Array): ReadableStream<any>;
|
|
201
200
|
export declare function getRedirectUrl(query: {
|
|
202
201
|
redirect_to?: string | undefined;
|
|
203
202
|
}, overrideHost: string | undefined): string | {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { FileMetadata, ImageMetadata, SerializedFileSchema, SerializedImageSchema } from "@valbuild/core";
|
|
2
|
-
export declare function checkRemoteRef(
|
|
2
|
+
export declare function checkRemoteRef(
|
|
3
|
+
/**
|
|
4
|
+
* Unused: a re-derived ref keeps the host it already names (see below).
|
|
5
|
+
* Kept so the exported signature does not change.
|
|
6
|
+
*/
|
|
7
|
+
_remoteHost: string, ref: string, projectRoot: string, schema: SerializedImageSchema | SerializedFileSchema, metadata: Record<string, unknown> | undefined,
|
|
8
|
+
/**
|
|
9
|
+
* Where the bytes come from when the ref has to be re-derived. Defaults to
|
|
10
|
+
* a download cached under `projectRoot`; the Studio passes one that keeps
|
|
11
|
+
* nothing on disk (`FixFiles.readRemoteFile`).
|
|
12
|
+
*/
|
|
13
|
+
readRemote?: ReadRemoteFile): Promise<{
|
|
3
14
|
status: "success";
|
|
4
15
|
} | {
|
|
5
16
|
status: "fix-required";
|
|
@@ -11,10 +22,37 @@ export declare function checkRemoteRef(remoteHost: string, ref: string, projectR
|
|
|
11
22
|
}>;
|
|
12
23
|
export declare function getCachedRemoteFileDir(projectRoot: string): string;
|
|
13
24
|
export declare function getCachedRemoteFilePath(fileExt: string, currentFileHash: string, remoteFileCacheDir: string): string;
|
|
14
|
-
|
|
25
|
+
/**
|
|
26
|
+
* A remote file's bytes, and the hash they actually have — which is the one a
|
|
27
|
+
* re-derived ref is made from, whatever hash the old ref claimed.
|
|
28
|
+
*/
|
|
29
|
+
export type ReadRemoteFile = (ref: string, fileExt: string, expectedFileHash: string) => Promise<{
|
|
30
|
+
status: "success";
|
|
31
|
+
fileBuffer: Buffer;
|
|
32
|
+
fileHash: string;
|
|
33
|
+
/** Where a download was kept, when it was kept: for error messages. */
|
|
34
|
+
cachedFilePath?: string;
|
|
35
|
+
} | {
|
|
36
|
+
status: "error";
|
|
37
|
+
error: string;
|
|
38
|
+
}>;
|
|
39
|
+
export declare function getFileBufferFromRemote(ref: string, fileExt: string, currentFileHash: string, projectRoot: string): ReturnType<ReadRemoteFile>;
|
|
40
|
+
/**
|
|
41
|
+
* Download a remote file to `filePath`, from where its ref says -- and, for a
|
|
42
|
+
* platform ref whose host fails, from the same file on the project's own
|
|
43
|
+
* `valstart.dev` address, which always serves it (`fallbackRefOf`). The bytes
|
|
44
|
+
* from the fallback are checked against the ref's file hash before they are
|
|
45
|
+
* kept: a different host answering is only safe because of that check.
|
|
46
|
+
*
|
|
47
|
+
* Rejects with the FIRST host's error when both fail: that is the one the ref
|
|
48
|
+
* names, and the one a person can recognise.
|
|
49
|
+
*/
|
|
50
|
+
export declare function downloadFileFromRemote(ref: string, filePath: string): ReturnType<typeof downloadFromUrl>;
|
|
51
|
+
declare function downloadFromUrl(ref: string, filePath: string): Promise<{
|
|
15
52
|
status: "success";
|
|
16
53
|
filePath: string;
|
|
17
54
|
} | {
|
|
18
55
|
status: "error";
|
|
19
56
|
error: string;
|
|
20
57
|
}>;
|
|
58
|
+
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { FileMetadata, ImageMetadata, VideoMetadata, SerializedSchema, Source, SourcePath, ValidationError } from "@valbuild/core";
|
|
2
2
|
import { JSONValue, Patch } from "@valbuild/core/patch";
|
|
3
|
+
import { type AskHostProject } from "./remoteHostCheck.js";
|
|
4
|
+
import { FixFiles } from "./fixFiles.js";
|
|
3
5
|
export type FixPatchRemainingError = ValidationError & {
|
|
4
6
|
sourcePath?: SourcePath;
|
|
5
7
|
};
|
|
@@ -17,6 +19,16 @@ export declare function mediaValue(path: string, metadata: unknown): Record<stri
|
|
|
17
19
|
export declare function createFixPatch(config: {
|
|
18
20
|
projectRoot: string;
|
|
19
21
|
remoteHost: string;
|
|
22
|
+
/**
|
|
23
|
+
* Where a fix reads a file's bytes and puts a downloaded one. Defaults to
|
|
24
|
+
* the disk under `projectRoot`; see {@link FixFiles}.
|
|
25
|
+
*/
|
|
26
|
+
files?: FixFiles;
|
|
27
|
+
/**
|
|
28
|
+
* How a platform ref's host is asked which project it serves. Defaults
|
|
29
|
+
* to `GET <host>/_val/project`; tests replace it.
|
|
30
|
+
*/
|
|
31
|
+
askHostProject?: AskHostProject;
|
|
20
32
|
}, apply: boolean, sourcePath: SourcePath, validationError: ValidationError, remoteFiles: {
|
|
21
33
|
[sourcePath: SourcePath]: {
|
|
22
34
|
ref: string;
|
|
@@ -27,6 +39,16 @@ export declare function createFixPatch(config: {
|
|
|
27
39
|
patch: Patch;
|
|
28
40
|
remainingErrors: FixPatchRemainingError[];
|
|
29
41
|
} | undefined>;
|
|
30
|
-
export declare function getVideoMetadata(
|
|
31
|
-
export declare function getImageMetadata(
|
|
32
|
-
export declare function getFileMetadata(
|
|
42
|
+
export declare function getVideoMetadata(files: FixFiles, fileRef: string): Promise<VideoMetadata>;
|
|
43
|
+
export declare function getImageMetadata(files: FixFiles, validationError: ValidationError): Promise<ImageMetadata>;
|
|
44
|
+
export declare function getFileMetadata(files: FixFiles, validationError: ValidationError): Promise<FileMetadata>;
|
|
45
|
+
/**
|
|
46
|
+
* What an image or file directory's entry records that its bytes disagree
|
|
47
|
+
* with, as `[field, recorded, actual]`. The one comparison: the CLI's
|
|
48
|
+
* `*:check-all-files` fix and the Studio's full check both ask it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function mediaDirEntryMismatches(type: "images" | "files", stored: Record<string, unknown>, actual: {
|
|
51
|
+
width?: number;
|
|
52
|
+
height?: number;
|
|
53
|
+
mimeType?: string;
|
|
54
|
+
}): [field: string, recorded: unknown, actual: unknown][];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { VideoMetadata } from "@valbuild/core";
|
|
2
|
+
import { type ReadRemoteFile } from "./checkRemoteRef.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where `createFixPatch` reads a project file's bytes, and where it puts a
|
|
5
|
+
* remote file it was asked to download.
|
|
6
|
+
*
|
|
7
|
+
* Every path is the file's path from the project root, the way a source names
|
|
8
|
+
* it: `/public/val/logo.png`, never an absolute path on some disk. That is the
|
|
9
|
+
* point: a project in http mode, or one the platform serves from a Worker, has
|
|
10
|
+
* no disk to join a path onto, and a fix that reads a file has to work there
|
|
11
|
+
* too. `diskFixFiles` is the one the CLI and the language server use, and does
|
|
12
|
+
* exactly what `createFixPatch` did with `fs` before this existed.
|
|
13
|
+
*
|
|
14
|
+
* Not yet covered, and still read from `projectRoot` directly: the CLI's
|
|
15
|
+
* listing of a media directory for files it does not track, and
|
|
16
|
+
* `videos:check-all-files` (`videosetCheckAllFilesPatch`), which lists one.
|
|
17
|
+
* See `docs/plans/studio-validate.md`.
|
|
18
|
+
*/
|
|
19
|
+
export type FixFiles = {
|
|
20
|
+
/** A project file's bytes. Rejects when it cannot be read. */
|
|
21
|
+
readFile(filePath: string): Promise<Buffer>;
|
|
22
|
+
/**
|
|
23
|
+
* A project video's size, length and mime type. Its own method rather than
|
|
24
|
+
* `readFile` because a video is mostly frames, and the disk reader reads
|
|
25
|
+
* only the headers.
|
|
26
|
+
*/
|
|
27
|
+
readVideoMetadata(filePath: string): Promise<VideoMetadata>;
|
|
28
|
+
/**
|
|
29
|
+
* A remote file's bytes, for re-deriving a stale ref (`*:check-remote`).
|
|
30
|
+
* Nothing is stored in the project.
|
|
31
|
+
*/
|
|
32
|
+
readRemoteFile: ReadRemoteFile;
|
|
33
|
+
/** Downloads `url` and stores it in the project at `filePath`. */
|
|
34
|
+
saveRemoteFile(url: string, filePath: string): Promise<{
|
|
35
|
+
status: "success";
|
|
36
|
+
} | {
|
|
37
|
+
status: "error";
|
|
38
|
+
error: string;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
/** {@link FixFiles} over a project on disk. */
|
|
42
|
+
export declare function diskFixFiles(projectRoot: string): FixFiles;
|
|
@@ -44,6 +44,8 @@ export type IValRemote = {
|
|
|
44
44
|
remoteFileBuckets: {
|
|
45
45
|
bucket: string;
|
|
46
46
|
}[];
|
|
47
|
+
/** See `getSettings`: the host new refs name. */
|
|
48
|
+
remoteHost?: string;
|
|
47
49
|
};
|
|
48
50
|
} | {
|
|
49
51
|
success: false;
|
|
@@ -134,12 +136,16 @@ export type ValidationEvent = {
|
|
|
134
136
|
sourcePath: string;
|
|
135
137
|
message: string;
|
|
136
138
|
keyError?: boolean;
|
|
139
|
+
/** See `isRepairedOnSave`: the Studio would not hold a publish for it. */
|
|
140
|
+
repairedOnSave?: boolean;
|
|
137
141
|
} | {
|
|
138
142
|
type: "validation-fixable-error";
|
|
139
143
|
sourcePath: string;
|
|
140
144
|
message: string;
|
|
141
145
|
fixable: boolean;
|
|
142
146
|
keyError?: boolean;
|
|
147
|
+
/** See `isRepairedOnSave`: the Studio would not hold a publish for it. */
|
|
148
|
+
repairedOnSave?: boolean;
|
|
143
149
|
} | {
|
|
144
150
|
type: "unknown-fix";
|
|
145
151
|
sourcePath: string;
|
|
@@ -246,6 +252,14 @@ export declare function handleJsonValuesExtractEntry(ctx: FixHandlerContext): Pr
|
|
|
246
252
|
* exists to send it there, and to say what `--fix` would do when it is off.
|
|
247
253
|
*/
|
|
248
254
|
export declare function handleViewCheckModule(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
255
|
+
/**
|
|
256
|
+
* `remote:rewrite-host`: a platform ref whose host no longer serves its
|
|
257
|
+
* project. The server's full check reports it, and so does `createFixPatch`'s
|
|
258
|
+
* `image:check-remote` branch, which is how `val validate` finds it: that
|
|
259
|
+
* branch asks the host first. Either way the patch is `createFixPatch`'s, and
|
|
260
|
+
* needs nothing but the ref -- the new host is derived from it.
|
|
261
|
+
*/
|
|
262
|
+
export declare function handleRemoteRewriteHost(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
249
263
|
export declare function handleExternalUpload(): Promise<FixHandlerResult>;
|
|
250
264
|
export declare const currentFixHandlers: Record<Exclude<ValidationFix, "keyof:check-keys" | "image:check-gallery" | "file:check-gallery" | "video:check-gallery" | "router:check-route" | "locale:check-locale" | "router:check-locale-param" | "record:fill-keys">, FixHandler>;
|
|
251
265
|
export declare const fixHandlers: Record<string, FixHandler>;
|
|
@@ -4,6 +4,7 @@ declare const SettingsSchema: z.ZodObject<{
|
|
|
4
4
|
remoteFileBuckets: z.ZodArray<z.ZodObject<{
|
|
5
5
|
bucket: z.ZodString;
|
|
6
6
|
}, z.core.$strip>>;
|
|
7
|
+
remoteHost: z.ZodOptional<z.ZodString>;
|
|
7
8
|
}, z.core.$strip>;
|
|
8
9
|
type Settings = z.infer<typeof SettingsSchema>;
|
|
9
10
|
export declare function getSettings(projectName: string, auth: {
|
|
@@ -19,6 +19,7 @@ export { formatSyntaxErrorTree } from "./patch/ts/syntax.js";
|
|
|
19
19
|
export { analyzeValModule } from "./patch/ts/valModule.js";
|
|
20
20
|
export type { ValModuleAnalysis } from "./patch/ts/valModule.js";
|
|
21
21
|
export { createFixPatch } from "./createFixPatch.js";
|
|
22
|
+
export { askOncePerHost } from "./remoteHostCheck.js";
|
|
22
23
|
export { fixHandlers, currentFixHandlers, createDefaultValFSHost, handleFileMetadata, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleRemoteFileDownload, handleRemoteFileCheck, handleUniqueFolderCheck, handleCheckAllFiles, handleJsonValuesExtractEntry, handleExternalUpload, } from "./fixHandlers.js";
|
|
23
24
|
export { handleVideosetMetadata, handleVideosetUploadRemote, handleVideosetCheckRemote, handleVideosetCheckAllFiles, videosetEntryVideoSchema, filesOfVideosetEntry, } from "./videosetFixes.js";
|
|
24
25
|
export type { FixHandler, ModulePatch, FixHandlerContext, FixHandlerResult, IValRemote, ValidationEvent, ValidationError, ValModule, } from "./fixHandlers.js";
|
|
@@ -6,6 +6,7 @@ import type { ValServerConfig } from "./ValServer.js";
|
|
|
6
6
|
import type { ResolveRemoteFileAuthResult } from "./valServerConfig.js";
|
|
7
7
|
import type { JwtResult } from "./jwt.js";
|
|
8
8
|
import type { ProbeResult } from "./linkCheck/probeUrl.js";
|
|
9
|
+
import type { checkModuleForStudio, fixForStudio } from "./studioValidation.js";
|
|
9
10
|
/**
|
|
10
11
|
* What `ValServer` needs from the machine it runs on.
|
|
11
12
|
*
|
|
@@ -32,8 +33,23 @@ export type ValServerPlatform = {
|
|
|
32
33
|
encodeJwt: (payload: object, sessionKey: string) => string;
|
|
33
34
|
getExpire: () => number;
|
|
34
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* `bytes` as base64. For the paths that encode every file a publish commits:
|
|
38
|
+
* Node's `Buffer` is ten times faster than `btoa`, and a browser has only
|
|
39
|
+
* `btoa` (`bytesToBase64` in `bytes.ts`). Same answer either way.
|
|
40
|
+
*/
|
|
41
|
+
bytesToBase64: (bytes: Uint8Array) => string;
|
|
35
42
|
/** The developer's `val login` token. See `readValLoginToken`. */
|
|
36
43
|
readValLoginToken: (options: ValServerConfig) => Promise<string | null>;
|
|
37
44
|
/** Which credential talks to the content host. See `resolveRemoteFileAuth`. */
|
|
38
45
|
resolveRemoteFileAuth: (options: ValServerConfig) => Promise<ResolveRemoteFileAuthResult>;
|
|
46
|
+
/**
|
|
47
|
+
* The Studio's full check (`/validate`, `/validate/fix`). It reads files'
|
|
48
|
+
* bytes and asks remote hosts, so it needs Node; absent in a browser, where
|
|
49
|
+
* both routes answer that it is not available.
|
|
50
|
+
*/
|
|
51
|
+
studioValidation?: {
|
|
52
|
+
checkModuleForStudio: typeof checkModuleForStudio;
|
|
53
|
+
fixForStudio: typeof fixForStudio;
|
|
54
|
+
};
|
|
39
55
|
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { MediaSource, SerializedSchema, SourcePath, ValidationError } from "@valbuild/core";
|
|
2
|
+
import { type AskHostProject } from "./remoteHostCheck.js";
|
|
3
|
+
/**
|
|
4
|
+
* Does the host a remote ref names still serve it?
|
|
5
|
+
*
|
|
6
|
+
* `found` and `missing` are the host's answer. `unreachable` is no answer at
|
|
7
|
+
* all — a timeout, a refused connection, a 5xx — and says nothing about the
|
|
8
|
+
* file, only about the host.
|
|
9
|
+
*/
|
|
10
|
+
export type RemoteFileAnswer = {
|
|
11
|
+
status: "found";
|
|
12
|
+
} | {
|
|
13
|
+
status: "missing";
|
|
14
|
+
httpStatus: number;
|
|
15
|
+
} | {
|
|
16
|
+
status: "unreachable";
|
|
17
|
+
reason: string;
|
|
18
|
+
};
|
|
19
|
+
export type HeadRemoteFile = (url: string) => Promise<RemoteFileAnswer>;
|
|
20
|
+
/**
|
|
21
|
+
* How the remote check reaches the network: what it asks a host about a file,
|
|
22
|
+
* and which project a platform host serves. Both default to the real thing;
|
|
23
|
+
* tests replace them.
|
|
24
|
+
*/
|
|
25
|
+
export type RemoteNetwork = {
|
|
26
|
+
head?: HeadRemoteFile;
|
|
27
|
+
askHostProject?: AskHostProject;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A HEAD, falling back to the first byte of a GET for a host that does not
|
|
31
|
+
* answer HEAD (405, 501): either way no file is downloaded to learn that it is
|
|
32
|
+
* there.
|
|
33
|
+
*/
|
|
34
|
+
export declare const headRemoteFile: HeadRemoteFile;
|
|
35
|
+
/** For tests: forget every answer. */
|
|
36
|
+
export declare function clearRemoteFileAnswers(): void;
|
|
37
|
+
/** A host that did not serve the refs of a module it was asked about. */
|
|
38
|
+
export type RemoteHostProblem = {
|
|
39
|
+
/** `https://remote.val.build`, as the ref names it. */
|
|
40
|
+
host: string;
|
|
41
|
+
status: "missing" | "unreachable";
|
|
42
|
+
/** What the host answered, for "Details": never the row's sentence. */
|
|
43
|
+
details: string;
|
|
44
|
+
/** Every ref on that host in the module, checked or vouched for. */
|
|
45
|
+
sourcePaths: SourcePath[];
|
|
46
|
+
};
|
|
47
|
+
export type RemoteFilesCheck = {
|
|
48
|
+
/** Wrong refs, one by one: each has its own row. */
|
|
49
|
+
errors: Record<SourcePath, ValidationError[]>;
|
|
50
|
+
/** Hosts that failed: one row each, however many refs they cover. */
|
|
51
|
+
hosts: RemoteHostProblem[];
|
|
52
|
+
};
|
|
53
|
+
export type RemoteFilesCheckInput = {
|
|
54
|
+
/** What `validateSources` set aside as remote: every remote media value. */
|
|
55
|
+
remoteFiles: Record<SourcePath, MediaSource>;
|
|
56
|
+
/** The serialized schema at a path, or undefined when it does not resolve. */
|
|
57
|
+
schemaAt: (sourcePath: SourcePath) => SerializedSchema | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Does this ref have its bytes in a pending patch? They go to the remote
|
|
60
|
+
* host at publish, so until then asking the host for it is asking a
|
|
61
|
+
* question whose answer is "not yet".
|
|
62
|
+
*/
|
|
63
|
+
pendingBytes: (ref: string) => Promise<"present" | "missing" | "not-pending">;
|
|
64
|
+
/**
|
|
65
|
+
* Is this value's ref the one already published at this path? Only those
|
|
66
|
+
* may be vouched for by another ref on the same host; see `sampleByHost`.
|
|
67
|
+
*/
|
|
68
|
+
isPublished: (sourcePath: SourcePath, ref: string) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Ask one published ref per host and take its answer for the rest.
|
|
71
|
+
*
|
|
72
|
+
* Right for a managed project, whose refs are only ever written by the
|
|
73
|
+
* Studio and whose host changes for every ref at once. Wrong for a connected
|
|
74
|
+
* one, where a developer can edit any single ref by hand. See
|
|
75
|
+
* `docs/plans/studio-validate.md`, "Remote refs in the full check".
|
|
76
|
+
*/
|
|
77
|
+
sampleByHost: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Ask only whether the host serves each ref. For the keys of a media
|
|
80
|
+
* directory: what is recorded about the file is the entry, not a value at
|
|
81
|
+
* the path, and the ref's hash was made from a schema the directory
|
|
82
|
+
* synthesizes for it, so there is no schema here to judge it by.
|
|
83
|
+
*/
|
|
84
|
+
existenceOnly?: boolean;
|
|
85
|
+
head?: HeadRemoteFile;
|
|
86
|
+
/** How a platform ref's host is asked which project it serves. */
|
|
87
|
+
askHostProject?: AskHostProject;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The remote half of the Studio's full check, for one module's remote values.
|
|
91
|
+
*
|
|
92
|
+
* Four questions, cheapest first, and a ref stops at the first one it fails:
|
|
93
|
+
*
|
|
94
|
+
* 1. **Is it a ref at all?** `splitRemoteRef` parses it.
|
|
95
|
+
* 2. **Does the ref agree with what is recorded about it?** A ref carries a
|
|
96
|
+
* hash of the schema's options, the file's extension and its width, height
|
|
97
|
+
* and mime type. When the value or the schema changed and the ref did not,
|
|
98
|
+
* the ref is stale, and **Fix** (`image:check-remote`) re-reads the file and
|
|
99
|
+
* writes the ref it should have. Computed with the ref's OWN core version:
|
|
100
|
+
* the CLI also re-derives every ref after a Val upgrade, and listing every
|
|
101
|
+
* remote image in the project after each upgrade would bury the rows that
|
|
102
|
+
* matter. Nothing here downloads.
|
|
103
|
+
* 3. **Does its host still serve its project?** Only for a platform ref
|
|
104
|
+
* (`…/_val/file/…`) that is not on the project's own `valstart.dev`
|
|
105
|
+
* address: one `GET <host>/_val/project` per host. A host that dropped the
|
|
106
|
+
* project gets a **Fix** (`remote:rewrite-host`) that points the ref at
|
|
107
|
+
* `<public id>.valstart.dev`. See `remoteHostCheck.ts`.
|
|
108
|
+
* 4. **Does the host have it?** A HEAD.
|
|
109
|
+
*
|
|
110
|
+
* Neither host question is asked of a ref whose bytes are in a pending patch,
|
|
111
|
+
* which the host will not have until publish: there the question is whether
|
|
112
|
+
* the patch still has them.
|
|
113
|
+
*/
|
|
114
|
+
export declare function checkRemoteFiles(input: RemoteFilesCheckInput): Promise<RemoteFilesCheck>;
|
|
115
|
+
/** `www.acme.no`, from `https://www.acme.no`: how a person names a host. */
|
|
116
|
+
export declare function hostName(host: string): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does the host a platform ref names still serve the project the ref names?
|
|
3
|
+
*
|
|
4
|
+
* A ref served by the platform loader names the project's host at the time
|
|
5
|
+
* the file was added: `https://www.acme.no/_val/file/p/3f9a2c1/…`. When the
|
|
6
|
+
* project drops `www.acme.no`, the bytes are still there, at
|
|
7
|
+
* `3f9a2c1.valstart.dev`, but every ref that names the old domain points at a
|
|
8
|
+
* host that no longer serves them. This is the question that finds those refs,
|
|
9
|
+
* and `rewriteRefHost` is what the fix (`remote:rewrite-host`) does about it.
|
|
10
|
+
* See valbuild/home `docs/remote-files-on-platform.md`, "Rewriting refs after a
|
|
11
|
+
* domain is removed", and `docs/plans/studio-validate.md`, step 6.
|
|
12
|
+
*
|
|
13
|
+
* Asked of the host itself (`GET <host>/_val/project`), not of content: the
|
|
14
|
+
* host's answer covers DNS, the certificate and the loader's mapping in one,
|
|
15
|
+
* it needs no credential, and it works for a ref that names a project other
|
|
16
|
+
* than this one -- a project seeded from a template has refs naming the
|
|
17
|
+
* template's.
|
|
18
|
+
*/
|
|
19
|
+
/** The domain every platform project is always served at, by its public id. */
|
|
20
|
+
export declare const PLATFORM_SITES_DOMAIN = "valstart.dev";
|
|
21
|
+
/**
|
|
22
|
+
* The host every platform ref of `publicProjectId` can always be pointed at:
|
|
23
|
+
* `<public id>.valstart.dev` is permanent (decision 4 of the remote-files
|
|
24
|
+
* plan), and the loader serves a project's files there whatever else it has.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fallbackRemoteHost(publicProjectId: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Only a platform ref is asked about. `remote.val.build` is not a platform
|
|
29
|
+
* host and is not going away, and a ref already on the project's own
|
|
30
|
+
* `valstart.dev` address is on the fallback, which always serves it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function needsHostCheck(remoteHost: string, projectId: string): boolean;
|
|
33
|
+
/** `ref` with its host part replaced, and everything else as it was. */
|
|
34
|
+
export declare function rewriteRefHost(ref: string, remoteHost: string): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* - `serves`: the host answered for the project the ref names.
|
|
37
|
+
* - `other-project`: it answered, for another one -- the domain was moved to
|
|
38
|
+
* a different project, and decision 5 means it will not serve these files.
|
|
39
|
+
* - `no-site`: it answered, but serves no project at all (the loader's 404 for
|
|
40
|
+
* a domain nobody has mapped any more).
|
|
41
|
+
* - `unreachable`: no answer. A domain whose DNS was removed looks like this,
|
|
42
|
+
* and so does a host that is down for a minute, which is why the row says
|
|
43
|
+
* which it saw.
|
|
44
|
+
*/
|
|
45
|
+
export type HostServes = {
|
|
46
|
+
status: "serves";
|
|
47
|
+
} | {
|
|
48
|
+
status: "other-project";
|
|
49
|
+
publicProjectId: string;
|
|
50
|
+
} | {
|
|
51
|
+
status: "no-site";
|
|
52
|
+
httpStatus: number;
|
|
53
|
+
} | {
|
|
54
|
+
status: "unreachable";
|
|
55
|
+
reason: string;
|
|
56
|
+
};
|
|
57
|
+
export type AskHostProject = (remoteHost: string) => Promise<{
|
|
58
|
+
status: "answered";
|
|
59
|
+
publicProjectId: string;
|
|
60
|
+
} | {
|
|
61
|
+
status: "no-site";
|
|
62
|
+
httpStatus: number;
|
|
63
|
+
} | {
|
|
64
|
+
status: "unreachable";
|
|
65
|
+
reason: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** `GET <host>/_val/project`, answered by the platform loader. */
|
|
68
|
+
export declare const askHostProject: AskHostProject;
|
|
69
|
+
export declare function clearHostAnswers(): void;
|
|
70
|
+
export declare function hostServes(remoteHost: string, projectId: string, ask?: AskHostProject): Promise<HostServes>;
|
|
71
|
+
/**
|
|
72
|
+
* `ask`, asking each host once for as long as the returned function is kept:
|
|
73
|
+
* one check, not the life of the server. A check walks its refs in order, so
|
|
74
|
+
* without it every ref on a host that does not answer waits out its own
|
|
75
|
+
* timeout -- 150 images on a dead domain is 20 minutes. Kept per check rather
|
|
76
|
+
* than in {@link hostServes}'s cache because a host that did not answer has
|
|
77
|
+
* to be asked again by the next one.
|
|
78
|
+
*/
|
|
79
|
+
export declare function askOncePerHost(ask?: AskHostProject): AskHostProject;
|
|
80
|
+
/** `www.acme.no`, from `https://www.acme.no/_val`: how a person names a host. */
|
|
81
|
+
export declare function hostNameOfRemoteHost(remoteHost: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* Where a platform ref has to move to, when its host no longer serves the
|
|
84
|
+
* project it names; `undefined` when it does not have to (or is not a
|
|
85
|
+
* platform ref). `why` and `to` are for the caller's sentence: what can be
|
|
86
|
+
* done about it differs by caller (a Studio **Fix**, `val validate --fix`, or
|
|
87
|
+
* nothing yet).
|
|
88
|
+
*/
|
|
89
|
+
export declare function movedRemoteHost(ref: string, ask?: AskHostProject): Promise<{
|
|
90
|
+
ref: string;
|
|
91
|
+
why: string;
|
|
92
|
+
to: string;
|
|
93
|
+
} | undefined>;
|
|
94
|
+
/**
|
|
95
|
+
* Where else a ref's file can be downloaded from: the same file on the
|
|
96
|
+
* project's own `valstart.dev` address, for a platform ref on any other host.
|
|
97
|
+
* `null` when there is nowhere else -- a `remote.val.build` ref, or one already
|
|
98
|
+
* on the fallback. See valbuild/home `docs/remote-files-on-platform.md`,
|
|
99
|
+
* "Tooling: the fallback".
|
|
100
|
+
*/
|
|
101
|
+
export declare function fallbackRefOf(ref: string): string | null;
|