@shipstatic/types 2.23.0 → 2.24.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -848,9 +848,58 @@ export declare const DEPLOY_FIELDS: {
848
848
  readonly PRERENDER: "prerender";
849
849
  /** @internal Server-processing flag — first-party `/upload` only. */
850
850
  readonly SPA: "spa";
851
+ /**
852
+ * @internal The build settings, first-party `/upload` only and meaningful
853
+ * only with `BUILD`: the command to run instead of the manifest's build
854
+ * script, and the folder the site lands in. See {@link BUILD_SETTINGS}.
855
+ */
856
+ readonly BUILD_COMMAND: "buildCommand";
857
+ /** @internal See {@link DEPLOY_FIELDS.BUILD_COMMAND}. */
858
+ readonly OUTPUT_DIR: "outputDir";
851
859
  /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
852
860
  readonly CAPTCHA: "captcha";
853
861
  };
862
+ /**
863
+ * The JSON deploy body's file grammar: the OTHER body `POST /upload` accepts,
864
+ * beside the multipart one {@link DEPLOY_FIELDS} names. A JSON caller sends
865
+ * `{ files: [{ path, content, encoding? }] }`, and this is the whole of what
866
+ * one entry may say: three field names, two encodings, one default.
867
+ *
868
+ * Declared once because the grammar has three independent holders that the
869
+ * wire forces to restate it: the API's zod schema (the original), the hosted
870
+ * MCP's tool input, and the n8n community node, which cannot import this
871
+ * under n8n Cloud's zero-dependency rule and fences its copy against this
872
+ * object instead. Each had its own literal table until 2.24.0; the last two
873
+ * types convoys walked without minting the owner, which is exactly the drift
874
+ * the No-Fourth-Category Law's deferral clause names.
875
+ *
876
+ * `content` is the file's bytes as text: raw for `utf-8` (the default, and
877
+ * the right choice for HTML, CSS, JS, JSON and SVG), base64 for binary.
878
+ */
879
+ export declare const DEPLOY_FILE_GRAMMAR: {
880
+ /** Relative path within the site, no leading slash. */
881
+ readonly PATH: "path";
882
+ /** The file's bytes, as text in the entry's encoding. */
883
+ readonly CONTENT: "content";
884
+ /** Optional; one of {@link DEPLOY_FILE_GRAMMAR.ENCODINGS}. */
885
+ readonly ENCODING: "encoding";
886
+ /** What an entry that names no encoding means. */
887
+ readonly DEFAULT_ENCODING: "utf-8";
888
+ /** The closed set. `utf-8` is raw text; `base64` is for binary only. */
889
+ readonly ENCODINGS: readonly ["utf-8", "base64"];
890
+ };
891
+ /** One of the two encodings a JSON deploy entry may name. */
892
+ export type DeployFileEncoding = (typeof DEPLOY_FILE_GRAMMAR.ENCODINGS)[number];
893
+ /**
894
+ * One file entry of a JSON deploy, in the grammar above. The wire shape the
895
+ * API parses and the hosted MCP's tool input decodes; `encoding` absent means
896
+ * {@link DEPLOY_FILE_GRAMMAR.DEFAULT_ENCODING}.
897
+ */
898
+ export interface DeployFileSpec {
899
+ path: string;
900
+ content: string;
901
+ encoding?: DeployFileEncoding;
902
+ }
854
903
  /**
855
904
  * All possible error types in the ShipStatic platform.
856
905
  *
@@ -1806,6 +1855,20 @@ export interface DeploymentUploadOptions {
1806
1855
  prerender?: boolean;
1807
1856
  /** @internal Trigger server-side SPA detection. Only available via /upload endpoint. */
1808
1857
  spa?: boolean;
