@bensandee/tooling 0.21.0 → 0.23.0

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/bin.mjs CHANGED
@@ -7,9 +7,9 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, unlinkSync, w
7
7
  import JSON5 from "json5";
8
8
  import { parse } from "jsonc-parser";
9
9
  import { z } from "zod";
10
+ import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
10
11
  import { isMap, isScalar, isSeq, parse as parse$1, parseDocument, stringify } from "yaml";
11
12
  import { execSync } from "node:child_process";
12
- import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
13
13
  import { tmpdir } from "node:os";
14
14
  //#region src/types.ts
15
15
  const LEGACY_TOOLS = [
@@ -201,7 +201,7 @@ function computeDefaults(targetDir) {
201
201
  setupVitest: !isMonorepo && !detected.hasVitestConfig,
202
202
  ci: detectCiPlatform(targetDir),
203
203
  setupRenovate: true,
204
- releaseStrategy: isMonorepo ? "changesets" : "simple",
204
+ releaseStrategy: "none",
205
205
  projectType: isMonorepo ? "default" : detectProjectType(targetDir),
206
206
  detectPackageTypes: true
207
207
  };
@@ -493,7 +493,8 @@ const DockerCheckConfigSchema = z.object({
493
493
  timeoutMs: z.number().int().positive().optional(),
494
494
  pollIntervalMs: z.number().int().positive().optional()
495
495
  });
496
- const ToolingConfigSchema = z.object({
496
+ const ToolingConfigSchema = z.strictObject({
497
+ $schema: z.string().optional(),
497
498
  structure: z.enum(["single", "monorepo"]).optional(),
498
499
  useEslintPlugin: z.boolean().optional(),
499
500
  formatter: z.enum(["oxfmt", "prettier"]).optional(),
@@ -524,17 +525,14 @@ const ToolingConfigSchema = z.object({
524
525
  })).optional(),
525
526
  dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional()
526
527
  });
527
- /** Load saved tooling config from the target directory. Returns undefined if missing or invalid. */
528
+ /** Load saved tooling config from the target directory. Returns undefined if missing, throws on invalid. */
528
529
  function loadToolingConfig(targetDir) {
529
530
  const fullPath = path.join(targetDir, CONFIG_FILE);
530
531
  if (!existsSync(fullPath)) return void 0;
531
- try {
532
- const raw = readFileSync(fullPath, "utf-8");
533
- const result = ToolingConfigSchema.safeParse(JSON.parse(raw));
534
- return result.success ? result.data : void 0;
535
- } catch {
536
- return;
537
- }
532
+ const raw = readFileSync(fullPath, "utf-8");
533
+ const result = ToolingConfigSchema.safeParse(JSON.parse(raw));
534
+ if (!result.success) throw new FatalError(`Invalid .tooling.json:\n${result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`);
535
+ return result.data;
538
536
  }
539
537
  /** Config fields that can be overridden in .tooling.json. */
540
538
  const OVERRIDE_KEYS = [
@@ -558,7 +556,7 @@ const MONOREPO_IGNORED_KEYS = new Set(["setupVitest", "projectType"]);
558
556
  function saveToolingConfig(ctx, config) {
559
557
  const defaults = computeDefaults(config.targetDir);
560
558
  const isMonorepo = config.structure === "monorepo";
561
- const overrides = {};
559
+ const overrides = { $schema: "node_modules/@bensandee/tooling/tooling.schema.json" };
562
560
  for (const key of OVERRIDE_KEYS) {
563
561
  if (isMonorepo && MONOREPO_IGNORED_KEYS.has(key)) continue;
564
562
  if (config[key] !== defaults[key]) overrides[key] = config[key];
@@ -740,34 +738,6 @@ function ensureWorkflowConcurrency(existing, concurrency) {
740
738
  };
741
739
  }
742
740
  }
743
- function addWorkflowJob(existing, jobName, jobConfig) {
744
- if (isToolingIgnored(existing)) return {
745
- content: existing,
746
- changed: false
747
- };
748
- try {
749
- const doc = parseDocument(existing);
750
- const jobs = doc.getIn(["jobs"]);
751
- if (!isMap(jobs)) return {
752
- content: existing,
753
- changed: false
754
- };
755
- if (jobs.has(jobName)) return {
756
- content: existing,
757
- changed: false
758
- };
759
- jobs.set(jobName, doc.createNode(jobConfig));
760
- return {
761
- content: doc.toString(),
762
- changed: true
763
- };
764
- } catch {
765
- return {
766
- content: existing,
767
- changed: false
768
- };
769
- }
770
- }
771
741
  //#endregion
772
742
  //#region src/generators/deploy-ci.ts
773
743
  /** Build a GitHub Actions expression like `${{ expr }}` without triggering no-template-curly-in-string. */
@@ -1011,7 +981,7 @@ function getAddedDevDepNames(config) {
1011
981
  const deps = { ...ROOT_DEV_DEPS };
1012
982
  if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
1013
983
  deps["@bensandee/config"] = "0.8.2";
1014
- deps["@bensandee/tooling"] = "0.21.0";
984
+ deps["@bensandee/tooling"] = "0.23.0";
1015
985
  if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
1016
986
  if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
1017
987
  addReleaseDeps(deps, config);
@@ -1036,7 +1006,7 @@ async function generatePackageJson(ctx) {
1036
1006
  const devDeps = { ...ROOT_DEV_DEPS };
1037
1007
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1038
1008
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.8.2";
1039
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.21.0";
1009
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.23.0";
1040
1010
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
1041
1011
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
1042
1012
  if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
@@ -1522,25 +1492,26 @@ function actionsExpr$1(expr) {
1522
1492
  }
1523
1493
  const CI_CONCURRENCY = {
1524
1494
  group: `ci-${actionsExpr$1("github.ref")}`,
1525
- "cancel-in-progress": true
1495
+ "cancel-in-progress": actionsExpr$1("github.ref != 'refs/heads/main'")
1526
1496
  };
1527
1497
  function hasEnginesNode$1(ctx) {
1528
1498
  const raw = ctx.read("package.json");
1529
1499
  if (!raw) return false;
1530
1500
  return typeof parsePackageJson(raw)?.engines?.["node"] === "string";
1531
1501
  }
1532
- function ciWorkflow(nodeVersionYaml, isForgejo) {
1502
+ function ciWorkflow(nodeVersionYaml, isForgejo, isChangesets) {
1533
1503
  const emailNotifications = isForgejo ? "\nenable-email-notifications: true\n" : "";
1504
+ const concurrencyBlock = isChangesets ? `
1505
+ concurrency:
1506
+ group: ci-${actionsExpr$1("github.ref")}
1507
+ cancel-in-progress: ${actionsExpr$1("github.ref != 'refs/heads/main'")}
1508
+ ` : "";
1534
1509
  return `${workflowSchemaComment(isForgejo ? "forgejo" : "github")}name: CI
1535
1510
  ${emailNotifications}on:
1536
1511
  push:
1537
1512
  branches: [main]
1538
1513
  pull_request:
1539
-
1540
- concurrency:
1541
- group: ci-${actionsExpr$1("github.ref")}
1542
- cancel-in-progress: true
1543
-
1514
+ ${concurrencyBlock}
1544
1515
  jobs:
1545
1516
  check:
1546
1517
  runs-on: ubuntu-latest
@@ -1589,6 +1560,10 @@ function requiredCheckSteps(nodeVersionYaml) {
1589
1560
  }
1590
1561
  ];
1591
1562
  }
1563
+ /** Resolve the CI workflow filename based on release strategy. */
1564
+ function ciWorkflowPath(ci, releaseStrategy) {
1565
+ return `${ci === "github" ? ".github/workflows" : ".forgejo/workflows"}/${releaseStrategy === "changesets" ? "ci.yml" : "check.yml"}`;
1566
+ }
1592
1567
  async function generateCi(ctx) {
1593
1568
  if (ctx.config.ci === "none") return {
1594
1569
  filePath: "ci",
@@ -1596,16 +1571,23 @@ async function generateCi(ctx) {
1596
1571
  description: "CI workflow not requested"
1597
1572
  };
1598
1573
  const isGitHub = ctx.config.ci === "github";
1574
+ const isChangesets = ctx.config.releaseStrategy === "changesets";
1599
1575
  const nodeVersionYaml = hasEnginesNode$1(ctx) ? "node-version-file: package.json" : "node-version: \"24\"";
1600
- const filePath = isGitHub ? ".github/workflows/check.yml" : ".forgejo/workflows/check.yml";
1601
- const content = ciWorkflow(nodeVersionYaml, !isGitHub);
1576
+ const filePath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
1577
+ const content = ciWorkflow(nodeVersionYaml, !isGitHub, isChangesets);
1602
1578
  if (ctx.exists(filePath)) {
1603
1579
  const existing = ctx.read(filePath);
1604
1580
  if (existing) {
1605
- const merged = mergeWorkflowSteps(existing, "check", requiredCheckSteps(nodeVersionYaml));
1606
- const withConcurrency = ensureWorkflowConcurrency(merged.content, CI_CONCURRENCY);
1607
- const withComment = ensureSchemaComment(withConcurrency.content, isGitHub ? "github" : "forgejo");
1608
- if (merged.changed || withConcurrency.changed || withComment !== withConcurrency.content) {
1581
+ let result = mergeWorkflowSteps(existing, "check", requiredCheckSteps(nodeVersionYaml));
1582
+ if (isChangesets) {
1583
+ const withConcurrency = ensureWorkflowConcurrency(result.content, CI_CONCURRENCY);
1584
+ result = {
1585
+ content: withConcurrency.content,
1586
+ changed: result.changed || withConcurrency.changed
1587
+ };
1588
+ }
1589
+ const withComment = ensureSchemaComment(result.content, isGitHub ? "github" : "forgejo");
1590
+ if (result.changed || withComment !== result.content) {
1609
1591
  ctx.write(filePath, withComment);
1610
1592
  return {
1611
1593
  filePath,
@@ -2147,89 +2129,36 @@ jobs:
2147
2129
  ${commonSteps(nodeVersionYaml, publishesNpm)}${gitConfigStep}${releaseStep}
2148
2130
  `;
2149
2131
  }
2150
- function changesetsReleaseJobConfig(ci, nodeVersionYaml, publishesNpm) {
2151
- const isGitHub = ci === "github";
2152
- const nodeWith = {
2153
- ...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
2154
- cache: "pnpm",
2155
- ...publishesNpm && { "registry-url": "https://registry.npmjs.org" }
2156
- };
2157
- if (isGitHub) {
2158
- const changesetsEnv = {
2159
- GITHUB_TOKEN: actionsExpr("github.token"),
2160
- ...publishesNpm && { NPM_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
2161
- };
2162
- return {
2163
- needs: "check",
2132
+ /** Build the required release step for the check job (changesets). */
2133
+ function changesetsReleaseStep(ci, publishesNpm) {
2134
+ if (ci === "github") return {
2135
+ match: { uses: "changesets/action" },
2136
+ step: {
2137
+ uses: "changesets/action@v1",
2164
2138
  if: "github.ref == 'refs/heads/main'",
2165
- concurrency: {
2166
- group: "release",
2167
- "cancel-in-progress": false
2139
+ with: {
2140
+ publish: "pnpm changeset publish",
2141
+ version: "pnpm changeset version"
2168
2142
  },
2169
- "runs-on": "ubuntu-latest",
2170
- permissions: {
2171
- contents: "write",
2172
- "pull-requests": "write"
2173
- },
2174
- steps: [
2175
- {
2176
- uses: "actions/checkout@v4",
2177
- with: { "fetch-depth": 0 }
2178
- },
2179
- { uses: "pnpm/action-setup@v4" },
2180
- {
2181
- uses: "actions/setup-node@v4",
2182
- with: nodeWith
2183
- },
2184
- { run: "pnpm install --frozen-lockfile" },
2185
- { run: "pnpm build" },
2186
- {
2187
- uses: "changesets/action@v1",
2188
- with: {
2189
- publish: "pnpm changeset publish",
2190
- version: "pnpm changeset version"
2191
- },
2192
- env: changesetsEnv
2193
- }
2194
- ]
2195
- };
2196
- }
2197
- const releaseEnv = {
2198
- FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
2199
- FORGEJO_REPOSITORY: actionsExpr("github.repository"),
2200
- FORGEJO_TOKEN: actionsExpr("secrets.FORGEJO_TOKEN"),
2201
- ...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
2143
+ env: {
2144
+ GITHUB_TOKEN: actionsExpr("github.token"),
2145
+ ...publishesNpm && { NPM_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
2146
+ }
2147
+ }
2202
2148
  };
2203
2149
  return {
2204
- needs: "check",
2205
- if: "github.ref == 'refs/heads/main'",
2206
- concurrency: {
2207
- group: "release",
2208
- "cancel-in-progress": false
2209
- },
2210
- "runs-on": "ubuntu-latest",
2211
- steps: [
2212
- {
2213
- uses: "actions/checkout@v4",
2214
- with: { "fetch-depth": 0 }
2215
- },
2216
- { uses: "pnpm/action-setup@v4" },
2217
- {
2218
- uses: "actions/setup-node@v4",
2219
- with: nodeWith
2220
- },
2221
- { run: "pnpm install --frozen-lockfile" },
2222
- { run: "pnpm build" },
2223
- {
2224
- name: "Configure git",
2225
- run: "git config user.name \"forgejo-actions[bot]\"\ngit config user.email \"forgejo-actions[bot]@noreply.localhost\"\n"
2150
+ match: { run: "release:changesets" },
2151
+ step: {
2152
+ name: "Release",
2153
+ if: "github.ref == 'refs/heads/main'",
2154
+ env: {
2155
+ FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
2156
+ FORGEJO_REPOSITORY: actionsExpr("github.repository"),
2157
+ FORGEJO_TOKEN: actionsExpr("secrets.FORGEJO_TOKEN"),
2158
+ ...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
2226
2159
  },
2227
- {
2228
- name: "Release",
2229
- env: releaseEnv,
2230
- run: "pnpm exec tooling release:changesets"
2231
- }
2232
- ]
2160
+ run: "pnpm exec tooling release:changesets"
2161
+ }
2233
2162
  };
2234
2163
  }
2235
2164
  function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm) {
@@ -2296,36 +2225,25 @@ function buildWorkflow(strategy, ci, nodeVersionYaml, publishesNpm) {
2296
2225
  }
2297
2226
  }
2298
2227
  function generateChangesetsReleaseCi(ctx, publishesNpm) {
2299
- const checkPath = ctx.config.ci === "github" ? ".github/workflows/check.yml" : ".forgejo/workflows/check.yml";
2300
- const nodeVersionYaml = hasEnginesNode(ctx) ? "node-version-file: package.json" : "node-version: \"24\"";
2301
- const existing = ctx.read(checkPath);
2228
+ const ciPath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
2229
+ const existing = ctx.read(ciPath);
2302
2230
  if (!existing) return {
2303
- filePath: checkPath,
2231
+ filePath: ciPath,
2304
2232
  action: "skipped",
2305
2233
  description: "CI workflow not found — run check generator first"
2306
2234
  };
2307
- const addResult = addWorkflowJob(existing, "release", changesetsReleaseJobConfig(ctx.config.ci, nodeVersionYaml, publishesNpm));
2308
- if (addResult.changed) {
2309
- const withComment = ensureSchemaComment(addResult.content, ctx.config.ci);
2310
- ctx.write(checkPath, withComment);
2311
- return {
2312
- filePath: checkPath,
2313
- action: "updated",
2314
- description: "Added release job to CI workflow"
2315
- };
2316
- }
2317
- const merged = mergeWorkflowSteps(existing, "release", requiredReleaseSteps("changesets", nodeVersionYaml, publishesNpm));
2235
+ const merged = mergeWorkflowSteps(existing, "check", [changesetsReleaseStep(ctx.config.ci, publishesNpm)]);
2318
2236
  if (!merged.changed) return {
2319
- filePath: checkPath,
2237
+ filePath: ciPath,
2320
2238
  action: "skipped",
2321
- description: "Release job in CI workflow already up to date"
2239
+ description: "Release step in CI workflow already up to date"
2322
2240
  };
2323
2241
  const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
2324
- ctx.write(checkPath, withComment);
2242
+ ctx.write(ciPath, withComment);
2325
2243
  return {
2326
- filePath: checkPath,
2244
+ filePath: ciPath,
2327
2245
  action: "updated",
2328
- description: "Added missing steps to release job in CI workflow"
2246
+ description: "Added release step to CI workflow"
2329
2247
  };
2330
2248
  }
2331
2249
  async function generateReleaseCi(ctx) {
@@ -3614,8 +3532,21 @@ function buildReleaseConfig(flags) {
3614
3532
  verbose: flags.verbose ?? false
3615
3533
  };
3616
3534
  }
3535
+ /** Resolve the current branch from CI env vars or git. */
3536
+ function getCurrentBranch(executor, cwd) {
3537
+ const ref = process.env["GITHUB_REF"];
3538
+ if (ref?.startsWith("refs/heads/")) return ref.slice(11);
3539
+ return executor.exec("git rev-parse --abbrev-ref HEAD", { cwd }).stdout.trim();
3540
+ }
3617
3541
  /** Core release logic — testable with a mock executor. */
3618
3542
  async function runRelease(config, executor) {
3543
+ const branch = getCurrentBranch(executor, config.cwd);
3544
+ if (branch !== "main") {
3545
+ debug$1(config, `Skipping release on non-main branch: ${branch}`);
3546
+ return { mode: "none" };
3547
+ }
3548
+ executor.exec("git config user.name \"forgejo-actions[bot]\"", { cwd: config.cwd });
3549
+ executor.exec("git config user.email \"forgejo-actions[bot]@noreply.localhost\"", { cwd: config.cwd });
3619
3550
  const changesetFiles = executor.listChangesetFiles(config.cwd);
3620
3551
  debug$1(config, `Changeset files found: ${changesetFiles.length > 0 ? changesetFiles.join(", ") : "(none)"}`);
3621
3552
  if (changesetFiles.length > 0) {
@@ -4656,7 +4587,7 @@ const dockerCheckCommand = defineCommand({
4656
4587
  const main = defineCommand({
4657
4588
  meta: {
4658
4589
  name: "tooling",
4659
- version: "0.21.0",
4590
+ version: "0.23.0",
4660
4591
  description: "Bootstrap and maintain standardized TypeScript project tooling"
4661
4592
  },
4662
4593
  subCommands: {
@@ -4672,7 +4603,7 @@ const main = defineCommand({
4672
4603
  "docker:check": dockerCheckCommand
4673
4604
  }
4674
4605
  });
4675
- console.log(`@bensandee/tooling v0.21.0`);
4606
+ console.log(`@bensandee/tooling v0.23.0`);
4676
4607
  runMain(main);
4677
4608
  //#endregion
4678
4609
  export {};
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "tooling": "./dist/bin.mjs"
7
7
  },
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "tooling.schema.json"
10
11
  ],
11
12
  "type": "module",
12
13
  "imports": {
@@ -22,7 +23,8 @@
22
23
  "types": "./dist/docker-check/index.d.mts",
23
24
  "default": "./dist/docker-check/index.mjs"
24
25
  },
25
- "./package.json": "./package.json"
26
+ "./package.json": "./package.json",
27
+ "./tooling.schema.json": "./tooling.schema.json"
26
28
  },
27
29
  "publishConfig": {
28
30
  "access": "public"
@@ -0,0 +1,148 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "@bensandee/tooling configuration",
4
+ "description": "Override convention-detected defaults for repo:sync. Only fields that differ from detected defaults need to be specified.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "JSON Schema reference (ignored by tooling)"
11
+ },
12
+ "structure": {
13
+ "type": "string",
14
+ "enum": ["single", "monorepo"],
15
+ "description": "Project structure"
16
+ },
17
+ "useEslintPlugin": {
18
+ "type": "boolean",
19
+ "description": "Include @bensandee/eslint-plugin oxlint plugin"
20
+ },
21
+ "formatter": {
22
+ "type": "string",
23
+ "enum": ["oxfmt", "prettier"],
24
+ "description": "Formatter choice"
25
+ },
26
+ "setupVitest": {
27
+ "type": "boolean",
28
+ "description": "Generate vitest config and example test"
29
+ },
30
+ "ci": {
31
+ "type": "string",
32
+ "enum": ["github", "forgejo", "none"],
33
+ "description": "CI platform"
34
+ },
35
+ "setupRenovate": {
36
+ "type": "boolean",
37
+ "description": "Generate Renovate config"
38
+ },
39
+ "releaseStrategy": {
40
+ "type": "string",
41
+ "enum": ["release-it", "simple", "changesets", "none"],
42
+ "description": "Release management strategy"
43
+ },
44
+ "projectType": {
45
+ "type": "string",
46
+ "enum": ["default", "node", "react", "library"],
47
+ "description": "Project type (determines tsconfig base)"
48
+ },
49
+ "detectPackageTypes": {
50
+ "type": "boolean",
51
+ "description": "Auto-detect project types for monorepo packages"
52
+ },
53
+ "setupDocker": {
54
+ "type": "boolean",
55
+ "description": "Generate Docker build/check scripts"
56
+ },
57
+ "docker": {
58
+ "type": "object",
59
+ "description": "Docker package overrides (package name → config)",
60
+ "additionalProperties": {
61
+ "type": "object",
62
+ "required": ["dockerfile"],
63
+ "properties": {
64
+ "dockerfile": {
65
+ "type": "string",
66
+ "description": "Path to Dockerfile relative to package"
67
+ },
68
+ "context": {
69
+ "type": "string",
70
+ "default": ".",
71
+ "description": "Docker build context relative to package"
72
+ }
73
+ },
74
+ "additionalProperties": false
75
+ }
76
+ },
77
+ "dockerCheck": {
78
+ "oneOf": [
79
+ {
80
+ "const": false,
81
+ "description": "Disable Docker health checks"
82
+ },
83
+ {
84
+ "type": "object",
85
+ "additionalProperties": false,
86
+ "properties": {
87
+ "composeFiles": {
88
+ "type": "array",
89
+ "items": { "type": "string" },
90
+ "description": "Compose files to use"
91
+ },
92
+ "envFile": {
93
+ "type": "string",
94
+ "description": "Environment file for compose"
95
+ },
96
+ "services": {
97
+ "type": "array",
98
+ "items": { "type": "string" },
99
+ "description": "Services to check (default: all)"
100
+ },
101
+ "healthChecks": {
102
+ "type": "array",
103
+ "items": {
104
+ "type": "object",
105
+ "required": ["name", "url"],
106
+ "additionalProperties": false,
107
+ "properties": {
108
+ "name": {
109
+ "type": "string",
110
+ "description": "Service name"
111
+ },
112
+ "url": {
113
+ "type": "string",
114
+ "description": "Health check URL"
115
+ },
116
+ "status": {
117
+ "type": "integer",
118
+ "description": "Expected HTTP status code"
119
+ }
120
+ }
121
+ },
122
+ "description": "Health check definitions"
123
+ },
124
+ "buildCommand": {
125
+ "type": "string",
126
+ "description": "Command to build images before checking"
127
+ },
128
+ "buildCwd": {
129
+ "type": "string",
130
+ "description": "Working directory for build command"
131
+ },
132
+ "timeoutMs": {
133
+ "type": "integer",
134
+ "minimum": 1,
135
+ "description": "Overall timeout in milliseconds"
136
+ },
137
+ "pollIntervalMs": {
138
+ "type": "integer",
139
+ "minimum": 1,
140
+ "description": "Poll interval in milliseconds"
141
+ }
142
+ }
143
+ }
144
+ ],
145
+ "description": "Docker health check configuration or false to disable"
146
+ }
147
+ }
148
+ }