@shipstatic/types 2.24.0-beta.1 → 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,6 +848,14 @@ 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
  };
@@ -1847,6 +1855,20 @@ export interface DeploymentUploadOptions {
1847
1855
  prerender?: boolean;
1848
1856
  /** @internal Trigger server-side SPA detection. Only available via /upload endpoint. */
1849
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;
1850
1872
  /** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
1851
1873
  captcha?: string;
1852
1874
  /**
@@ -2338,6 +2360,41 @@ export declare function serializeLabels(labels: string[] | undefined): string |
2338
2360
  * @example deserializeLabels('') → []
2339
2361
  */
2340
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;
2341
2398
  /**
2342
2399
  * Length constraints for the optional deployment password
2343
2400
  * (`DeploymentUploadOptions.password`). Single source of truth shared across
package/dist/index.js CHANGED
@@ -310,6 +310,14 @@ 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
  };
@@ -1841,6 +1849,74 @@ export function deserializeLabels(labelsJson) {
1841
1849
  }
1842
1850
  }
1843
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
+ // =============================================================================
1844
1920
  // PASSWORD UTILITIES
1845
1921
  // =============================================================================
1846
1922
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "2.24.0-beta.1",
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,6 +932,14 @@ 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;
@@ -2705,6 +2713,20 @@ export interface DeploymentUploadOptions {
2705
2713
  prerender?: boolean;
2706
2714
  /** @internal Trigger server-side SPA detection. Only available via /upload endpoint. */
2707
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;
2708
2730
  /** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
2709
2731
  captcha?: string;
2710
2732
  /**
@@ -3344,6 +3366,84 @@ export function deserializeLabels(labelsJson: string | null): string[] {
3344
3366
  }
3345
3367
  }
3346
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
+
3347
3447
  // =============================================================================
3348
3448
  // PASSWORD UTILITIES
3349
3449
  // =============================================================================