@intentius/chant-lexicon-docker 0.124.31 → 0.124.33
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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker lexicon docs generation.
|
|
3
|
+
*
|
|
4
|
+
* Uses the shared docsPipeline/writeDocsSite flow like every other lexicon
|
|
5
|
+
* (chant #1731, #1755). Hand-written prose lives under docs/pages/, each
|
|
6
|
+
* page tagged with a Diátaxis quadrant; index.mdx and serialization.mdx are
|
|
7
|
+
* generated from the overview/outputFormat strings below, and rules.mdx is
|
|
8
|
+
* generated from the rule sources under src/lint/ the same way it always
|
|
9
|
+
* was — this used to be the one lexicon whose rules table could drift from
|
|
10
|
+
* source without anything catching it (#1312), and the shared pipeline
|
|
11
|
+
* keeps that guarantee.
|
|
12
|
+
*/
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
|
|
16
|
+
const overview = `
|
|
17
|
+
The **Docker** lexicon provides typed constructors for Docker Compose services
|
|
18
|
+
and Dockerfile build instructions — services, volumes, networks, configs, secrets,
|
|
19
|
+
and multi-stage Dockerfiles.
|
|
20
|
+
|
|
21
|
+
\`\`\`bash
|
|
22
|
+
npm install --save-dev @intentius/chant-lexicon-docker
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
\`\`\`typescript
|
|
28
|
+
import { Service, Volume, Dockerfile, env } from "@intentius/chant-lexicon-docker";
|
|
29
|
+
|
|
30
|
+
export const db = new Service({
|
|
31
|
+
image: "postgres:16-alpine",
|
|
32
|
+
environment: {
|
|
33
|
+
POSTGRES_DB: "myapp",
|
|
34
|
+
POSTGRES_PASSWORD: env("DB_PASSWORD", { required: true }),
|
|
35
|
+
},
|
|
36
|
+
volumes: ["pgdata:/var/lib/postgresql/data"],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const pgdata = new Volume({});
|
|
40
|
+
|
|
41
|
+
export const api = new Service({
|
|
42
|
+
image: "myapp:1.0",
|
|
43
|
+
ports: ["8080:8080"],
|
|
44
|
+
depends_on: ["db"],
|
|
45
|
+
});
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
Build:
|
|
49
|
+
|
|
50
|
+
\`\`\`bash
|
|
51
|
+
chant build src --lexicon docker -o docker-compose.yml
|
|
52
|
+
\`\`\`
|
|
53
|
+
|
|
54
|
+
## Output Domains
|
|
55
|
+
|
|
56
|
+
| Entity | Output file | Section |
|
|
57
|
+
|--------|-------------|---------|
|
|
58
|
+
| \`Service\` | \`docker-compose.yml\` | \`services:\` |
|
|
59
|
+
| \`Volume\` | \`docker-compose.yml\` | \`volumes:\` |
|
|
60
|
+
| \`Network\` | \`docker-compose.yml\` | \`networks:\` |
|
|
61
|
+
| \`Config\` | \`docker-compose.yml\` | \`configs:\` |
|
|
62
|
+
| \`Secret\` | \`docker-compose.yml\` | \`secrets:\` |
|
|
63
|
+
| \`Dockerfile\` | \`Dockerfile.{name}\` | — |`;
|
|
64
|
+
const outputFormat = `
|
|
65
|
+
The Docker lexicon serializes to two output formats.
|
|
66
|
+
|
|
67
|
+
## docker-compose.yml
|
|
68
|
+
|
|
69
|
+
\`\`\`bash
|
|
70
|
+
chant build src --lexicon docker -o docker-compose.yml
|
|
71
|
+
\`\`\`
|
|
72
|
+
|
|
73
|
+
Each entity type maps to a top-level Compose section. The TypeScript export name becomes the key within that section.
|
|
74
|
+
|
|
75
|
+
\`\`\`typescript
|
|
76
|
+
export const db = new Service({ image: "postgres:16-alpine" });
|
|
77
|
+
export const pgdata = new Volume({});
|
|
78
|
+
export const backend = new Network({ driver: "bridge" });
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
Produces:
|
|
82
|
+
|
|
83
|
+
\`\`\`yaml
|
|
84
|
+
services:
|
|
85
|
+
db:
|
|
86
|
+
image: postgres:16-alpine
|
|
87
|
+
|
|
88
|
+
volumes:
|
|
89
|
+
pgdata: null
|
|
90
|
+
|
|
91
|
+
networks:
|
|
92
|
+
backend:
|
|
93
|
+
driver: bridge
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
\`defaultLabels()\` entities are not emitted as separate keys — their labels are merged into each service.
|
|
97
|
+
|
|
98
|
+
## Dockerfile.\\{name\\}
|
|
99
|
+
|
|
100
|
+
\`\`\`bash
|
|
101
|
+
chant build src --lexicon docker
|
|
102
|
+
# Writes Dockerfile.app, Dockerfile.builder, etc.
|
|
103
|
+
\`\`\`
|
|
104
|
+
|
|
105
|
+
Each \`Dockerfile\` entity produces a separate file. The export name is the suffix.
|
|
106
|
+
|
|
107
|
+
\`\`\`typescript
|
|
108
|
+
export const builder = new Dockerfile({ from: "node:20-alpine", ... });
|
|
109
|
+
// → Dockerfile.builder
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
## Field mapping
|
|
113
|
+
|
|
114
|
+
| TypeScript | docker-compose.yml | Notes |
|
|
115
|
+
|-----------|-------------------|-------|
|
|
116
|
+
| \`env("X", { required: true })\` | \`\${X:?X is required}\` | Compose interpolation |
|
|
117
|
+
| \`env("X", { default: "v" })\` | \`\${X:-v}\` | Compose interpolation |
|
|
118
|
+
| \`env("X")\` | \`\${X}\` | Compose interpolation |
|
|
119
|
+
| \`external: true\` | \`external: true\` | Volume/Network |
|
|
120
|
+
|
|
121
|
+
## Specifying lexicon at build time
|
|
122
|
+
|
|
123
|
+
The \`--lexicon docker\` flag restricts output to Docker entities only — useful in mixed-lexicon projects:
|
|
124
|
+
|
|
125
|
+
\`\`\`bash
|
|
126
|
+
# Only Docker output
|
|
127
|
+
chant build src --lexicon docker -o docker-compose.yml
|
|
128
|
+
|
|
129
|
+
# Other lexicons still get their own outputs
|
|
130
|
+
chant build src --lexicon aws -o template.json
|
|
131
|
+
\`\`\``;
|
|
132
|
+
// "Optional: hand this page to your coding agent", shown under the overview's
|
|
133
|
+
// title. The Dockerfile shape is the ordered \`steps\` list from chant #3926.
|
|
134
|
+
const prompt = `Read https://intentius.io/chant/lexicons/docker/ and https://intentius.io/chant/lexicons/docker/dockerfiles/ first.
|
|
135
|
+
Bring this project's Docker files into chant TypeScript under src/.
|
|
136
|
+
Import each existing file. \`-o\` names a directory, and each import writes main.ts in it: \`chant import --lexicon docker docker-compose.yml -o src/compose/\`, and \`chant import --lexicon docker Dockerfile -o src/<name>/\` for each Dockerfile.
|
|
137
|
+
An imported Dockerfile is exported as \`app\`. Rename each export after its image, since the export name sets the output file (Dockerfile.<export>), and point the Compose service's \`build.dockerfile\` at that export (\`dockerfile: webImage\`) instead of the old file name.
|
|
138
|
+
Write every Dockerfile stage as an ordered \`steps\` list, one instruction per step, with FROM as the stage's \`from\` and ARGs before the first FROM in \`globalArgs\`:
|
|
139
|
+
steps: [{ workdir: "/app" }, { copy: "package*.json ./" }, { run: "npm ci" }, { copy: ". ." }, { user: "node" }, { cmd: ["node", "dist/index.js"] }]
|
|
140
|
+
Where two Dockerfiles repeat a stage or a run of steps, export it once as a const (a \`DockerfileStage\` or a \`DockerfileStep[]\`) from its own module and import it in both. Spread imported consts, not function results; a Dockerfile that needs parameters is a \`Composite\`.
|
|
141
|
+
Run \`chant build src --lexicon docker -o build/docker-compose.yml\` (Dockerfiles are written beside it) and compare with the original files, then \`chant lint src\`. Run \`chant lint src --fix\` for DKRS002 (per-kind fields to \`steps\`, then reorder the steps it writes), fix every other finding by hand, and repeat until lint is clean.
|
|
142
|
+
Report the generated docker-compose.yml and each Dockerfile.<name>, and any finding you left and why.
|
|
143
|
+
Do not push an image, run \`docker compose up\` against anything but the local Docker daemon, or change files outside this project.
|
|
144
|
+
Never deploy to a real cloud account or shared environment (with \`chant run\`, \`chant components\` or a platform's own CLI), approve (a pull request review, \`chant approve\` or \`chant run approve\`), override a policy denial (\`chant approve policy-override\`), or merge; never cut or publish a release (\`chant release cut --yes\` or \`chant release publish\`), push or delete a \`ci/*\` tag (\`chant ci tick\` pushes them), or change the value of an existing flag; never touch \`.chant/allowed_signers\` or \`chant/lifecycle\`.`;
|
|
145
|
+
export async function generateDocs(opts) {
|
|
146
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
147
|
+
const config = {
|
|
148
|
+
name: "docker",
|
|
149
|
+
displayName: "Docker",
|
|
150
|
+
description: "Typed constructors for Docker Compose and Dockerfile configuration",
|
|
151
|
+
distDir: join(pkgDir, "dist"),
|
|
152
|
+
outDir: join(pkgDir, "docs"),
|
|
153
|
+
basePath: "/chant/lexicons/docker/",
|
|
154
|
+
overview,
|
|
155
|
+
outputFormat,
|
|
156
|
+
prompt,
|
|
157
|
+
};
|
|
158
|
+
const result = docsPipeline(config);
|
|
159
|
+
writeDocsSite(config, result);
|
|
160
|
+
if (opts?.verbose) {
|
|
161
|
+
console.error("Documentation generated");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for Docker lexicon generation.
|
|
4
|
+
*/
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { generate, writeGeneratedFiles } from "./generate.js";
|
|
8
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
9
|
+
async function main() {
|
|
10
|
+
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
11
|
+
const force = process.argv.includes("--force") || process.argv.includes("-f");
|
|
12
|
+
console.error("Generating Docker lexicon...");
|
|
13
|
+
const result = await generate({ verbose, force });
|
|
14
|
+
writeGeneratedFiles(result, pkgDir);
|
|
15
|
+
console.error(`Generated ${result.resources} entities, ${result.properties} property types, ${result.enums} enums`);
|
|
16
|
+
if (result.warnings.length > 0) {
|
|
17
|
+
console.error(`${result.warnings.length} warnings:`);
|
|
18
|
+
for (const w of result.warnings) {
|
|
19
|
+
console.error(` ${w.file}: ${w.error}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
main().catch((err) => {
|
|
24
|
+
console.error("Generation failed:", err);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Compose entity types from the Compose Spec JSON Schema.
|
|
3
|
+
*/
|
|
4
|
+
import { fetchComposeSpec } from "../spec/fetch-compose.js";
|
|
5
|
+
import { parseComposeSpec } from "../spec/parse-compose.js";
|
|
6
|
+
export async function generateComposePipeline(opts = {}) {
|
|
7
|
+
if (opts.verbose)
|
|
8
|
+
console.error("Fetching Compose Spec...");
|
|
9
|
+
const data = await fetchComposeSpec(opts.force);
|
|
10
|
+
if (opts.verbose)
|
|
11
|
+
console.error("Parsing Compose Spec...");
|
|
12
|
+
const results = parseComposeSpec(data);
|
|
13
|
+
if (opts.verbose)
|
|
14
|
+
console.error(`Parsed ${results.length} Compose entity types`);
|
|
15
|
+
return { results };
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Dockerfile entity types from the Docker Engine API OpenAPI spec.
|
|
3
|
+
*/
|
|
4
|
+
import { fetchEngineApi } from "../spec/fetch-engine.js";
|
|
5
|
+
import { parseEngineApi } from "../spec/parse-engine.js";
|
|
6
|
+
export async function generateDockerfilePipeline(opts = {}) {
|
|
7
|
+
if (opts.verbose)
|
|
8
|
+
console.error("Fetching Docker Engine API...");
|
|
9
|
+
const data = await fetchEngineApi(opts.force);
|
|
10
|
+
if (opts.verbose)
|
|
11
|
+
console.error("Parsing Engine API for Dockerfile types...");
|
|
12
|
+
const result = parseEngineApi(data);
|
|
13
|
+
if (opts.verbose)
|
|
14
|
+
console.error(`Parsed ${result.instructions.length} Dockerfile instructions`);
|
|
15
|
+
return { result };
|
|
16
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/codegen/generate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAwB/G;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/codegen/generate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAwB/G;AA8LD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAWvF"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker lexicon generation pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates both Compose and Dockerfile type generation,
|
|
5
|
+
* then writes merged artifacts to src/generated/.
|
|
6
|
+
*/
|
|
7
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { generateComposePipeline } from "./generate-compose.js";
|
|
10
|
+
import { generateDockerfilePipeline } from "./generate-dockerfile.js";
|
|
11
|
+
import { entityTypeToTsName } from "./naming.js";
|
|
12
|
+
import { PER_KIND_ORDER } from "../dockerfile-order.js";
|
|
13
|
+
import { COMPOSE_STRING } from "../spec/parse-compose.js";
|
|
14
|
+
/**
|
|
15
|
+
* Run the full Docker generation pipeline.
|
|
16
|
+
*/
|
|
17
|
+
export async function generate(opts = {}) {
|
|
18
|
+
const [composeResult, dockerfileResult] = await Promise.all([
|
|
19
|
+
generateComposePipeline(opts),
|
|
20
|
+
generateDockerfilePipeline(opts),
|
|
21
|
+
]);
|
|
22
|
+
const allCompose = composeResult.results;
|
|
23
|
+
const dockerfile = dockerfileResult.result;
|
|
24
|
+
const lexiconJSON = generateLexiconJSON(allCompose, dockerfile);
|
|
25
|
+
const typesDTS = generateTypesDTS(allCompose, dockerfile);
|
|
26
|
+
const indexTS = generateIndexTS(allCompose, dockerfile);
|
|
27
|
+
const propertyCount = allCompose.reduce((n, c) => n + c.properties.length, 0) + dockerfile.instructions.length;
|
|
28
|
+
return {
|
|
29
|
+
resources: allCompose.length + 1, // +1 for Dockerfile
|
|
30
|
+
properties: propertyCount,
|
|
31
|
+
enums: 0,
|
|
32
|
+
warnings: [],
|
|
33
|
+
lexiconJSON,
|
|
34
|
+
typesDTS,
|
|
35
|
+
indexTS,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function generateLexiconJSON(compose, dockerfile) {
|
|
39
|
+
const entries = {};
|
|
40
|
+
for (const c of compose) {
|
|
41
|
+
const tsName = entityTypeToTsName(c.typeName);
|
|
42
|
+
entries[tsName] = {
|
|
43
|
+
resourceType: c.typeName,
|
|
44
|
+
kind: "resource",
|
|
45
|
+
description: c.description ?? "",
|
|
46
|
+
properties: Object.fromEntries(c.properties.map((p) => [p.name, { type: p.type, description: p.description ?? "" }])),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const dfName = entityTypeToTsName(dockerfile.typeName);
|
|
50
|
+
entries[dfName] = {
|
|
51
|
+
resourceType: dockerfile.typeName,
|
|
52
|
+
kind: "resource",
|
|
53
|
+
description: dockerfile.description ?? "",
|
|
54
|
+
properties: {
|
|
55
|
+
...Object.fromEntries(dockerfile.instructions.filter((i) => !i.stepOnly).map((i) => [i.name, { description: i.description ?? "" }])),
|
|
56
|
+
globalArgs: { description: "ARG instructions written before the first FROM" },
|
|
57
|
+
steps: { description: "Instructions after FROM, written in the order given; one instruction per step" },
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return JSON.stringify(entries, null, 2);
|
|
61
|
+
}
|
|
62
|
+
function generateTypesDTS(compose, dockerfile) {
|
|
63
|
+
const lines = [
|
|
64
|
+
"// Code generated by chant generate. DO NOT EDIT.",
|
|
65
|
+
'import type { Declarable } from "@intentius/chant/declarable";',
|
|
66
|
+
"",
|
|
67
|
+
// This file is type-checked on its own (validate's types-compile), so it
|
|
68
|
+
// can't import the interpolation module; an env() value is matched by shape.
|
|
69
|
+
"/** An `env()` interpolation, as returned by the lexicon's `env`. */",
|
|
70
|
+
"interface EnvIntrinsic { toJSON(): string; toString(): string; }",
|
|
71
|
+
"",
|
|
72
|
+
];
|
|
73
|
+
lines.push(...composeTypeLines(compose));
|
|
74
|
+
for (const c of compose) {
|
|
75
|
+
const tsName = entityTypeToTsName(c.typeName);
|
|
76
|
+
lines.push(`export declare const ${tsName}: new (props?: ${tsName}Props, attributes?: Record<string, unknown>) => Declarable;`);
|
|
77
|
+
}
|
|
78
|
+
lines.push("");
|
|
79
|
+
const dfName = entityTypeToTsName(dockerfile.typeName);
|
|
80
|
+
lines.push(...dockerfileTypeLines(dockerfile));
|
|
81
|
+
lines.push(`export declare const ${dfName}: new (props?: DockerfileProps, attributes?: Record<string, unknown>) => Declarable;`);
|
|
82
|
+
lines.push("");
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
function generateIndexTS(compose, dockerfile) {
|
|
86
|
+
const lines = [
|
|
87
|
+
"// Code generated by chant generate. DO NOT EDIT.",
|
|
88
|
+
'import { createResource } from "./runtime";',
|
|
89
|
+
'import type { Declarable } from "@intentius/chant/declarable";',
|
|
90
|
+
'import type { EnvIntrinsic } from "../interpolation.js";',
|
|
91
|
+
"",
|
|
92
|
+
// createResource returns a constructor with untyped props. Each constructor
|
|
93
|
+
// below is cast to one that takes its props type, so a misspelled key or a
|
|
94
|
+
// wrong field type is a compile error where the resource is written, and
|
|
95
|
+
// the editor completes the fields (#3934).
|
|
96
|
+
"/** A docker resource constructor whose props are type-checked. */",
|
|
97
|
+
"export type DockerResourceConstructor<P> = new (props?: P, attributes?: Record<string, unknown>) => InstanceType<ReturnType<typeof createResource>>;",
|
|
98
|
+
"",
|
|
99
|
+
];
|
|
100
|
+
lines.push(...composeTypeLines(compose));
|
|
101
|
+
for (const c of compose) {
|
|
102
|
+
const tsName = entityTypeToTsName(c.typeName);
|
|
103
|
+
lines.push(`export const ${tsName} = createResource("${c.typeName}", "docker", {}) as unknown as DockerResourceConstructor<${tsName}Props>;`);
|
|
104
|
+
}
|
|
105
|
+
lines.push("");
|
|
106
|
+
// The Dockerfile prop types are exported from here too (not only from
|
|
107
|
+
// index.d.ts, which index.ts shadows) so a module can type a shared stage
|
|
108
|
+
// or step list: `export const install: DockerfileStep[] = [...]`.
|
|
109
|
+
lines.push(...dockerfileTypeLines(dockerfile));
|
|
110
|
+
lines.push("");
|
|
111
|
+
const dfName = entityTypeToTsName(dockerfile.typeName);
|
|
112
|
+
lines.push(`export const ${dfName} = createResource("${dockerfile.typeName}", "docker", {}) as unknown as DockerResourceConstructor<DockerfileProps>;`);
|
|
113
|
+
lines.push("");
|
|
114
|
+
return lines.join("\n");
|
|
115
|
+
}
|
|
116
|
+
/** One props interface per Compose entity. */
|
|
117
|
+
function composeTypeLines(compose) {
|
|
118
|
+
const lines = [
|
|
119
|
+
"/**",
|
|
120
|
+
" * A Compose string value: a string, an `env()` interpolation, or an entity",
|
|
121
|
+
" * (service, network, volume, config, secret or Dockerfile), which is written as its name.",
|
|
122
|
+
" */",
|
|
123
|
+
`export type ${COMPOSE_STRING} = string | EnvIntrinsic | Declarable;`,
|
|
124
|
+
"",
|
|
125
|
+
];
|
|
126
|
+
for (const c of compose) {
|
|
127
|
+
const tsName = entityTypeToTsName(c.typeName);
|
|
128
|
+
if (c.description)
|
|
129
|
+
lines.push(`/** ${c.description} */`);
|
|
130
|
+
lines.push(`export interface ${tsName}Props {`);
|
|
131
|
+
for (const p of c.properties) {
|
|
132
|
+
const opt = p.required ? "" : "?";
|
|
133
|
+
if (p.description)
|
|
134
|
+
lines.push(` /** ${p.description} */`);
|
|
135
|
+
lines.push(` ${p.name}${opt}: ${p.type};`);
|
|
136
|
+
}
|
|
137
|
+
// The Compose spec allows `x-` extension fields on every entity, and import keeps them (#4136).
|
|
138
|
+
lines.push(` /** A Compose extension field. The spec allows any \`x-\` key, and Compose itself ignores it. */`);
|
|
139
|
+
lines.push(" [extension: `x-${string}`]: unknown;");
|
|
140
|
+
lines.push(`}`);
|
|
141
|
+
lines.push("");
|
|
142
|
+
}
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The Dockerfile prop types: one `DockerfileStep` variant per instruction,
|
|
147
|
+
* the stage, and the props. The per-kind fields keep the instructions they
|
|
148
|
+
* always had; SHELL, STOPSIGNAL and ONBUILD are steps only.
|
|
149
|
+
*/
|
|
150
|
+
function dockerfileTypeLines(dockerfile) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
const stepInstructions = dockerfile.instructions.filter((i) => i.name !== "from");
|
|
153
|
+
const perKind = stepInstructions.filter((i) => !i.stepOnly);
|
|
154
|
+
const stepValue = (i) => ["string", ...(i.numeric ? ["number"] : []), ...(i.execForm ? ["string[]"] : [])].join(" | ");
|
|
155
|
+
const fixed = PER_KIND_ORDER.map((k) => k.toUpperCase()).join(", ");
|
|
156
|
+
lines.push(`/** The Dockerfile instructions a step can name. FROM is the stage's own \`from\` field. */`);
|
|
157
|
+
lines.push(`export type DockerfileInstructionName = ${stepInstructions.map((i) => JSON.stringify(i.name)).join(" | ")};`);
|
|
158
|
+
lines.push(`type DockerfileStepOf<K extends DockerfileInstructionName, V> = { [P in K]: V } & { [P in Exclude<DockerfileInstructionName, K>]?: never };`);
|
|
159
|
+
lines.push(`/**`);
|
|
160
|
+
lines.push(` * One Dockerfile instruction, named by its single key: \`{ run: "npm ci" }\` is \`RUN npm ci\`.`);
|
|
161
|
+
lines.push(` * A string[] value (run, cmd, entrypoint, shell) is written in JSON exec form.`);
|
|
162
|
+
lines.push(` */`);
|
|
163
|
+
lines.push(`export type DockerfileStep =`);
|
|
164
|
+
for (const i of stepInstructions) {
|
|
165
|
+
lines.push(` /** ${i.name.toUpperCase()}: ${i.description ?? ""} */`);
|
|
166
|
+
lines.push(` | DockerfileStepOf<${JSON.stringify(i.name)}, ${stepValue(i)}>`);
|
|
167
|
+
}
|
|
168
|
+
lines[lines.length - 1] += ";";
|
|
169
|
+
const fields = () => {
|
|
170
|
+
lines.push(` /** Instructions after FROM, written exactly in this order. Can't be combined with the per-kind fields. */`);
|
|
171
|
+
lines.push(` steps?: DockerfileStep[];`);
|
|
172
|
+
for (const i of perKind) {
|
|
173
|
+
lines.push(` /** ${i.name.toUpperCase()} as a per-kind field, written in the fixed order. Use \`steps\` to choose the order. */`);
|
|
174
|
+
lines.push(` ${i.name}?: ${i.multi ? "string[]" : "string"};`);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const fixedDoc = ` * Without \`steps\`, the per-kind fields are written in a fixed order: ${fixed}.`;
|
|
178
|
+
lines.push(`/**`);
|
|
179
|
+
lines.push(` * One build stage: FROM, then its instructions.`);
|
|
180
|
+
lines.push(fixedDoc);
|
|
181
|
+
lines.push(` */`);
|
|
182
|
+
lines.push(`export interface DockerfileStage {`);
|
|
183
|
+
lines.push(` from: string;`);
|
|
184
|
+
lines.push(` as?: string;`);
|
|
185
|
+
fields();
|
|
186
|
+
lines.push(`}`);
|
|
187
|
+
lines.push(`/**`);
|
|
188
|
+
lines.push(` * A Dockerfile: \`stages\` for a multi-stage build, or \`from\` and the stage fields for one stage.`);
|
|
189
|
+
lines.push(fixedDoc);
|
|
190
|
+
lines.push(` */`);
|
|
191
|
+
lines.push(`export interface DockerfileProps {`);
|
|
192
|
+
lines.push(` name?: string;`);
|
|
193
|
+
lines.push(` /** ARG instructions written before the first FROM, e.g. \`["NODE_VERSION=20"]\`. */`);
|
|
194
|
+
lines.push(` globalArgs?: string[];`);
|
|
195
|
+
lines.push(` from?: string;`);
|
|
196
|
+
lines.push(` as?: string;`);
|
|
197
|
+
lines.push(` stages?: DockerfileStage[];`);
|
|
198
|
+
fields();
|
|
199
|
+
lines.push(`}`);
|
|
200
|
+
return lines;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Write generated artifacts to disk under baseDir/src/generated/.
|
|
204
|
+
*/
|
|
205
|
+
export function writeGeneratedFiles(result, baseDir) {
|
|
206
|
+
const generatedDir = join(baseDir, "src", "generated");
|
|
207
|
+
mkdirSync(generatedDir, { recursive: true });
|
|
208
|
+
writeFileSync(join(generatedDir, "lexicon-docker.json"), result.lexiconJSON);
|
|
209
|
+
writeFileSync(join(generatedDir, "index.d.ts"), result.typesDTS);
|
|
210
|
+
writeFileSync(join(generatedDir, "index.ts"), result.indexTS);
|
|
211
|
+
writeFileSync(join(generatedDir, "runtime.ts"), `/**\n * Runtime factory constructors — re-exported from core.\n */\nexport { createResource, createProperty } from "@intentius/chant/runtime";\n`);
|
|
212
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming strategy for Docker lexicon entities.
|
|
3
|
+
*
|
|
4
|
+
* Converts Docker/Compose entity type names to TypeScript class names.
|
|
5
|
+
*/
|
|
6
|
+
/** Map entityType → TypeScript class name */
|
|
7
|
+
const ENTITY_TYPE_TO_TS_NAME = {
|
|
8
|
+
"Docker::Compose::Service": "Service",
|
|
9
|
+
"Docker::Compose::Volume": "Volume",
|
|
10
|
+
"Docker::Compose::Network": "Network",
|
|
11
|
+
"Docker::Compose::Config": "DockerConfig",
|
|
12
|
+
"Docker::Compose::Secret": "DockerSecret",
|
|
13
|
+
"Docker::Dockerfile": "Dockerfile",
|
|
14
|
+
};
|
|
15
|
+
/** Map TypeScript class name → entityType */
|
|
16
|
+
const TS_NAME_TO_ENTITY_TYPE = Object.fromEntries(Object.entries(ENTITY_TYPE_TO_TS_NAME).map(([k, v]) => [v, k]));
|
|
17
|
+
/**
|
|
18
|
+
* Convert a Docker entity type name to a TypeScript class name.
|
|
19
|
+
* e.g. "Docker::Compose::Service" → "Service"
|
|
20
|
+
*/
|
|
21
|
+
export function entityTypeToTsName(entityType) {
|
|
22
|
+
return ENTITY_TYPE_TO_TS_NAME[entityType] ?? entityType.split("::").pop() ?? entityType;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Convert a TypeScript class name back to an entity type.
|
|
26
|
+
*/
|
|
27
|
+
export function tsNameToEntityType(tsName) {
|
|
28
|
+
return TS_NAME_TO_ENTITY_TYPE[tsName];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* All known Docker entity types, in declaration order.
|
|
32
|
+
*/
|
|
33
|
+
export const ALL_ENTITY_TYPES = Object.keys(ENTITY_TYPE_TO_TS_NAME);
|
|
34
|
+
/**
|
|
35
|
+
* Check whether an entity type is a Compose resource.
|
|
36
|
+
*/
|
|
37
|
+
export function isComposeType(entityType) {
|
|
38
|
+
return entityType.startsWith("Docker::Compose::");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check whether an entity type is the Dockerfile resource.
|
|
42
|
+
*/
|
|
43
|
+
export function isDockerfileType(entityType) {
|
|
44
|
+
return entityType === "Docker::Dockerfile";
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker lexicon packaging — delegates to core packagePipeline.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { dockerPlugin } from "../plugin.js";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { moduleSourceDir } from "@intentius/chant/module-source";
|
|
8
|
+
import { packagePipeline, collectSkills, } from "@intentius/chant/codegen/package";
|
|
9
|
+
import { generate } from "./generate.js";
|
|
10
|
+
const pkgDir = dirname(moduleSourceDir(import.meta.url));
|
|
11
|
+
/**
|
|
12
|
+
* Package the Docker lexicon into a distributable BundleSpec.
|
|
13
|
+
*/
|
|
14
|
+
export async function packageLexicon(opts = {}) {
|
|
15
|
+
const pkgJson = JSON.parse(readFileSync(join(pkgDir, "..", "package.json"), "utf-8"));
|
|
16
|
+
return packagePipeline({
|
|
17
|
+
generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
|
|
18
|
+
buildManifest: (_genResult) => {
|
|
19
|
+
// Derived from the plugin's own registration (chant #1067) — this
|
|
20
|
+
// used to be a hand-maintained array independent of ../plugin.ts,
|
|
21
|
+
// free to drift from the real registration without anything
|
|
22
|
+
// noticing.
|
|
23
|
+
const intrinsics = dockerPlugin.intrinsics?.() ?? [];
|
|
24
|
+
return {
|
|
25
|
+
name: "docker",
|
|
26
|
+
version: pkgJson.version ?? "0.0.0",
|
|
27
|
+
chantVersion: ">=0.1.0",
|
|
28
|
+
namespace: "Docker",
|
|
29
|
+
intrinsics,
|
|
30
|
+
pseudoParameters: {},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
srcDir: pkgDir,
|
|
34
|
+
collectSkills: () => {
|
|
35
|
+
const skillDefs = dockerPlugin.skills?.() ?? [];
|
|
36
|
+
return collectSkills(skillDefs);
|
|
37
|
+
},
|
|
38
|
+
version: pkgJson.version ?? "0.0.0",
|
|
39
|
+
}, opts);
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual overrides for upstream Docker spec quirks.
|
|
3
|
+
*
|
|
4
|
+
* These patches adjust entity names, property types, or descriptions
|
|
5
|
+
* where the upstream spec is ambiguous, incorrect, or overly broad.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Patches keyed by entity typeName.
|
|
9
|
+
*/
|
|
10
|
+
export const ENTITY_PATCHES = {
|
|
11
|
+
"Docker::Compose::Service": {
|
|
12
|
+
propertyOverrides: {
|
|
13
|
+
// The spec allows both string and object forms for these — normalize to object
|
|
14
|
+
depends_on: {
|
|
15
|
+
description: "Service dependencies (may be string[] or condition map)",
|
|
16
|
+
},
|
|
17
|
+
// healthcheck interval/timeout are duration strings in Docker syntax
|
|
18
|
+
healthcheck: {
|
|
19
|
+
description: "Health check configuration with Docker duration strings (e.g., '30s', '1m30s')",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
"Docker::Dockerfile": {
|
|
24
|
+
propertyOverrides: {
|
|
25
|
+
// Multi-stage builds use stages array
|
|
26
|
+
stages: {
|
|
27
|
+
description: "Multi-stage build stages — each stage has its own FROM and instructions",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned URLs and version tags for upstream Docker specs.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Pinned commit of compose-spec/compose-spec (#1146).
|
|
6
|
+
*
|
|
7
|
+
* compose-spec/compose-spec has no tagged releases, so unlike
|
|
8
|
+
* ENGINE_API_URL below (pinned to the `v27.3.1` tag) there is no version tag
|
|
9
|
+
* to pin against — codegen used to fetch the default branch directly (the
|
|
10
|
+
* repo's default branch is `main`; the old URL fetched `master`, a legacy
|
|
11
|
+
* alias GitHub still redirects, but a moving-branch alias all the same).
|
|
12
|
+
* That meant a cold cache picked up whatever upstream state had landed since
|
|
13
|
+
* the cache last expired, while a warm cache kept serving whatever was
|
|
14
|
+
* current when it was last filled — the exact CI nondeterminism this fix
|
|
15
|
+
* closed for azure (#1144).
|
|
16
|
+
*
|
|
17
|
+
* Pinning to a commit SHA makes generation reproducible regardless of cache
|
|
18
|
+
* state. Bump policy: #523's scheduled lexicon-upgrade cron (rolling-spec
|
|
19
|
+
* bucket) is meant to propose bumps to this constant as a reviewable PR once
|
|
20
|
+
* built. Until then, bump by hand: take the current `main` HEAD sha from
|
|
21
|
+
* https://github.com/compose-spec/compose-spec/commits/main, regenerate, fix
|
|
22
|
+
* up any composite left referencing a renamed or vanished export, and update
|
|
23
|
+
* this constant + comment.
|
|
24
|
+
*/
|
|
25
|
+
export const COMPOSE_SPEC_COMMIT = "11296e387ba76c77db1db768b9153a4304a3c9bd";
|
|
26
|
+
/** Compose Spec JSON Schema — pinned to COMPOSE_SPEC_COMMIT, not a moving branch */
|
|
27
|
+
export const COMPOSE_SPEC_URL = `https://raw.githubusercontent.com/compose-spec/compose-spec/${COMPOSE_SPEC_COMMIT}/schema/compose-spec.json`;
|
|
28
|
+
/** Docker Engine API OpenAPI spec (v1.45) */
|
|
29
|
+
export const ENGINE_API_URL = "https://raw.githubusercontent.com/moby/moby/v27.3.1/api/swagger.yaml";
|
|
30
|
+
/**
|
|
31
|
+
* Cache filenames under ~/.chant/.
|
|
32
|
+
*
|
|
33
|
+
* COMPOSE_SPEC_CACHE embeds the pin so a stale schema file fetched before a
|
|
34
|
+
* bump can never satisfy the new pin: restoring an old ~/.chant (e.g. a CI
|
|
35
|
+
* cache partial-key fallback, or a developer's pre-existing cache) simply
|
|
36
|
+
* misses this filename and re-downloads, instead of silently serving
|
|
37
|
+
* pre-bump schema under a name that still matches (#1146, mirroring azure's
|
|
38
|
+
* AZURE_SCHEMA_COMMIT-keyed cache filename, #1144).
|
|
39
|
+
*/
|
|
40
|
+
export const COMPOSE_SPEC_CACHE = `docker-compose-spec-${COMPOSE_SPEC_COMMIT}.json`;
|
|
41
|
+
export const ENGINE_API_CACHE = "docker-engine-api.yaml";
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
14
|
+
export declare const COMPOSE_KEY: unique symbol;
|
|
15
|
+
/** Give a compose entity an explicit key in docker-compose.yml. Returns the entity. */
|
|
16
|
+
export declare function composeKey<T extends Declarable>(key: string, entity: T): T;
|
|
17
|
+
/** The key set by `composeKey`, or undefined. */
|
|
18
|
+
export declare function getComposeKey(entity: Declarable): string | undefined;
|
|
19
|
+
//# sourceMappingURL=compose-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-key.d.ts","sourceRoot":"","sources":["../src/compose-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,eAAO,MAAM,WAAW,eAAkC,CAAC;AAE3D,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAM1E;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAGpE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
export const COMPOSE_KEY = Symbol.for("docker.composeKey");
|
|
14
|
+
/** Give a compose entity an explicit key in docker-compose.yml. Returns the entity. */
|
|
15
|
+
export function composeKey(key, entity) {
|
|
16
|
+
if (typeof key !== "string" || key === "") {
|
|
17
|
+
throw new Error("composeKey: the key must be a non-empty string");
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(entity, COMPOSE_KEY, { value: key, enumerable: false, configurable: true });
|
|
20
|
+
return entity;
|
|
21
|
+
}
|
|
22
|
+
/** The key set by `composeKey`, or undefined. */
|
|
23
|
+
export function getComposeKey(entity) {
|
|
24
|
+
const key = entity[COMPOSE_KEY];
|
|
25
|
+
return typeof key === "string" ? key : undefined;
|
|
26
|
+
}
|