@intentius/chant-lexicon-docker 0.124.31 → 0.124.32

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 (97) hide show
  1. package/dist/codegen/docs-cli.js +7 -0
  2. package/dist/codegen/docs.js +163 -0
  3. package/dist/codegen/generate-cli.js +26 -0
  4. package/dist/codegen/generate-compose.js +16 -0
  5. package/dist/codegen/generate-dockerfile.js +16 -0
  6. package/dist/codegen/generate.d.ts.map +1 -1
  7. package/dist/codegen/generate.js +212 -0
  8. package/dist/codegen/naming.js +45 -0
  9. package/dist/codegen/package.js +40 -0
  10. package/dist/codegen/patches.js +31 -0
  11. package/dist/codegen/versions.js +41 -0
  12. package/dist/compose-key.d.ts +19 -0
  13. package/dist/compose-key.d.ts.map +1 -0
  14. package/dist/compose-key.js +26 -0
  15. package/dist/composites/catalog.js +113 -0
  16. package/dist/composites/docker-otel-collector.js +43 -0
  17. package/dist/composites/docker-web-service.js +51 -0
  18. package/dist/composites/index.js +6 -0
  19. package/dist/coverage.js +35 -0
  20. package/dist/default-labels.js +52 -0
  21. package/dist/detect.js +8 -0
  22. package/dist/dockerfile-order.js +16 -0
  23. package/dist/generated/index.d.ts +10 -0
  24. package/dist/generated/index.d.ts.map +1 -1
  25. package/dist/generated/index.js +8 -0
  26. package/dist/generated/runtime.js +4 -0
  27. package/dist/import/adapter.d.ts.map +1 -1
  28. package/dist/import/adapter.js +86 -0
  29. package/dist/import/generator.d.ts.map +1 -1
  30. package/dist/import/generator.js +159 -0
  31. package/dist/import/parser.d.ts +31 -0
  32. package/dist/import/parser.d.ts.map +1 -1
  33. package/dist/import/parser.js +181 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +20 -0
  37. package/dist/integrity.json +3 -3
  38. package/dist/interpolation.js +54 -0
  39. package/dist/lint/audit-catalog.js +25 -0
  40. package/dist/lint/audit-lineage.js +39 -0
  41. package/dist/lint/post-synth/add-url-checksum.js +35 -0
  42. package/dist/lint/post-synth/apt-no-recommends.js +34 -0
  43. package/dist/lint/post-synth/apt-update-alone.js +34 -0
  44. package/dist/lint/post-synth/cache-busting-copy.js +61 -0
  45. package/dist/lint/post-synth/copy-from-stage.js +53 -0
  46. package/dist/lint/post-synth/docker-helpers.js +148 -0
  47. package/dist/lint/post-synth/index.js +30 -0
  48. package/dist/lint/post-synth/no-latest-image.js +30 -0
  49. package/dist/lint/post-synth/no-root-user.js +35 -0
  50. package/dist/lint/post-synth/prefer-copy.js +43 -0
  51. package/dist/lint/post-synth/relative-workdir.js +34 -0
  52. package/dist/lint/post-synth/repeated-cmd.js +30 -0
  53. package/dist/lint/post-synth/root-after-user.js +39 -0
  54. package/dist/lint/post-synth/ssh-port-exposed.js +58 -0
  55. package/dist/lint/post-synth/unused-global-arg.js +34 -0
  56. package/dist/lint/post-synth/unused-volume.js +41 -0
  57. package/dist/lint/rules/data/deprecated-images.js +26 -0
  58. package/dist/lint/rules/data/known-base-images.js +18 -0
  59. package/dist/lint/rules/index.js +5 -0
  60. package/dist/lint/rules/no-latest-tag.js +57 -0
  61. package/dist/lint/rules/per-kind-order.js +129 -0
  62. package/dist/list-artifacts.js +133 -0
  63. package/dist/lsp/completions.d.ts.map +1 -1
  64. package/dist/lsp/completions.js +18 -0
  65. package/dist/lsp/hover.d.ts.map +1 -1
  66. package/dist/lsp/hover.js +33 -0
  67. package/dist/manifest.json +1 -1
  68. package/dist/package-cli.js +20 -0
  69. package/dist/plugin.js +229 -0
  70. package/dist/serializer.d.ts.map +1 -1
  71. package/dist/serializer.js +390 -0
  72. package/dist/spec/fetch-compose.js +28 -0
  73. package/dist/spec/fetch-engine.js +19 -0
  74. package/dist/spec/parse-compose.js +160 -0
  75. package/dist/spec/parse-engine.js +36 -0
  76. package/dist/types/index.d.ts +10 -0
  77. package/dist/validate-cli.js +16 -0
  78. package/dist/validate.js +37 -0
  79. package/dist/variables.js +42 -0
  80. package/package.json +8 -8
  81. package/src/codegen/generate.test.ts +10 -0
  82. package/src/codegen/generate.ts +3 -0
  83. package/src/codegen/package.ts +2 -2
  84. package/src/compose-key.ts +31 -0
  85. package/src/generated/index.d.ts +10 -0
  86. package/src/generated/index.ts +10 -0
  87. package/src/import/adapter.ts +2 -1
  88. package/src/import/generator.test.ts +5 -4
  89. package/src/import/generator.ts +75 -48
  90. package/src/import/parser.ts +58 -65
  91. package/src/import/roundtrip.test.ts +46 -3
  92. package/src/import/testdata/names-and-fields.yaml +44 -0
  93. package/src/index.ts +3 -0
  94. package/src/lsp/completions.ts +2 -1
  95. package/src/lsp/hover.ts +2 -1
  96. package/src/serializer.test.ts +31 -0
  97. package/src/serializer.ts +15 -2
