@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.
- package/dist/codegen/docs-cli.js +7 -0
- package/dist/codegen/docs.js +163 -0
- package/dist/codegen/generate-cli.js +26 -0
- package/dist/codegen/generate-compose.js +16 -0
- package/dist/codegen/generate-dockerfile.js +16 -0
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +212 -0
- package/dist/codegen/naming.js +45 -0
- package/dist/codegen/package.js +40 -0
- package/dist/codegen/patches.js +31 -0
- package/dist/codegen/versions.js +41 -0
- package/dist/compose-key.d.ts +19 -0
- package/dist/compose-key.d.ts.map +1 -0
- package/dist/compose-key.js +26 -0
- package/dist/composites/catalog.js +113 -0
- package/dist/composites/docker-otel-collector.js +43 -0
- package/dist/composites/docker-web-service.js +51 -0
- package/dist/composites/index.js +6 -0
- package/dist/coverage.js +35 -0
- package/dist/default-labels.js +52 -0
- package/dist/detect.js +8 -0
- package/dist/dockerfile-order.js +16 -0
- package/dist/generated/index.d.ts +10 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/generated/index.js +8 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/import/adapter.d.ts.map +1 -1
- package/dist/import/adapter.js +86 -0
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/generator.js +159 -0
- package/dist/import/parser.d.ts +31 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/import/parser.js +181 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/integrity.json +3 -3
- package/dist/interpolation.js +54 -0
- package/dist/lint/audit-catalog.js +25 -0
- package/dist/lint/audit-lineage.js +39 -0
- package/dist/lint/post-synth/add-url-checksum.js +35 -0
- package/dist/lint/post-synth/apt-no-recommends.js +34 -0
- package/dist/lint/post-synth/apt-update-alone.js +34 -0
- package/dist/lint/post-synth/cache-busting-copy.js +61 -0
- package/dist/lint/post-synth/copy-from-stage.js +53 -0
- package/dist/lint/post-synth/docker-helpers.js +148 -0
- package/dist/lint/post-synth/index.js +30 -0
- package/dist/lint/post-synth/no-latest-image.js +30 -0
- package/dist/lint/post-synth/no-root-user.js +35 -0
- package/dist/lint/post-synth/prefer-copy.js +43 -0
- package/dist/lint/post-synth/relative-workdir.js +34 -0
- package/dist/lint/post-synth/repeated-cmd.js +30 -0
- package/dist/lint/post-synth/root-after-user.js +39 -0
- package/dist/lint/post-synth/ssh-port-exposed.js +58 -0
- package/dist/lint/post-synth/unused-global-arg.js +34 -0
- package/dist/lint/post-synth/unused-volume.js +41 -0
- package/dist/lint/rules/data/deprecated-images.js +26 -0
- package/dist/lint/rules/data/known-base-images.js +18 -0
- package/dist/lint/rules/index.js +5 -0
- package/dist/lint/rules/no-latest-tag.js +57 -0
- package/dist/lint/rules/per-kind-order.js +129 -0
- package/dist/list-artifacts.js +133 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +33 -0
- package/dist/manifest.json +1 -1
- package/dist/package-cli.js +20 -0
- package/dist/plugin.js +229 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +390 -0
- package/dist/spec/fetch-compose.js +28 -0
- package/dist/spec/fetch-engine.js +19 -0
- package/dist/spec/parse-compose.js +160 -0
- package/dist/spec/parse-engine.js +36 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +37 -0
- package/dist/variables.js +42 -0
- package/package.json +8 -8
- package/src/codegen/generate.test.ts +10 -0
- package/src/codegen/generate.ts +3 -0
- package/src/codegen/package.ts +2 -2
- package/src/compose-key.ts +31 -0
- package/src/generated/index.d.ts +10 -0
- package/src/generated/index.ts +10 -0
- package/src/import/adapter.ts +2 -1
- package/src/import/generator.test.ts +5 -4
- package/src/import/generator.ts +75 -48
- package/src/import/parser.ts +58 -65
- package/src/import/roundtrip.test.ts +46 -3
- package/src/import/testdata/names-and-fields.yaml +44 -0
- package/src/index.ts +3 -0
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/serializer.test.ts +31 -0
- package/src/serializer.ts +15 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the Compose Spec JSON Schema into ParsedResult entries.
|
|
3
|
+
*
|
|
4
|
+
* The Compose spec defines a top-level "services", "volumes", "networks",
|
|
5
|
+
* "configs", and "secrets" map — each value is a typed resource.
|
|
6
|
+
* We model those as the five Compose entity types.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The TypeScript type of a Compose string slot. Compose interpolates every
|
|
10
|
+
* value, so `env()` fits where a string does; an entity (a service, network,
|
|
11
|
+
* volume, config, secret or Dockerfile) is written as its name.
|
|
12
|
+
*/
|
|
13
|
+
export const COMPOSE_STRING = "ComposeString";
|
|
14
|
+
const ENTITIES = [
|
|
15
|
+
{ def: "service", shortName: "Service", description: "A containerized service definition in Docker Compose" },
|
|
16
|
+
{ def: "volume", shortName: "Volume", description: "A named volume definition in Docker Compose" },
|
|
17
|
+
{ def: "network", shortName: "Network", description: "A network definition in Docker Compose" },
|
|
18
|
+
{ def: "config", shortName: "Config", description: "A config definition in Docker Compose" },
|
|
19
|
+
{ def: "secret", shortName: "Secret", description: "A secret definition in Docker Compose" },
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Parse the Compose Spec JSON Schema buffer into ParsedResult entries.
|
|
23
|
+
* Returns one entry per top-level Compose resource type, with a property for
|
|
24
|
+
* every field the spec defines on it (#3934). Each property's `type` is a
|
|
25
|
+
* TypeScript type converted from the schema; string slots use
|
|
26
|
+
* {@link COMPOSE_STRING}.
|
|
27
|
+
*/
|
|
28
|
+
export function parseComposeSpec(data) {
|
|
29
|
+
const spec = JSON.parse(data.toString("utf-8"));
|
|
30
|
+
const defs = spec.$defs ?? spec.definitions ?? {};
|
|
31
|
+
return ENTITIES.map(({ def, shortName, description }) => {
|
|
32
|
+
const schema = defs[def];
|
|
33
|
+
const required = new Set(schema?.required ?? []);
|
|
34
|
+
const properties = Object.entries(schema?.properties ?? {}).map(([name, prop]) => ({
|
|
35
|
+
name,
|
|
36
|
+
type: toTsType(prop, defs, new Set([def])),
|
|
37
|
+
...(prop.description ? { description: oneLine(prop.description) } : {}),
|
|
38
|
+
...(required.has(name) ? { required: true } : {}),
|
|
39
|
+
}));
|
|
40
|
+
return { typeName: `Docker::Compose::${shortName}`, shortName, description, isResource: true, properties };
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function oneLine(text) {
|
|
44
|
+
return text.replace(/\s+/g, " ").replace(/\*\//g, "* /").trim();
|
|
45
|
+
}
|
|
46
|
+
/** A property name as a TypeScript key: bare when it is an identifier. */
|
|
47
|
+
function key(name) {
|
|
48
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
49
|
+
}
|
|
50
|
+
/** Convert one JSON Schema node to a TypeScript type. `seen` guards $ref cycles. */
|
|
51
|
+
export function toTsType(schema, defs, seen = new Set()) {
|
|
52
|
+
if (schema === undefined || schema === true)
|
|
53
|
+
return "unknown";
|
|
54
|
+
if (schema === false)
|
|
55
|
+
return "never";
|
|
56
|
+
if (schema.$ref) {
|
|
57
|
+
const name = schema.$ref.replace(/^#\/(\$defs|definitions)\//, "");
|
|
58
|
+
const target = defs[name];
|
|
59
|
+
if (!target || seen.has(name))
|
|
60
|
+
return "unknown";
|
|
61
|
+
return toTsType(target, defs, new Set([...seen, name]));
|
|
62
|
+
}
|
|
63
|
+
const alts = schema.oneOf ?? schema.anyOf;
|
|
64
|
+
if (alts) {
|
|
65
|
+
// A oneOf beside a type or properties narrows them; the alternatives carry the shape.
|
|
66
|
+
return union(alts.map((a) => toTsType(a, defs, seen)));
|
|
67
|
+
}
|
|
68
|
+
if (schema.allOf) {
|
|
69
|
+
const parts = schema.allOf.map((a) => toTsType(a, defs, seen)).filter((t) => t !== "unknown");
|
|
70
|
+
return parts.length ? parts.map(paren).join(" & ") : "unknown";
|
|
71
|
+
}
|
|
72
|
+
if (schema.const !== undefined)
|
|
73
|
+
return JSON.stringify(schema.const);
|
|
74
|
+
if (schema.enum)
|
|
75
|
+
return union(schema.enum.map((v) => JSON.stringify(v)));
|
|
76
|
+
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
77
|
+
if (types.length === 0) {
|
|
78
|
+
if (schema.properties || schema.patternProperties || schema.additionalProperties !== undefined)
|
|
79
|
+
return objectType(schema, defs, seen);
|
|
80
|
+
if (schema.items)
|
|
81
|
+
return `Array<${toTsType(schema.items, defs, seen)}>`;
|
|
82
|
+
return "unknown";
|
|
83
|
+
}
|
|
84
|
+
return union(types.map((t) => {
|
|
85
|
+
switch (t) {
|
|
86
|
+
case "string":
|
|
87
|
+
return COMPOSE_STRING;
|
|
88
|
+
case "number":
|
|
89
|
+
case "integer":
|
|
90
|
+
return "number";
|
|
91
|
+
case "boolean":
|
|
92
|
+
return "boolean";
|
|
93
|
+
case "null":
|
|
94
|
+
return "null";
|
|
95
|
+
case "array":
|
|
96
|
+
return `Array<${schema.items ? toTsType(schema.items, defs, seen) : "unknown"}>`;
|
|
97
|
+
case "object":
|
|
98
|
+
return objectType(schema, defs, seen);
|
|
99
|
+
default:
|
|
100
|
+
return "unknown";
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
function objectType(schema, defs, seen) {
|
|
105
|
+
const props = Object.entries(schema.properties ?? {});
|
|
106
|
+
const patterns = Object.entries(schema.patternProperties ?? {});
|
|
107
|
+
const extensions = patterns.some(([p]) => p === "^x-");
|
|
108
|
+
const others = patterns.filter(([p]) => p !== "^x-");
|
|
109
|
+
const additional = schema.additionalProperties;
|
|
110
|
+
if (props.length === 0) {
|
|
111
|
+
const values = others.map(([, v]) => toTsType(v, defs, seen));
|
|
112
|
+
if (additional && additional !== true)
|
|
113
|
+
values.push(toTsType(additional, defs, seen));
|
|
114
|
+
if (values.length)
|
|
115
|
+
return `Record<string, ${union(values)}>`;
|
|
116
|
+
return additional === false && !extensions ? "Record<string, never>" : "Record<string, unknown>";
|
|
117
|
+
}
|
|
118
|
+
const required = new Set(schema.required ?? []);
|
|
119
|
+
const fields = props.map(([name, p]) => `${key(name)}${required.has(name) ? "" : "?"}: ${toTsType(p, defs, seen)}`);
|
|
120
|
+
if (extensions)
|
|
121
|
+
fields.push("[key: `x-${string}`]: unknown");
|
|
122
|
+
// Free-form keys beside the named ones: the named ones keep their types.
|
|
123
|
+
if (others.length || (additional && additional !== true))
|
|
124
|
+
fields.push("[key: string]: unknown");
|
|
125
|
+
return `{ ${fields.join("; ")} }`;
|
|
126
|
+
}
|
|
127
|
+
function paren(t) {
|
|
128
|
+
return /[|&]/.test(t) && !/^[{(]/.test(t) ? `(${t})` : t;
|
|
129
|
+
}
|
|
130
|
+
function union(types) {
|
|
131
|
+
const unique = [...new Set(types.flatMap(splitTop))];
|
|
132
|
+
if (unique.includes("unknown"))
|
|
133
|
+
return "unknown";
|
|
134
|
+
return unique.join(" | ");
|
|
135
|
+
}
|
|
136
|
+
/** The members of a union type, split only at the top level. */
|
|
137
|
+
function splitTop(type) {
|
|
138
|
+
const out = [];
|
|
139
|
+
let depth = 0;
|
|
140
|
+
let quote = false;
|
|
141
|
+
let start = 0;
|
|
142
|
+
for (let i = 0; i < type.length; i++) {
|
|
143
|
+
const c = type[i];
|
|
144
|
+
if (c === '"')
|
|
145
|
+
quote = !quote;
|
|
146
|
+
if (quote)
|
|
147
|
+
continue;
|
|
148
|
+
if (c === "{" || c === "(" || c === "<" || c === "[")
|
|
149
|
+
depth++;
|
|
150
|
+
else if (c === "}" || c === ")" || c === ">" || c === "]")
|
|
151
|
+
depth--;
|
|
152
|
+
else if (depth === 0 && type.startsWith(" | ", i)) {
|
|
153
|
+
out.push(type.slice(start, i));
|
|
154
|
+
start = i + 3;
|
|
155
|
+
i += 2;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
out.push(type.slice(start));
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the Docker Engine API OpenAPI spec for Dockerfile-related types.
|
|
3
|
+
*
|
|
4
|
+
* We extract only the image-builder relevant definitions:
|
|
5
|
+
* ContainerConfig, BuildInfo, and related types.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse the Docker Engine API YAML buffer for Dockerfile instruction types.
|
|
9
|
+
* Returns a single DockerfileParseResult describing the Dockerfile entity.
|
|
10
|
+
*/
|
|
11
|
+
export function parseEngineApi(_data) {
|
|
12
|
+
return {
|
|
13
|
+
typeName: "Docker::Dockerfile",
|
|
14
|
+
shortName: "Dockerfile",
|
|
15
|
+
description: "A Dockerfile definition with ordered build instructions",
|
|
16
|
+
instructions: [
|
|
17
|
+
{ name: "from", description: "Base image (required first instruction)" },
|
|
18
|
+
{ name: "arg", description: "Build-time argument", multi: true },
|
|
19
|
+
{ name: "env", description: "Environment variable", multi: true },
|
|
20
|
+
{ name: "run", description: "Run a command during build", multi: true, execForm: true },
|
|
21
|
+
{ name: "copy", description: "Copy files from build context", multi: true },
|
|
22
|
+
{ name: "add", description: "Add files (supports URLs and archives)", multi: true },
|
|
23
|
+
{ name: "workdir", description: "Set working directory" },
|
|
24
|
+
{ name: "user", description: "Set user for subsequent instructions" },
|
|
25
|
+
{ name: "expose", description: "Document exposed ports", multi: true, numeric: true },
|
|
26
|
+
{ name: "volume", description: "Create mount points", multi: true },
|
|
27
|
+
{ name: "label", description: "Add metadata labels", multi: true },
|
|
28
|
+
{ name: "entrypoint", description: "Set the entrypoint executable", execForm: true },
|
|
29
|
+
{ name: "cmd", description: "Default command arguments", execForm: true },
|
|
30
|
+
{ name: "healthcheck", description: "Health check instruction" },
|
|
31
|
+
{ name: "shell", description: "Set the shell for shell-form commands (JSON array form)", stepOnly: true, execForm: true },
|
|
32
|
+
{ name: "stopsignal", description: "Signal sent to stop the container", stepOnly: true },
|
|
33
|
+
{ name: "onbuild", description: "Instruction to run when the image is used as a base", stepOnly: true },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -194,6 +194,8 @@ export interface ServiceProps {
|
|
|
194
194
|
volumes_from?: Array<ComposeString>;
|
|
195
195
|
/** The working directory in which the entrypoint or command will be run */
|
|
196
196
|
working_dir?: ComposeString;
|
|
197
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
198
|
+
[extension: `x-${string}`]: unknown;
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
/** A named volume definition in Docker Compose */
|
|
@@ -208,6 +210,8 @@ export interface VolumeProps {
|
|
|
208
210
|
external?: boolean | ComposeString | { name?: ComposeString; [key: `x-${string}`]: unknown };
|
|
209
211
|
/** Add metadata to the volume using labels. */
|
|
210
212
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
213
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
214
|
+
[extension: `x-${string}`]: unknown;
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
/** A network definition in Docker Compose */
|
|
@@ -232,6 +236,8 @@ export interface NetworkProps {
|
|
|
232
236
|
attachable?: boolean | ComposeString;
|
|
233
237
|
/** Add metadata to the network using labels. */
|
|
234
238
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
239
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
240
|
+
[extension: `x-${string}`]: unknown;
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
/** A config definition in Docker Compose */
|
|
@@ -250,6 +256,8 @@ export interface DockerConfigProps {
|
|
|
250
256
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
251
257
|
/** Driver to use for templating the config's value. */
|
|
252
258
|
template_driver?: ComposeString;
|
|
259
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
260
|
+
[extension: `x-${string}`]: unknown;
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
/** A secret definition in Docker Compose */
|
|
@@ -270,6 +278,8 @@ export interface DockerSecretProps {
|
|
|
270
278
|
driver_opts?: Record<string, ComposeString | number>;
|
|
271
279
|
/** Driver to use for templating the secret's value. */
|
|
272
280
|
template_driver?: ComposeString;
|
|
281
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
282
|
+
[extension: `x-${string}`]: unknown;
|
|
273
283
|
}
|
|
274
284
|
|
|
275
285
|
export declare const Service: new (props?: ServiceProps, attributes?: Record<string, unknown>) => Declarable;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Thin entry point for `npm run validate` in lexicon-docker.
|
|
4
|
+
*/
|
|
5
|
+
import { validate } from "./validate.js";
|
|
6
|
+
const result = await validate();
|
|
7
|
+
for (const check of result.checks) {
|
|
8
|
+
const status = check.ok ? "OK" : "FAIL";
|
|
9
|
+
const msg = check.error ? ` — ${check.error}` : "";
|
|
10
|
+
console.error(` [${status}] ${check.name}${msg}`);
|
|
11
|
+
}
|
|
12
|
+
if (!result.success) {
|
|
13
|
+
console.error("Validation failed");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
console.error("All validation checks passed.");
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate generated lexicon-docker artifacts.
|
|
3
|
+
*/
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { validateLexiconArtifacts } from "@intentius/chant/codegen/validate";
|
|
8
|
+
const REQUIRED_NAMES = [
|
|
9
|
+
"Service",
|
|
10
|
+
"Volume",
|
|
11
|
+
"Network",
|
|
12
|
+
"DockerConfig",
|
|
13
|
+
"DockerSecret",
|
|
14
|
+
"Dockerfile",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Validate the generated lexicon-docker artifacts.
|
|
18
|
+
*/
|
|
19
|
+
export async function validate(opts) {
|
|
20
|
+
const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
21
|
+
const generatedDir = join(basePath, "src", "generated");
|
|
22
|
+
if (!existsSync(generatedDir)) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
checks: [{
|
|
26
|
+
name: "generated-dir",
|
|
27
|
+
ok: false,
|
|
28
|
+
error: 'src/generated/ not found — run "chant dev generate" first',
|
|
29
|
+
}],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return validateLexiconArtifacts({
|
|
33
|
+
lexiconJsonFilename: "lexicon-docker.json",
|
|
34
|
+
requiredNames: REQUIRED_NAMES,
|
|
35
|
+
basePath,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Well-known Docker and Docker Compose environment variable names.
|
|
3
|
+
*
|
|
4
|
+
* Use with the env() intrinsic for type-safe interpolation.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { env, DOCKER_VARS } from "@intentius/chant-lexicon-docker";
|
|
8
|
+
*
|
|
9
|
+
* export const api = new Service({
|
|
10
|
+
* environment: {
|
|
11
|
+
* BUILDKIT_ENABLED: env(DOCKER_VARS.DOCKER_BUILDKIT),
|
|
12
|
+
* },
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
/** Docker daemon and client environment variables */
|
|
16
|
+
export const DOCKER_VARS = {
|
|
17
|
+
/** Enable Docker BuildKit: DOCKER_BUILDKIT=1 */
|
|
18
|
+
DOCKER_BUILDKIT: "DOCKER_BUILDKIT",
|
|
19
|
+
/** Docker host socket/address */
|
|
20
|
+
DOCKER_HOST: "DOCKER_HOST",
|
|
21
|
+
/** Docker TLS verify */
|
|
22
|
+
DOCKER_TLS_VERIFY: "DOCKER_TLS_VERIFY",
|
|
23
|
+
/** Docker config directory */
|
|
24
|
+
DOCKER_CONFIG: "DOCKER_CONFIG",
|
|
25
|
+
/** Docker content trust */
|
|
26
|
+
DOCKER_CONTENT_TRUST: "DOCKER_CONTENT_TRUST",
|
|
27
|
+
};
|
|
28
|
+
/** Docker Compose specific environment variables */
|
|
29
|
+
export const COMPOSE_VARS = {
|
|
30
|
+
/** Override project name: COMPOSE_PROJECT_NAME */
|
|
31
|
+
COMPOSE_PROJECT_NAME: "COMPOSE_PROJECT_NAME",
|
|
32
|
+
/** Override compose file: COMPOSE_FILE */
|
|
33
|
+
COMPOSE_FILE: "COMPOSE_FILE",
|
|
34
|
+
/** Path separator for COMPOSE_FILE */
|
|
35
|
+
COMPOSE_PATH_SEPARATOR: "COMPOSE_PATH_SEPARATOR",
|
|
36
|
+
/** Docker compose profiles to enable */
|
|
37
|
+
COMPOSE_PROFILES: "COMPOSE_PROFILES",
|
|
38
|
+
/** Compose conversion flag */
|
|
39
|
+
COMPOSE_CONVERT_WINDOWS_PATHS: "COMPOSE_CONVERT_WINDOWS_PATHS",
|
|
40
|
+
/** Docker host for compose */
|
|
41
|
+
DOCKER_DEFAULT_PLATFORM: "DOCKER_DEFAULT_PLATFORM",
|
|
42
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-docker",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.32",
|
|
4
4
|
"description": "Docker lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -33,22 +33,22 @@
|
|
|
33
33
|
".": {
|
|
34
34
|
"development": "./src/index.ts",
|
|
35
35
|
"types": "./dist/index.d.ts",
|
|
36
|
-
"default": "./
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
37
|
},
|
|
38
38
|
"./*": {
|
|
39
39
|
"development": "./src/*.ts",
|
|
40
40
|
"types": "./dist/*.d.ts",
|
|
41
|
-
"default": "./
|
|
41
|
+
"default": "./dist/*.js"
|
|
42
42
|
},
|
|
43
43
|
"./detect": {
|
|
44
44
|
"development": "./src/detect.ts",
|
|
45
45
|
"types": "./dist/detect.d.ts",
|
|
46
|
-
"default": "./
|
|
46
|
+
"default": "./dist/detect.js"
|
|
47
47
|
},
|
|
48
48
|
"./lint/post-synth": {
|
|
49
49
|
"development": "./src/lint/post-synth/index.ts",
|
|
50
50
|
"types": "./dist/lint/post-synth/index.d.ts",
|
|
51
|
-
"default": "./
|
|
51
|
+
"default": "./dist/lint/post-synth/index.js"
|
|
52
52
|
},
|
|
53
53
|
"./manifest": "./dist/manifest.json",
|
|
54
54
|
"./meta": "./dist/meta.json",
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"validate": "tsx src/validate-cli.ts",
|
|
61
61
|
"docs": "tsx src/codegen/docs-cli.ts",
|
|
62
62
|
"prepack": "npm run generate && npm run bundle && npm run validate && npm run build",
|
|
63
|
-
"build": "../../scripts/tsc7.sh -p tsconfig.build.json && tsc-alias -p tsconfig.build.json
|
|
63
|
+
"build": "../../scripts/tsc7.sh -p tsconfig.build.json --sourceMap false && tsc-alias -p tsconfig.build.json"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@intentius/chant-lexicon-otel": "^0.124.
|
|
66
|
+
"@intentius/chant-lexicon-otel": "^0.124.32"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@intentius/chant": "*",
|
|
70
70
|
"typescript": "^5.9.3"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@intentius/chant": "^0.124.
|
|
73
|
+
"@intentius/chant": "^0.124.32"
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -67,6 +67,16 @@ describe("generate()", () => {
|
|
|
67
67
|
expect(result.typesDTS).toContain("declare const Service");
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
test("every Compose entity's props accept x- extension fields, which import keeps (#4136)", async () => {
|
|
71
|
+
const result = await generate({ verbose: false });
|
|
72
|
+
for (const out of [result.typesDTS, result.indexTS]) {
|
|
73
|
+
for (const name of ["ServiceProps", "VolumeProps", "NetworkProps", "DockerConfigProps", "DockerSecretProps"]) {
|
|
74
|
+
const body = out.slice(out.indexOf(`export interface ${name} {`));
|
|
75
|
+
expect(body.slice(0, body.indexOf("\n}")), name).toContain("[extension: `x-${string}`]: unknown;");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
70
80
|
test("properties count is non-zero", async () => {
|
|
71
81
|
const result = await generate({ verbose: false });
|
|
72
82
|
expect(result.properties).toBeGreaterThan(0);
|
package/src/codegen/generate.ts
CHANGED
|
@@ -170,6 +170,9 @@ function composeTypeLines(compose: ComposeParseResult[]): string[] {
|
|
|
170
170
|
if (p.description) lines.push(` /** ${p.description} */`);
|
|
171
171
|
lines.push(` ${p.name}${opt}: ${p.type};`);
|
|
172
172
|
}
|
|
173
|
+
// The Compose spec allows `x-` extension fields on every entity, and import keeps them (#4136).
|
|
174
|
+
lines.push(` /** A Compose extension field. The spec allows any \`x-\` key, and Compose itself ignores it. */`);
|
|
175
|
+
lines.push(" [extension: `x-${string}`]: unknown;");
|
|
173
176
|
lines.push(`}`);
|
|
174
177
|
lines.push("");
|
|
175
178
|
}
|
package/src/codegen/package.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { readFileSync } from "fs";
|
|
6
6
|
import { dockerPlugin } from "../plugin";
|
|
7
7
|
import { join, dirname } from "path";
|
|
8
|
-
import {
|
|
8
|
+
import { moduleSourceDir } from "@intentius/chant/module-source";
|
|
9
9
|
import type { IntrinsicDef } from "@intentius/chant/lexicon";
|
|
10
10
|
import {
|
|
11
11
|
packagePipeline,
|
|
@@ -17,7 +17,7 @@ import { generate } from "./generate";
|
|
|
17
17
|
|
|
18
18
|
export type { PackageOptions, PackageResult };
|
|
19
19
|
|
|
20
|
-
const pkgDir = dirname(
|
|
20
|
+
const pkgDir = dirname(moduleSourceDir(import.meta.url));
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Package the Docker lexicon into a distributable BundleSpec.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The key an entity takes in docker-compose.yml.
|
|
3
|
+
*
|
|
4
|
+
* A compose entity's key is its export name by default. A key that is not a
|
|
5
|
+
* TypeScript identifier (`db-password`, `nginx.conf`) cannot be an export
|
|
6
|
+
* name, so `composeKey` sets it explicitly (#3937):
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* export const dbPassword = composeKey("db-password", new DockerSecret({ file: "./db-password.txt" }));
|
|
10
|
+
*
|
|
11
|
+
* References to the entity (`secrets: [dbPassword]`) serialize as the key.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
15
|
+
|
|
16
|
+
export const COMPOSE_KEY = Symbol.for("docker.composeKey");
|
|
17
|
+
|
|
18
|
+
/** Give a compose entity an explicit key in docker-compose.yml. Returns the entity. */
|
|
19
|
+
export function composeKey<T extends Declarable>(key: string, entity: T): T {
|
|
20
|
+
if (typeof key !== "string" || key === "") {
|
|
21
|
+
throw new Error("composeKey: the key must be a non-empty string");
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(entity, COMPOSE_KEY, { value: key, enumerable: false, configurable: true });
|
|
24
|
+
return entity;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The key set by `composeKey`, or undefined. */
|
|
28
|
+
export function getComposeKey(entity: Declarable): string | undefined {
|
|
29
|
+
const key = (entity as unknown as Record<symbol, unknown>)[COMPOSE_KEY];
|
|
30
|
+
return typeof key === "string" ? key : undefined;
|
|
31
|
+
}
|
package/src/generated/index.d.ts
CHANGED
|
@@ -194,6 +194,8 @@ export interface ServiceProps {
|
|
|
194
194
|
volumes_from?: Array<ComposeString>;
|
|
195
195
|
/** The working directory in which the entrypoint or command will be run */
|
|
196
196
|
working_dir?: ComposeString;
|
|
197
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
198
|
+
[extension: `x-${string}`]: unknown;
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
/** A named volume definition in Docker Compose */
|
|
@@ -208,6 +210,8 @@ export interface VolumeProps {
|
|
|
208
210
|
external?: boolean | ComposeString | { name?: ComposeString; [key: `x-${string}`]: unknown };
|
|
209
211
|
/** Add metadata to the volume using labels. */
|
|
210
212
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
213
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
214
|
+
[extension: `x-${string}`]: unknown;
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
/** A network definition in Docker Compose */
|
|
@@ -232,6 +236,8 @@ export interface NetworkProps {
|
|
|
232
236
|
attachable?: boolean | ComposeString;
|
|
233
237
|
/** Add metadata to the network using labels. */
|
|
234
238
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
239
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
240
|
+
[extension: `x-${string}`]: unknown;
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
/** A config definition in Docker Compose */
|
|
@@ -250,6 +256,8 @@ export interface DockerConfigProps {
|
|
|
250
256
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
251
257
|
/** Driver to use for templating the config's value. */
|
|
252
258
|
template_driver?: ComposeString;
|
|
259
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
260
|
+
[extension: `x-${string}`]: unknown;
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
/** A secret definition in Docker Compose */
|
|
@@ -270,6 +278,8 @@ export interface DockerSecretProps {
|
|
|
270
278
|
driver_opts?: Record<string, ComposeString | number>;
|
|
271
279
|
/** Driver to use for templating the secret's value. */
|
|
272
280
|
template_driver?: ComposeString;
|
|
281
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
282
|
+
[extension: `x-${string}`]: unknown;
|
|
273
283
|
}
|
|
274
284
|
|
|
275
285
|
export declare const Service: new (props?: ServiceProps, attributes?: Record<string, unknown>) => Declarable;
|
package/src/generated/index.ts
CHANGED
|
@@ -196,6 +196,8 @@ export interface ServiceProps {
|
|
|
196
196
|
volumes_from?: Array<ComposeString>;
|
|
197
197
|
/** The working directory in which the entrypoint or command will be run */
|
|
198
198
|
working_dir?: ComposeString;
|
|
199
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
200
|
+
[extension: `x-${string}`]: unknown;
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
/** A named volume definition in Docker Compose */
|
|
@@ -210,6 +212,8 @@ export interface VolumeProps {
|
|
|
210
212
|
external?: boolean | ComposeString | { name?: ComposeString; [key: `x-${string}`]: unknown };
|
|
211
213
|
/** Add metadata to the volume using labels. */
|
|
212
214
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
215
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
216
|
+
[extension: `x-${string}`]: unknown;
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
/** A network definition in Docker Compose */
|
|
@@ -234,6 +238,8 @@ export interface NetworkProps {
|
|
|
234
238
|
attachable?: boolean | ComposeString;
|
|
235
239
|
/** Add metadata to the network using labels. */
|
|
236
240
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
241
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
242
|
+
[extension: `x-${string}`]: unknown;
|
|
237
243
|
}
|
|
238
244
|
|
|
239
245
|
/** A config definition in Docker Compose */
|
|
@@ -252,6 +258,8 @@ export interface DockerConfigProps {
|
|
|
252
258
|
labels?: Record<string, ComposeString | number | boolean | null> | Array<ComposeString>;
|
|
253
259
|
/** Driver to use for templating the config's value. */
|
|
254
260
|
template_driver?: ComposeString;
|
|
261
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
262
|
+
[extension: `x-${string}`]: unknown;
|
|
255
263
|
}
|
|
256
264
|
|
|
257
265
|
/** A secret definition in Docker Compose */
|
|
@@ -272,6 +280,8 @@ export interface DockerSecretProps {
|
|
|
272
280
|
driver_opts?: Record<string, ComposeString | number>;
|
|
273
281
|
/** Driver to use for templating the secret's value. */
|
|
274
282
|
template_driver?: ComposeString;
|
|
283
|
+
/** A Compose extension field. The spec allows any `x-` key, and Compose itself ignores it. */
|
|
284
|
+
[extension: `x-${string}`]: unknown;
|
|
275
285
|
}
|
|
276
286
|
|
|
277
287
|
export const Service = createResource("Docker::Compose::Service", "docker", {}) as unknown as DockerResourceConstructor<ServiceProps>;
|
package/src/import/adapter.ts
CHANGED
|
@@ -72,10 +72,11 @@ export class DockerTemplateParser implements TemplateParser {
|
|
|
72
72
|
if (looksLikeDockerfile(content)) {
|
|
73
73
|
return { resources: [dockerIrToResource(new DockerfileParser().parse("app", content))], parameters: [] };
|
|
74
74
|
}
|
|
75
|
-
const { entities } = new DockerParser().parse(content);
|
|
75
|
+
const { entities, warnings } = new DockerParser().parse(content);
|
|
76
76
|
return {
|
|
77
77
|
resources: entities.map(dockerIrToResource),
|
|
78
78
|
parameters: [],
|
|
79
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
}
|
|
@@ -36,8 +36,9 @@ describe("DockerGenerator", () => {
|
|
|
36
36
|
{ kind: "config", name: "app-config", props: { file: "./config/app.conf" } },
|
|
37
37
|
];
|
|
38
38
|
const { source } = new DockerGenerator().generate(entities);
|
|
39
|
-
expect(source).toContain("import { DockerConfig }");
|
|
40
|
-
|
|
39
|
+
expect(source).toContain("import { DockerConfig, composeKey }");
|
|
40
|
+
// app-config is not an identifier, so the key is set explicitly (#3937).
|
|
41
|
+
expect(source).toContain('export const appConfig = composeKey("app-config", new DockerConfig(');
|
|
41
42
|
expect(source).toContain("app.conf");
|
|
42
43
|
});
|
|
43
44
|
|
|
@@ -46,8 +47,8 @@ describe("DockerGenerator", () => {
|
|
|
46
47
|
{ kind: "secret", name: "db-password", props: { file: "./secrets/db.txt" } },
|
|
47
48
|
];
|
|
48
49
|
const { source } = new DockerGenerator().generate(entities);
|
|
49
|
-
expect(source).toContain("import { DockerSecret }");
|
|
50
|
-
expect(source).toContain("new DockerSecret(
|
|
50
|
+
expect(source).toContain("import { DockerSecret, composeKey }");
|
|
51
|
+
expect(source).toContain('export const dbPassword = composeKey("db-password", new DockerSecret(');
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
test("single-stage Dockerfile generates flat props (existing behaviour)", () => {
|