1858
+ /**
1859
+ * @internal The command the build runs instead of the manifest's own
1860
+ * `build` script (`npm run build:site`, `hugo`, …). Only with `build`, only
1861
+ * via /upload; format in {@link BUILD_SETTINGS}, checked by
1862
+ * {@link validateBuildCommand}.
1863
+ */
1864
+ buildCommand?: string;
1865
+ /**
1866
+ * @internal The folder the built site lands in (`public`, `dist/site`),
1867
+ * relative to the project root, for projects whose output the builder does
1868
+ * not find on its own. Only with `build`, only via /upload; format in
1869
+ * {@link BUILD_SETTINGS}, checked by {@link validateOutputDir}.
1870
+ */
1871
+ outputDir?: string;
1809
1872
  /** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
1810
1873
  captcha?: string;
1811
1874
  /**
@@ -2297,6 +2360,41 @@ export declare function serializeLabels(labels: string[] | undefined): string |
2297
2360
  * @example deserializeLabels('') → []
2298
2361
  */
2299
2362
  export declare function deserializeLabels(labelsJson: string | null): string[];
2363
+ /**
2364
+ * The format of the two per-deploy build settings a caller may name when the
2365
+ * builder's own detection is not enough: the command to run and the folder
2366
+ * the site lands in. Format rules only, the format/policy split this file
2367
+ * keeps everywhere: WHAT a value may look like lives here so both the console
2368
+ * (fast feedback) and the API (the boundary) refuse the same strings; whether
2369
+ * the command builds anything is the build's verdict, not a rule.
2370
+ *
2371
+ * Both are read only by first-party `/upload`, only with `build`, and both
2372
+ * run inside the throwaway container that already runs the project's own
2373
+ * arbitrary code, which is why the command's format is a shape rule (one
2374
+ * line, bounded) and not a safety rule. The folder's rule is what keeps it a
2375
+ * folder OF the project: relative, no parent segments, no leading slash.
2376
+ */
2377
+ export declare const BUILD_SETTINGS: {
2378
+ /** A build command is one line, non-empty, and bounded. */
2379
+ readonly COMMAND_MAX_LENGTH: 200;
2380
+ /** An output folder is a bounded relative path. */
2381
+ readonly OUTPUT_DIR_MAX_LENGTH: 100;
2382
+ /** Path segments and separators only: `dist`, `dist/site`, `.output/public`. */
2383
+ readonly OUTPUT_DIR_PATTERN: RegExp;
2384
+ };
2385
+ /**
2386
+ * Validate an optional build command and return it normalized (trimmed).
2387
+ * Absent → `undefined`. Present → one line, 1 to
2388
+ * {@link BUILD_SETTINGS.COMMAND_MAX_LENGTH} characters, no control characters.
2389
+ */
2390
+ export declare function validateBuildCommand(value: unknown): string | undefined;
2391
+ /**
2392
+ * Validate an optional output folder and return it normalized (trimmed, no
2393
+ * trailing slash). Absent → `undefined`. Present → a relative path of plain
2394
+ * segments, no `..`, no leading slash, at most
2395
+ * {@link BUILD_SETTINGS.OUTPUT_DIR_MAX_LENGTH} characters.
2396
+ */
2397
+ export declare function validateOutputDir(value: unknown): string | undefined;
2300
2398
  /**
2301
2399
  * Length constraints for the optional deployment password
2302
2400
  * (`DeploymentUploadOptions.password`). Single source of truth shared across
package/dist/index.js CHANGED
@@ -310,9 +310,46 @@ export const DEPLOY_FIELDS = {
310
310
  PRERENDER: 'prerender',
311
311
  /** @internal Server-processing flag — first-party `/upload` only. */
312
312
  SPA: 'spa',
313
+ /**
314
+ * @internal The build settings, first-party `/upload` only and meaningful
315
+ * only with `BUILD`: the command to run instead of the manifest's build
316
+ * script, and the folder the site lands in. See {@link BUILD_SETTINGS}.
317
+ */
318
+ BUILD_COMMAND: 'buildCommand',
319
+ /** @internal See {@link DEPLOY_FIELDS.BUILD_COMMAND}. */
320
+ OUTPUT_DIR: 'outputDir',
313
321
  /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
