@intentius/chant-lexicon-docker 0.84.0 → 0.86.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.
@@ -0,0 +1,3 @@
1
+ import type { CompositeEntry } from "@intentius/chant/lexicon";
2
+ export declare const compositeCatalog: CompositeEntry[];
3
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/composites/catalog.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAgH5C,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * DockerWebService composite: one HTTP service as a Compose service, built
3
+ * from a Dockerfile or run from an image, listening on one port, with a
4
+ * health check on an HTTP path.
5
+ *
6
+ * It is the shape a single web app takes when Compose runs it, and the kind a
7
+ * component names in its contract's `composites` when it deploys one
8
+ * (#2662), so `chant workspace graph --composites` can offer that component
9
+ * for the instance.
10
+ */
11
+ export interface DockerWebServiceProps {
12
+ /** Build the image from a Dockerfile: the context, and the Dockerfile's path in it (default: "Dockerfile"). */
13
+ build?: {
14
+ context: string;
15
+ dockerfile?: string;
16
+ };
17
+ /** The image to run, or the tag to give the built one. One of `build` and `image` is required. */
18
+ image?: string;
19
+ /** The port the app listens on in the container. It is also passed to the app as `PORT`. */
20
+ port: number;
21
+ /** The port published on the host (default: `port`). */
22
+ hostPort?: number;
23
+ /** More environment variables. A `PORT` here overrides the one from `port`. */
24
+ environment?: Record<string, string>;
25
+ /** An HTTP path the health check fetches with `wget` inside the container (default: no health check). */
26
+ healthPath?: string;
27
+ /** Restart policy (default: "unless-stopped"). */
28
+ restart?: string;
29
+ /** Per-member defaults for fine-grained overrides. */
30
+ defaults?: {
31
+ service?: Partial<Record<string, unknown>>;
32
+ };
33
+ }
34
+ /**
35
+ * One HTTP service as a Compose service, with a published port and an HTTP
36
+ * health check. Returns the service, keyed by the export name plus `Service`.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { DockerWebService } from "@intentius/chant-lexicon-docker";
41
+ *
42
+ * export const app = DockerWebService({
43
+ * build: { context: "../app" },
44
+ * image: "app:local",
45
+ * port: 8080,
46
+ * healthPath: "/healthz",
47
+ * });
48
+ * ```
49
+ */
50
+ export declare const DockerWebService: import("@intentius/chant").CompositeDefinition<DockerWebServiceProps, {
51
+ service: import("@intentius/chant").Declarable & Record<string, string>;
52
+ }>;
53
+ //# sourceMappingURL=docker-web-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker-web-service.d.ts","sourceRoot":"","sources":["../../src/composites/docker-web-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,WAAW,qBAAqB;IACpC,+GAA+G;IAC/G,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,kGAAkG;IAClG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KAC5C,CAAC;CACH;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB;;EAuBP,CAAC"}
@@ -4,4 +4,6 @@
4
4
  */
5
5
  export { DockerOtelCollector } from "./docker-otel-collector.js";
6
6
  export type { DockerOtelCollectorProps } from "./docker-otel-collector.js";
7
+ export { DockerWebService } from "./docker-web-service.js";
8
+ export type { DockerWebServiceProps } from "./docker-web-service.js";
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composites/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composites/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "542ffd6f86ba0bee3394f81625a067c44548f8edda15f15c97298f0ff135b19e",
4
+ "manifest.json": "ffc4f346c3f94cc4da268771c6dc9976d4d30a3d81c409fcdd39cd6382a1a57c",
5
5
  "meta.json": "2996be8fdb14f3f935e1e1441fed8f69af7b6aeeefb14dac96301cc41eba8046",
6
6
  "types/index.d.ts": "7b2e6f784b7dbecbbe7924fa1aec7e2520ac0547069311253a006bc2267a5646",
7
7
  "rules/no-latest-tag.ts": "efd060453d8ca3d5b85c3972906c6650b6f63eb5f67cb6cdb0f16a41cad91228",
@@ -15,5 +15,5 @@
15
15
  "skills/chant-docker.md": "3ff0708e3c76e245f202948949c768464563af2b60cc9aa2ca52f494ef8357f1",
16
16
  "skills/chant-docker-patterns.md": "ad1b0196d8150b2df579a699ff107f604340c799c04f3460ebf65003e287b12a"
17
17
  },
