@valbuild/server 0.146.4 → 0.148.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/browser/dist/valbuild-server-browser.cjs.d.ts +2 -0
  3. package/browser/dist/valbuild-server-browser.cjs.dev.js +146 -0
  4. package/browser/dist/valbuild-server-browser.cjs.js +7 -0
  5. package/browser/dist/valbuild-server-browser.cjs.prod.js +146 -0
  6. package/browser/dist/valbuild-server-browser.esm.js +142 -0
  7. package/browser/package.json +4 -0
  8. package/dist/ValRouterCore-42772c21.cjs.prod.js +10824 -0
  9. package/dist/ValRouterCore-7ca3dc68.esm.js +10784 -0
  10. package/dist/ValRouterCore-9bdaaf89.cjs.dev.js +10824 -0
  11. package/dist/declarations/src/ValOps.d.ts +44 -11
  12. package/dist/declarations/src/ValOpsFS.d.ts +1 -0
  13. package/dist/declarations/src/ValOpsHttp.d.ts +1 -0
  14. package/dist/declarations/src/ValOpsMemory.d.ts +13 -4
  15. package/dist/declarations/src/ValRouter.d.ts +1 -1
  16. package/dist/declarations/src/ValServer.d.ts +9 -3
  17. package/dist/declarations/src/browser/inTabFetch.d.ts +27 -0
  18. package/dist/declarations/src/browser/index.d.ts +9 -0
  19. package/dist/declarations/src/checkRemoteRef.d.ts +40 -2
  20. package/dist/declarations/src/createFixPatch.d.ts +25 -3
  21. package/dist/declarations/src/fixFiles.d.ts +42 -0
  22. package/dist/declarations/src/fixHandlers.d.ts +15 -1
  23. package/dist/declarations/src/getSettings.d.ts +1 -0
  24. package/dist/declarations/src/index.d.ts +1 -0
  25. package/dist/declarations/src/linkCheck/probeUrl.d.ts +40 -0
  26. package/dist/declarations/src/platform.d.ts +55 -0
  27. package/dist/declarations/src/remoteFileCheck.d.ts +116 -0
  28. package/dist/declarations/src/remoteHostCheck.d.ts +101 -0
  29. package/dist/declarations/src/studioValidation.d.ts +84 -0
  30. package/dist/declarations/src/uploadRemoteFile.d.ts +1 -1
  31. package/dist/declarations/src/videosetFixes.d.ts +3 -2
  32. package/dist/getJsonEntryAtCommit-2e36e805.cjs.prod.js +142 -0
  33. package/dist/getJsonEntryAtCommit-40b6d9c1.esm.js +135 -0
  34. package/dist/getJsonEntryAtCommit-64f67a50.cjs.dev.js +142 -0
  35. package/dist/jsonValuesModule-427d68c8.esm.js +273 -0
  36. package/dist/jsonValuesModule-8ae503a8.cjs.dev.js +288 -0
  37. package/dist/jsonValuesModule-d1bb8d8f.cjs.prod.js +288 -0
  38. package/dist/ops-0d05feaf.esm.js +353 -0
  39. package/dist/ops-b15239ac.cjs.prod.js +361 -0
  40. package/dist/ops-f37d18c6.cjs.dev.js +361 -0
  41. package/dist/publishKit-580f558a.cjs.prod.js +41 -0
  42. package/dist/publishKit-5865095c.cjs.dev.js +41 -0
  43. package/dist/publishKit-bc8df53d.esm.js +31 -0
  44. package/dist/valbuild-server.cjs.dev.js +9239 -18895
  45. package/dist/valbuild-server.cjs.prod.js +9239 -18895
  46. package/dist/valbuild-server.esm.js +9107 -18763
  47. package/package.json +17 -5
@@ -4,7 +4,8 @@ import { JSONValue, ParentRef, Patch, PatchError } from "@valbuild/core/patch";
4
4
  import type { HistoryError } from "./history/HistoryError.js";
