@catladder/cli 0.0.0-next-b3e567e8 → 0.0.0-next-dccc0a36

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.
Files changed (35) hide show
  1. package/dist/bundles/catenv/index.js +244 -67
  2. package/dist/bundles/cli/index.js +244 -67
  3. package/dist/bundles/skills/catladder-builds/SKILL.md +49 -2
  4. package/dist/bundles/skills/catladder-builds/references/build-types.md +2 -2
  5. package/dist/bundles/skills/catladder-pipelines/SKILL.md +13 -3
  6. package/dist/packages/pipeline/src/backends/github/GithubBackend.js +4 -4
  7. package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  8. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js +4 -4
  9. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
  10. package/dist/packages/pipeline/src/build/types.d.ts +6 -5
  11. package/dist/packages/pipeline/src/build/types.js.map +1 -1
  12. package/dist/packages/pipeline/src/customImages/hash.d.ts +9 -0
  13. package/dist/packages/pipeline/src/customImages/hash.js +41 -16
  14. package/dist/packages/pipeline/src/customImages/hash.js.map +1 -1
  15. package/dist/packages/pipeline/src/customImages/jobImagesPlan.d.ts +23 -5
  16. package/dist/packages/pipeline/src/customImages/jobImagesPlan.js +177 -43
  17. package/dist/packages/pipeline/src/customImages/jobImagesPlan.js.map +1 -1
  18. package/dist/packages/pipeline/src/customImages/projectImages.d.ts +67 -0
  19. package/dist/packages/pipeline/src/customImages/projectImages.js +9 -0
  20. package/dist/packages/pipeline/src/customImages/projectImages.js.map +1 -0
  21. package/dist/packages/pipeline/src/deploy/types/custom.d.ts +2 -2
  22. package/dist/packages/pipeline/src/deploy/types/index.d.ts +4 -4
  23. package/dist/packages/pipeline/src/deploy/types/pages.d.ts +2 -2
  24. package/dist/packages/pipeline/src/index.d.ts +2 -0
  25. package/dist/packages/pipeline/src/index.js +2 -0
  26. package/dist/packages/pipeline/src/index.js.map +1 -1
  27. package/dist/packages/pipeline/src/types/config.d.ts +8 -0
  28. package/dist/packages/pipeline/src/types/config.js.map +1 -1
  29. package/dist/packages/pipeline/src/types/jobs.d.ts +2 -1
  30. package/dist/packages/pipeline/src/types/jobs.js.map +1 -1
  31. package/dist/skills/catladder-builds/SKILL.md +49 -2
  32. package/dist/skills/catladder-builds/references/build-types.md +2 -2
  33. package/dist/skills/catladder-pipelines/SKILL.md +13 -3
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +1 -1
@@ -0,0 +1,67 @@
1
+ import type { GitlabJobImage } from "../types/gitlab-types";
2
+ /**
3
+ * a docker image declared by the project (`config.images`): built
4
+ * automatically in the pipeline, content-hashed into the project's own
5
+ * registry and rebuilt only when its inputs change.
6
+ */
7
+ type ProjectImageCommon = {
8
+ /**
9
+ * the docker build context, relative to the repository root.
10
+ *
11
+ * Defaults to `dir` for a directory-based image, and to the
12
+ * repository root (`.`) for an inline `dockerfile`.
13
+ *
14
+ * NOTE: the context itself is not hashed — only the Dockerfile (or
15
+ * `dir`), `buildArgs` and `hashExtraPaths` are. List files that are
16
+ * `COPY`ed from a wider context in `hashExtraPaths` if changing them
17
+ * should rebuild the image.
18
+ */
19
+ context?: string;
20
+ /**
21
+ * extra files that influence the image content — they become part of
22
+ * the content hash and the change detection
23
+ */
24
+ hashExtraPaths?: string[];
25
+ /**
26
+ * docker `--build-arg` values passed to the image build, part of the
27
+ * content hash
28
+ */
29
+ buildArgs?: Record<string, string>;
30
+ };
31
+ /**
32
+ * where the image definition comes from: a directory in the repository
33
+ * containing a `Dockerfile`, or an inline Dockerfile written straight
34
+ * into the config (materialized into the generated files)
35
+ */
36
+ export type ProjectImageConfig = ProjectImageCommon & ({
37
+ /**
38
+ * directory containing the `Dockerfile`, relative to the
39
+ * repository root. Also the default build context.
40
+ */
41
+ dir: string;
42
+ dockerfile?: never;
43
+ } | {
44
+ /**
45
+ * the Dockerfile content, as one string or as lines. Written to
46
+ * `.catladder-generated/images/project/<name>/Dockerfile`; the
47
+ * build context defaults to the repository root.
48
+ */
49
+ dockerfile: string | string[];
50
+ dir?: never;
51
+ });
52
+ /**
53
+ * a reference to a project-declared job image (`config.images`), usable
54
+ * in every `jobImage` field. The pipeline backends resolve it to the
55
+ * concrete content-hashed image url in the project registry.
56
+ */
57
+ export type ProjectImageRef = {
58
+ /** the key of the image in `config.images` */
59
+ image: string;
60
+ };
61
+ export declare const isProjectImageRef: (value: unknown) => value is ProjectImageRef;
62
+ /**
63
+ * everything a `jobImage` config field accepts: a concrete image (url
64
+ * or gitlab image object) or a reference to a project-declared image
65
+ */
66
+ export type JobImageConfig = GitlabJobImage | ProjectImageRef;
67
+ export {};
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isProjectImageRef = void 0;
4
+ const isProjectImageRef = (value) => typeof value === "object" &&
5
+ value !== null &&
6
+ "image" in value &&
7
+ typeof value.image === "string";
8
+ exports.isProjectImageRef = isProjectImageRef;
9
+ //# sourceMappingURL=projectImages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectImages.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/customImages/projectImages.ts"],"names":[],"mappings":";;;AAoEO,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAA4B,EAAE,CAC5E,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,OAAO,IAAI,KAAK;IAChB,OAAQ,KAAyB,CAAC,KAAK,KAAK,QAAQ,CAAC;AAJ1C,QAAA,iBAAiB,qBAIyB"}
@@ -1,5 +1,5 @@
1
1
  import type { WithCacheConfig } from "../../build";
