Skip to content
File

Blob: src/worker/contracts/repo-config.ts

typescript70 lines
1import { eg, type TypeFromCodec } from "@cloudflare/util-en-garde";
2 
3export const MAX_REPO_CONFIG_FILE_BYTES = 64 * 1024;
4 
5const byteLength = (value: string): number => new TextEncoder().encode(value).length;
6 
7const isRepoRelativePath = (value: string): boolean => {
8 if (value.length === 0 || value.startsWith("/") || value.startsWith("\\") || value.includes("\\")) {
9 return false;
10 }
11 
12 if (/^[A-Za-z]:\//u.test(value)) {
13 return false;
14 }
15 
16 return value.split("/").every((segment) => segment.length > 0 && segment !== "..");
17};
18 
19const RepoRelativePath = eg.brand("RepoRelativePath", eg.string, isRepoRelativePath);
20const CheckoutDepth = eg.brand("CheckoutDepth", eg.number, (value) => Number.isInteger(value) && value > 0);
21const StepName = eg.brand("RepoConfigStepName", eg.string, (value) => byteLength(value) <= 64);
22const StepCommand = eg.brand("RepoConfigStepCommand", eg.string, (value) => byteLength(value) <= 4096);
23const RunTimeoutSeconds = eg.brand(
24 "RunTimeoutSeconds",
25 eg.number,
26 (value) => Number.isInteger(value) && value > 0 && value <= 720,
27);
28 
29export const RepoConfigStep = eg.exactStrict(
30 eg.object({
31 name: StepName,
32 run: StepCommand,
33 }),
34);
35export type RepoConfigStep = TypeFromCodec<typeof RepoConfigStep>;
36 
37const RepoConfigSteps = eg.brand("RepoConfigSteps", eg.array(RepoConfigStep), (value) => value.length <= 20);
38 
39export const RepoConfigCheckout = eg.exactStrict(
40 eg.object({
41 depth: CheckoutDepth.optional,
42 }),
43);
44export type RepoConfigCheckout = TypeFromCodec<typeof RepoConfigCheckout>;
45 
46export const RepoConfigRun = eg.exactStrict(
47 eg.object({
48 workingDirectory: RepoRelativePath,
49 timeoutSeconds: RunTimeoutSeconds,
50 steps: RepoConfigSteps,
51 }),
52);
53export type RepoConfigRun = TypeFromCodec<typeof RepoConfigRun>;
54 
55export const RepoConfig = eg.exactStrict(
56 eg.object({
57 version: eg.literal(1),
58 checkout: RepoConfigCheckout,
59 run: RepoConfigRun,
60 }),
61);
62export type RepoConfig = TypeFromCodec<typeof RepoConfig>;
63 
64export const RepoConfigFileText = eg.brand(
65 "RepoConfigFileText",
66 eg.string,
67 (value) => byteLength(value) <= MAX_REPO_CONFIG_FILE_BYTES,
68);
69export type RepoConfigFileText = TypeFromCodec<typeof RepoConfigFileText>;