@intentius/chant-lexicon-docker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/integrity.json +19 -0
- package/dist/manifest.json +15 -0
- package/dist/meta.json +222 -0
- package/dist/rules/apt-no-recommends.ts +43 -0
- package/dist/rules/docker-helpers.ts +114 -0
- package/dist/rules/no-latest-image.ts +36 -0
- package/dist/rules/no-latest-tag.ts +63 -0
- package/dist/rules/no-root-user.ts +36 -0
- package/dist/rules/prefer-copy.ts +53 -0
- package/dist/rules/ssh-port-exposed.ts +68 -0
- package/dist/rules/unused-volume.ts +49 -0
- package/dist/skills/chant-docker-patterns.md +153 -0
- package/dist/skills/chant-docker.md +129 -0
- package/dist/types/index.d.ts +93 -0
- package/package.json +53 -0
- package/src/codegen/docs-cli.ts +10 -0
- package/src/codegen/docs.ts +12 -0
- package/src/codegen/generate-cli.ts +36 -0
- package/src/codegen/generate-compose.ts +21 -0
- package/src/codegen/generate-dockerfile.ts +21 -0
- package/src/codegen/generate.test.ts +105 -0
- package/src/codegen/generate.ts +158 -0
- package/src/codegen/naming.test.ts +81 -0
- package/src/codegen/naming.ts +54 -0
- package/src/codegen/package.ts +65 -0
- package/src/codegen/patches.ts +42 -0
- package/src/codegen/versions.ts +15 -0
- package/src/composites/index.ts +12 -0
- package/src/coverage.test.ts +33 -0
- package/src/coverage.ts +54 -0
- package/src/default-labels.test.ts +85 -0
- package/src/default-labels.ts +72 -0
- package/src/generated/index.d.ts +93 -0
- package/src/generated/index.ts +10 -0
- package/src/generated/lexicon-docker.json +222 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +133 -0
- package/src/import/generator.ts +127 -0
- package/src/import/parser.test.ts +137 -0
- package/src/import/parser.ts +190 -0
- package/src/import/roundtrip.test.ts +49 -0
- package/src/import/testdata/full.yaml +43 -0
- package/src/import/testdata/simple.yaml +9 -0
- package/src/import/testdata/webapp.yaml +41 -0
- package/src/index.ts +29 -0
- package/src/interpolation.test.ts +41 -0
- package/src/interpolation.ts +76 -0
- package/src/lint/post-synth/apt-no-recommends.ts +43 -0
- package/src/lint/post-synth/docker-helpers.ts +114 -0
- package/src/lint/post-synth/no-latest-image.ts +36 -0
- package/src/lint/post-synth/no-root-user.ts +36 -0
- package/src/lint/post-synth/post-synth.test.ts +181 -0
- package/src/lint/post-synth/prefer-copy.ts +53 -0
- package/src/lint/post-synth/ssh-port-exposed.ts +68 -0
- package/src/lint/post-synth/unused-volume.ts +49 -0
- package/src/lint/rules/data/deprecated-images.ts +28 -0
- package/src/lint/rules/data/known-base-images.ts +20 -0
- package/src/lint/rules/index.ts +5 -0
- package/src/lint/rules/no-latest-tag.ts +63 -0
- package/src/lint/rules/rules.test.ts +82 -0
- package/src/lsp/completions.test.ts +34 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +34 -0
- package/src/lsp/hover.ts +38 -0
- package/src/package-cli.ts +42 -0
- package/src/plugin.test.ts +117 -0
- package/src/plugin.ts +250 -0
- package/src/serializer.test.ts +294 -0
- package/src/serializer.ts +322 -0
- package/src/skills/chant-docker-patterns.md +153 -0
- package/src/skills/chant-docker.md +129 -0
- package/src/spec/fetch-compose.ts +35 -0
- package/src/spec/fetch-engine.ts +25 -0
- package/src/spec/parse-compose.ts +110 -0
- package/src/spec/parse-engine.ts +47 -0
- package/src/validate-cli.ts +19 -0
- package/src/validate.test.ts +16 -0
- package/src/validate.ts +44 -0
- package/src/variables.test.ts +32 -0
- package/src/variables.ts +47 -0
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
export interface ComposeParseResult {
|
|
10
|
+
typeName: string; // e.g. "Docker::Compose::Service"
|
|
11
|
+
shortName: string; // e.g. "Service"
|
|
12
|
+
description?: string;
|
|
13
|
+
properties: ComposeProperty[];
|
|
14
|
+
isResource: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ComposeProperty {
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
enum?: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse the Compose Spec JSON Schema buffer into ParsedResult entries.
|
|
27
|
+
* Returns one entry per top-level Compose resource type.
|
|
28
|
+
*/
|
|
29
|
+
export function parseComposeSpec(_data: Buffer): ComposeParseResult[] {
|
|
30
|
+
// The Compose spec is complex; we hand-define the five core resource types
|
|
31
|
+
// and extract key properties from the schema for documentation/type generation.
|
|
32
|
+
// Full deep parsing happens during codegen; here we return the structural outline.
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
typeName: "Docker::Compose::Service",
|
|
37
|
+
shortName: "Service",
|
|
38
|
+
description: "A containerized service definition in Docker Compose",
|
|
39
|
+
isResource: true,
|
|
40
|
+
properties: [
|
|
41
|
+
{ name: "image", type: "string", description: "Container image to use" },
|
|
42
|
+
{ name: "build", type: "object", description: "Build configuration" },
|
|
43
|
+
{ name: "command", type: "string | string[]", description: "Override the default command" },
|
|
44
|
+
{ name: "entrypoint", type: "string | string[]", description: "Override the default entrypoint" },
|
|
45
|
+
{ name: "environment", type: "Record<string, string>", description: "Environment variables" },
|
|
46
|
+
{ name: "ports", type: "string[]", description: "Published ports" },
|
|
47
|
+
{ name: "volumes", type: "string[]", description: "Volume mounts" },
|
|
48
|
+
{ name: "networks", type: "string[]", description: "Networks to attach" },
|
|
49
|
+
{ name: "depends_on", type: "string[]", description: "Service dependencies" },
|
|
50
|
+
{ name: "restart", type: "string", description: "Restart policy" },
|
|
51
|
+
{ name: "labels", type: "Record<string, string>", description: "Container labels" },
|
|
52
|
+
{ name: "healthcheck", type: "object", description: "Health check configuration" },
|
|
53
|
+
{ name: "deploy", type: "object", description: "Swarm deployment configuration" },
|
|
54
|
+
{ name: "secrets", type: "string[]", description: "Secrets to expose" },
|
|
55
|
+
{ name: "configs", type: "string[]", description: "Configs to expose" },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
typeName: "Docker::Compose::Volume",
|
|
60
|
+
shortName: "Volume",
|
|
61
|
+
description: "A named volume definition in Docker Compose",
|
|
62
|
+
isResource: true,
|
|
63
|
+
properties: [
|
|
64
|
+
{ name: "driver", type: "string", description: "Volume driver" },
|
|
65
|
+
{ name: "driver_opts", type: "Record<string, string>", description: "Driver options" },
|
|
66
|
+
{ name: "external", type: "boolean", description: "Whether the volume is external" },
|
|
67
|
+
{ name: "labels", type: "Record<string, string>", description: "Volume labels" },
|
|
68
|
+
{ name: "name", type: "string", description: "Custom volume name" },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
typeName: "Docker::Compose::Network",
|
|
73
|
+
shortName: "Network",
|
|
74
|
+
description: "A network definition in Docker Compose",
|
|
75
|
+
isResource: true,
|
|
76
|
+
properties: [
|
|
77
|
+
{ name: "driver", type: "string", description: "Network driver" },
|
|
78
|
+
{ name: "driver_opts", type: "Record<string, string>", description: "Driver options" },
|
|
79
|
+
{ name: "external", type: "boolean", description: "Whether the network is external" },
|
|
80
|
+
{ name: "labels", type: "Record<string, string>", description: "Network labels" },
|
|
81
|
+
{ name: "internal", type: "boolean", description: "Restrict external access" },
|
|
82
|
+
{ name: "ipam", type: "object", description: "IP address management" },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
typeName: "Docker::Compose::Config",
|
|
87
|
+
shortName: "Config",
|
|
88
|
+
description: "A config definition in Docker Compose",
|
|
89
|
+
isResource: true,
|
|
90
|
+
properties: [
|
|
91
|
+
{ name: "file", type: "string", description: "Path to the config file" },
|
|
92
|
+
{ name: "external", type: "boolean", description: "Whether the config is external" },
|
|
93
|
+
{ name: "labels", type: "Record<string, string>", description: "Config labels" },
|
|
94
|
+
{ name: "name", type: "string", description: "Custom config name" },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
typeName: "Docker::Compose::Secret",
|
|
99
|
+
shortName: "Secret",
|
|
100
|
+
description: "A secret definition in Docker Compose",
|
|
101
|
+
isResource: true,
|
|
102
|
+
properties: [
|
|
103
|
+
{ name: "file", type: "string", description: "Path to the secret file" },
|
|
104
|
+
{ name: "external", type: "boolean", description: "Whether the secret is external" },
|
|
105
|
+
{ name: "labels", type: "Record<string, string>", description: "Secret labels" },
|
|
106
|
+
{ name: "name", type: "string", description: "Custom secret name" },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
export interface DockerfileParseResult {
|
|
9
|
+
typeName: string; // "Docker::Dockerfile"
|
|
10
|
+
shortName: string; // "Dockerfile"
|
|
11
|
+
description?: string;
|
|
12
|
+
instructions: DockerfileInstruction[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DockerfileInstruction {
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
multi?: boolean; // can appear multiple times
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse the Docker Engine API YAML buffer for Dockerfile instruction types.
|
|
23
|
+
* Returns a single DockerfileParseResult describing the Dockerfile entity.
|
|
24
|
+
*/
|
|
25
|
+
export function parseEngineApi(_data: Buffer): DockerfileParseResult {
|
|
26
|
+
return {
|
|
27
|
+
typeName: "Docker::Dockerfile",
|
|
28
|
+
shortName: "Dockerfile",
|
|
29
|
+
description: "A Dockerfile definition with ordered build instructions",
|
|
30
|
+
instructions: [
|
|
31
|
+
{ name: "from", description: "Base image (required first instruction)" },
|
|
32
|
+
{ name: "arg", description: "Build-time argument", multi: true },
|
|
33
|
+
{ name: "env", description: "Environment variable", multi: true },
|
|
34
|
+
{ name: "run", description: "Run a command during build", multi: true },
|
|
35
|
+
{ name: "copy", description: "Copy files from build context", multi: true },
|
|
36
|
+
{ name: "add", description: "Add files (supports URLs and archives)", multi: true },
|
|
37
|
+
{ name: "workdir", description: "Set working directory" },
|
|
38
|
+
{ name: "user", description: "Set user for subsequent instructions" },
|
|
39
|
+
{ name: "expose", description: "Document exposed ports", multi: true },
|
|
40
|
+
{ name: "volume", description: "Create mount points", multi: true },
|
|
41
|
+
{ name: "label", description: "Add metadata labels", multi: true },
|
|
42
|
+
{ name: "entrypoint", description: "Set the entrypoint executable" },
|
|
43
|
+
{ name: "cmd", description: "Default command arguments" },
|
|
44
|
+
{ name: "healthcheck", description: "Health check instruction" },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Thin entry point for `bun run validate` in lexicon-docker.
|
|
4
|
+
*/
|
|
5
|
+
import { validate } from "./validate";
|
|
6
|
+
|
|
7
|
+
const result = await validate();
|
|
8
|
+
|
|
9
|
+
for (const check of result.checks) {
|
|
10
|
+
const status = check.ok ? "OK" : "FAIL";
|
|
11
|
+
const msg = check.error ? ` — ${check.error}` : "";
|
|
12
|
+
console.error(` [${status}] ${check.name}${msg}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
console.error("Validation failed");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
console.error("All validation checks passed.");
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { validate } from "./validate";
|
|
3
|
+
|
|
4
|
+
describe("validate", () => {
|
|
5
|
+
test("is an async function", () => {
|
|
6
|
+
expect(typeof validate).toBe("function");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("returns failure when generated dir does not exist", async () => {
|
|
10
|
+
const result = await validate({ basePath: "/tmp/nonexistent-docker-lexicon-test" });
|
|
11
|
+
expect(result.success).toBe(false);
|
|
12
|
+
expect(result.checks.length).toBeGreaterThanOrEqual(1);
|
|
13
|
+
const failedCheck = result.checks.find((c) => !c.ok);
|
|
14
|
+
expect(failedCheck).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
});
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate generated lexicon-docker artifacts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { validateLexiconArtifacts, type ValidateResult } from "@intentius/chant/codegen/validate";
|
|
9
|
+
|
|
10
|
+
export type { ValidateCheck, ValidateResult } from "@intentius/chant/codegen/validate";
|
|
11
|
+
|
|
12
|
+
const REQUIRED_NAMES = [
|
|
13
|
+
"Service",
|
|
14
|
+
"Volume",
|
|
15
|
+
"Network",
|
|
16
|
+
"DockerConfig",
|
|
17
|
+
"DockerSecret",
|
|
18
|
+
"Dockerfile",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate the generated lexicon-docker artifacts.
|
|
23
|
+
*/
|
|
24
|
+
export async function validate(opts?: { basePath?: string }): Promise<ValidateResult> {
|
|
25
|
+
const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
26
|
+
const generatedDir = join(basePath, "src", "generated");
|
|
27
|
+
|
|
28
|
+
if (!existsSync(generatedDir)) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
checks: [{
|
|
32
|
+
name: "generated-dir",
|
|
33
|
+
ok: false,
|
|
34
|
+
error: 'src/generated/ not found — run "chant dev generate" first',
|
|
35
|
+
}],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return validateLexiconArtifacts({
|
|
40
|
+
lexiconJsonFilename: "lexicon-docker.json",
|
|
41
|
+
requiredNames: REQUIRED_NAMES,
|
|
42
|
+
basePath,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { DOCKER_VARS, COMPOSE_VARS } from "./variables";
|
|
3
|
+
|
|
4
|
+
describe("DOCKER_VARS", () => {
|
|
5
|
+
test("exposes DOCKER_BUILDKIT", () => {
|
|
6
|
+
expect(DOCKER_VARS.DOCKER_BUILDKIT).toBe("DOCKER_BUILDKIT");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("exposes DOCKER_HOST", () => {
|
|
10
|
+
expect(DOCKER_VARS.DOCKER_HOST).toBe("DOCKER_HOST");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("is a const object", () => {
|
|
14
|
+
expect(typeof DOCKER_VARS).toBe("object");
|
|
15
|
+
expect(Object.keys(DOCKER_VARS).length).toBeGreaterThan(0);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("COMPOSE_VARS", () => {
|
|
20
|
+
test("exposes COMPOSE_PROJECT_NAME", () => {
|
|
21
|
+
expect(COMPOSE_VARS.COMPOSE_PROJECT_NAME).toBe("COMPOSE_PROJECT_NAME");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("exposes COMPOSE_FILE", () => {
|
|
25
|
+
expect(COMPOSE_VARS.COMPOSE_FILE).toBe("COMPOSE_FILE");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("is a const object", () => {
|
|
29
|
+
expect(typeof COMPOSE_VARS).toBe("object");
|
|
30
|
+
expect(Object.keys(COMPOSE_VARS).length).toBeGreaterThan(0);
|
|
31
|
+
});
|
|
32
|
+
});
|
package/src/variables.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
|
|
16
|
+
/** Docker daemon and client environment variables */
|
|
17
|
+
export const DOCKER_VARS = {
|
|
18
|
+
/** Enable Docker BuildKit: DOCKER_BUILDKIT=1 */
|
|
19
|
+
DOCKER_BUILDKIT: "DOCKER_BUILDKIT",
|
|
20
|
+
/** Docker host socket/address */
|
|
21
|
+
DOCKER_HOST: "DOCKER_HOST",
|
|
22
|
+
/** Docker TLS verify */
|
|
23
|
+
DOCKER_TLS_VERIFY: "DOCKER_TLS_VERIFY",
|
|
24
|
+
/** Docker config directory */
|
|
25
|
+
DOCKER_CONFIG: "DOCKER_CONFIG",
|
|
26
|
+
/** Docker content trust */
|
|
27
|
+
DOCKER_CONTENT_TRUST: "DOCKER_CONTENT_TRUST",
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
/** Docker Compose specific environment variables */
|
|
31
|
+
export const COMPOSE_VARS = {
|
|
32
|
+
/** Override project name: COMPOSE_PROJECT_NAME */
|
|
33
|
+
COMPOSE_PROJECT_NAME: "COMPOSE_PROJECT_NAME",
|
|
34
|
+
/** Override compose file: COMPOSE_FILE */
|
|
35
|
+
COMPOSE_FILE: "COMPOSE_FILE",
|
|
36
|
+
/** Path separator for COMPOSE_FILE */
|
|
37
|
+
COMPOSE_PATH_SEPARATOR: "COMPOSE_PATH_SEPARATOR",
|
|
38
|
+
/** Docker compose profiles to enable */
|
|
39
|
+
COMPOSE_PROFILES: "COMPOSE_PROFILES",
|
|
40
|
+
/** Compose conversion flag */
|
|
41
|
+
COMPOSE_CONVERT_WINDOWS_PATHS: "COMPOSE_CONVERT_WINDOWS_PATHS",
|
|
42
|
+
/** Docker host for compose */
|
|
43
|
+
DOCKER_DEFAULT_PLATFORM: "DOCKER_DEFAULT_PLATFORM",
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export type DockerVar = (typeof DOCKER_VARS)[keyof typeof DOCKER_VARS];
|
|
47
|
+
export type ComposeVar = (typeof COMPOSE_VARS)[keyof typeof COMPOSE_VARS];
|