314
322
  CAPTCHA: 'captcha',
315
323
  };
324
+ /**
325
+ * The JSON deploy body's file grammar: the OTHER body `POST /upload` accepts,
326
+ * beside the multipart one {@link DEPLOY_FIELDS} names. A JSON caller sends
327
+ * `{ files: [{ path, content, encoding? }] }`, and this is the whole of what
328
+ * one entry may say: three field names, two encodings, one default.
329
+ *
330
+ * Declared once because the grammar has three independent holders that the
331
+ * wire forces to restate it: the API's zod schema (the original), the hosted
332
+ * MCP's tool input, and the n8n community node, which cannot import this
333
+ * under n8n Cloud's zero-dependency rule and fences its copy against this
334
+ * object instead. Each had its own literal table until 2.24.0; the last two
335
+ * types convoys walked without minting the owner, which is exactly the drift
336
+ * the No-Fourth-Category Law's deferral clause names.
337
+ *
338
+ * `content` is the file's bytes as text: raw for `utf-8` (the default, and
339
+ * the right choice for HTML, CSS, JS, JSON and SVG), base64 for binary.
340
+ */
341
+ export const DEPLOY_FILE_GRAMMAR = {
342
+ /** Relative path within the site, no leading slash. */
343
+ PATH: 'path',
344
+ /** The file's bytes, as text in the entry's encoding. */
345
+ CONTENT: 'content',
346
+ /** Optional; one of {@link DEPLOY_FILE_GRAMMAR.ENCODINGS}. */
347
+ ENCODING: 'encoding',
348
+ /** What an entry that names no encoding means. */
349
+ DEFAULT_ENCODING: 'utf-8',
350
+ /** The closed set. `utf-8` is raw text; `base64` is for binary only. */
351
+ ENCODINGS: ['utf-8', 'base64'],
352
+ };
316
353
  // =============================================================================
317
354
  // ERROR SYSTEM
318
355
  // =============================================================================
@@ -1812,6 +1849,74 @@ export function deserializeLabels(labelsJson) {
1812
1849
  }
1813
1850
  }
1814
1851
  // =============================================================================
