container-superposition 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +3 -0
  2. package/dist/scripts/generate-schema.d.ts +14 -0
  3. package/dist/scripts/generate-schema.d.ts.map +1 -0
  4. package/dist/scripts/generate-schema.js +406 -0
  5. package/dist/scripts/generate-schema.js.map +1 -0
  6. package/dist/tool/cli/args.d.ts.map +1 -1
  7. package/dist/tool/cli/args.js +1 -1
  8. package/dist/tool/cli/args.js.map +1 -1
  9. package/dist/tool/commands/adopt.d.ts.map +1 -1
  10. package/dist/tool/commands/adopt.js +14 -20
  11. package/dist/tool/commands/adopt.js.map +1 -1
  12. package/dist/tool/questionnaire/composer.d.ts.map +1 -1
  13. package/dist/tool/questionnaire/composer.js +186 -2
  14. package/dist/tool/questionnaire/composer.js.map +1 -1
  15. package/dist/tool/readme/readme-generator.d.ts.map +1 -1
  16. package/dist/tool/readme/readme-generator.js +1 -1
  17. package/dist/tool/readme/readme-generator.js.map +1 -1
  18. package/dist/tool/schema/project-config.d.ts +8 -1
  19. package/dist/tool/schema/project-config.d.ts.map +1 -1
  20. package/dist/tool/schema/project-config.js +180 -6
  21. package/dist/tool/schema/project-config.js.map +1 -1
  22. package/dist/tool/schema/types.d.ts +51 -1
  23. package/dist/tool/schema/types.d.ts.map +1 -1
  24. package/docs/README.md +1 -0
  25. package/docs/overlays.md +40 -0
  26. package/docs/specs/019-project-mounts/spec.md +176 -0
  27. package/docs/specs/020-superposition-yml-schema.md +83 -0
  28. package/docs/specs/021-deterministic-generated-readme/spec.md +70 -0
  29. package/docs/superposition-yml.md +481 -0
  30. package/overlays/ansible/README.md +163 -0
  31. package/overlays/ansible/devcontainer.patch.json +14 -0
  32. package/overlays/ansible/overlay.yml +18 -0
  33. package/overlays/argocd/README.md +158 -0
  34. package/overlays/argocd/devcontainer.patch.json +9 -0
  35. package/overlays/argocd/overlay.yml +17 -0
  36. package/overlays/argocd/setup.sh +29 -0
  37. package/overlays/argocd/verify.sh +14 -0
  38. package/overlays/pi/README.md +141 -0
  39. package/overlays/pi/devcontainer.patch.json +6 -0
  40. package/overlays/pi/overlay.yml +15 -0
  41. package/overlays/pi/setup.sh +28 -0
  42. package/overlays/pi/verify.sh +23 -0
  43. package/overlays/task/README.md +47 -0
  44. package/overlays/task/devcontainer.patch.json +9 -0
  45. package/overlays/task/overlay.yml +16 -0
  46. package/overlays/task/setup.sh +29 -0
  47. package/overlays/task/verify.sh +14 -0
  48. package/package.json +2 -1
  49. package/tool/schema/config.schema.json +77 -2
  50. package/tool/schema/superposition.schema.json +569 -0
