@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 +98 -0
- package/dist/index.js +105 -0
- package/package.json +1 -1
- package/src/index.ts +144 -0
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
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
|
// =============================================================================
|