1852
+ // BUILD SETTINGS
1853
+ // =============================================================================
1854
+ /**
1855
+ * The format of the two per-deploy build settings a caller may name when the
1856
+ * builder's own detection is not enough: the command to run and the folder
1857
+ * the site lands in. Format rules only, the format/policy split this file
1858
+ * keeps everywhere: WHAT a value may look like lives here so both the console
1859
+ * (fast feedback) and the API (the boundary) refuse the same strings; whether
1860
+ * the command builds anything is the build's verdict, not a rule.
1861
+ *
1862
+ * Both are read only by first-party `/upload`, only with `build`, and both
1863
+ * run inside the throwaway container that already runs the project's own
1864
+ * arbitrary code, which is why the command's format is a shape rule (one
1865
+ * line, bounded) and not a safety rule. The folder's rule is what keeps it a
1866
+ * folder OF the project: relative, no parent segments, no leading slash.
1867
+ */
1868
+ export const BUILD_SETTINGS = {
1869
+ /** A build command is one line, non-empty, and bounded. */
1870
+ COMMAND_MAX_LENGTH: 200,
1871
+ /** An output folder is a bounded relative path. */
1872
+ OUTPUT_DIR_MAX_LENGTH: 100,
1873
+ /** Path segments and separators only: `dist`, `dist/site`, `.output/public`. */
1874
+ OUTPUT_DIR_PATTERN: /^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/,
1875
+ };
1876
+ /**
1877
+ * Validate an optional build command and return it normalized (trimmed).
1878
+ * Absent → `undefined`. Present → one line, 1 to
1879
+ * {@link BUILD_SETTINGS.COMMAND_MAX_LENGTH} characters, no control characters.
1880
+ */
1881
+ export function validateBuildCommand(value) {
1882
+ if (value === undefined || value === null || value === '')
1883
+ return undefined;
1884
+ if (typeof value !== 'string') {
1885
+ throw ShipError.validation('Build command must be a string');
1886
+ }
1887
+ const trimmed = value.trim();
1888
+ if (trimmed.length === 0 || trimmed.length > BUILD_SETTINGS.COMMAND_MAX_LENGTH) {
1889
+ throw ShipError.validation(`Build command must be between 1 and ${BUILD_SETTINGS.COMMAND_MAX_LENGTH} characters`);
1890
+ }
1891
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: a command is one line; a newline or any other control character is a second one
1892
+ if (/[\x00-\x1f\x7f]/.test(trimmed)) {
1893
+ throw ShipError.validation('Build command must be a single line');
1894
+ }
1895
+ return trimmed;
1896
+ }
1897
+ /**
1898
+ * Validate an optional output folder and return it normalized (trimmed, no
1899
+ * trailing slash). Absent → `undefined`. Present → a relative path of plain
1900
+ * segments, no `..`, no leading slash, at most
1901
+ * {@link BUILD_SETTINGS.OUTPUT_DIR_MAX_LENGTH} characters.
1902
+ */
1903
+ export function validateOutputDir(value) {
1904
+ if (value === undefined || value === null || value === '')
1905
+ return undefined;
1906
+ if (typeof value !== 'string') {
1907
+ throw ShipError.validation('Output folder must be a string');
1908
+ }
1909
+ const trimmed = value.trim().replace(/\/+$/, '');
1910
+ if (trimmed.length === 0 || trimmed.length > BUILD_SETTINGS.OUTPUT_DIR_MAX_LENGTH) {
1911
+ throw ShipError.validation(`Output folder must be between 1 and ${BUILD_SETTINGS.OUTPUT_DIR_MAX_LENGTH} characters`);
1912
+ }
1913
+ if (!BUILD_SETTINGS.OUTPUT_DIR_PATTERN.test(trimmed) ||
1914
+ trimmed.split('/').some((segment) => segment === '..' || segment === '.')) {
1915
+ throw ShipError.validation('Output folder must be a relative path inside the project, like dist or dist/site');
1916
+ }
1917
+ return trimmed;
1918
+ }
1919
+ // =============================================================================
1815
1920
  // PASSWORD UTILITIES
1816
1921
  // =============================================================================
1817
1922
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "2.23.0",
3
+ "version": "2.24.0-beta.2",
4
4
  "description": "Shared TypeScript types for the ShipStatic platform.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -932,10 +932,62 @@ export const DEPLOY_FIELDS = {
932
932
  PRERENDER: 'prerender',
933
933
  /** @internal Server-processing flag — first-party `/upload` only. */
934
934
  SPA: 'spa',
935
+ /**
936
+ * @internal The build settings, first-party `/upload` only and meaningful
937
+ * only with `BUILD`: the command to run instead of the manifest's build
938
+ * script, and the folder the site lands in. See {@link BUILD_SETTINGS}.
939
+ */
940
+ BUILD_COMMAND: 'buildCommand',
941
+ /** @internal See {@link DEPLOY_FIELDS.BUILD_COMMAND}. */
942
+ OUTPUT_DIR: 'outputDir',
935
943
  /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
936
944
  CAPTCHA: 'captcha',
937
945
  } as const;
938
946
 