@@ -0,0 +1,176 @@
1
+ # Feature Specification: First-Class Mounts Support
2
+
3
+ **Spec ID**: `019-project-mounts`
4
+ **Created**: 2026-05-03
5
+ **Status**: Approved
6
+ **Input**: User request
7
+
8
+ ## Overview
9
+
10
+ Add a top-level `mounts` field to `superposition.yml` that lets users declare filesystem mounts
11
+ once and have the generation pipeline route them to the correct devcontainer artifact based on
12
+ the active stack.
13
+
14
+ This feature is modeled after the existing first-class `env` behavior.
15
+
16
+ ---
17
+
18
+ ## User Scenarios & Testing
19
+
20
+ ### User Story 1 — Declare a mount on a plain stack (Priority: P1)
21
+
22
+ A developer adds a `mounts:` list to `superposition.yml` on a `plain` stack and expects each
23
+ entry to appear in `devcontainer.json → mounts[]` after regeneration.
24
+
25
+ **Why this priority**: Plain-stack users who need bind mounts (e.g. a shared workspace folder)
26
+ currently must write custom `devcontainerPatch` JSON, which is difficult to discover and
27
+ error-prone.
28
+
29
+ **Independent Test**: Create a `superposition.yml` with `stack: plain` and a `mounts:` entry.
30
+ Run `regen` and confirm the raw string appears in `devcontainer.json mounts`.
31
+
32
+ **Acceptance Scenarios**:
33
+
34
+ 1. **Given** `mounts: ["source=${localWorkspaceFolder}/../libs,target=/workspace/libs,type=bind"]`
35
+ with `stack: plain`, **When** generation runs, **Then** the string is present in
36
+ `devcontainer.json mounts[]`.
37
+ 2. **Given** a long-form entry `{value: "...", target: devcontainerMount}` with `stack: plain`,
38
+ **When** generation runs, **Then** the mount value is present in `devcontainer.json mounts[]`.
39
+ 3. **Given** a long-form entry with `target: auto` on `stack: plain`, **When** generation runs,
40
+ **Then** the mount routes to `devcontainer.json mounts[]` (same as default).
41
+
42
+ ---
43
+
44
+ ### User Story 2 — Declare a mount on a compose stack (Priority: P1)
45
+
46
+ A developer adds a `mounts:` list to `superposition.yml` on a `compose` stack and expects each
47
+ entry (with `target: auto` or string shorthand) to appear in `devcontainer.json mounts[]` —
48
+ the same as it would on a plain stack — so the project file is **stack-agnostic**.
49
+
50
+ **Why this priority**: Users should be able to swap `stack: plain` ↔ `stack: compose` without
51
+ needing to touch the `mounts:` block.
52
+
53
+ **Independent Test**: Create a `superposition.yml` with `stack: compose` and a `mounts:` entry.
54
+ Run `regen` and confirm the entry appears in `devcontainer.json mounts[]` (not in compose
55
+ volumes, unless `composeVolume` is explicitly requested).
56
+
57
+ **Acceptance Scenarios**:
58
+
59
+ 1. **Given** `mounts: ["./data:/workspace/data"]` with `stack: compose`, **When** generation runs,
60
+ **Then** `./data:/workspace/data` appears in `devcontainer.json mounts[]`.
61
+ 2. **Given** `target: auto` on `stack: compose`, **When** generation runs, **Then** the mount
62
+ routes to `devcontainer.json mounts[]` (same as plain — stack-agnostic).
63
+
64
+ ---
65
+
66
+ ### User Story 3 — Explicit `composeVolume` target for Docker Compose volumes (Priority: P2)
67
+
68
+ A developer who specifically wants a mount wired as a Docker Compose service volume (e.g. to
69
+ leverage named volumes or compose-specific semantics) sets `target: composeVolume`.
70
+
71
+ **Acceptance Scenarios**:
72
+
73
+ 1. **Given** `{value: "...", target: composeVolume}` on `stack: compose`, **When** generation
74
+ runs, **Then** the value appears in `docker-compose.yml services.devcontainer.volumes[]`.
75
+ 2. **Given** `{value: "...", target: composeVolume}` on `stack: plain`, **When** generation runs,
76
+ **Then** generation errors with a clear message about compose-only targets.
77
+
78
+ ---
79
+
80
+ ### User Story 4 — Mounts coexist with customizations patches (Priority: P1)
81
+
82
+ Mounts declared in the top-level `mounts:` field are applied before
83
+ `customizations.devcontainerPatch` and `customizations.dockerComposePatch`, so patch overrides
84
+ are still respected.
85
+
86
+ **Acceptance Scenarios**:
87
+
88
+ 1. **Given** a `mounts:` entry and a `customizations.devcontainerPatch` that adds an additional
89
+ mount, **When** generation runs, **Then** both mounts appear in `devcontainer.json` (union merge).
90
+ 2. **Given** a compose-stack `mounts:` entry and a `customizations.dockerComposePatch` that adds
91
+ extra volumes, **When** generation runs, **Then** both sets appear in
92
+ `services.devcontainer.volumes`.
93
+
94
+ ---
95
+
96
+ ### User Story 5 — Validation (Priority: P1)
97
+
98
+ The parser rejects clearly invalid mount declarations.
99
+
100
+ **Acceptance Scenarios**:
101
+
102
+ 1. `target: composeVolume` on `stack: plain` → error at compose/devcontainer generation time.
103
+ 2. An entry with an empty `value` string → `ProjectConfigError` at parse time.
104
+ 3. An entry that is neither a string nor an object with `value` → `ProjectConfigError`.
105
+
106
+ ---
107
+
108
+ ## Schema Design
109
+
110
+ ### `superposition.yml`
111
+
112
+ ```yaml
113
+ mounts:
114
+ # string shorthand — raw mount spec
115
+ - 'source=${localWorkspaceFolder}/../libs,target=/workspace/libs,type=bind'
116
+ # long form — explicit target routing
117
+ - value: './data:/workspace/data'
118
+ target: auto # default; always devcontainer.json mounts[] regardless of stack
119
+ - value: 'source=certs,target=/certs,type=volume'
120
+ target: devcontainerMount # always devcontainer.json
121
+ - value: './logs:/workspace/logs'
122
+ target: composeVolume # always docker-compose volumes (compose only)
123
+ ```
124
+
125
+ ### Routing table
126
+
127
+ | `target` | `stack: plain` | `stack: compose` |
128
+ | ------------------- | -------------------------- | -------------------------------------------------- |
129
+ | `auto` (default) | `devcontainer.json mounts` | `devcontainer.json mounts` |
130
+ | `devcontainerMount` | `devcontainer.json mounts` | `devcontainer.json mounts` |
131
+ | `composeVolume` | ❌ Error | `docker-compose.yml services.devcontainer.volumes` |
132
+
133
+ `auto` and `devcontainerMount` are stack-agnostic: they always route to `devcontainer.json
134
+ mounts[]` so that the same `superposition.yml` works without modification when swapping
135
+ `stack: plain` ↔ `stack: compose`.
136
+
137
+ ---
138
+
139
+ ## Implementation Plan
140
+
141
+ ### Types (`tool/schema/types.ts`)
142
+
143
+ - `ProjectMountTarget = 'auto' | 'devcontainerMount' | 'composeVolume'`
144
+ - `ProjectMount = { value: string; target?: ProjectMountTarget }`
145
+ - `QuestionnaireAnswers.projectMounts?: ProjectMount[]`
146
+ - `ProjectConfigSelection.mounts?: Array<string | ProjectMount>`
147
+
148
+ ### Schema (`tool/schema/config.schema.json`)
149
+
150
+ Add `mounts` as an array of `string | {value, target?}` (oneOf), matching the `env` pattern.
151
+
152
+ ### Parser (`tool/schema/project-config.ts`)
153
+
154
+ - `parseMounts(value): ProjectMount[] | undefined` — normalize strings to `{value}` objects
155
+ - Add `'mounts'` to the supported-keys set
156
+ - Thread `mounts` through `loadProjectConfig`, `buildAnswersFromProjectConfig`,
157
+ `buildProjectConfigSelectionFromAnswers`, and `buildProjectConfigDocument`
158
+
159
+ ### Composer (`tool/questionnaire/composer.ts`)
160
+
161
+ - `resolveProjectMountTarget(mount, stack)` — maps `ProjectMountTarget` to resolved destination
162
+ - `applyProjectMountsToDevcontainer(config, mounts, stack)` — deepMerge devcontainerMount entries
163
+ into `config.mounts`
164
+ - Thread compose-volume mounts into `mergeDockerComposeFiles` (analogous to `projectEnv`)
165
+ - Apply before custom patches
166
+
167
+ ### Application order
168
+
169
+ 1. Base template loaded
170
+ 2. Overlays applied
171
+ 3. Port offsets applied
172
+ 4. Project env applied to devcontainer + compose
173
+ 5. **Project mounts applied to devcontainer + compose** ← new
174
+ 6. Custom patches applied (devcontainerPatch, dockerComposePatch)
175
+ 7. Target patches applied
176
+ 8. Files written
@@ -0,0 +1,83 @@
1
+ # Spec 020: JSON Schema for `superposition.yml`
2
+
3
+ ## Overview
4
+
5
+ Introduce a JSON Schema for `superposition.yml` / `.superposition.yml` project files. The schema
6
+ enables editor auto-complete, inline validation, and tooling integration via the `$schema`
7
+ property. It is generated automatically from source and kept in sync as part of the
8
+ Definition of Done.
9
+
10
+ ## Goals
11
+
12
+ 1. **Authoring guidance** — editors that support JSON Schema (VS Code, JetBrains, etc.) can
13
+ provide completions and inline error highlighting when editing `superposition.yml`.
14
+ 2. **Generated automatically** — the schema is produced by `npm run schema:generate` from the
15
+ TypeScript types and live overlay registry, so it never goes stale.
16
+ 3. **Static referrable URL** — a stable URL pointing to the `main`-branch schema can be
17
+ added to any project's `superposition.yml` once and just works across versions.
18
+ 4. **Published with each release** — the schema file is uploaded as a release asset so
19
+ versioned pinning is also possible.
20
+ 5. **Embedded in generated files** — the tool injects a `$schema` line into every
21
+ `superposition.yml` it writes so users get validation for free without manual setup.
22
+
23
+ ## Schema URL
24
+
25
+ | Purpose | URL |
26
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------- |
27
+ | Static / latest | `https://raw.githubusercontent.com/veggerby/container-superposition/main/tool/schema/superposition.schema.json` |
28
+ | Versioned (per tag) | `https://raw.githubusercontent.com/veggerby/container-superposition/v{VERSION}/tool/schema/superposition.schema.json` |
29
+ | Release asset | Attached to each GitHub release as `superposition.schema.json` |
30
+
31
+ The static URL is always current and is the one written into generated `superposition.yml` files.
32
+
33
+ ## Schema file location
34
+
35
+ `tool/schema/superposition.schema.json` — committed to the repository and updated by
36
+ `npm run schema:generate`. Do not edit manually.
37
+
38
+ ## Generator script
39
+
40
+ `scripts/generate-schema.ts` — run via `tsx` with `npm run schema:generate`.
41
+
42
+ The generator:
43
+
44
+ 1. Imports the enum constant arrays (`STACK_VALUES`, `BASE_IMAGE_VALUES`, `TARGET_VALUES`,
45
+ `EDITOR_VALUES`, `PROJECT_ENV_TARGET_VALUES`, `PROJECT_MOUNT_TARGET_VALUES`) directly from
46
+ `tool/schema/project-config.ts`. These are the same arrays used at runtime for validation,
47
+ so the schema and the parser are always in agreement.
48
+ 2. Loads the live overlay registry (same path-resolution logic as `docs/generate-docs.ts`).
49
+ 3. Extracts overlay IDs by category, matching the `buildCategoryLookup()` rules (incl.
50
+ `database` + `messaging` both mapping to the `database` legacy field).
51
+ 4. Builds a JSON Schema (draft-07) describing `ProjectConfigSelection`, including:
52
+ - Enum values for `stack`, `baseImage`, `target`, `editor` derived from exported constants
53
+ - Dynamic `enum` for `overlays` items — non-preset overlay IDs only (presets are restricted
54
+ to the `preset` field)
55
+ - Per-category `enum` values for the legacy arrays (`language`, `database`, etc.) using the
56
+ same category rules as `buildCategoryLookup()`
57
+ - All compound types (`env`, `mounts`, `shell`, `customizations`, `parameters`)
58
+ 5. Writes the result to `tool/schema/superposition.schema.json`.
59
+
60
+ ## Definition of Done additions
61
+
62
+ - `npm run schema:generate` must be run (and its output committed) whenever:
63
+ - A new overlay is added or removed
64
+ - The `ProjectConfigSelection` type changes (new/renamed/removed fields)
65
+ - Enum values for `stack`, `baseImage`, `target`, `editor` change
66
+ - The CI validation workflow (`generate-docs.yml`) checks that the committed schema matches
67
+ a freshly generated one (same as the docs-sync check).
68
+
69
+ ## Serialization and round-trip
70
+
71
+ `buildProjectConfigDocument` in `tool/schema/project-config.ts` places `$schema` as the
72
+ first key in the YAML output. The value is taken from `selection.$schema` (the value that was
73
+ in the file when it was loaded) with `SUPERPOSITION_SCHEMA_URL` as the fallback for new files.
74
+ This means any user-pinned schema URL (e.g. a versioned release URL) is preserved on `regen`.
75
+
76
+ ## Release workflow
77
+
78
+ The `publish.yml` workflow gains two steps after build:
79
+
80
+ 1. `npm run schema:generate` — regenerates the schema using the published version's overlay
81
+ set.
82
+ 2. `gh release upload` — uploads `tool/schema/superposition.schema.json` as a release asset
83
+ to the current GitHub release.
@@ -0,0 +1,70 @@
1
+ # Feature Specification: Deterministic Generated README Header
2
+
3
+ **Spec ID**: `021-deterministic-generated-readme`
4
+ **Taxonomy**: `CLI-UX`
5
+ **Created**: 2026-05-06
6
+ **Author**: Codex
7
+ **Status**: Approved
8
+ **Input**: Bug report — `cs doctor` reproducibility checks can fail on a clean project because generated `README.md` content includes the current date in its header.
9
+
10
+ ## Problem Statement
11
+
12
+ The consolidated generated `README.md` currently includes a line of the form
13
+ `Generated by Container Superposition on YYYY-MM-DD`. That timestamp changes every day even when
14
+ the project file, overlays, and generated devcontainer output are otherwise identical.
15
+
16
+ This breaks the reproducibility contract behind `cs regen` and `cs doctor`: a no-op regeneration
17
+ should produce byte-for-byte stable output when the effective configuration has not changed.
18
+
19
+ ## Goals
20
+
21
+ - Make generated `README.md` output deterministic across repeated `cs regen` runs.
22
+ - Preserve the provenance signal that the file is generated by Container Superposition.
23
+ - Prevent future regressions with explicit test coverage.
24
+
25
+ ## Non-Goals
26
+
27
+ - Removing timestamps from manifest metadata or other files not involved in this failure.
28
+ - Redesigning the generated README structure or content beyond the unstable timestamp line.
29
+
30
+ ## Design
31
+
32
+ ### Header format
33
+
34
+ Replace the current header line:
35
+
36
+ ```md
37
+ > Generated by Container Superposition on YYYY-MM-DD
38
+ ```
39
+
40
+ with a stable line:
41
+
42
+ ```md
43
+ > Generated by Container Superposition
44
+ ```
45
+
46
+ The template/base-image/preset metadata line remains unchanged.
47
+
48
+ ### Test coverage
49
+
50
+ Add or update README generation tests to assert:
51
+
52
+ 1. The generated README still includes the provenance line.
53
+ 2. The provenance line does not include `on ` followed by a date fragment.
54
+
55
+ ## Requirements
56
+
57
+ - **FR-001**: Generated `README.md` headers MUST NOT include the current date or time.
58
+ - **FR-002**: Generated `README.md` headers MUST continue to indicate that the file was generated by Container Superposition.
59
+ - **FR-003**: Automated tests MUST cover the deterministic header format.
60
+
61
+ ## Dependencies & Impact
62
+
63
+ - **Affected Areas**: `tool/readme/readme-generator.ts`, `tool/__tests__/readme-generation.test.ts`, `CHANGELOG.md`
64
+ - **Compatibility Impact**: Low. Existing generated READMEs will change by one line on the next `regen`.
65
+ - **Required Documentation Updates**: `CHANGELOG.md`
66
+
67
+ ## Success Criteria
68
+
69
+ - `cs regen` run twice on the same project produces the same `README.md` bytes.
70
+ - `cs doctor` no longer flags README drift caused solely by the header date.