5
5
  import type { PlanRefusalErrorT } from "@valbuild/shared/internal";
6
6
  import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
7
- import { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js";
7
+ import { type RemoteFilesCheck, type RemoteNetwork } from "./remoteFileCheck.js";
8
+ import type { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js";
8
9
  import { ParentPatchId } from "@valbuild/core";
9
10
  import type { ReifiedPreview } from "@valbuild/core";
10
11
  import { ValCommit, ValDeployment } from "@valbuild/shared/internal";
@@ -382,7 +383,14 @@ export declare abstract class ValOps {
382
383
  * are absent. `/sources/~` overlays the two for exactly this reason.
383
384
  */
384
385
  getSourcesWithPatchesApplied(analysis: PatchAnalysis & OrderedPatches): Promise<Awaited<ReturnType<ValOps["getSources"]>>>;
385
- validateSources(schemas: Schemas, sources: Sources, patchesByModule?: PatchAnalysis["patchesByModule"]): Promise<{
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<{
386
394
  errors: Record<ModuleFilePath, {
387
395
  invalidSource?: {
388
396
  message: string;
@@ -392,7 +400,19 @@ export declare abstract class ValOps {
392
400
  files: Record<SourcePath, FileSource>;
393
401
  remoteFiles: Record<SourcePath, MediaSource>;
394
402
  }>;
395
- validateRemoteFiles(schemas: Schemas, sources: Sources, remoteFiles: Record<SourcePath, MediaSource>): Promise<Record<SourcePath, ValidationError[]>>;
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">;
396
416
  validateFiles(schemas: Schemas, sources: Sources, files: Record<SourcePath, FileSource>, fileLastUpdatedByPatchId?: PatchAnalysis["fileLastUpdatedByPatchId"]): Promise<Record<SourcePath, ValidationError[]>>;
397
417
  /**
398
418
  * Applies the pending patches to the source files so they can be committed.
@@ -598,6 +618,17 @@ export declare abstract class ValOps {
598
618
  * the ones that create patches and publish.
599
619
  */
600
620
  abstract readonly requiresAuth: boolean;
621
+ /**
622
+ * Which implementation this is, for the routes that differ by mode.
623
+ *
624
+ * A tag rather than `instanceof`, because `instanceof ValOpsFS` needs the
625
+ * class itself, and importing the class imports `fs`: `ValServer` asking
626
+ * which mode it is in was what put the filesystem and the content-service
627
+ * client in the module graph of a server that runs in a browser tab (the
628
+ * demo, `@valbuild/server/browser`). `isFsOps` and friends in `opsKind.ts`
629
+ * narrow on it with type-only imports.
630
+ */
631
+ abstract readonly kind: "fs" | "http" | "memory";
601
632
  abstract onInit(baseSha: BaseSha, schemaSha: SchemaSha): Promise<void>;
602
633
  abstract fetchPatches<ExcludePatchOps extends boolean>(filters: {
603
634
  patchIds?: PatchId[];
@@ -629,13 +660,13 @@ export declare abstract class ValOps {
629
660
  * gets this for free; a backend that can take bytes straight through should
630
661
  * override it.
631
662
  */
632
- saveBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, bytes: Buffer, mimeType: string, type: "file" | "image", metadata: MetadataOfType<"file" | "image"> | undefined): Promise<WithGenericError<{
663
+ saveBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, bytes: Uint8Array, mimeType: string, type: "file" | "image", metadata: MetadataOfType<"file" | "image"> | undefined): Promise<WithGenericError<{
633
664
  patchId: PatchId;
634
665
  filePath: string;
635
666
  }>>;
636
- abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<Buffer | null>;
667
+ abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<Uint8Array | null>;
637
668
  protected abstract getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, remote: boolean): Promise<OpsMetadata<T>>;
638
- abstract getBinaryFile(filePathOrRef: string): Promise<Buffer | null>;
669
+ abstract getBinaryFile(filePathOrRef: string): Promise<Uint8Array | null>;
639
670
  /**
640
671
  * A file's bytes as something a `Range` request can be answered from
641
672
  * without reading the rest: a video is seeked in by many small ranges, and
@@ -698,7 +729,7 @@ export declare abstract class ValOps {
698
729
  /** Which files the commit touched, and how. Names them; does not fetch them. */
699
730
  abstract getCommitAffectedFiles(commitSha: string): Promise<result.Result<AffectedFile[], HistoryError>>;
700
731
  /** One file's bytes as they were at one commit. */
701
- abstract getFileAtCommit(commitSha: string, filePath: string, remote: boolean): Promise<result.Result<Buffer, HistoryError>>;
732
+ abstract getFileAtCommit(commitSha: string, filePath: string, remote: boolean): Promise<result.Result<Uint8Array, HistoryError>>;
702
733
  /**
703
734
  * Where a module lives in the REPOSITORY, as `getFileAtCommit` wants it.
704
735
  *
@@ -1007,13 +1038,15 @@ export type OrderedPatchesMetadata = {
1007
1038
  networkError?: boolean;
1008
1039
  };
1009
1040
  export declare function getFieldsForType<T extends BinaryFileType>(type: T): (keyof MetadataOfType<T> & string)[];
1010
- export declare function createMetadataFromBuffer<T extends BinaryFileType>(type: BinaryFileType, mimeType: string, buffer: Buffer): OpsMetadata<T>;
1041
+ export declare function createMetadataFromBuffer<T extends BinaryFileType>(type: BinaryFileType, mimeType: string, buffer: Uint8Array): OpsMetadata<T>;
1011
1042
  export declare function guessMimeTypeFromPath(filePath: string): string | null;
1012
- export declare function bufferFromDataUrl(dataUrl: string): Buffer | undefined;
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;
1013
1046
  /** A file of known size whose bytes are read a range at a time. */
1014
1047
  export type BinaryFileReader = {
1015
1048
  readonly size: number;
1016
1049
  /** Bytes `start` to `end`, both inclusive, as an HTTP range is. */
1017
- read(start: number, end: number): Promise<Buffer>;
1050
+ read(start: number, end: number): Promise<Uint8Array>;
1018
1051
  };
1019
- export declare function bufferReader(buffer: Buffer): BinaryFileReader;
1052
+ export declare function bufferReader(buffer: Uint8Array): BinaryFileReader;
@@ -12,6 +12,7 @@ export declare class ValOpsFS extends ValOps {
12
12
  private readonly host;
13
13
  /** The developer's own working tree. See {@link ValOps.patchesAreLocal}. */
14
14
  readonly patchesAreLocal = true;
15
+ readonly kind = "fs";
15
16
  /**
16
17
  * The developer's own machine, where there is no credential to require and
17
18
  * nothing to protect it from. See {@link ValOps.requiresAuth}.
@@ -43,6 +43,7 @@ export declare class ValOpsHttp extends ValOps {
43
43
  private readonly root;
44
44
  /** Val's content service owns the store. See {@link ValOps.patchesAreLocal}. */
45
45
  readonly patchesAreLocal = false;
46
+ readonly kind = "http";
46
47
  /** See {@link ValOps.requiresAuth}. */
47
48
  readonly requiresAuth = true;
48
49
  /**
@@ -34,7 +34,7 @@ export type StoredFile = {
34
34
  * has resolved that before it asks.
35
35
  */
36
36
  filePath: string;
37
- data: Buffer;
37
+ data: Uint8Array;
38
38
  metadata: MetadataOfType<"file" | "image"> | undefined;
39
39
  };
40
40
  /**
@@ -121,6 +121,14 @@ export type ValOpsMemoryOptions = ValOpsOptions & {
121
121
  * startup.
122
122
  */
123
123
  unsafelyAllowUnauthenticated?: boolean;
124
+ /**
125
+ * Whether to warn that the server is unauthenticated. On by default, because
126
+ * a deployed server serving without authentication is a server anyone can
127
+ * write to. Off only where nobody else CAN reach it: a server running in a
128
+ * browser tab (`@valbuild/server/browser`), where the warning would be
129
+ * printed into a visitor's console about a port that does not exist.
130
+ */
131
+ warnUnauthenticated?: boolean;
124
132
  /**
125
133
  * Val's content host, for pushing remote files at publish.
126
134
  *
@@ -168,6 +176,7 @@ export declare class ValOpsMemory extends ValOps {
168
176
  * in. Where the two differ is not something a route asks about.
169
177
  */
170
178
  readonly patchesAreLocal = true;
179
+ readonly kind = "memory";
171
180
  /**
172
181
  * Required, unless the host explicitly takes the boundary itself.
173
182
  *
@@ -285,9 +294,9 @@ export declare class ValOpsMemory extends ValOps {
285
294
  patchId: PatchId;
286
295
  filePath: string;
287
296
  }>>;
288
- getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, _remote?: boolean): Promise<Buffer | null>;
297
+ getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, _remote?: boolean): Promise<Uint8Array | null>;
289
298
  protected getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, _remote?: boolean): Promise<OpsMetadata<T>>;
290
- getBinaryFile(_filePathOrRef: string): Promise<Buffer | null>;
299
+ getBinaryFile(_filePathOrRef: string): Promise<Uint8Array | null>;
291
300
  protected getBinaryFileMetadata<T extends "file" | "image">(_filePath: string, _type: T): Promise<OpsMetadata<T>>;
292
301
  listCommits(): Promise<result.Result<CommitPage, HistoryError>>;
293
302
  getCommitPatches(): Promise<result.Result<{
@@ -299,7 +308,7 @@ export declare class ValOpsMemory extends ValOps {
299
308
  complete: boolean;
300
309
  }, HistoryError>>;
301
310
  getCommitAffectedFiles(): Promise<result.Result<AffectedFile[], HistoryError>>;
302
- getFileAtCommit(): Promise<result.Result<Buffer, HistoryError>>;
311
+ getFileAtCommit(): Promise<result.Result<Uint8Array, HistoryError>>;
303
312
  gitPathOfModule(_moduleFilePath: ModuleFilePath): result.Result<string, HistoryError>;
304
313
  }
305
314
  /** Kept exported so a host can name the error shape it may get back. */
@@ -1,5 +1,5 @@
1
1
  import { ValConfig, ValModules } from "@valbuild/core";
2
- import { ValServerGenericResult } from "@valbuild/shared/internal";
2
+ import type { ValServerGenericResult } from "@valbuild/shared/internal";
3
3
  import { ValServer, ValServerCallbacks } from "./ValServer.js";
4
4
  type Versions = {
5
5
  versions?: {
@@ -3,7 +3,8 @@ import { ValModules, ValConfig } from "@valbuild/core";
3
3
  import { Api, ServerOf } from "@valbuild/shared/internal";
4
4
  import { z } from "zod";
5
5
  import { AuthorId, CommitSha, type GenericErrorMessage, type PreparedCommit } from "./ValOps.js";
6
- import { type ValPatchStore } from "./ValOpsMemory.js";
6
+ import type { ValPatchStore } from "./ValOpsMemory.js";
7
+ import type { ValServerPlatform } from "./platform.js";
7
8
  export type ValServerOptions = {
8
9
  route: string;
9
10
  valEnableRedirectUrl?: string;
@@ -169,7 +170,12 @@ export type ValServerConfig = ValServerOptions & ({
169
170
  config: ValConfig;
170
171
  });
171
172
  export type ValServer = ServerOf<Api>;
172
- export declare const ValServer: (valModules: ValModules, options: ValServerConfig, callbacks: ValServerCallbacks) => ServerOf<Api>;
173
+ export declare const ValServer: (valModules: ValModules, options: ValServerConfig, callbacks: ValServerCallbacks,
174
+ /**
175
+ * The Node-only parts, passed in. `createValServer` passes `nodePlatform`;
176
+ * the demo passes the browser one. See {@link ValServerPlatform}.
177
+ */
178
+ platform: ValServerPlatform) => ServerOf<Api>;
173
179
  export type ValServerCallbacks = {
174
180
  isEnabled: () => Promise<boolean>;
175
181
  onEnable: (success: boolean) => Promise<void>;
@@ -190,7 +196,7 @@ export declare const ENABLE_COOKIE_VALUE: {
190
196
  readonly sameSite: "lax";
191
197
  };
192
198
  };
193
- export declare function bufferToReadableStream(buffer: Buffer): ReadableStream<any>;
199
+ export declare function bufferToReadableStream(buffer: Uint8Array): ReadableStream<any>;
194
200
  export declare function getRedirectUrl(query: {
195
201
  redirect_to?: string | undefined;
196
202
  }, overrideHost: string | undefined): string | {
@@ -0,0 +1,27 @@
1
+ import { type ValConfig, type ValModules } from "@valbuild/core";
2
+ import type { ValPatchStore } from "../ValOpsMemory.js";
3
+ /** A `fetch`, as far as the Studio's client calls one. */
4
+ export type InTabFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
5
+ /**
6
+ * Val's API, served in this tab: a `fetch` that never touches the network.
7
+ *
8
+ * The whole server -- the router's request parsing, `ValServer`'s routes,
9
+ * memory mode's patch chain -- runs here, exactly as it runs on a server.
10
+ * What is removed is the hop between them, and nothing else, which is why the
11
+ * demo built on it cannot drift from the product: there is no second
12
+ * implementation to drift. See `docs/plans/demo-mode.md`.
13
+ *
14
+ * Memory mode with no source files, because nothing here publishes (only a
15
+ * publish reads `.val.ts` text), and without authentication, because the only
16
+ * caller is the page this runs in.
17
+ */
18
+ export declare function createInTabValFetch(valModules: ValModules, options: {
19
+ config: ValConfig;
20
+ /**
21
+ * Where edits live. Memory by default, which is what the demo wants: the
22
+ * edits last as long as the tab's page does, and a reload starts over.
23
+ */
24
+ patchStore?: ValPatchStore;
25
+ /** The path the Studio's client calls. */
26
+ route?: string;
27
+ }): InTabFetch;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@valbuild/server/browser`: Val's server, for a browser tab.
3
+ *
4
+ * Nothing reachable from here may import a Node builtin --
5
+ * `noNodeFromBrowser.test.ts` walks the graph and fails if one appears. The
6
+ * Node-only parts of the server reach it through `ValServerPlatform`, and this
7
+ * entry passes the browser's.
8
+ */
9
+ export { createInTabValFetch, type InTabFetch } from "./inTabFetch.js";
@@ -1,5 +1,16 @@
1
1
  import { FileMetadata, ImageMetadata, SerializedFileSchema, SerializedImageSchema } from "@valbuild/core";
2
- export declare function checkRemoteRef(remoteHost: string, ref: string, projectRoot: string, schema: SerializedImageSchema | SerializedFileSchema, metadata: Record<string, unknown> | undefined): Promise<{
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
- export declare function downloadFileFromRemote(ref: string, filePath: string): Promise<{
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(projectRoot: string, fileRef: string): Promise<VideoMetadata>;
31
- export declare function getImageMetadata(projectRoot: string, validationError: ValidationError): Promise<ImageMetadata>;
32
- export declare function getFileMetadata(projectRoot: string, validationError: ValidationError): Promise<FileMetadata>;
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,7 +252,15 @@ 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
- 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" | "record:fill-keys">, FixHandler>;
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>;
252
266
  export declare function createDefaultValFSHost(): IValFSHost;
@@ -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";
@@ -0,0 +1,40 @@
1
+ import https from "node:https";
2
+ import { type LookupFunction } from "node:net";
3
+ /**
4
+ * Opening one external URL and reporting what answered.
5
+ *
6
+ * Mirrors `ExternalUrlProbeResult` in the Studio - the two are checked against
7
+ * each other by the API route's zod schema, which is the only place they meet.
8
+ */
9
+ export type ProbeResult = {
10
+ kind: "answered";
11
+ code: number;
12
+ finalUrl: string;
13
+ ms: number;
14
+ } | {
15
+ kind: "unreachable";
16
+ message: string;
17
+ } | {
18
+ kind: "timeout";
19
+ ms: number;
20
+ } | {
21
+ kind: "skipped";
22
+ message: string;
23
+ };
24
+ export type ProbeOptions = {
25
+ /** The whole check, redirects included. */
26
+ timeoutMs?: number;
27
+ /** How many redirects to follow before calling it a loop. */
28
+ maxRedirects?: number;
29
+ /**
30
+ * The transport, injected so the redirect and status logic can be tested
31
+ * without a network. Production passes nothing and gets `node:https`.
32
+ */
33
+ request?: typeof https.request;
34
+ /** Injected for the same reason, and never to relax the address guard. */
35
+ now?: () => number;
36
+ };
37
+ export declare const DEFAULT_TIMEOUT_MS = 5000;
38
+ export declare const DEFAULT_MAX_REDIRECTS = 5;
39
+ export declare function guardedLookup(lookup: LookupFunction): LookupFunction;
40
+ export declare function probeUrl(rawUrl: string, options?: ProbeOptions): Promise<ProbeResult>;
@@ -0,0 +1,55 @@
1
+ import type { ValModules } from "@valbuild/core";
2
+ import type { ValOpsFS } from "./ValOpsFS.js";
3
+ import type { ValOpsHttp } from "./ValOpsHttp.js";
4
+ import type { ValOpsMemory } from "./ValOpsMemory.js";
5
+ import type { ValServerConfig } from "./ValServer.js";
6
+ import type { ResolveRemoteFileAuthResult } from "./valServerConfig.js";
7
+ import type { JwtResult } from "./jwt.js";
8
+ import type { ProbeResult } from "./linkCheck/probeUrl.js";
9
+ import type { checkModuleForStudio, fixForStudio } from "./studioValidation.js";
10
+ /**
11
+ * What `ValServer` needs from the machine it runs on.
12
+ *
13
+ * Everything else in `ValServer` is request handling, and runs anywhere. These
14
+ * are the parts that reach for Node -- a filesystem, a socket, a keyed hash --
15
+ * and they are passed in rather than imported so that the same server can run
16
+ * in a browser tab: the demo (`@valbuild/server/browser`) serves a memory-mode
17
+ * server to the Studio with no network in between.
18
+ *
19
+ * `nodePlatform` is the real one, and `createValServer` passes it. A function
20
+ * here is whatever an implementation can honestly answer: the browser one has
21
+ * no filesystem to read a token from, so it answers as a server without one
22
+ * does.
23
+ */
24
+ export type ValServerPlatform = {
25
+ /** Builds the data layer the config asks for. See `createValOps`. */
26
+ createOps: (valModules: ValModules, options: ValServerConfig) => ValOpsFS | ValOpsHttp | ValOpsMemory;
27
+ /** Whether an external URL answers. See `linkCheck/probeUrl`. */
28
+ probeUrl: (url: string) => Promise<ProbeResult>;
29
+ /** The session cookie's signature. See `jwt.ts`. */
30
+ jwt: {
31
+ verifyJwt: (token: string, secretKey: string) => JwtResult;
32
+ decodeJwtWithoutVerifying: (token: string) => JwtResult;
33
+ encodeJwt: (payload: object, sessionKey: string) => string;
34
+ getExpire: () => number;
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;
42
+ /** The developer's `val login` token. See `readValLoginToken`. */
43
+ readValLoginToken: (options: ValServerConfig) => Promise<string | null>;
44
+ /** Which credential talks to the content host. See `resolveRemoteFileAuth`. */
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
+ };
55
+ };