947
+ /**
948
+ * The JSON deploy body's file grammar: the OTHER body `POST /upload` accepts,
949
+ * beside the multipart one {@link DEPLOY_FIELDS} names. A JSON caller sends
950
+ * `{ files: [{ path, content, encoding? }] }`, and this is the whole of what
951
+ * one entry may say: three field names, two encodings, one default.
952
+ *
953
+ * Declared once because the grammar has three independent holders that the
954
+ * wire forces to restate it: the API's zod schema (the original), the hosted
955
+ * MCP's tool input, and the n8n community node, which cannot import this
956
+ * under n8n Cloud's zero-dependency rule and fences its copy against this
957
+ * object instead. Each had its own literal table until 2.24.0; the last two
958
+ * types convoys walked without minting the owner, which is exactly the drift
959
+ * the No-Fourth-Category Law's deferral clause names.
960
+ *
961
+ * `content` is the file's bytes as text: raw for `utf-8` (the default, and
962
+ * the right choice for HTML, CSS, JS, JSON and SVG), base64 for binary.
963
+ */
964
+ export const DEPLOY_FILE_GRAMMAR = {
965
+ /** Relative path within the site, no leading slash. */
966
+ PATH: 'path',
967
+ /** The file's bytes, as text in the entry's encoding. */
968
+ CONTENT: 'content',
969
+ /** Optional; one of {@link DEPLOY_FILE_GRAMMAR.ENCODINGS}. */
970
+ ENCODING: 'encoding',
971
+ /** What an entry that names no encoding means. */
972
+ DEFAULT_ENCODING: 'utf-8',
973
+ /** The closed set. `utf-8` is raw text; `base64` is for binary only. */
974
+ ENCODINGS: ['utf-8', 'base64'],
975
+ } as const;
976
+
977
+ /** One of the two encodings a JSON deploy entry may name. */
978
+ export type DeployFileEncoding = (typeof DEPLOY_FILE_GRAMMAR.ENCODINGS)[number];
979
+
980
+ /**
981
+ * One file entry of a JSON deploy, in the grammar above. The wire shape the
982
+ * API parses and the hosted MCP's tool input decodes; `encoding` absent means
983
+ * {@link DEPLOY_FILE_GRAMMAR.DEFAULT_ENCODING}.
984
+ */
985
+ export interface DeployFileSpec {
986
+ path: string;
987
+ content: string;
988
+ encoding?: DeployFileEncoding;
989
+ }
990
+
939
991
  // =============================================================================
940
992
  // ERROR SYSTEM
941
993
  // =============================================================================
