@intentius/chant-lexicon-docker 0.1.4 → 0.1.8

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.
@@ -1,19 +1,19 @@
1
1
  {
2
- "algorithm": "xxhash64",
2
+ "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "11a2fcc44f1b0873",
5
- "meta.json": "6a8cbb16d88dd81",
6
- "types/index.d.ts": "61a04a2aae8fb72d",
7
- "rules/no-latest-tag.ts": "9a513a0d0801cfbd",
8
- "rules/unused-volume.ts": "b6101cbbfef97481",
9
- "rules/no-latest-image.ts": "1730d370550a5f5e",
10
- "rules/prefer-copy.ts": "c2eb8a86389a6b1",
11
- "rules/ssh-port-exposed.ts": "73a6e5d39eebcc28",
12
- "rules/docker-helpers.ts": "667d796e1dd6771f",
13
- "rules/apt-no-recommends.ts": "dce8c901982e2d4",
14
- "rules/no-root-user.ts": "5aad839aa8ef0407",
15
- "skills/chant-docker.md": "419ee2f3187d9527",
16
- "skills/chant-docker-patterns.md": "cea0225dd8450081"
4
+ "manifest.json": "87023f1c5a044cffac469511ec4fa7f58262f6b74e2382dddca406d47eec1ed0",
5
+ "meta.json": "e39b7b9e67e980b070486b5cc6908b1b1bc16e3360c3356dd0923cf4db80133e",
6
+ "types/index.d.ts": "f0bb7df2a41a10f7cdb14869dd3b23d3a3f944f2b58ee953bf252f430e0cbbd9",
7
+ "rules/no-latest-tag.ts": "efd060453d8ca3d5b85c3972906c6650b6f63eb5f67cb6cdb0f16a41cad91228",
8
+ "rules/apt-no-recommends.ts": "579ea54435b1cbd59088994aaaf7b249a42064facd74b62163b302fe31e769e0",
9
+ "rules/docker-helpers.ts": "c76ea1294b630df086a23b24c0435c9daac8cdf21dd371a8aabf74e9984e6b13",
10
+ "rules/no-latest-image.ts": "3e49602822e0645173a961b9c0686b669e846957486781a881693dbe04790b88",
11
+ "rules/no-root-user.ts": "fb47adc8523e07a8307253dcffeeccd7528e655bf737ef7e313e76a3df2c9a2d",
12
+ "rules/prefer-copy.ts": "b422a77b94023f7356d6b6326b6552c459cfbd282468f38214e19a105a59b742",
13
+ "rules/ssh-port-exposed.ts": "1df86b498787cbcc5ac8995c7099956576d9d105b4da71b0c225790be585c4c9",
14
+ "rules/unused-volume.ts": "ffcfc731782a4443a2eb34b1c0f6230b094c18c6ad104e4b81074bb799a3ce40",
15
+ "skills/chant-docker.md": "3ff0708e3c76e245f202948949c768464563af2b60cc9aa2ca52f494ef8357f1",
16
+ "skills/chant-docker-patterns.md": "ad1b0196d8150b2df579a699ff107f604340c799c04f3460ebf65003e287b12a"
17
17
  },
18
- "composite": "b35b47b51ec2e075"
18
+ "composite": "ff82c863447932fee4a358399636de04932adb25894772f0e0fd03941ed152d6"
19
19
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docker",
3
- "version": "0.1.4",
3
+ "version": "0.1.8",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Docker",
6
6
  "intrinsics": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-docker",
3
- "version": "0.1.4",
3
+ "version": "0.1.8",
4
4
  "description": "Docker lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -37,14 +37,14 @@
37
37
  "./types": "./dist/types/index.d.ts"
38
38
  },