@@ -0,0 +1,41 @@
1
+ /**
2
+ * DKRD002: Unused Named Volume
3
+ *
4
+ * Detects top-level named volumes that are not mounted by any service.
5
+ */
6
+ import { getPrimaryOutput, extractServices, extractNamedVolumes } from "./docker-helpers.js";
7
+ export const dkrd002 = {
8
+ id: "DKRD002",
9
+ description: "Named volume is declared but not mounted by any service",
10
+ check(ctx) {
11
+ const diagnostics = [];
12
+ for (const [_outputName, output] of ctx.outputs) {
13
+ const yaml = getPrimaryOutput(output);
14
+ if (!yaml)
15
+ continue;
16
+ const namedVolumes = extractNamedVolumes(yaml);
17
+ if (namedVolumes.size === 0)
18
+ continue;
19
+ const services = extractServices(yaml);
20
+ const mountedVolumes = new Set();
21
+ for (const svc of services.values()) {
22
+ for (const vol of svc.volumes ?? []) {
23
+ // Volume mount format: "volname:/container/path" or just "volname"
24
+ const volumeName = vol.split(":")[0].trim();
25
+ mountedVolumes.add(volumeName);
26
+ }
27
+ }
28
+ for (const volName of namedVolumes) {
29
+ if (!mountedVolumes.has(volName)) {
30
+ diagnostics.push({
31
+ checkId: "DKRD002",
32
+ severity: "warning",
33
+ message: `Named volume "${volName}" is declared but not mounted by any service.`,
34
+ lexicon: "docker",
35
+ });
36
+ }
37
+ }
38
+ }
39
+ return diagnostics;
40
+ },
41
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Known deprecated or EOL Docker base images.
3
+ */
4
+ export const DEPRECATED_IMAGES = [
5
+ {
6
+ image: "centos:8",
7
+ reason: "CentOS 8 reached EOL on December 31, 2021",
8
+ replacement: "rockylinux:8 or almalinux:8",
9
+ },
10
+ {
11
+ image: "centos:latest",
12
+ reason: "CentOS Stream is a rolling release; use a specific version",
13
+ replacement: "rockylinux:9 or almalinux:9",
14
+ },
15
+ {
16
+ image: "node:lts",
17
+ reason: "Use a specific LTS version tag for reproducible builds",
18
+ replacement: "node:20-alpine or node:22-alpine",
19
+ },
20
+ ];
21
+ /**
22
+ * Check if an image reference matches a deprecated entry.
23
+ */
24
+ export function findDeprecatedImage(imageRef) {
25
+ return DEPRECATED_IMAGES.find((d) => imageRef === d.image || imageRef.startsWith(d.image + "@"));
26
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Well-known Docker base images for rule validation.
3
+ */
4
+ export const KNOWN_BASE_IMAGES = new Set([
5
+ "alpine", "ubuntu", "debian", "centos", "fedora", "amazonlinux",
6
+ "node", "python", "ruby", "golang", "rust", "java", "openjdk",
7
+ "nginx", "httpd", "caddy", "traefik",
8
+ "postgres", "mysql", "mariadb", "mongodb", "redis", "memcached",
9
+ "rabbitmq", "kafka", "zookeeper",
10
+ "elasticsearch", "kibana", "logstash",
11
+ "busybox", "scratch",
12
+ ]);
13
+ /**
14
+ * Images that are safe to use without a tag (they have meaningful `latest`).
15
+ */
16
+ export const IMAGES_SAFE_LATEST = new Set([
17
+ "scratch",
18
+ ]);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * All imperative lint rules for the Docker lexicon.
3
+ */
4
+ export { noLatestTagRule } from "./no-latest-tag.js";
5
+ export { perKindOrderRule } from "./per-kind-order.js";
@@ -0,0 +1,57 @@
1
+ /**
2
+ * DKRS001: No Latest Tag
3
+ *
4
+ * Warns when a Service's image prop is set to a literal string
5
+ * ending with `:latest` or has no tag (implies latest).
6
+ * Using `:latest` prevents reproducible builds.
7
+ */
8
+ import { ts } from "@intentius/chant/lint/typescript";
9
+ function isLatestImage(imageValue) {
10
+ const trimmed = imageValue.trim();
11
+ if (!trimmed || trimmed.startsWith("${"))
12
+ return false;
13
+ // Has :latest tag
14
+ if (trimmed.endsWith(":latest"))
15
+ return true;
16
+ // No tag at all (no colon after image name, no @sha256)
17
+ const parts = trimmed.split("/");
18
+ const lastPart = parts[parts.length - 1];
19
+ if (!lastPart.includes(":") && !lastPart.includes("@"))
20
+ return true;
21
+ return false;
22
+ }
23
+ export const noLatestTagRule = {
24
+ id: "DKRS001",
25
+ severity: "warning",
26
+ category: "correctness",
27
+ description: "Avoid :latest or untagged image references — use explicit version tags for reproducible builds",
28
+ check(context) {
29
+ const { sourceFile } = context;
30
+ const diagnostics = [];
31
+ function visit(node) {
32
+ // Look for `image: "..."` in object literals (properties named "image")
33
+ if (ts.isPropertyAssignment(node) &&
34
+ ts.isIdentifier(node.name) &&
35
+ node.name.text === "image") {
36
+ const init = node.initializer;
37
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
38
+ const text = init.text;
39
+ if (isLatestImage(text)) {
40
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
41
+ diagnostics.push({
42
+ file: sourceFile.fileName,
43
+ line: line + 1,
44
+ column: character + 1,
45
+ ruleId: "DKRS001",
46
+ severity: "warning",
47
+ message: `Image "${text}" uses :latest or has no tag. Use an explicit version tag (e.g., "nginx:1.25-alpine") for reproducible builds.`,
48
+ });
49
+ }
50
+ }
51
+ }
52
+ ts.forEachChild(node, visit);
53
+ }
54
+ visit(sourceFile);
55
+ return diagnostics;
56
+ },
57
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * DKRS002: Per-kind Dockerfile fields written in a different order
3
+ *
4
+ * A Dockerfile stage without `steps` writes its per-kind fields (`run`,
5
+ * `copy`, `user`, ...) in a fixed order: ARG, ENV, WORKDIR, USER, RUN, COPY,
6
+ * ADD, EXPOSE, VOLUME, LABEL, ENTRYPOINT, CMD, HEALTHCHECK. When the fields
7
+ * are declared in another order (`copy` before `run`, `run` before `user`)
8
+ * the generated Dockerfile does not run them in the order they read in.
9
+ *
10
+ * The fix rewrites the fields as `steps` in the fixed order, so the
11
+ * generated Dockerfile stays the same, with a comment to reorder them.
12
+ */
13
+ import { ts } from "@intentius/chant/lint/typescript";
14
+ import { PER_KIND_ORDER } from "../../dockerfile-order.js";
15
+ const PER_KIND = new Set(PER_KIND_ORDER);
16
+ /** Per-kind fields that take one string; the rest take a list. */
17
+ const SINGLE = new Set(["workdir", "user", "entrypoint", "cmd", "healthcheck"]);
18
+ const FIX_NOTE = "// Written by the DKRS002 fix in the fixed order the per-kind fields used, so the output is unchanged. Reorder the steps as the build needs.";
19
+ function propName(p) {
20
+ if (!(ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)))
21
+ return undefined;
22
+ const n = p.name;
23
+ if (ts.isIdentifier(n) || ts.isStringLiteral(n))
24
+ return n.text;
25
+ return undefined;
26
+ }
27
+ function isDockerfileNew(node) {
28
+ if (!ts.isNewExpression(node))
29
+ return false;
30
+ const e = node.expression;
31
+ return (ts.isIdentifier(e) && e.text === "Dockerfile") || (ts.isPropertyAccessExpression(e) && e.name.text === "Dockerfile");
32
+ }
33
+ /** The stage-shaped object literals of one `new Dockerfile({...})`. */
34
+ function stageLiterals(arg) {
35
+ const stagesProp = arg.properties.find((p) => propName(p) === "stages");
36
+ if (!stagesProp)
37
+ return [arg];
38
+ // With stages, the top-level per-kind fields are not written at all.
39
+ if (!ts.isPropertyAssignment(stagesProp) || !ts.isArrayLiteralExpression(stagesProp.initializer))
40
+ return [];
41
+ return stagesProp.initializer.elements.filter(ts.isObjectLiteralExpression);
42
+ }
43
+ /** The `{ key: value }` step texts for one per-kind field, or undefined when its value can't be split. */
44
+ function stepTexts(sf, key, p) {
45
+ if (!ts.isPropertyAssignment(p))
46
+ return undefined;
47
+ const init = p.initializer;
48
+ if (SINGLE.has(key))
49
+ return [`{ ${key}: ${init.getText(sf)} }`];
50
+ if (!ts.isArrayLiteralExpression(init))
51
+ return undefined;
52
+ const out = [];
53
+ for (const el of init.elements) {
54
+ if (ts.isSpreadElement(el) || ts.isOmittedExpression(el))
55
+ return undefined;
56
+ out.push(`{ ${key}: ${el.getText(sf)} }`);
57
+ }
58
+ return out;
59
+ }
60
+ function buildFix(sf, obj, perKind) {
61
+ const byKey = new Map();
62
+ for (const p of perKind)
63
+ byKey.set(propName(p), p);
64
+ const steps = [];
65
+ for (const key of PER_KIND_ORDER) {
66
+ const p = byKey.get(key);
67
+ if (!p)
68
+ continue;
69
+ const texts = stepTexts(sf, key, p);
70
+ if (!texts)
71
+ return undefined;
72
+ steps.push(...texts);
73
+ }
74
+ const first = perKind[0];
75
+ const last = perKind[perKind.length - 1];
76
+ const start = first.getStart(sf);
77
+ const end = last.getEnd();
78
+ const lineStart = sf.text.lastIndexOf("\n", start - 1) + 1;
79
+ const indent = /^[ \t]*/.exec(sf.text.slice(lineStart, start))[0];
80
+ // Other props between the first and last per-kind field (rare) are kept, ahead of steps.
81
+ const kept = obj.properties.filter((p) => p.getStart(sf) > start && p.getEnd() < end && !perKind.includes(p)).map((p) => p.getText(sf));
82
+ const stepsText = `${FIX_NOTE}\n${indent}steps: [\n${steps.map((s) => `${indent} ${s},`).join("\n")}\n${indent}]`;
83
+ return { range: [start, end], replacement: [...kept, stepsText].join(`,\n${indent}`), kind: "replace" };
84
+ }
85
+ function check(context) {
86
+ const { sourceFile: sf } = context;
87
+ const diagnostics = [];
88
+ const visit = (node) => {
89
+ if (isDockerfileNew(node)) {
90
+ const arg = node.arguments?.[0];
91
+ if (arg && ts.isObjectLiteralExpression(arg)) {
92
+ for (const obj of stageLiterals(arg)) {
93
+ if (obj.properties.some((p) => propName(p) === "steps"))
94
+ continue;
95
+ const perKind = obj.properties.filter((p) => PER_KIND.has(propName(p) ?? ""));
96
+ const declared = perKind.map((p) => propName(p));
97
+ const fixed = [...declared].sort((a, b) => PER_KIND_ORDER.indexOf(a) - PER_KIND_ORDER.indexOf(b));
98
+ if (declared.join() === fixed.join())
99
+ continue;
100
+ const { line, character } = sf.getLineAndCharacterOfPosition(perKind[0].getStart(sf));
101
+ const fix = buildFix(sf, obj, perKind);
102
+ diagnostics.push({
103
+ file: sf.fileName,
104
+ line: line + 1,
105
+ column: character + 1,
106
+ ruleId: "DKRS002",
107
+ severity: "warning",
108
+ message: `Dockerfile fields declared as ${declared.join(", ")} are written in the fixed order ${fixed.join(", ")}. ` +
109
+ `Use steps to write the instructions in the order the build needs${fix ? " (chant lint --fix rewrites them as steps)" : ""}.`,
110
+ ...(fix ? { fix } : {}),
111
+ });
112
+ }
113
+ }
114
+ }
115
+ ts.forEachChild(node, visit);
116
+ };
117
+ visit(sf);
118
+ return diagnostics;
119
+ }
120
+ export const perKindOrderRule = {
121
+ id: "DKRS002",
122
+ severity: "warning",
123
+ category: "correctness",
124
+ description: "Dockerfile per-kind fields are written in a fixed order that differs from the declared order — use steps",
125
+ check,
126
+ fix(context) {
127
+ return check(context).flatMap((d) => (d.fix ? [d.fix] : []));
128
+ },
129
+ };
@@ -0,0 +1,133 @@
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
+ import { exec } from "node:child_process";
18
+ import { promisify } from "node:util";
19
+ const execAsync = promisify(exec);
20
+ function parseNdjson(stdout) {
21
+ const out = [];
22
+ for (const line of stdout.split("\n")) {
23
+ const trimmed = line.trim();
24
+ if (!trimmed)
25
+ continue;
26
+ try {
27
+ out.push(JSON.parse(trimmed));
28
+ }
29
+ catch {
30
+ // skip malformed lines
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+ function pruneUndefined(obj) {
36
+ const out = {};
37
+ for (const [k, v] of Object.entries(obj)) {
38
+ if (v !== undefined && v !== "")
39
+ out[k] = v;
40
+ }
41
+ return out;
42
+ }
43
+ async function listContainers() {
44
+ const result = {};
45
+ try {
46
+ const { stdout } = await execAsync("docker ps --format '{{json .}}'");
47
+ const containers = parseNdjson(stdout);
48
+ for (const c of containers) {
49
+ const name = c.Names;
50
+ if (!name)
51
+ continue;
52
+ result[`container/${name}`] = {
53
+ type: "Docker::Container",
54
+ physicalId: c.ID,
55
+ status: c.State ?? c.Status ?? "PRESENT",
56
+ attributes: pruneUndefined({
57
+ image: c.Image,
58
+ command: c.Command,
59
+ ports: c.Ports,
60
+ mounts: c.Mounts,
61
+ fullStatus: c.Status,
62
+ }),
63
+ };
64
+ }
65
+ }
66
+ catch {
67
+ // Docker daemon unreachable — return empty, don't fail the lexicon
68
+ }
69
+ return result;
70
+ }
71
+ async function listImages() {
72
+ const result = {};
73
+ try {
74
+ const { stdout } = await execAsync("docker image ls --format '{{json .}}'");
75
+ const images = parseNdjson(stdout);
76
+ for (const img of images) {
77
+ if (!img.Repository || img.Repository === "<none>")
78
+ continue;
79
+ const tag = img.Tag && img.Tag !== "<none>" ? img.Tag : "latest";
80
+ const key = `image/${img.Repository}:${tag}`;
81
+ result[key] = {
82
+ type: "Docker::Image",
83
+ physicalId: img.ID,
84
+ status: "PRESENT",
85
+ lastUpdated: img.CreatedAt,
86
+ attributes: pruneUndefined({
87
+ repository: img.Repository,
88
+ tag,
89
+ size: img.Size,
90
+ }),
91
+ };
92
+ }
93
+ }
94
+ catch {
95
+ // Docker daemon unreachable
96
+ }
97
+ return result;
98
+ }
99
+ async function listNetworks() {
100
+ const result = {};
101
+ try {
102
+ const { stdout } = await execAsync("docker network ls --format '{{json .}}'");
103
+ const networks = parseNdjson(stdout);
104
+ for (const net of networks) {
105
+ const name = net.Name;
106
+ if (!name)
107
+ continue;
108
+ result[`network/${name}`] = {
109
+ type: "Docker::Network",
110
+ physicalId: net.ID,
111
+ status: "PRESENT",
112
+ lastUpdated: net.CreatedAt,
113
+ attributes: pruneUndefined({
114
+ driver: net.Driver,
115
+ scope: net.Scope,
116
+ }),
117
+ };
118
+ }
119
+ }
120
+ catch {
121
+ // Docker daemon unreachable
122
+ }
123
+ return result;
124
+ }
125
+ export async function listArtifacts(_options) {
126
+ // Three independent queries — failure of one doesn't block the others.
127
+ const [containers, images, networks] = await Promise.all([
128
+ listContainers(),
129
+ listImages(),
130
+ listNetworks(),
131
+ ]);
132
+ return { ...containers, ...images, ...networks };
133
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/lsp/completions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAapF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAE1E"}
1
+ {"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/lsp/completions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAapF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAE1E"}
@@ -0,0 +1,18 @@
1
+ import { createRequire } from "module";
2
+ import { moduleSourceUrl } from "@intentius/chant/module-source";
3
+ import { LexiconIndex, lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
4
+ const require = createRequire(moduleSourceUrl(import.meta.url));
5
+ let cachedIndex = null;
6
+ function getIndex() {
7
+ if (cachedIndex)
8
+ return cachedIndex;
9
+ const data = require("../generated/lexicon-docker.json");
10
+ cachedIndex = new LexiconIndex(data);
11
+ return cachedIndex;
12
+ }
13
+ /**
14
+ * Provide Docker entity completions based on context.
15
+ */
16
+ export function dockerCompletions(ctx) {
17
+ return lexiconCompletions(ctx, getIndex(), "Docker entity");
18
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAa1E;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAEpE"}
1
+ {"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAa1E;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAEpE"}
@@ -0,0 +1,33 @@
1
+ import { createRequire } from "module";
2
+ import { moduleSourceUrl } from "@intentius/chant/module-source";
3
+ import { LexiconIndex, lexiconHover } from "@intentius/chant/lsp/lexicon-providers";
4
+ const require = createRequire(moduleSourceUrl(import.meta.url));
5
+ let cachedIndex = null;
6
+ function getIndex() {
7
+ if (cachedIndex)
8
+ return cachedIndex;
9
+ const data = require("../generated/lexicon-docker.json");
10
+ cachedIndex = new LexiconIndex(data);
11
+ return cachedIndex;
12
+ }
13
+ /**
14
+ * Provide hover information for Docker entity types.
15
+ */
16
+ export function dockerHover(ctx) {
17
+ return lexiconHover(ctx, getIndex(), resourceHover);
18
+ }
19
+ function resourceHover(className, entry) {
20
+ const lines = [];
21
+ lines.push(`**${className}**`);
22
+ lines.push("");
23
+ lines.push(`Docker type: \`${entry.resourceType}\``);
24
+ if (entry.resourceType.startsWith("Docker::Compose::")) {
25
+ lines.push("");
26
+ lines.push("*Compose resource — serialized into docker-compose.yml*");
27
+ }
28
+ else if (entry.resourceType === "Docker::Dockerfile") {
29
+ lines.push("");
30
+ lines.push("*Dockerfile resource — serialized as Dockerfile.{name}*");
31
+ }
32
+ return { contents: lines.join("\n") };
33
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docker",
3
- "version": "0.124.31",
3
+ "version": "0.124.32",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Docker",
6
6
  "intrinsics": [
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Thin entry point for `npm run bundle` in lexicon-docker.
4
+ */
5
+ import { generate, writeGeneratedFiles } from "./codegen/generate.js";
6
+ import { packageLexicon } from "./codegen/package.js";
7
+ import { writeBundleSpec } from "@intentius/chant/codegen/package";
8
+ import { join, dirname } from "path";
9
+ import { fileURLToPath } from "url";
10
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
11
+ // 1. Generate src/generated/ files
12
+ const genResult = await generate({ verbose: true });
13
+ writeGeneratedFiles(genResult, pkgDir);
14
+ console.error(`Generated ${genResult.resources} entities, ${genResult.properties} property types, ${genResult.enums} enums`);
15
+ // 2. Run package pipeline and write dist/
16
+ const { spec, stats } = await packageLexicon({ verbose: true });
17
+ const distDir = join(pkgDir, "dist");
18
+ writeBundleSpec(spec, distDir);
19
+ console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
20
+ console.error(`dist/ written to ${distDir}`);