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.
- package/README.md +3 -0
- package/dist/scripts/generate-schema.d.ts +14 -0
- package/dist/scripts/generate-schema.d.ts.map +1 -0
- package/dist/scripts/generate-schema.js +406 -0
- package/dist/scripts/generate-schema.js.map +1 -0
- package/dist/tool/cli/args.d.ts.map +1 -1
- package/dist/tool/cli/args.js +1 -1
- package/dist/tool/cli/args.js.map +1 -1
- package/dist/tool/commands/adopt.d.ts.map +1 -1
- package/dist/tool/commands/adopt.js +14 -20
- package/dist/tool/commands/adopt.js.map +1 -1
- package/dist/tool/questionnaire/composer.d.ts.map +1 -1
- package/dist/tool/questionnaire/composer.js +186 -2
- package/dist/tool/questionnaire/composer.js.map +1 -1
- package/dist/tool/readme/readme-generator.d.ts.map +1 -1
- package/dist/tool/readme/readme-generator.js +1 -1
- package/dist/tool/readme/readme-generator.js.map +1 -1
- package/dist/tool/schema/project-config.d.ts +8 -1
- package/dist/tool/schema/project-config.d.ts.map +1 -1
- package/dist/tool/schema/project-config.js +180 -6
- package/dist/tool/schema/project-config.js.map +1 -1
- package/dist/tool/schema/types.d.ts +51 -1
- package/dist/tool/schema/types.d.ts.map +1 -1
- package/docs/README.md +1 -0
- package/docs/overlays.md +40 -0
- package/docs/specs/019-project-mounts/spec.md +176 -0
- package/docs/specs/020-superposition-yml-schema.md +83 -0
- package/docs/specs/021-deterministic-generated-readme/spec.md +70 -0
- package/docs/superposition-yml.md +481 -0
- package/overlays/ansible/README.md +163 -0
- package/overlays/ansible/devcontainer.patch.json +14 -0
- package/overlays/ansible/overlay.yml +18 -0
- package/overlays/argocd/README.md +158 -0
- package/overlays/argocd/devcontainer.patch.json +9 -0
- package/overlays/argocd/overlay.yml +17 -0
- package/overlays/argocd/setup.sh +29 -0
- package/overlays/argocd/verify.sh +14 -0
- package/overlays/pi/README.md +141 -0
- package/overlays/pi/devcontainer.patch.json +6 -0
- package/overlays/pi/overlay.yml +15 -0
- package/overlays/pi/setup.sh +28 -0
- package/overlays/pi/verify.sh +23 -0
- package/overlays/task/README.md +47 -0
- package/overlays/task/devcontainer.patch.json +9 -0
- package/overlays/task/overlay.yml +16 -0
- package/overlays/task/setup.sh +29 -0
- package/overlays/task/verify.sh +14 -0
- package/package.json +2 -1
- package/tool/schema/config.schema.json +77 -2
- 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.
|