@@ -2661,6 +2713,20 @@ export interface DeploymentUploadOptions {
2661
2713
  prerender?: boolean;
2662
2714
  /** @internal Trigger server-side SPA detection. Only available via /upload endpoint. */
2663
2715
  spa?: boolean;
2716
+ /**
2717
+ * @internal The command the build runs instead of the manifest's own
2718
+ * `build` script (`npm run build:site`, `hugo`, …). Only with `build`, only
2719
+ * via /upload; format in {@link BUILD_SETTINGS}, checked by
2720
+ * {@link validateBuildCommand}.
2721
+ */
2722
+ buildCommand?: string;
2723
+ /**
2724
+ * @internal The folder the built site lands in (`public`, `dist/site`),
2725
+ * relative to the project root, for projects whose output the builder does
2726
+ * not find on its own. Only with `build`, only via /upload; format in
2727
+ * {@link BUILD_SETTINGS}, checked by {@link validateOutputDir}.
2728
+ */
2729
+ outputDir?: string;
2664
2730
  /** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
2665
2731
  captcha?: string;
2666
2732
  /**
@@ -3300,6 +3366,84 @@ export function deserializeLabels(labelsJson: string | null): string[] {
3300
3366
  }
3301
3367
  }
3302
3368
 
3369
+ // =============================================================================
3370
+ // BUILD SETTINGS
3371
+ // =============================================================================
3372
+
3373
+ /**
3374
+ * The format of the two per-deploy build settings a caller may name when the
3375
+ * builder's own detection is not enough: the command to run and the folder
3376
+ * the site lands in. Format rules only, the format/policy split this file
3377
+ * keeps everywhere: WHAT a value may look like lives here so both the console
3378
+ * (fast feedback) and the API (the boundary) refuse the same strings; whether
3379
+ * the command builds anything is the build's verdict, not a rule.
3380
+ *
3381
+ * Both are read only by first-party `/upload`, only with `build`, and both
3382
+ * run inside the throwaway container that already runs the project's own
3383
+ * arbitrary code, which is why the command's format is a shape rule (one
3384
+ * line, bounded) and not a safety rule. The folder's rule is what keeps it a
3385
+ * folder OF the project: relative, no parent segments, no leading slash.
3386
+ */
3387
+ export const BUILD_SETTINGS = {
3388
+ /** A build command is one line, non-empty, and bounded. */
3389
+ COMMAND_MAX_LENGTH: 200,
3390
+ /** An output folder is a bounded relative path. */
3391
+ OUTPUT_DIR_MAX_LENGTH: 100,
3392
+ /** Path segments and separators only: `dist`, `dist/site`, `.output/public`. */
3393
+ OUTPUT_DIR_PATTERN: /^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/,
3394
+ } as const;
3395
+
3396
+ /**
3397
+ * Validate an optional build command and return it normalized (trimmed).
3398
+ * Absent → `undefined`. Present → one line, 1 to
3399
+ * {@link BUILD_SETTINGS.COMMAND_MAX_LENGTH} characters, no control characters.
3400
+ */
3401
+ export function validateBuildCommand(value: unknown): string | undefined {
3402
+ if (value === undefined || value === null || value === '') return undefined;
3403
+ if (typeof value !== 'string') {
3404
+ throw ShipError.validation('Build command must be a string');
3405
+ }
3406
+ const trimmed = value.trim();
3407
+ if (trimmed.length === 0 || trimmed.length > BUILD_SETTINGS.COMMAND_MAX_LENGTH) {
3408
+ throw ShipError.validation(
3409
+ `Build command must be between 1 and ${BUILD_SETTINGS.COMMAND_MAX_LENGTH} characters`,
3410
+ );
3411
+ }
3412
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: a command is one line; a newline or any other control character is a second one
3413
+ if (/[\x00-\x1f\x7f]/.test(trimmed)) {
3414
+ throw ShipError.validation('Build command must be a single line');
3415
+ }
3416
+ return trimmed;
3417
+ }
3418
+
3419
+ /**
3420
+ * Validate an optional output folder and return it normalized (trimmed, no
3421
+ * trailing slash). Absent → `undefined`. Present → a relative path of plain
3422
+ * segments, no `..`, no leading slash, at most
3423
+ * {@link BUILD_SETTINGS.OUTPUT_DIR_MAX_LENGTH} characters.
3424
+ */
3425
+ export function validateOutputDir(value: unknown): string | undefined {
3426
+ if (value === undefined || value === null || value === '') return undefined;
3427
+ if (typeof value !== 'string') {
3428
+ throw ShipError.validation('Output folder must be a string');
3429
+ }
3430
+ const trimmed = value.trim().replace(/\/+$/, '');
3431
+ if (trimmed.length === 0 || trimmed.length > BUILD_SETTINGS.OUTPUT_DIR_MAX_LENGTH) {
3432
+ throw ShipError.validation(
3433
+ `Output folder must be between 1 and ${BUILD_SETTINGS.OUTPUT_DIR_MAX_LENGTH} characters`,
3434
+ );
3435
+ }
3436
+ if (
3437
+ !BUILD_SETTINGS.OUTPUT_DIR_PATTERN.test(trimmed) ||
3438
+ trimmed.split('/').some((segment) => segment === '..' || segment === '.')
3439
+ ) {
3440
+ throw ShipError.validation(
3441
+ 'Output folder must be a relative path inside the project, like dist or dist/site',
3442
+ );
3443
+ }
3444
+ return trimmed;
3445
+ }
3446
+
3303
3447
  // =============================================================================
3304
3448
  // PASSWORD UTILITIES
3305
3449
  // =============================================================================