agent-gauntlet 0.1.10 → 0.1.12
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/README.md +55 -87
- package/package.json +4 -2
- package/src/bun-plugins.d.ts +4 -0
- package/src/cli-adapters/claude.ts +139 -108
- package/src/cli-adapters/codex.ts +141 -117
- package/src/cli-adapters/cursor.ts +152 -0
- package/src/cli-adapters/gemini.ts +171 -139
- package/src/cli-adapters/github-copilot.ts +153 -0
- package/src/cli-adapters/index.ts +77 -48
- package/src/commands/check.test.ts +24 -20
- package/src/commands/check.ts +86 -59
- package/src/commands/ci/index.ts +15 -0
- package/src/commands/ci/init.ts +96 -0
- package/src/commands/ci/list-jobs.ts +78 -0
- package/src/commands/detect.test.ts +38 -32
- package/src/commands/detect.ts +89 -61
- package/src/commands/health.test.ts +67 -53
- package/src/commands/health.ts +167 -145
- package/src/commands/help.test.ts +37 -37
- package/src/commands/help.ts +31 -22
- package/src/commands/index.ts +10 -9
- package/src/commands/init.test.ts +120 -107
- package/src/commands/init.ts +514 -417
- package/src/commands/list.test.ts +87 -70
- package/src/commands/list.ts +28 -24
- package/src/commands/rerun.ts +157 -119
- package/src/commands/review.test.ts +26 -20
- package/src/commands/review.ts +86 -59
- package/src/commands/run.test.ts +22 -20
- package/src/commands/run.ts +85 -58
- package/src/commands/shared.ts +44 -35
- package/src/config/ci-loader.ts +33 -0
- package/src/config/ci-schema.ts +52 -0
- package/src/config/loader.test.ts +112 -90
- package/src/config/loader.ts +132 -123
- package/src/config/schema.ts +48 -47
- package/src/config/types.ts +28 -13
- package/src/config/validator.ts +521 -454
- package/src/core/change-detector.ts +122 -104
- package/src/core/entry-point.test.ts +60 -62
- package/src/core/entry-point.ts +120 -74
- package/src/core/job.ts +69 -59
- package/src/core/runner.ts +264 -230
- package/src/gates/check.ts +78 -69
- package/src/gates/result.ts +7 -7
- package/src/gates/review.test.ts +277 -138
- package/src/gates/review.ts +724 -561
- package/src/index.ts +18 -15
- package/src/output/console.ts +253 -214
- package/src/output/logger.ts +66 -52
- package/src/templates/run_gauntlet.template.md +18 -0
- package/src/templates/workflow.yml +77 -0
- package/src/utils/diff-parser.ts +64 -62
- package/src/utils/log-parser.ts +227 -206
- package/src/utils/sanitizer.ts +1 -1
package/src/commands/review.ts
CHANGED
|
@@ -1,72 +1,99 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { loadConfig } from
|
|
4
|
-
import { ChangeDetector } from
|
|
5
|
-
import { EntryPointExpander } from
|
|
6
|
-
import { JobGenerator } from
|
|
7
|
-
import { Runner } from
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { rotateLogs } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import { loadConfig } from "../config/loader.js";
|
|
4
|
+
import { ChangeDetector } from "../core/change-detector.js";
|
|
5
|
+
import { EntryPointExpander } from "../core/entry-point.js";
|
|
6
|
+
import { JobGenerator } from "../core/job.js";
|
|
7
|
+
import { Runner } from "../core/runner.js";
|
|
8
|
+
import { ConsoleReporter } from "../output/console.js";
|
|
9
|
+
import { Logger } from "../output/logger.js";
|
|
10
|
+
import { rotateLogs } from "./shared.js";
|
|
11
11
|
|
|
12
12
|
export function registerReviewCommand(program: Command): void {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
program
|
|
14
|
+
.command("review")
|
|
15
|
+
.description("Run only applicable reviews for detected changes")
|
|
16
|
+
.option(
|
|
17
|
+
"-b, --base-branch <branch>",
|
|
18
|
+
"Override base branch for change detection",
|
|
19
|
+
)
|
|
20
|
+
.option("-g, --gate <name>", "Run specific review gate only")
|
|
21
|
+
.option("-c, --commit <sha>", "Use diff for a specific commit")
|
|
22
|
+
.option(
|
|
23
|
+
"-u, --uncommitted",
|
|
24
|
+
"Use diff for current uncommitted changes (staged and unstaged)",
|
|
25
|
+
)
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
try {
|
|
28
|
+
const config = await loadConfig();
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
// Rotate logs before starting
|
|
31
|
+
await rotateLogs(config.project.log_dir);
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (changes.length === 0) {
|
|
37
|
-
console.log(chalk.green('No changes detected.'));
|
|
38
|
-
process.exit(0);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
console.log(chalk.dim(`Found ${changes.length} changed files.`));
|
|
33
|
+
// Determine effective base branch
|
|
34
|
+
// Priority: CLI override > CI env var > config
|
|
35
|
+
const effectiveBaseBranch =
|
|
36
|
+
options.baseBranch ||
|
|
37
|
+
(process.env.GITHUB_BASE_REF &&
|
|
38
|
+
(process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true")
|
|
39
|
+
? process.env.GITHUB_BASE_REF
|
|
40
|
+
: null) ||
|
|
41
|
+
config.project.base_branch;
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const changeDetector = new ChangeDetector(effectiveBaseBranch, {
|
|
44
|
+
commit: options.commit,
|
|
45
|
+
uncommitted: options.uncommitted,
|
|
46
|
+
});
|
|
47
|
+
const expander = new EntryPointExpander();
|
|
48
|
+
const jobGen = new JobGenerator(config);
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
console.log(chalk.dim("Detecting changes..."));
|
|
51
|
+
const changes = await changeDetector.getChangedFiles();
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if (changes.length === 0) {
|
|
54
|
+
console.log(chalk.green("No changes detected."));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
console.log(chalk.yellow('No applicable reviews for these changes.'));
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
58
|
+
console.log(chalk.dim(`Found ${changes.length} changed files.`));
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
const entryPoints = await expander.expand(
|
|
61
|
+
config.project.entry_points,
|
|
62
|
+
changes,
|
|
63
|
+
);
|
|
64
|
+
let jobs = jobGen.generateJobs(entryPoints);
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const runner = new Runner(config, logger, reporter);
|
|
66
|
+
// Filter to only reviews
|
|
67
|
+
jobs = jobs.filter((j) => j.type === "review");
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
if (options.gate) {
|
|
70
|
+
jobs = jobs.filter((j) => j.name === options.gate);
|
|
71
|
+
}
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
if (jobs.length === 0) {
|
|
74
|
+
console.log(chalk.yellow("No applicable reviews for these changes."));
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(chalk.dim(`Running ${jobs.length} review(s)...`));
|
|
79
|
+
|
|
80
|
+
const logger = new Logger(config.project.log_dir);
|
|
81
|
+
const reporter = new ConsoleReporter();
|
|
82
|
+
const runner = new Runner(
|
|
83
|
+
config,
|
|
84
|
+
logger,
|
|
85
|
+
reporter,
|
|
86
|
+
undefined,
|
|
87
|
+
undefined,
|
|
88
|
+
effectiveBaseBranch,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const success = await runner.run(jobs);
|
|
92
|
+
process.exit(success ? 0 : 1);
|
|
93
|
+
} catch (error: unknown) {
|
|
94
|
+
const err = error as { message?: string };
|
|
95
|
+
console.error(chalk.red("Error:"), err.message);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
72
99
|
}
|
package/src/commands/run.test.ts
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Command } from
|
|
3
|
-
import { registerRunCommand } from
|
|
1
|
+
import { beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerRunCommand } from "./run.js";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
|
|
5
|
+
describe("Run Command", () => {
|
|
6
|
+
let program: Command;
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
program = new Command();
|
|
10
|
+
registerRunCommand(program);
|
|
11
|
+
});
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
it("should register the run command", () => {
|
|
14
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
15
|
+
expect(runCmd).toBeDefined();
|
|
16
|
+
expect(runCmd?.description()).toBe("Run gates for detected changes");
|
|
17
|
+
});
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
it("should have correct options", () => {
|
|
20
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
21
|
+
expect(runCmd?.options.some((opt) => opt.long === "--gate")).toBe(true);
|
|
22
|
+
expect(runCmd?.options.some((opt) => opt.long === "--commit")).toBe(true);
|
|
23
|
+
expect(runCmd?.options.some((opt) => opt.long === "--uncommitted")).toBe(
|
|
24
|
+
true,
|
|
25
|
+
);
|
|
26
|
+
});
|
|
25
27
|
});
|
package/src/commands/run.ts
CHANGED
|
@@ -1,69 +1,96 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { loadConfig } from
|
|
4
|
-
import { ChangeDetector } from
|
|
5
|
-
import { EntryPointExpander } from
|
|
6
|
-
import { JobGenerator } from
|
|
7
|
-
import { Runner } from
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { rotateLogs } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import { loadConfig } from "../config/loader.js";
|
|
4
|
+
import { ChangeDetector } from "../core/change-detector.js";
|
|
5
|
+
import { EntryPointExpander } from "../core/entry-point.js";
|
|
6
|
+
import { JobGenerator } from "../core/job.js";
|
|
7
|
+
import { Runner } from "../core/runner.js";
|
|
8
|
+
import { ConsoleReporter } from "../output/console.js";
|
|
9
|
+
import { Logger } from "../output/logger.js";
|
|
10
|
+
import { rotateLogs } from "./shared.js";
|
|
11
11
|
|
|
12
12
|
export function registerRunCommand(program: Command): void {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
program
|
|
14
|
+
.command("run")
|
|
15
|
+
.description("Run gates for detected changes")
|
|
16
|
+
.option(
|
|
17
|
+
"-b, --base-branch <branch>",
|
|
18
|
+
"Override base branch for change detection",
|
|
19
|
+
)
|
|
20
|
+
.option("-g, --gate <name>", "Run specific gate only")
|
|
21
|
+
.option("-c, --commit <sha>", "Use diff for a specific commit")
|
|
22
|
+
.option(
|
|
23
|
+
"-u, --uncommitted",
|
|
24
|
+
"Use diff for current uncommitted changes (staged and unstaged)",
|
|
25
|
+
)
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
try {
|
|
28
|
+
const config = await loadConfig();
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
uncommitted: options.uncommitted
|
|
29
|
-
});
|
|
30
|
-
const expander = new EntryPointExpander();
|
|
31
|
-
const jobGen = new JobGenerator(config);
|
|
32
|
-
|
|
33
|
-
console.log(chalk.dim('Detecting changes...'));
|
|
34
|
-
const changes = await changeDetector.getChangedFiles();
|
|
35
|
-
|
|
36
|
-
if (changes.length === 0) {
|
|
37
|
-
console.log(chalk.green('No changes detected.'));
|
|
38
|
-
process.exit(0);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
console.log(chalk.dim(`Found ${changes.length} changed files.`));
|
|
30
|
+
// Rotate logs before starting
|
|
31
|
+
await rotateLogs(config.project.log_dir);
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
// Determine effective base branch
|
|
34
|
+
// Priority: CLI override > CI env var > config
|
|
35
|
+
const effectiveBaseBranch =
|
|
36
|
+
options.baseBranch ||
|
|
37
|
+
(process.env.GITHUB_BASE_REF &&
|
|
38
|
+
(process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true")
|
|
39
|
+
? process.env.GITHUB_BASE_REF
|
|
40
|
+
: null) ||
|
|
41
|
+
config.project.base_branch;
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
const changeDetector = new ChangeDetector(effectiveBaseBranch, {
|
|
44
|
+
commit: options.commit,
|
|
45
|
+
uncommitted: options.uncommitted,
|
|
46
|
+
});
|
|
47
|
+
const expander = new EntryPointExpander();
|
|
48
|
+
const jobGen = new JobGenerator(config);
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
50
|
+
console.log(chalk.dim("Detecting changes..."));
|
|
51
|
+
const changes = await changeDetector.getChangedFiles();
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
if (changes.length === 0) {
|
|
54
|
+
console.log(chalk.green("No changes detected."));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
const reporter = new ConsoleReporter();
|
|
59
|
-
const runner = new Runner(config, logger, reporter);
|
|
58
|
+
console.log(chalk.dim(`Found ${changes.length} changed files.`));
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const entryPoints = await expander.expand(
|
|
61
|
+
config.project.entry_points,
|
|
62
|
+
changes,
|
|
63
|
+
);
|
|
64
|
+
let jobs = jobGen.generateJobs(entryPoints);
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
if (options.gate) {
|
|
67
|
+
jobs = jobs.filter((j) => j.name === options.gate);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (jobs.length === 0) {
|
|
71
|
+
console.log(chalk.yellow("No applicable gates for these changes."));
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(chalk.dim(`Running ${jobs.length} gates...`));
|
|
76
|
+
|
|
77
|
+
const logger = new Logger(config.project.log_dir);
|
|
78
|
+
const reporter = new ConsoleReporter();
|
|
79
|
+
const runner = new Runner(
|
|
80
|
+
config,
|
|
81
|
+
logger,
|
|
82
|
+
reporter,
|
|
83
|
+
undefined,
|
|
84
|
+
undefined,
|
|
85
|
+
effectiveBaseBranch,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const success = await runner.run(jobs);
|
|
89
|
+
process.exit(success ? 0 : 1);
|
|
90
|
+
} catch (error: unknown) {
|
|
91
|
+
const err = error as { message?: string };
|
|
92
|
+
console.error(chalk.red("Error:"), err.message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
69
96
|
}
|
package/src/commands/shared.ts
CHANGED
|
@@ -1,44 +1,53 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
export async function exists(path: string): Promise<boolean> {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
try {
|
|
6
|
+
await fs.stat(path);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export async function rotateLogs(logDir: string): Promise<void> {
|
|
14
|
-
|
|
14
|
+
const previousDir = path.join(logDir, "previous");
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
try {
|
|
17
|
+
// 1. Ensure logDir exists (if not, nothing to rotate, but we should create it for future use if needed,
|
|
18
|
+
// though usually the logger creates it. If it doesn't exist, we can just return).
|
|
19
|
+
if (!(await exists(logDir))) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
// 2. Clear .gauntlet_logs/previous if it exists
|
|
24
|
+
if (await exists(previousDir)) {
|
|
25
|
+
const previousFiles = await fs.readdir(previousDir);
|
|
26
|
+
await Promise.all(
|
|
27
|
+
previousFiles.map((file) =>
|
|
28
|
+
fs.rm(path.join(previousDir, file), { recursive: true, force: true }),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
await fs.mkdir(previousDir, { recursive: true });
|
|
33
|
+
}
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
// 3. Move all existing files in .gauntlet_logs/ to .gauntlet_logs/previous
|
|
36
|
+
const files = await fs.readdir(logDir);
|
|
37
|
+
await Promise.all(
|
|
38
|
+
files
|
|
39
|
+
.filter((file) => file !== "previous")
|
|
40
|
+
.map((file) =>
|
|
41
|
+
fs.rename(path.join(logDir, file), path.join(previousDir, file)),
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Log warning but don't crash the run as log rotation failure isn't critical
|
|
46
|
+
console.warn(
|
|
47
|
+
"Failed to rotate logs in",
|
|
48
|
+
logDir,
|
|
49
|
+
":",
|
|
50
|
+
error instanceof Error ? error.message : error,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
44
53
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { ciConfigSchema } from "./ci-schema.js";
|
|
5
|
+
import type { CIConfig } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const GAUNTLET_DIR = ".gauntlet";
|
|
8
|
+
const CI_FILE = "ci.yml";
|
|
9
|
+
|
|
10
|
+
export async function loadCIConfig(
|
|
11
|
+
rootDir: string = process.cwd(),
|
|
12
|
+
): Promise<CIConfig> {
|
|
13
|
+
const ciPath = path.join(rootDir, GAUNTLET_DIR, CI_FILE);
|
|
14
|
+
|
|
15
|
+
if (!(await fileExists(ciPath))) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`CI configuration file not found at ${ciPath}. Run 'agent-gauntlet ci init' to create it.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const content = await fs.readFile(ciPath, "utf-8");
|
|
22
|
+
const raw = YAML.parse(content);
|
|
23
|
+
return ciConfigSchema.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
const stat = await fs.stat(path);
|
|
29
|
+
return stat.isFile();
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const runtimeConfigSchema = z.record(
|
|
4
|
+
z.string(),
|
|
5
|
+
z
|
|
6
|
+
.object({
|
|
7
|
+
version: z.string().min(1),
|
|
8
|
+
bundler_cache: z.boolean().optional(),
|
|
9
|
+
})
|
|
10
|
+
.passthrough(),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export const serviceConfigSchema = z.record(
|
|
14
|
+
z.string(),
|
|
15
|
+
z
|
|
16
|
+
.object({
|
|
17
|
+
image: z.string().min(1),
|
|
18
|
+
env: z.record(z.string()).optional(),
|
|
19
|
+
ports: z.array(z.string()).optional(),
|
|
20
|
+
options: z.string().optional(),
|
|
21
|
+
health_check: z
|
|
22
|
+
.object({
|
|
23
|
+
cmd: z.string().optional(),
|
|
24
|
+
interval: z.string().optional(),
|
|
25
|
+
timeout: z.string().optional(),
|
|
26
|
+
retries: z.number().optional(),
|
|
27
|
+
})
|
|
28
|
+
.optional(),
|
|
29
|
+
})
|
|
30
|
+
.passthrough(),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const ciSetupStepSchema = z.object({
|
|
34
|
+
name: z.string().min(1),
|
|
35
|
+
run: z.string().min(1),
|
|
36
|
+
working_directory: z.string().optional(),
|
|
37
|
+
if: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const ciCheckConfigSchema = z.object({
|
|
41
|
+
name: z.string().min(1),
|
|
42
|
+
requires_runtimes: z.array(z.string()).optional(),
|
|
43
|
+
requires_services: z.array(z.string()).optional(),
|
|
44
|
+
setup: z.array(ciSetupStepSchema).optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const ciConfigSchema = z.object({
|
|
48
|
+
runtimes: runtimeConfigSchema.nullable().optional(),
|
|
49
|
+
services: serviceConfigSchema.nullable().optional(),
|
|
50
|
+
setup: z.array(ciSetupStepSchema).nullable().optional(),
|
|
51
|
+
checks: z.array(ciCheckConfigSchema).nullable().optional(),
|
|
52
|
+
});
|