18
- "composite": "6c6c23de9acb2f0baad71559798045a4679f198524e8317cf735a5abd0c91041"
18
+ "composite": "3e8a9c15a72bccd7184e6b39857c6d9180fcd23920c9195aff25b1fba235e258"
19
19
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docker",
3
- "version": "0.84.0",
3
+ "version": "0.86.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Docker",
6
6
  "intrinsics": [
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,0BAA0B,CAAC;AAc7F,eAAO,MAAM,YAAY,EAAE,aA0O1B,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,0BAA0B,CAAC;AAc7F,eAAO,MAAM,YAAY,EAAE,aA8O1B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-docker",
3
- "version": "0.84.0",
3
+ "version": "0.86.0",
4
4
  "description": "Docker lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -70,7 +70,7 @@
70
70
  "typescript": "^5.9.3"
71
71
  },
72
72
  "peerDependencies": {
73
- "@intentius/chant": "^0.84.0",
73
+ "@intentius/chant": "^0.86.0",
74
74
  "typescript": "^5.9.3"
75
75
  }
76
76
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The composite catalog matches what this lexicon exports (#2662).
3
+ *
4
+ * `catalog.ts` is generated by `npm run generate:composite-catalogs` and is
5
+ * what `chant serve mcp` lists. A composite added without regenerating it would
6
+ * be invisible to an agent asking what docker offers; one renamed or removed
7
+ * would be offered and fail to import.
8
+ */
9
+
10
+ import { describe, expect, test } from "vitest";
11
+ import { isCompositeDefinition } from "@intentius/chant";
12
+ import * as composites from "./index";
13
+ import { compositeCatalog } from "./catalog";
14
+ import { dockerPlugin } from "../plugin";
15
+
16
+ const exported = Object.entries(composites)
17
+ .filter(([, value]) => isCompositeDefinition(value))
18
+ .map(([name]) => name)
19
+ .sort();
20
+ const catalogued = compositeCatalog.map((entry) => entry.name).sort();
21
+
22
+ describe("the docker composite catalog", () => {
23
+ test("every exported composite has an entry, and every entry names an exported composite", () => {
24
+ expect(exported.length).toBeGreaterThan(0);
25
+ expect(catalogued, "regenerate with `npm run generate:composite-catalogs -- docker`").toEqual(exported);
26
+ });
27
+
28
+ test("every entry says what it is and what it bundles", () => {
29
+ for (const entry of compositeCatalog) {
30
+ expect(entry.lexicon, entry.name).toBe("docker");
31
+ expect(entry.description.length, entry.name).toBeGreaterThan(0);
32
+ expect(entry.bundles.length, entry.name).toBeGreaterThan(0);
33
+ }
34
+ });
35
+
36
+ test("the plugin contributes it", () => {
37
+ expect(dockerPlugin.composites?.()).toBe(compositeCatalog);
38
+ });
39
+ });
@@ -0,0 +1,118 @@
1
+ // Generated by scripts/generate-composite-catalogs.ts (#2662) from this
2
+ // lexicon's composite exports. Do not edit by hand: run
3
+ // `npm run generate:composite-catalogs`. catalog.test.ts holds it to the exports.
4
+ import type { CompositeEntry } from "@intentius/chant/lexicon";
5
+
6
+ export const compositeCatalog: CompositeEntry[] = [
7
+ {
8
+ "name": "DockerOtelCollector",
9
+ "lexicon": "docker",
10
+ "description": "Create a DockerOtelCollector composite.",
11
+ "bundles": [
12
+ "DockerConfig",
13
+ "Service"
14
+ ],
15
+ "params": [
16
+ {
17
+ "name": "image",
18
+ "type": "string",
19
+ "required": false,
20
+ "description": "Collector image (default: the contrib image at the otel lexicon's pinned collector version)."
21
+ },
22
+ {
23
+ "name": "config",
24
+ "type": "Iterable<Declarable>",
25
+ "required": false,
26
+ "description": "The collector config, as otel lexicon entities (components and pipelines)."
27
+ },
28
+ {
29
+ "name": "exporters",
30
+ "type": "OTelComponent<\"exporter\", string, any>[]",
31
+ "required": false,
32
+ "description": "Exporters for the default config (default: one `debug` exporter)."
33
+ },
34
+ {
35
+ "name": "signals",
36
+ "type": "Signal[]",
37
+ "required": false,
38
+ "description": "Signals the default config has pipelines for (default: traces, metrics and logs)."
39
+ },
40
+ {
41
+ "name": "publishPorts",
42
+ "type": "boolean",
43
+ "required": false,
44
+ "description": "Publish the receiver ports on the host (default: true)."
45
+ },
46
+ {
47
+ "name": "restart",
48
+ "type": "string",
49
+ "required": false,
50
+ "description": "Restart policy (default: \"unless-stopped\")."
51
+ },
52
+ {
53
+ "name": "defaults",
54
+ "type": "{ service?: Partial<Record<string, unknown>>; config?: Partial<Record<string, unknown>>; }",
55
+ "required": false,
56
+ "description": "Per-member defaults for fine-grained overrides."
57
+ }
58
+ ]
59
+ },
60
+ {
61
+ "name": "DockerWebService",
62
+ "lexicon": "docker",
63
+ "description": "One HTTP service as a Compose service, with a published port and an HTTP health check.",
64
+ "bundles": [
65
+ "Service"
66
+ ],
67
+ "params": [
68
+ {
69
+ "name": "build",
70
+ "type": "{ context: string; dockerfile?: string }",
71
+ "required": false,
72
+ "description": "Build the image from a Dockerfile: the context, and the Dockerfile's path in it (default: \"Dockerfile\")."
73
+ },
74
+ {
75
+ "name": "image",
76
+ "type": "string",
77
+ "required": false,
78
+ "description": "The image to run, or the tag to give the built one."
79
+ },
80
+ {
81
+ "name": "port",
82
+ "type": "number",
83
+ "required": true,
84
+ "description": "The port the app listens on in the container."
85
+ },
86
+ {
87
+ "name": "hostPort",
88
+ "type": "number",
89
+ "required": false,
90
+ "description": "The port published on the host (default: `port`)."
91
+ },
92
+ {
93
+ "name": "environment",
94
+ "type": "Record<string, string>",
95
+ "required": false,
96
+ "description": "More environment variables."
97
+ },
98
+ {
99
+ "name": "healthPath",
100
+ "type": "string",
101
+ "required": false,
102
+ "description": "An HTTP path the health check fetches with `wget` inside the container (default: no health check)."
103
+ },
104
+ {
105
+ "name": "restart",
106
+ "type": "string",
107
+ "required": false,
108
+ "description": "Restart policy (default: \"unless-stopped\")."
109
+ },
110
+ {
111
+ "name": "defaults",
112
+ "type": "{ service?: Partial<Record<string, unknown>>; }",
113
+ "required": false,
114
+ "description": "Per-member defaults for fine-grained overrides."
115
+ }
116
+ ]
117
+ }
118
+ ];
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { expandComposite } from "@intentius/chant";
3
+ import { DockerWebService } from "./docker-web-service";
4
+
5
+ const props = (s: unknown) => (s as { props: Record<string, unknown> }).props;
6
+
7
+ describe("DockerWebService", () => {
8
+ test("one Compose service, keyed by the export name plus Service", () => {
9
+ const expanded = expandComposite("app", DockerWebService({ build: { context: "../app" }, port: 8080 }));
10
+ expect([...expanded.keys()]).toEqual(["appService"]);
11
+ expect(expanded.get("appService")?.entityType).toBe("Docker::Compose::Service");
12
+ });
13
+
14
+ test("builds from the Dockerfile, publishes the port, passes it as PORT, and checks the health path", () => {
15
+ const { service } = DockerWebService({ build: { context: "../app" }, image: "app:local", port: 8080, healthPath: "/healthz" }).members;
16
+ expect(props(service)).toEqual({
17
+ build: { context: "../app", dockerfile: "Dockerfile" },
18
+ image: "app:local",
19
+ ports: ["8080:8080"],
20
+ environment: { PORT: "8080" },
21
+ restart: "unless-stopped",
22
+ healthcheck: { test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"], interval: "30s", timeout: "5s", retries: 3 },
23
+ });
24
+ });
25
+
26
+ test("an image alone, another host port, extra environment, and no health check without a path", () => {
27
+ const { service } = DockerWebService({ image: "nginx:1", port: 80, hostPort: 8081, environment: { MODE: "prod" } }).members;
28
+ expect(props(service)).toEqual({ image: "nginx:1", ports: ["8081:80"], environment: { PORT: "80", MODE: "prod" }, restart: "unless-stopped" });
29
+ });
30
+
31
+ test("needs a build or an image", () => {
32
+ expect(() => DockerWebService({ port: 8080 })).toThrow(/give `build`, `image`, or both/);
33
+ });
34
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * DockerWebService composite: one HTTP service as a Compose service, built
3
+ * from a Dockerfile or run from an image, listening on one port, with a
4
+ * health check on an HTTP path.
5
+ *
6
+ * It is the shape a single web app takes when Compose runs it, and the kind a
7
+ * component names in its contract's `composites` when it deploys one
8
+ * (#2662), so `chant workspace graph --composites` can offer that component
9
+ * for the instance.
10
+ */
11
+
12
+ import { Composite, mergeDefaults } from "@intentius/chant";
13
+ import { Service } from "../generated/index";
14
+
15
+ export interface DockerWebServiceProps {
16
+ /** Build the image from a Dockerfile: the context, and the Dockerfile's path in it (default: "Dockerfile"). */
17
+ build?: { context: string; dockerfile?: string };
18
+ /** The image to run, or the tag to give the built one. One of `build` and `image` is required. */
19
+ image?: string;
20
+ /** The port the app listens on in the container. It is also passed to the app as `PORT`. */
21
+ port: number;
22
+ /** The port published on the host (default: `port`). */
23
+ hostPort?: number;
24
+ /** More environment variables. A `PORT` here overrides the one from `port`. */
25
+ environment?: Record<string, string>;
26
+ /** An HTTP path the health check fetches with `wget` inside the container (default: no health check). */
27
+ healthPath?: string;
28
+ /** Restart policy (default: "unless-stopped"). */
29
+ restart?: string;
30
+ /** Per-member defaults for fine-grained overrides. */
31
+ defaults?: {
32
+ service?: Partial<Record<string, unknown>>;
33
+ };
34
+ }
35
+
36
+ /**
37
+ * One HTTP service as a Compose service, with a published port and an HTTP
38
+ * health check. Returns the service, keyed by the export name plus `Service`.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { DockerWebService } from "@intentius/chant-lexicon-docker";
43
+ *
44
+ * export const app = DockerWebService({
45
+ * build: { context: "../app" },
46
+ * image: "app:local",
47
+ * port: 8080,
48
+ * healthPath: "/healthz",
49
+ * });
50
+ * ```
51
+ */
52
+ export const DockerWebService = Composite((props: DockerWebServiceProps) => {
53
+ const { build, image, port, hostPort = port, environment, healthPath, restart = "unless-stopped", defaults: defs } = props;
54
+ if (!build && !image) throw new Error("DockerWebService: give `build`, `image`, or both");
55
+
56
+ const service = new Service(mergeDefaults({
57
+ ...(build ? { build: { context: build.context, dockerfile: build.dockerfile ?? "Dockerfile" } } : {}),
58
+ ...(image ? { image } : {}),
59
+ ports: [`${hostPort}:${port}`],
60
+ environment: { PORT: String(port), ...environment },
61
+ restart,
62
+ ...(healthPath
63
+ ? {
64
+ healthcheck: {
65
+ test: ["CMD", "wget", "-qO-", `http://127.0.0.1:${port}${healthPath}`],
66
+ interval: "30s",
67
+ timeout: "5s",
68
+ retries: 3,
69
+ },
70
+ }
71
+ : {}),
72
+ }, defs?.service));
73
+
74
+ return { service };
75
+ }, "DockerWebService");
@@ -5,3 +5,5 @@
5
5
 
6
6
  export { DockerOtelCollector } from "./docker-otel-collector";
7
7
  export type { DockerOtelCollectorProps } from "./docker-otel-collector";
8
+ export { DockerWebService } from "./docker-web-service";
9
+ export type { DockerWebServiceProps } from "./docker-web-service";
package/src/plugin.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * and code generation for Docker Compose and Dockerfile resources.
6
6
  */
7
7
 
8
+ import { compositeCatalog } from "./composites/catalog";
8
9
  import type { LexiconPlugin, IntrinsicDef, InitTemplateSet } from "@intentius/chant/lexicon";
9
10
  import { detectTemplate } from "./detect";
10
11
  import type { LintRule } from "@intentius/chant/lint/rule";
@@ -161,6 +162,10 @@ export const api = new Service({
161
162
  console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
162
163
  },
163
164
 
165
+ composites() {
166
+ return compositeCatalog;
167
+ },
168
+
164
169
  mcpTools() {
165
170
  return [createDiffTool(dockerSerializer, "Compare current build output against previous output for Docker Compose", "docker")];
166
171
  },