39
39
  "scripts": {
40
- "generate": "bun run src/codegen/generate-cli.ts",
41
- "bundle": "bun run src/package-cli.ts",
42
- "validate": "bun run src/validate-cli.ts",
43
- "docs": "bun run src/codegen/docs-cli.ts",
44
- "prepack": "bun run generate && bun run bundle && bun run validate"
40
+ "generate": "tsx src/codegen/generate-cli.ts",
41
+ "bundle": "tsx src/package-cli.ts",
42
+ "validate": "tsx src/validate-cli.ts",
43
+ "docs": "tsx src/codegen/docs-cli.ts",
44
+ "prepack": "npm run generate && npm run bundle && npm run validate"
45
45
  },
46
46
  "devDependencies": {
47
- "@intentius/chant": "0.1.4",
47
+ "@intentius/chant": "*",
48
48
  "typescript": "^5.9.3"
49
49
  },
50
50
  "peerDependencies": {
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  /**
3
3
  * CLI entry for Docker lexicon docs generation.
4
4
  */
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  /**
3
3
  * CLI entry point for Docker lexicon generation.
4
4
  */
@@ -1,15 +1,15 @@
1
- import { describe, test, expect, mock } from "bun:test";
1
+ import { describe, test, expect, vi } from "vitest";
2
2
  import { generate, writeGeneratedFiles } from "./generate";
3
3
  import { mkdirSync, rmSync, existsSync, readFileSync } from "fs";
4
4
  import { join } from "path";
5
5
  import { tmpdir } from "os";
6
6
 
7
7
  // Mock the network fetches so tests run offline and fast
8
- mock.module("../spec/fetch-compose", () => ({
8
+ vi.mock("../spec/fetch-compose", () => ({
9
9
  fetchComposeSpec: async () => Buffer.from("{}"),
10
10
  }));
11
11
 
12
- mock.module("../spec/fetch-engine", () => ({
12
+ vi.mock("../spec/fetch-engine", () => ({
13
13
  fetchEngineApi: async () => Buffer.from("{}"),
14
14
  }));
15
15
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import {
3
3
  entityTypeToTsName,
4
4
  tsNameToEntityType,
@@ -2,9 +2,8 @@
2
2
  * Docker lexicon packaging — delegates to core packagePipeline.
3
3
  */
4
4
 
5
- import { createRequire } from "module";
6
5
  import { readFileSync } from "fs";
7
- const require = createRequire(import.meta.url);
6
+ import { dockerPlugin } from "../plugin";
8
7
  import { join, dirname } from "path";
9
8
  import { fileURLToPath } from "url";
10
9
  import type { IntrinsicDef } from "@intentius/chant/lexicon";
@@ -52,9 +51,7 @@ export async function packageLexicon(opts: PackageOptions = {}): Promise<Package
52
51
 
53
52
  srcDir: pkgDir,
54
53
 
55
- collectSkills: () => {
56
- const { dockerPlugin } = require("../plugin");
57
- const skillDefs = dockerPlugin.skills?.() ?? [];
54
+ collectSkills: () => { const skillDefs = dockerPlugin.skills?.() ?? [];
58
55
  return collectSkills(skillDefs);
59
56
  },
60
57
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { analyzeDockerCoverage, computeCoverage } from "./coverage";
3
3
 
4
4
  describe("analyzeDockerCoverage", () => {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import {
3
3
  DEFAULT_LABELS_MARKER,
4
4
  DEFAULT_ANNOTATIONS_MARKER,
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { DockerGenerator } from "./generator";
3
3
  import type { ServiceIR, VolumeIR, NetworkIR, ConfigIR, SecretIR, DockerfileIR } from "./parser";
4
4
 
@@ -1,10 +1,10 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { readFileSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { DockerParser, DockerfileParser } from "./parser";
5
5
 
6
6
  const testdata = (file: string) =>
7
- readFileSync(join(import.meta.dir, "testdata", file), "utf8");
7
+ readFileSync(join(import.meta.dirname, "testdata", file), "utf8");
8
8
 
9
9
  describe("DockerParser — services", () => {
10
10
  test("image extracted correctly", () => {
@@ -1,11 +1,11 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { readFileSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { DockerParser, DockerfileParser } from "./parser";
5
5
  import { DockerGenerator } from "./generator";
6
6
 
7
7
  const testdata = (file: string) =>
8
- readFileSync(join(import.meta.dir, "testdata", file), "utf8");
8
+ readFileSync(join(import.meta.dirname, "testdata", file), "utf8");
9
9
 
10
10
  describe("roundtrip: parse → generate", () => {
11
11
  test("simple.yaml → Service + Volume constructors", () => {
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ export { defaultLabels, defaultAnnotations, isDefaultLabels, isDefaultAnnotation
17
17
  export { DEFAULT_LABELS_MARKER, DEFAULT_ANNOTATIONS_MARKER } from "./default-labels";
18
18
  export type { DefaultLabels, DefaultAnnotations } from "./default-labels";
19
19
 
20
- // Generated entities — populated by `bun run generate`
20
+ // Generated entities — populated by `npm run generate`
21
21
  export * from "./generated/index";
22
22
 
23
23
  // Composites (to be added in Tier 2)
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { env } from "./interpolation";
3
3
  import { INTRINSIC_MARKER } from "@intentius/chant/intrinsic";
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
3
3
  import { dkrd001 } from "./no-latest-image";
4
4
  import { dkrd002 } from "./unused-volume";
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { noLatestTagRule } from "./no-latest-tag";
3
3
  import type { LintContext } from "@intentius/chant/lint/rule";
4
4
  import * as ts from "typescript";
@@ -0,0 +1,141 @@
1
+ import { describe, test, expect, vi, beforeEach } from "vitest";
2
+
3
+ const execMock = vi.fn();
4
+ vi.mock("node:child_process", async () => {
5
+ const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
6
+ return { ...actual, exec: (cmd: string, cb: (err: Error | null, out: { stdout: string; stderr: string }) => void) => {
7
+ Promise.resolve(execMock(cmd)).then(
8
+ (out) => cb(null, out),
9
+ (err) => cb(err as Error, { stdout: "", stderr: "" }),
10
+ );
11
+ } };
12
+ });
13
+
14
+ const { listArtifacts } = await import("./list-artifacts");
15
+
16
+ describe("docker listArtifacts", () => {
17
+ beforeEach(() => {
18
+ execMock.mockReset();
19
+ });
20
+
21
+ test("happy path: containers + images + networks all reported", async () => {
22
+ execMock.mockImplementation((cmd: string) => {
23
+ if (cmd.startsWith("docker ps")) {
24
+ return {
25
+ stdout: [
26
+ JSON.stringify({ Names: "web", ID: "abc123", Image: "nginx:latest", State: "running", Status: "Up 5 minutes" }),
27
+ JSON.stringify({ Names: "db", ID: "def456", Image: "postgres:16", State: "running", Status: "Up 2 hours" }),
28
+ ].join("\n"),
29
+ stderr: "",
30
+ };
31
+ }
32
+ if (cmd.startsWith("docker image ls")) {
33
+ return {
34
+ stdout: [
35
+ JSON.stringify({ ID: "img1", Repository: "nginx", Tag: "latest", Size: "187MB", CreatedAt: "2026-04-01" }),
36
+ JSON.stringify({ ID: "img2", Repository: "postgres", Tag: "16", Size: "412MB", CreatedAt: "2026-03-20" }),
37
+ ].join("\n"),
38
+ stderr: "",
39
+ };
40
+ }
41
+ if (cmd.startsWith("docker network ls")) {
42
+ return {
43
+ stdout: [
44
+ JSON.stringify({ ID: "net1", Name: "bridge", Driver: "bridge", Scope: "local" }),
45
+ ].join("\n"),
46
+ stderr: "",
47
+ };
48
+ }
49
+ throw new Error(`unexpected cmd: ${cmd}`);
50
+ });
51
+
52
+ const result = await listArtifacts({ environment: "prod", entities: new Map() });
53
+
54
+ expect(Object.keys(result).sort()).toEqual([
55
+ "container/db",
56
+ "container/web",
57
+ "image/nginx:latest",
58
+ "image/postgres:16",
59
+ "network/bridge",
60
+ ]);
61
+ expect(result["container/web"]).toMatchObject({
62
+ type: "Docker::Container",
63
+ physicalId: "abc123",
64
+ status: "running",
65
+ });
66
+ expect(result["image/nginx:latest"]).toMatchObject({ type: "Docker::Image", physicalId: "img1" });
67
+ expect(result["network/bridge"]).toMatchObject({ type: "Docker::Network", physicalId: "net1" });
68
+ });
69
+
70
+ test("daemon unreachable on all queries → returns {}", async () => {
71
+ execMock.mockImplementation(() => { throw new Error("Cannot connect to the Docker daemon"); });
72
+ const result = await listArtifacts({ environment: "prod", entities: new Map() });
73
+ expect(result).toEqual({});
74
+ });
75
+
76
+ test("per-query failure isolation: containers fail → still get images + networks", async () => {
77
+ execMock.mockImplementation((cmd: string) => {
78
+ if (cmd.startsWith("docker ps")) {
79
+ throw new Error("ps failed");
80
+ }
81
+ if (cmd.startsWith("docker image ls")) {
82
+ return { stdout: JSON.stringify({ ID: "img1", Repository: "alpine", Tag: "3.20", Size: "8MB" }), stderr: "" };
83
+ }
84
+ if (cmd.startsWith("docker network ls")) {
85
+ return { stdout: JSON.stringify({ ID: "net1", Name: "host", Driver: "host", Scope: "local" }), stderr: "" };
86
+ }
87
+ throw new Error(`unexpected cmd: ${cmd}`);
88
+ });
89
+
90
+ const result = await listArtifacts({ environment: "prod", entities: new Map() });
91
+
92
+ expect(Object.keys(result).filter((k) => k.startsWith("container/"))).toEqual([]);
93
+ expect(result["image/alpine:3.20"]).toBeDefined();
94
+ expect(result["network/host"]).toBeDefined();
95
+ });
96
+
97
+ test("container Status surfaces in attributes.fullStatus", async () => {
98
+ execMock.mockImplementation((cmd: string) => {
99
+ if (cmd.startsWith("docker ps")) {
100
+ return { stdout: JSON.stringify({ Names: "web", ID: "x", Image: "i", State: "running", Status: "Up 5 minutes" }), stderr: "" };
101
+ }
102
+ return { stdout: "", stderr: "" };
103
+ });
104
+ const result = await listArtifacts({ environment: "prod", entities: new Map() });
105
+ expect(result["container/web"].attributes).toMatchObject({ fullStatus: "Up 5 minutes" });
106
+ });
107
+
108
+ test("dangling images (Repository=<none>) are skipped", async () => {
109
+ execMock.mockImplementation((cmd: string) => {
110
+ if (cmd.startsWith("docker image ls")) {
111
+ return {
112
+ stdout: [
113
+ JSON.stringify({ ID: "img1", Repository: "<none>", Tag: "<none>", Size: "8MB" }),
114
+ JSON.stringify({ ID: "img2", Repository: "nginx", Tag: "latest", Size: "187MB" }),
115
+ ].join("\n"),
116
+ stderr: "",
117
+ };
118
+ }
119
+ return { stdout: "", stderr: "" };
120
+ });
121
+ const result = await listArtifacts({ environment: "prod", entities: new Map() });
122
+ expect(Object.keys(result).filter((k) => k.startsWith("image/"))).toEqual(["image/nginx:latest"]);
123
+ });
124
+
125
+ test("malformed NDJSON line is skipped, others still parsed", async () => {
126
+ execMock.mockImplementation((cmd: string) => {
127
+ if (cmd.startsWith("docker ps")) {
128
+ return {
129
+ stdout: [
130
+ "this is not json",
131
+ JSON.stringify({ Names: "web", ID: "x", State: "running", Status: "Up", Image: "i" }),
132
+ ].join("\n"),
133
+ stderr: "",
134
+ };
135
+ }
136
+ return { stdout: "", stderr: "" };
137
+ });
138
+ const result = await listArtifacts({ environment: "prod", entities: new Map() });
139
+ expect(result["container/web"]).toBeDefined();
140
+ });
141
+ });
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Live introspection of a Docker host via three independent CLI queries.
3
+ *
4
+ * The Docker lexicon's chant entities describe Compose / Dockerfile
5
+ * authoring primitives. The runtime concept (running containers, local
6
+ * images, networks) is created by `docker compose up` / `docker run` /
7
+ * `docker network create` outside chant's entity model.
8
+ *
9
+ * docker ps --format '{{json .}}' → running containers
10
+ * docker image ls --format '{{json .}}' → local images
11
+ * docker network ls --format '{{json .}}' → networks
12
+ *
13
+ * Output is one JSON object per line (NDJSON), not a JSON array. Daemon
14
+ * unreachable on any query → that query returns nothing; other queries
15
+ * still proceed.
16
+ */
17
+
18
+ import { exec } from "node:child_process";
19
+ import { promisify } from "node:util";
20
+ import type { ArtifactMetadata } from "@intentius/chant/lexicon";
21
+
22
+ const execAsync = promisify(exec);
23
+
24
+ interface DockerContainer {
25
+ Names?: string;
26
+ ID?: string;
27
+ Image?: string;
28
+ Command?: string;
29
+ State?: string; // "running" | "exited" | "created" | ...
30
+ Status?: string; // "Up 5 minutes" | "Exited (0) 1 hour ago" | ...
31
+ Ports?: string;
32
+ Mounts?: string;
33
+ }
34
+
35
+ interface DockerImage {
36
+ ID?: string;
37
+ Repository?: string;
38
+ Tag?: string;
39
+ CreatedAt?: string;
40
+ Size?: string;
41
+ }
42
+
43
+ interface DockerNetwork {
44
+ ID?: string;
45
+ Name?: string;
46
+ Driver?: string;
47
+ Scope?: string;
48
+ CreatedAt?: string;
49
+ }
50
+
51
+ function parseNdjson<T>(stdout: string): T[] {
52
+ const out: T[] = [];
53
+ for (const line of stdout.split("\n")) {
54
+ const trimmed = line.trim();
55
+ if (!trimmed) continue;
56
+ try {
57
+ out.push(JSON.parse(trimmed));
58
+ } catch {
59
+ // skip malformed lines
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+
65
+ function pruneUndefined<T extends Record<string, unknown>>(obj: T): Record<string, unknown> {
66
+ const out: Record<string, unknown> = {};
67
+ for (const [k, v] of Object.entries(obj)) {
68
+ if (v !== undefined && v !== "") out[k] = v;
69
+ }
70
+ return out;
71
+ }
72
+
73
+ async function listContainers(): Promise<Record<string, ArtifactMetadata>> {
74
+ const result: Record<string, ArtifactMetadata> = {};
75
+ try {
76
+ const { stdout } = await execAsync("docker ps --format '{{json .}}'");
77
+ const containers = parseNdjson<DockerContainer>(stdout);
78
+ for (const c of containers) {
79
+ const name = c.Names;
80
+ if (!name) continue;
81
+ result[`container/${name}`] = {
82
+ type: "Docker::Container",
83
+ physicalId: c.ID,
84
+ status: c.State ?? c.Status ?? "PRESENT",
85
+ attributes: pruneUndefined({
86
+ image: c.Image,
87
+ command: c.Command,
88
+ ports: c.Ports,
89
+ mounts: c.Mounts,
90
+ fullStatus: c.Status,
91
+ }),
92
+ };
93
+ }
94
+ } catch {
95
+ // Docker daemon unreachable — return empty, don't fail the lexicon
96
+ }
97
+ return result;
98
+ }
99
+
100
+ async function listImages(): Promise<Record<string, ArtifactMetadata>> {
101
+ const result: Record<string, ArtifactMetadata> = {};
102
+ try {
103
+ const { stdout } = await execAsync("docker image ls --format '{{json .}}'");
104
+ const images = parseNdjson<DockerImage>(stdout);
105
+ for (const img of images) {
106
+ if (!img.Repository || img.Repository === "<none>") continue;
107
+ const tag = img.Tag && img.Tag !== "<none>" ? img.Tag : "latest";
108
+ const key = `image/${img.Repository}:${tag}`;
109
+ result[key] = {
110
+ type: "Docker::Image",
111
+ physicalId: img.ID,
112
+ status: "PRESENT",
113
+ lastUpdated: img.CreatedAt,
114
+ attributes: pruneUndefined({
115
+ repository: img.Repository,
116
+ tag,
117
+ size: img.Size,
118
+ }),
119
+ };
120
+ }
121
+ } catch {
122
+ // Docker daemon unreachable
123
+ }
124
+ return result;
125
+ }
126
+
127
+ async function listNetworks(): Promise<Record<string, ArtifactMetadata>> {
128
+ const result: Record<string, ArtifactMetadata> = {};
129
+ try {
130
+ const { stdout } = await execAsync("docker network ls --format '{{json .}}'");
131
+ const networks = parseNdjson<DockerNetwork>(stdout);
132
+ for (const net of networks) {
133
+ const name = net.Name;
134
+ if (!name) continue;
135
+ result[`network/${name}`] = {
136
+ type: "Docker::Network",
137
+ physicalId: net.ID,
138
+ status: "PRESENT",
139
+ lastUpdated: net.CreatedAt,
140
+ attributes: pruneUndefined({
141
+ driver: net.Driver,
142
+ scope: net.Scope,
143
+ }),
144
+ };
145
+ }
146
+ } catch {
147
+ // Docker daemon unreachable
148
+ }
149
+ return result;
150
+ }
151
+
152
+ export async function listArtifacts(_options: {
153
+ environment: string;
154
+ entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
155
+ }): Promise<Record<string, ArtifactMetadata>> {
156
+ // Three independent queries — failure of one doesn't block the others.
157
+ const [containers, images, networks] = await Promise.all([
158
+ listContainers(),
159
+ listImages(),
160
+ listNetworks(),
161
+ ]);
162
+
163
+ return { ...containers, ...images, ...networks };
164
+ }
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { dockerCompletions } from "./completions";
3
3
  import type { CompletionContext } from "@intentius/chant/lsp/types";
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { dockerHover } from "./hover";
3
3
  import type { HoverContext } from "@intentius/chant/lsp/types";
4
4
 
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  /**
3
- * Thin entry point for `bun run bundle` in lexicon-docker.
3
+ * Thin entry point for `npm run bundle` in lexicon-docker.
4
4
  */
5
5
  import { generate, writeGeneratedFiles } from "./codegen/generate";
6
6
  import { packageLexicon } from "./codegen/package";
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { dockerPlugin } from "./plugin";
3
3
 
4
4
  describe("dockerPlugin", () => {
@@ -105,13 +105,13 @@ describe("dockerPlugin", () => {
105
105
  test("provides MCP tools", () => {
106
106
  const tools = dockerPlugin.mcpTools!();
107
107
  expect(tools.length).toBe(1);
108
- expect(tools[0].name).toBe("diff");
108
+ expect(tools[0].name).toBe("docker:diff");
109
109
  });
110
110
 
111
111
  test("provides MCP resources", () => {
112
112
  const resources = dockerPlugin.mcpResources!();
113
113
  expect(resources.length).toBe(2);
114
- expect(resources[0].uri).toBe("resource-catalog");
114
+ expect(resources[0].uri).toBe("docker:resource-catalog");
115
115
  expect(resources[1].uri).toBe("examples/basic-app");
116
116
  });
117
117
  });
package/src/plugin.ts CHANGED
@@ -5,22 +5,24 @@
5
5
  * and code generation for Docker Compose and Dockerfile resources.
6
6
  */
7
7
 
8
- import { createRequire } from "module";
9
8
  import type { LexiconPlugin, IntrinsicDef, InitTemplateSet } from "@intentius/chant/lexicon";
10
- const require = createRequire(import.meta.url);
11
9
  import type { LintRule } from "@intentius/chant/lint/rule";
12
10
  import { discoverPostSynthChecks } from "@intentius/chant/lint/discover";
13
11
  import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
14
12
  import { join, dirname } from "path";
15
13
  import { fileURLToPath } from "url";
16
14
  import { dockerSerializer } from "./serializer";
15
+ import { noLatestTagRule } from "./lint/rules/no-latest-tag";
16
+ import { dockerCompletions } from "./lsp/completions";
17
+ import { dockerHover } from "./lsp/hover";
18
+ import { DockerParser } from "./import/parser";
19
+ import { DockerGenerator } from "./import/generator";
17
20
 
18
21
  export const dockerPlugin: LexiconPlugin = {
19
22
  name: "docker",
20
23
  serializer: dockerSerializer,
21
24
 
22
25
  lintRules(): LintRule[] {
23
- const { noLatestTagRule } = require("./lint/rules/no-latest-tag");
24
26
  return [noLatestTagRule];
25
27
  },
26
28
 
@@ -94,22 +96,18 @@ export const api = new Service({
94
96
  },
95
97
 
96
98
  completionProvider(ctx: import("@intentius/chant/lsp/types").CompletionContext) {
97
- const { dockerCompletions } = require("./lsp/completions");
98
99
  return dockerCompletions(ctx);
99
100
  },
100
101
 
101
102
  hoverProvider(ctx: import("@intentius/chant/lsp/types").HoverContext) {
102
- const { dockerHover } = require("./lsp/hover");
103
103
  return dockerHover(ctx);
104
104
  },
105
105
 
106
106
  templateParser() {
107
- const { DockerParser } = require("./import/parser");
108
107
  return new DockerParser();
109
108
  },
110
109
 
111
110
  templateGenerator() {
112
- const { DockerGenerator } = require("./import/generator");
113
111
  return new DockerGenerator();
114
112
  },
115
113
 
@@ -161,12 +159,12 @@ export const api = new Service({
161
159
  },
162
160
 
163
161
  mcpTools() {
164
- return [createDiffTool(dockerSerializer, "Compare current build output against previous output for Docker Compose")];
162
+ return [createDiffTool(dockerSerializer, "Compare current build output against previous output for Docker Compose", "docker")];
165
163
  },
166
164
 
167
165
  mcpResources() {
168
166
  return [
169
- createCatalogResource(import.meta.url, "Docker Entity Catalog", "JSON list of all supported Docker entity types", "lexicon-docker.json"),
167
+ createCatalogResource(import.meta.url, "Docker Entity Catalog", "JSON list of all supported Docker entity types", "lexicon-docker.json", "docker"),
170
168
  {
171
169
  uri: "examples/basic-app",
172
170
  name: "Basic App Example",
@@ -247,4 +245,9 @@ export const api = new Service({
247
245
  ],
248
246
  },
249
247
  ]),
248
+
249
+ async listArtifacts(options) {
250
+ const { listArtifacts } = await import("./list-artifacts");
251
+ return listArtifacts(options);
252
+ },
250
253
  };
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { dockerSerializer } from "./serializer";
3
3
  import { DECLARABLE_MARKER, type Declarable } from "@intentius/chant/declarable";
4
4
  import { INTRINSIC_MARKER } from "@intentius/chant/intrinsic";
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  /**
3
- * Thin entry point for `bun run validate` in lexicon-docker.
3
+ * Thin entry point for `npm run validate` in lexicon-docker.
4
4
  */
5
5
  import { validate } from "./validate";
6
6
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { validate } from "./validate";
3
3
 
4
4
  describe("validate", () => {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { DOCKER_VARS, COMPOSE_VARS } from "./variables";
3
3
 
4
4
  describe("DOCKER_VARS", () => {