2
- import type { GitlabJobImage } from "../../types";
2
+ import type { JobImageConfig } from "../../customImages/projectImages";
3
3
  import type { DeployConfigBase } from "./base";
4
4
  export type DeployConfigCustom = {
5
5
  /**
@@ -31,7 +31,7 @@ export type DeployConfigCustom = {
31
31
  /**
32
32
  * image to use
33
33
  */
34
- jobImage?: GitlabJobImage;
34
+ jobImage?: JobImageConfig;
35
35
  /**
36
36
  * job artifact paths the deploy produces (relative to the repo root)
37
37
  */
@@ -15,17 +15,17 @@ export type DeployConfigType = DeployConfig["type"];
15
15
  export type DeployConfigGeneric<T extends DeployConfigType> = Extract<DeployConfig, {
16
16
  type: T;
17
17
  }>;
18
- export declare const isOfDeployType: <T extends ("custom" | "kubernetes" | "dockerTag" | "google-cloudrun" | "npmPackage" | "pages")[]>(t: DeployConfig | false | null | undefined, ...types: T) => t is Extract<DeployConfigCustom, {
18
+ export declare const isOfDeployType: <T extends ("custom" | "kubernetes" | "pages" | "google-cloudrun" | "dockerTag" | "npmPackage")[]>(t: DeployConfig | false | null | undefined, ...types: T) => t is Extract<DeployConfigKubernetes, {
19
19
  type: T[number];
20
- }> | Extract<DeployConfigDockerTag, {
20
+ }> | Extract<DeployConfigCustom, {
21
21
  type: T[number];
22
22
  }> | Extract<DeployConfigCloudRun, {
23
23
  type: T[number];
24
- }> | Extract<DeployConfigKubernetes, {
24
+ }> | Extract<DeployConfigDockerTag, {
25
25
  type: T[number];
26
26
  }> | Extract<DeployConfigNpmPackage, {
27
27
  type: T[number];
28
28
  }> | Extract<DeployConfigPages, {
29
29
  type: T[number];
30
30
  }>;
31
- export declare const getDeployConfigType: (t: DeployConfig | false | null | undefined) => "custom" | "kubernetes" | "dockerTag" | "google-cloudrun" | "npmPackage" | "pages";
31
+ export declare const getDeployConfigType: (t: DeployConfig | false | null | undefined) => "custom" | "kubernetes" | "pages" | "google-cloudrun" | "dockerTag" | "npmPackage";
@@ -1,5 +1,5 @@
1
1
  import type { WithCacheConfig } from "../../build";
2
- import type { GitlabJobImage } from "../../types";
2
+ import type { JobImageConfig } from "../../customImages/projectImages";
3
3
  import type { DeployConfigBase } from "./base";
4
4
  export type DeployConfigPages = {
5
5
  /**
@@ -39,5 +39,5 @@ export type DeployConfigPages = {
39
39
  /**
40
40
  * image to use
41
41
  */
42
- jobImage?: GitlabJobImage;
42
+ jobImage?: JobImageConfig;
43
43
  } & DeployConfigBase & WithCacheConfig;
@@ -3,6 +3,8 @@ export * from "./backends";
3
3
  export * from "./types";
4
4
  export * from "./rules";
5
5
  export * from "./runner";
6
+ export * from "./customImages/projectImages";
7
+ export * from "./customImages/jobImagesPlan";
6
8
  export * from "./config";
7
9
  export * from "./store";
8
10
  export * from "./context";
@@ -19,6 +19,8 @@ __exportStar(require("./backends"), exports);
19
19
  __exportStar(require("./types"), exports);
20
20
  __exportStar(require("./rules"), exports);
21
21
  __exportStar(require("./runner"), exports);
22
+ __exportStar(require("./customImages/projectImages"), exports);
23
+ __exportStar(require("./customImages/jobImagesPlan"), exports);
22
24
  __exportStar(require("./config"), exports);
23
25
  __exportStar(require("./store"), exports);
24
26
  __exportStar(require("./context"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/pipeline/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,6CAA2B;AAC3B,0CAAwB;AACxB,0CAAwB;AACxB,2CAAyB;AACzB,2CAAyB;AACzB,0CAAwB;AACxB,4CAA0B;AAC1B,0CAAwB;AACxB,2CAAyB;AACzB,2CAAyB;AACzB,4CAA0B;AAC1B,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,2CAAyB;AACzB,gDAA8B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/pipeline/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,6CAA2B;AAC3B,0CAAwB;AACxB,0CAAwB;AACxB,2CAAyB;AACzB,+DAA6C;AAC7C,+DAA6C;AAC7C,2CAAyB;AACzB,0CAAwB;AACxB,4CAA0B;AAC1B,0CAAwB;AACxB,2CAAyB;AACzB,2CAAyB;AACzB,4CAA0B;AAC1B,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,2CAAyB;AACzB,gDAA8B"}
@@ -9,6 +9,7 @@ import type { Hooks } from "./hooks";
9
9
  import type { ReleaseConfig } from "./release";
10
10
  import type { CatladderStore } from "../store";
11
11
  import type { AgentSkillsConfig } from "../agentSkills/types";
12
+ import type { ProjectImageConfig } from "../customImages/projectImages";
12
13
  import type { VerifyConfig } from "../verify/types";
13
14
  export declare const ALL_PIPELINE_TRIGGERS: readonly ["mainBranch", "mr", "taggedRelease"];
14
15
  export type PipelineTrigger = (typeof ALL_PIPELINE_TRIGGERS)[number];
@@ -294,6 +295,13 @@ export type Config<C extends ConfigProps = never> = {
294
295
  */
295
296
  domainCanonical?: string;
296
297
  agents?: Record<string, AgentConfig>;
298
+ /**
299
+ * docker images declared by the project, built automatically in the
300
+ * pipeline: content-hashed into the project's own registry and only
301
+ * rebuilt when their inputs change. Reference them in any `jobImage`
302
+ * field via `{ image: "<key>" }`.
303
+ */
304
+ images?: Record<string, ProjectImageConfig>;
297
305
  builds?: Record<string, WorkspaceBuildConfig>;
298
306
  /**
299
307
  * components (sub apps)
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/types/config.ts"],"names":[],"mappings":";;;AAca,QAAA,qBAAqB,GAAG;IACnC,YAAY;IACZ,IAAI;IACJ,eAAe;CACP,CAAC;AAGX;;;GAGG;AACU,QAAA,SAAS,GAAG;IACvB,GAAG,EAAE;QACH,QAAQ,EAAE,CAAC,YAAY,CAAC;KACzB;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,IAAI,CAAC;KACjB;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,IAAI,EAAE;QACJ,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,EAAE;KACb;CACO,CAAC;AAEX;;;;GAIG;AACI,MAAM,oBAAoB,GAAG,CAAC,OAAwB,EAAE,EAAE,CAC/D,MAAM,CAAC,OAAO,CAAC,iBAAS,CAAC;KACtB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CACf,CAAC,CAAC,QAAuC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAC7D;KACA,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAY,CAAC,CAAC;AALnB,QAAA,oBAAoB,wBAKD;AAEnB,QAAA,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAS,CAAC,CAAC;AACtC,QAAA,iBAAiB,GAAG,oBAAyB,CAAC;AAGpD,MAAM,aAAa,GAAG,CAAC,GAAW,EAAkB,EAAE;IAC3D,OAAO,GAAG,IAAI,iBAAS,CAAC;AAC1B,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/types/config.ts"],"names":[],"mappings":";;;AAea,QAAA,qBAAqB,GAAG;IACnC,YAAY;IACZ,IAAI;IACJ,eAAe;CACP,CAAC;AAGX;;;GAGG;AACU,QAAA,SAAS,GAAG;IACvB,GAAG,EAAE;QACH,QAAQ,EAAE,CAAC,YAAY,CAAC;KACzB;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,IAAI,CAAC;KACjB;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,IAAI,EAAE;QACJ,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,EAAE;KACb;CACO,CAAC;AAEX;;;;GAIG;AACI,MAAM,oBAAoB,GAAG,CAAC,OAAwB,EAAE,EAAE,CAC/D,MAAM,CAAC,OAAO,CAAC,iBAAS,CAAC;KACtB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CACf,CAAC,CAAC,QAAuC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAC7D;KACA,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAY,CAAC,CAAC;AALnB,QAAA,oBAAoB,wBAKD;AAEnB,QAAA,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAS,CAAC,CAAC;AACtC,QAAA,iBAAiB,GAAG,oBAAyB,CAAC;AAGpD,MAAM,aAAa,GAAG,CAAC,GAAW,EAAkB,EAAE;IAC3D,OAAO,GAAG,IAAI,iBAAS,CAAC;AAC1B,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB"}
@@ -1,5 +1,6 @@
1
1
  import type { UnspecifiedEnvVars } from "..";
2
2
  import type { CatladderImageRef } from "../runner";
3
+ import type { ProjectImageRef } from "../customImages/projectImages";
3
4
  import type { CacheConfig } from "../build/types";
4
5
  import type { Artifacts, GitlabJobCache, GitlabJobDef, GitlabJobImage, GitlabRule } from "./gitlab-types";
5
6
  export type CatladderJobCache = GitlabJobCache & {
@@ -186,7 +187,7 @@ export type GitlabJobFields = {
186
187
  * catladder-provided job image (resolved by the backend to an image
187
188
  * built in the repository's own registry)
188
189
  */
189
- image?: GitlabJobImage | CatladderImageRef;
190
+ image?: GitlabJobImage | CatladderImageRef | ProjectImageRef;
190
191
  /**
191
192
  * whether failures are allowed
192
193
  */
@@ -1 +1 @@
1
- {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/types/jobs.ts"],"names":[],"mappings":";;;AAGA,8DAA2D;AAsB9C,QAAA,WAAW,GAAG;IACzB,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,MAAM;CACE,CAAC;AAWE,QAAA,YAAY,GAAG;IAC1B,OAAO;IACP,gBAAgB;IAChB,aAAa;IACb,aAAa;IACb,YAAY;CACJ,CAAC;AA6NX;;;;;;;GAOG;AACH,4JAA4J;AAC5J,MAAa,YAAY;IACvB,YAAY,IAAyB;QACnC,uDAAuD;QACvD,2DAA2D;QAC3D,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAA,iCAAe,EAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,IAAI,CACT,GAAkD;QAElD,OAAO,GAAG,YAAY,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;IACnE,CAAC;CACF;AAZD,oCAYC"}
1
+ {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/types/jobs.ts"],"names":[],"mappings":";;;AAIA,8DAA2D;AAsB9C,QAAA,WAAW,GAAG;IACzB,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,MAAM;CACE,CAAC;AAWE,QAAA,YAAY,GAAG;IAC1B,OAAO;IACP,gBAAgB;IAChB,aAAa;IACb,aAAa;IACb,YAAY;CACJ,CAAC;AA6NX;;;;;;;GAOG;AACH,4JAA4J;AAC5J,MAAa,YAAY;IACvB,YAAY,IAAyB;QACnC,uDAAuD;QACvD,2DAA2D;QAC3D,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAA,iCAAe,EAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,IAAI,CACT,GAAkD;QAElD,OAAO,GAAG,YAAY,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;IACnE,CAAC;CACF;AAZD,oCAYC"}
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: catladder-builds
3
- description: Configuring how a component is built in a catladder project — the `build` config in catladder.ts (build types node / rails / meteor / custom, build & start commands, artifacts, caching, lint/test/audit jobs, the Docker image, and shared workspace builds). Use when adding or changing a component's build, choosing a build type, wiring a Dockerfile/nginx image, tuning the build/test/lint jobs, or setting up a monorepo workspace build. Triggers on "build config", "buildCommand", "build type", "Dockerfile", "docker image", "rails build", "monorepo build", "artifacts".
3
+ description: Configuring how a component is built in a catladder project — the `build` config in catladder.ts (build types node / rails / meteor / custom, build & start commands, artifacts, caching, lint/test/audit jobs, the Docker image, project-declared job images, and shared workspace builds). Use when adding or changing a component's build, choosing a build type, wiring a Dockerfile/nginx image, declaring a custom CI job image (`images` config), tuning the build/test/lint jobs, or setting up a monorepo workspace build. Triggers on "build config", "buildCommand", "build type", "Dockerfile", "docker image", "job image", "jobImage", "custom image", "project image", "rails build", "monorepo build", "artifacts".
4
4
  ---
5
5
 
6
6
  # Component builds with catladder
@@ -71,11 +71,58 @@ type: `build: { from: "web" }` (see workspace builds below).
71
71
  - `cache` — build caching (see the `catladder-pipelines` skill for the
72
72
  caching model).
73
73
  - `jobImage`, `jobTags`, `jobVars`, `runnerVariables` — the CI image,
74
- runner tags, build-only env vars, and extra runner variables.
74
+ runner tags, build-only env vars, and extra runner variables. A
75
+ `jobImage` is a concrete image url or `{ image: "<name>" }`
76
+ referencing a project image (see below).
75
77
  - `docker` — the image build strategy: a built-in
76
78
  (`{ type: "nginx" | "node" | "meteor" }`) or
77
79
  `{ type: "custom" }` (expects a `Dockerfile`).
78
80
 
81
+ ## Project images (custom CI job images)
82
+
83
+ When a job needs a toolchain catladder doesn't ship (Java, Playwright,
84
+ …), declare a Docker image at the top level under `images` and reference
85
+ it in any `jobImage` field (build, `test.jobImage`, custom/pages deploy,
86
+ verify):
87
+
88
+ ```ts
89
+ images: {
90
+ // (a) a directory in the repo (default context = the dir)
91
+ "java-build": {
92
+ dir: "docker/java-build", // contains the Dockerfile
93
+ buildArgs: { MAVEN_VERSION: "3.9.9" }, // optional, part of the content hash
94
+ hashExtraPaths: ["shared/settings.xml"], // optional extra hashed+watched files
95
+ },
96
+ // (b) inline (default context = repo root); materialized into
97
+ // .catladder-generated/images/project/<name>/Dockerfile
98
+ "db-tools": {
99
+ dockerfile: ["FROM alpine:3.21", "RUN apk add --no-cache postgresql17-client"],
100
+ },
101
+ },
102
+ components: {
103
+ api: {
104
+ build: { type: "custom", jobImage: { image: "java-build" }, docker: { type: "custom" } },
105
+ },
106
+ }
107
+ ```
108
+
109
+ `dir` and `dockerfile` are mutually exclusive. `context` overrides the
110
+ build context (relative to the repo root) — needed when the image
111
+ `COPY`s files from outside its `dir`.
112
+
113
+ Catladder generates a `🐳 image <name>` job (setup stage) that builds
114
+ the image content-hashed into the project registry
115
+ (`…/job-images/<name>:<hash>`) and skips when it already exists — works
116
+ on GitLab and GitHub.
117
+
118
+ **The build context is not hashed** (it can be the whole repo) — only
119
+ the Dockerfile / `dir`, `buildArgs` and `hashExtraPaths` are. Files
120
+ pulled in via `COPY` that should trigger a rebuild belong in
121
+ `hashExtraPaths`.
122
+
123
+ Generation (`yarn catenv`) fails fast on an undeclared image name, a
124
+ missing `dir`, or a `dir` without a `Dockerfile`.
125
+
79
126
  ## Workspace builds (monorepos)
80
127
 
81
128
  For a shared build across several components, declare it once at the top
@@ -19,7 +19,7 @@ generated pipeline files.
19
19
  | `jobVars` | `EnvVars` | env vars only in build jobs |
20
20
  | `runnerVariables` | `Record<string,string>` | extra runner vars (also for `services:`) |
21
21
  | `jobTags` | `string[]` | runner tags |
22
- | `jobImage` | `GitlabJobImage` | custom CI image for the build |
22
+ | `jobImage` | `JobImageConfig` | CI image for the build: a concrete image or `{ image: "<name>" }` referencing a project image |
23
23
  | `docker` | see below | the deployable image strategy |
24
24
 
25
25
  `TestJobCustom` (for `lint`/`test`/`audit`): `command`, `jobImage`,
@@ -48,7 +48,7 @@ generated pipeline files.
48
48
 
49
49
  ### `custom`
50
50
  - `type: "custom"` — **required**.
51
- - `jobImage: GitlabJobImage` — **required** CI image.
51
+ - `jobImage: JobImageConfig` — **required** CI image (concrete image or `{ image: "<name>" }` project-image ref).
52
52
  - `docker: BuildConfigDocker` — **required** image build config.
53
53
  - `jobServices?: Services` — services for lint/test/audit/build jobs.
54
54
  - `lint?` / `test?` / `audit?: TestJobCustom` — disabled unless set.
@@ -29,13 +29,23 @@ plus stop jobs for review-app teardown.
29
29
 
30
30
  ## Job images and catci
31
31
 
32
- Jobs run in catladder-provided images. Their definitions are
33
- materialized into `.catladder-generated/images/` and built in the
34
- project's own registry (content-hashed, rebuilt only on change).
32
+ Jobs run in catladder-provided images (`🐳 catladder image <name>` build
33
+ jobs). Their definitions are materialized into
34
+ `.catladder-generated/images/` and built in the project's own registry
35
+ under `catladder/` (content-hashed, rebuilt only on change).
35
36
  `.catladder-generated/catci/` holds a small bundled CI companion used by
36
37
  generated jobs (e.g. the release security audit) — all generated, never
37
38
  edit.
38
39
 
40
+ Projects can declare their own job images under `images` in
41
+ `catladder.ts` (`🐳 image <name>` build jobs, pushed to `job-images/` in
42
+ the registry) and reference them in any `jobImage` field via
43
+ `{ image: "<name>" }` — see the `catladder-builds` skill. A declared
44
+ Dockerfile directory is used in place; an inline `dockerfile` is
45
+ materialized into `.catladder-generated/images/project/<name>/`. Either
46
+ way the build job is skipped when the content hash already exists in
47
+ the registry.
48
+
39
49
  ## Caching
40
50
 
41
51
  Cache configuration is generated per build type; node builds cache