container-superposition 0.1.1 → 0.1.3

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 (136) hide show
  1. package/README.md +206 -1
  2. package/dist/scripts/init.js +235 -179
  3. package/dist/scripts/init.js.map +1 -1
  4. package/dist/tool/commands/doctor.d.ts +15 -0
  5. package/dist/tool/commands/doctor.d.ts.map +1 -0
  6. package/dist/tool/commands/doctor.js +862 -0
  7. package/dist/tool/commands/doctor.js.map +1 -0
  8. package/dist/tool/commands/explain.d.ts +13 -0
  9. package/dist/tool/commands/explain.d.ts.map +1 -0
  10. package/dist/tool/commands/explain.js +211 -0
  11. package/dist/tool/commands/explain.js.map +1 -0
  12. package/dist/tool/commands/list.d.ts +16 -0
  13. package/dist/tool/commands/list.d.ts.map +1 -0
  14. package/dist/tool/commands/list.js +121 -0
  15. package/dist/tool/commands/list.js.map +1 -0
  16. package/dist/tool/commands/plan.d.ts +16 -0
  17. package/dist/tool/commands/plan.d.ts.map +1 -0
  18. package/dist/tool/commands/plan.js +329 -0
  19. package/dist/tool/commands/plan.js.map +1 -0
  20. package/dist/tool/questionnaire/composer.d.ts +6 -1
  21. package/dist/tool/questionnaire/composer.d.ts.map +1 -1
  22. package/dist/tool/questionnaire/composer.js +300 -202
  23. package/dist/tool/questionnaire/composer.js.map +1 -1
  24. package/dist/tool/readme/markdown-parser.d.ts.map +1 -1
  25. package/dist/tool/readme/markdown-parser.js.map +1 -1
  26. package/dist/tool/readme/readme-generator.d.ts.map +1 -1
  27. package/dist/tool/readme/readme-generator.js +11 -6
  28. package/dist/tool/readme/readme-generator.js.map +1 -1
  29. package/dist/tool/schema/deployment-targets.d.ts +77 -0
  30. package/dist/tool/schema/deployment-targets.d.ts.map +1 -0
  31. package/dist/tool/schema/deployment-targets.js +91 -0
  32. package/dist/tool/schema/deployment-targets.js.map +1 -0
  33. package/dist/tool/schema/manifest-migrations.d.ts +51 -0
  34. package/dist/tool/schema/manifest-migrations.d.ts.map +1 -0
  35. package/dist/tool/schema/manifest-migrations.js +159 -0
  36. package/dist/tool/schema/manifest-migrations.js.map +1 -0
  37. package/dist/tool/schema/overlay-loader.d.ts +1 -1
  38. package/dist/tool/schema/overlay-loader.d.ts.map +1 -1
  39. package/dist/tool/schema/overlay-loader.js +42 -14
  40. package/dist/tool/schema/overlay-loader.js.map +1 -1
  41. package/dist/tool/schema/types.d.ts +44 -2
  42. package/dist/tool/schema/types.d.ts.map +1 -1
  43. package/dist/tool/utils/merge.d.ts +134 -0
  44. package/dist/tool/utils/merge.d.ts.map +1 -0
  45. package/dist/tool/utils/merge.js +277 -0
  46. package/dist/tool/utils/merge.js.map +1 -0
  47. package/dist/tool/utils/port-utils.d.ts +29 -0
  48. package/dist/tool/utils/port-utils.d.ts.map +1 -0
  49. package/dist/tool/utils/port-utils.js +128 -0
  50. package/dist/tool/utils/port-utils.js.map +1 -0
  51. package/dist/tool/utils/version.d.ts +9 -0
  52. package/dist/tool/utils/version.d.ts.map +1 -0
  53. package/dist/tool/utils/version.js +32 -0
  54. package/dist/tool/utils/version.js.map +1 -0
  55. package/docs/architecture.md +25 -21
  56. package/docs/deployment-targets.md +150 -0
  57. package/docs/discovery-commands.md +442 -0
  58. package/docs/merge-strategy.md +700 -0
  59. package/docs/minimal-and-editor.md +265 -0
  60. package/docs/overlay-imports.md +209 -0
  61. package/docs/overlay-manifest-refactoring.md +2 -2
  62. package/docs/overlay-metadata-archive.md +1 -1
  63. package/docs/overlays.md +91 -23
  64. package/docs/presets-architecture.md +3 -3
  65. package/docs/presets.md +1 -1
  66. package/docs/publishing.md +36 -35
  67. package/docs/team-workflow.md +540 -0
  68. package/overlays/.presets/data-engineering.yml +392 -0
  69. package/overlays/.presets/event-sourced-service.yml +262 -0
  70. package/overlays/.presets/frontend.yml +287 -0
  71. package/overlays/.presets/k8s-operator-dev.yml +462 -0
  72. package/overlays/.registry/README.md +1 -1
  73. package/overlays/.registry/deployment-targets.yml +54 -0
  74. package/overlays/.shared/README.md +43 -0
  75. package/overlays/.shared/compose/common-healthchecks.yml +38 -0
  76. package/overlays/.shared/otel/instrumentation.env +20 -0
  77. package/overlays/.shared/otel/otel-base-config.yaml +30 -0
  78. package/overlays/.shared/vscode/recommended-extensions.json +14 -0
  79. package/overlays/README.md +1 -1
  80. package/overlays/codex/overlay.yml +1 -0
  81. package/overlays/duckdb/README.md +274 -0
  82. package/overlays/duckdb/devcontainer.patch.json +10 -0
  83. package/overlays/duckdb/overlay.yml +17 -0
  84. package/overlays/duckdb/setup.sh +45 -0
  85. package/overlays/duckdb/verify.sh +32 -0
  86. package/overlays/git-helpers/overlay.yml +1 -0
  87. package/overlays/grafana/README.md +5 -5
  88. package/overlays/grafana/dashboard-provider.yml +1 -1
  89. package/overlays/grafana/docker-compose.yml +2 -2
  90. package/overlays/grafana/overlay.yml +6 -1
  91. package/overlays/jaeger/overlay.yml +16 -3
  92. package/overlays/jupyter/.env.example +6 -0
  93. package/overlays/jupyter/README.md +210 -0
  94. package/overlays/jupyter/devcontainer.patch.json +14 -0
  95. package/overlays/jupyter/docker-compose.yml +23 -0
  96. package/overlays/jupyter/overlay.yml +18 -0
  97. package/overlays/jupyter/verify.sh +35 -0
  98. package/overlays/kind/README.md +221 -0
  99. package/overlays/kind/devcontainer.patch.json +10 -0
  100. package/overlays/kind/overlay.yml +18 -0
  101. package/overlays/kind/setup.sh +43 -0
  102. package/overlays/kind/verify.sh +40 -0
  103. package/overlays/localstack/.env.example +6 -0
  104. package/overlays/localstack/README.md +188 -0
  105. package/overlays/localstack/devcontainer.patch.json +21 -0
  106. package/overlays/localstack/docker-compose.yml +25 -0
  107. package/overlays/localstack/overlay.yml +18 -0
  108. package/overlays/localstack/verify.sh +47 -0
  109. package/overlays/loki/overlay.yml +6 -1
  110. package/overlays/modern-cli-tools/overlay.yml +1 -0
  111. package/overlays/mongodb/overlay.yml +12 -2
  112. package/overlays/mysql/overlay.yml +12 -2
  113. package/overlays/nats/overlay.yml +12 -2
  114. package/overlays/openapi-tools/README.md +243 -0
  115. package/overlays/openapi-tools/devcontainer.patch.json +10 -0
  116. package/overlays/openapi-tools/overlay.yml +16 -0
  117. package/overlays/openapi-tools/setup.sh +45 -0
  118. package/overlays/openapi-tools/verify.sh +51 -0
  119. package/overlays/otel-collector/overlay.yml.example +26 -0
  120. package/overlays/postgres/overlay.yml +6 -1
  121. package/overlays/prometheus/overlay.yml +6 -1
  122. package/overlays/rabbitmq/overlay.yml +12 -2
  123. package/overlays/redis/overlay.yml +6 -1
  124. package/overlays/tilt/README.md +259 -0
  125. package/overlays/tilt/devcontainer.patch.json +17 -0
  126. package/overlays/tilt/overlay.yml +19 -0
  127. package/overlays/tilt/setup.sh +25 -0
  128. package/overlays/tilt/verify.sh +24 -0
  129. package/package.json +8 -6
  130. package/tool/README.md +12 -16
  131. package/tool/schema/overlay-manifest.schema.json +64 -4
  132. package/tool/schema/superposition-manifest.schema.json +104 -0
  133. /package/overlays/{presets → .presets}/docs-site.yml +0 -0
  134. /package/overlays/{presets → .presets}/fullstack.yml +0 -0
  135. /package/overlays/{presets → .presets}/microservice.yml +0 -0
  136. /package/overlays/{presets → .presets}/web-api.yml +0 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Merge utilities for container-superposition
3
+ *
4
+ * This module implements the formal merge strategy specification for combining
5
+ * devcontainer configurations, docker-compose files, and environment variables.
6
+ *
7
+ * See docs/merge-strategy.md for the complete specification.
8
+ */
9
+ /**
10
+ * Deep merge two objects with special handling for arrays and specific fields.
11
+ *
12
+ * Merge strategy:
13
+ * - Objects: Recursively merged
14
+ * - Arrays: Concatenated and deduplicated (union strategy)
15
+ * - Primitives: Source overwrites target (last writer wins)
16
+ * - Special cases: remoteEnv (PATH handling), features, etc.
17
+ *
18
+ * @param target - Base object to merge into
19
+ * @param source - Object to merge from (takes precedence)
20
+ * @returns Merged object
21
+ *
22
+ * @example
23
+ * const base = { features: { "node:1": { version: "lts" } } };
24
+ * const overlay = { features: { "node:1": { nodeGypDependencies: true } } };
25
+ * const result = deepMerge(base, overlay);
26
+ * // Result: { features: { "node:1": { version: "lts", nodeGypDependencies: true } } }
27
+ */
28
+ export function deepMerge(target, source) {
29
+ const output = { ...target };
30
+ for (const key in source) {
31
+ if (source[key] instanceof Object && key in target) {
32
+ if (Array.isArray(source[key])) {
33
+ // For arrays, concatenate and deduplicate
34
+ // Empty source arrays do not clear target arrays
35
+ if (source[key].length === 0) {
36
+ output[key] = target[key];
37
+ }
38
+ else {
39
+ output[key] = Array.isArray(target[key])
40
+ ? [...new Set([...target[key], ...source[key]])]
41
+ : source[key];
42
+ }
43
+ }
44
+ else if (key === 'remoteEnv') {
45
+ // Special handling for remoteEnv to merge PATH variables intelligently
46
+ output[key] = mergeRemoteEnv(target[key], source[key]);
47
+ }
48
+ else {
49
+ // Recursively merge objects
50
+ output[key] = deepMerge(target[key], source[key]);
51
+ }
52
+ }
53
+ else {
54
+ // Source value overwrites target (last writer wins)
55
+ output[key] = source[key];
56
+ }
57
+ }
58
+ return output;
59
+ }
60
+ /**
61
+ * Split PATH string on colons, preserving ${...} variable references.
62
+ *
63
+ * Handles environment variable references like ${containerEnv:HOME} without
64
+ * splitting them on the colon inside the braces.
65
+ *
66
+ * @param pathString - PATH value with colon-separated components
67
+ * @returns Array of path components
68
+ *
69
+ * @example
70
+ * splitPath("${containerEnv:HOME}/bin:${containerEnv:PATH}")
71
+ * // Returns: ["${containerEnv:HOME}/bin", "${containerEnv:PATH}"]
72
+ */
73
+ function splitPath(pathString) {
74
+ const paths = [];
75
+ let current = '';
76
+ let braceDepth = 0;
77
+ for (let i = 0; i < pathString.length; i++) {
78
+ const char = pathString[i];
79
+ const nextChar = pathString[i + 1];
80
+ if (char === '$' && nextChar === '{') {
81
+ current += char;
82
+ braceDepth++;
83
+ }
84
+ else if (char === '}' && braceDepth > 0) {
85
+ current += char;
86
+ braceDepth--;
87
+ }
88
+ else if (char === ':' && braceDepth === 0) {
89
+ // Split here - we're not inside ${...}
90
+ if (current) {
91
+ paths.push(current);
92
+ }
93
+ current = '';
94
+ }
95
+ else {
96
+ current += char;
97
+ }
98
+ }
99
+ // Add the last component
100
+ if (current) {
101
+ paths.push(current);
102
+ }
103
+ return paths;
104
+ }
105
+ /**
106
+ * Merge remoteEnv objects with intelligent PATH handling.
107
+ *
108
+ * For PATH variables:
109
+ * 1. Split on colons (preserving ${...} references)
110
+ * 2. Remove ${containerEnv:PATH} placeholders
111
+ * 3. Concatenate and deduplicate
112
+ * 4. Append ${containerEnv:PATH} at end
113
+ *
114
+ * For other variables:
115
+ * - Source overwrites target (last writer wins)
116
+ *
117
+ * @param target - Base environment variables
118
+ * @param source - Overlay environment variables
119
+ * @returns Merged environment variables
120
+ *
121
+ * @example
122
+ * mergeRemoteEnv(
123
+ * { PATH: "/usr/local/bin:${containerEnv:PATH}", NODE_ENV: "development" },
124
+ * { PATH: "${containerEnv:HOME}/.local/bin:${containerEnv:PATH}", NODE_ENV: "production" }
125
+ * )
126
+ * // Returns:
127
+ * // {
128
+ * // PATH: "/usr/local/bin:${containerEnv:HOME}/.local/bin:${containerEnv:PATH}",
129
+ * // NODE_ENV: "production"
130
+ * // }
131
+ */
132
+ export function mergeRemoteEnv(target, source) {
133
+ const output = { ...target };
134
+ for (const key in source) {
135
+ if (key === 'PATH' && target[key]) {
136
+ // Collect PATH components from both target and source using smart split
137
+ const targetPaths = splitPath(target[key]).filter((p) => p && p !== '${containerEnv:PATH}');
138
+ const sourcePaths = splitPath(source[key]).filter((p) => p && p !== '${containerEnv:PATH}');
139
+ // Combine and deduplicate paths, preserving order
140
+ const allPaths = [...new Set([...targetPaths, ...sourcePaths])];
141
+ // Rebuild PATH with original ${containerEnv:PATH} at the end
142
+ output[key] = [...allPaths, '${containerEnv:PATH}'].join(':');
143
+ }
144
+ else {
145
+ // For non-PATH variables, source overwrites target
146
+ output[key] = source[key];
147
+ }
148
+ }
149
+ return output;
150
+ }
151
+ /**
152
+ * Merge space-separated package lists with deduplication.
153
+ *
154
+ * Used for apt, apk, and other package manager lists.
155
+ *
156
+ * @param existing - Current package list (space-separated)
157
+ * @param additional - Additional packages to merge (space-separated)
158
+ * @returns Merged package list (space-separated, deduplicated)
159
+ *
160
+ * @example
161
+ * mergePackages("curl wget", "wget jq")
162
+ * // Returns: "curl wget jq"
163
+ */
164
+ export function mergePackages(existing, additional) {
165
+ // Filter out empty tokens from split to avoid leading/trailing spaces
166
+ const existingPackages = existing.split(' ').filter((p) => p);
167
+ const newPackages = additional.split(' ').filter((p) => p);
168
+ // Merge and deduplicate
169
+ const merged = [...new Set([...existingPackages, ...newPackages])];
170
+ return merged.join(' ');
171
+ }
172
+ /**
173
+ * Filter depends_on to only include services that exist in the final composition.
174
+ *
175
+ * Supports both Docker Compose syntaxes:
176
+ * - Array form: depends_on: [serviceA, serviceB]
177
+ * - Object form: depends_on: { serviceA: { condition: ... } }
178
+ *
179
+ * @param dependsOn - Original depends_on configuration
180
+ * @param existingServices - Set of service names that exist
181
+ * @returns Filtered depends_on (undefined if empty after filtering)
182
+ *
183
+ * @example
184
+ * filterDependsOn(
185
+ * ["postgres", "redis", "rabbitmq"],
186
+ * new Set(["postgres", "redis"])
187
+ * )
188
+ * // Returns: ["postgres", "redis"]
189
+ */
190
+ export function filterDependsOn(dependsOn, existingServices) {
191
+ if (Array.isArray(dependsOn)) {
192
+ const filtered = dependsOn.filter((dep) => typeof dep === 'string' && existingServices.has(dep));
193
+ return filtered.length > 0 ? filtered : undefined;
194
+ }
195
+ if (dependsOn && typeof dependsOn === 'object') {
196
+ const filtered = Object.fromEntries(Object.entries(dependsOn).filter(([dep]) => existingServices.has(dep)));
197
+ return Object.keys(filtered).length > 0 ? filtered : undefined;
198
+ }
199
+ return dependsOn;
200
+ }
201
+ /**
202
+ * Apply port offset to port string or number.
203
+ *
204
+ * @param port - Port value (string or number)
205
+ * @param offset - Offset to apply
206
+ * @returns Port with offset applied
207
+ *
208
+ * @example
209
+ * applyPortOffset("5432:5432", 100)
210
+ * // Returns: "5532:5432" (only host port offset)
211
+ *
212
+ * applyPortOffset(3000, 100)
213
+ * // Returns: 3100
214
+ */
215
+ export function applyPortOffset(port, offset) {
216
+ if (typeof port === 'number') {
217
+ return port + offset;
218
+ }
219
+ // Handle string port mappings like "5432:5432" or "127.0.0.1:5432:5432"
220
+ const parts = port.split(':');
221
+ if (parts.length >= 2) {
222
+ // Format: "host:container" or "ip:host:container"
223
+ // Offset the host port (last or second-to-last number)
224
+ const isThreePart = parts.length === 3;
225
+ const hostPortIndex = isThreePart ? 1 : 0;
226
+ const hostPort = parseInt(parts[hostPortIndex], 10);
227
+ if (!isNaN(hostPort)) {
228
+ parts[hostPortIndex] = String(hostPort + offset);
229
+ return parts.join(':');
230
+ }
231
+ }
232
+ // If we can't parse it, return as-is
233
+ return port;
234
+ }
235
+ /**
236
+ * Apply port offset to environment variable content.
237
+ *
238
+ * Finds variables matching *PORT*=number pattern and applies offset.
239
+ *
240
+ * @param envContent - Environment file content
241
+ * @param offset - Port offset to apply
242
+ * @returns Modified environment content
243
+ *
244
+ * @example
245
+ * applyPortOffsetToEnv("POSTGRES_PORT=5432\nAPP_NAME=myapp", 100)
246
+ * // Returns: "POSTGRES_PORT=5532\nAPP_NAME=myapp"
247
+ */
248
+ export function applyPortOffsetToEnv(envContent, offset) {
249
+ const lines = envContent.split('\n');
250
+ const portVarPattern = /^([A-Z_]*PORT[A-Z_]*)=(\d+)$/;
251
+ const modifiedLines = lines.map((line) => {
252
+ const match = line.match(portVarPattern);
253
+ if (match) {
254
+ const [, varName, portValue] = match;
255
+ const newPort = parseInt(portValue, 10) + offset;
256
+ return `${varName}=${newPort}`;
257
+ }
258
+ return line;
259
+ });
260
+ return modifiedLines.join('\n');
261
+ }
262
+ /**
263
+ * Merge strategy metadata for documentation and testing.
264
+ */
265
+ export const MERGE_STRATEGY = {
266
+ version: '1.0.0',
267
+ description: 'Formal merge strategy specification for container-superposition',
268
+ rules: {
269
+ objects: 'deep-merge',
270
+ arrays: 'union-deduplicate',
271
+ primitives: 'last-writer-wins',
272
+ remoteEnv: 'intelligent-PATH-merging',
273
+ packages: 'space-separated-union',
274
+ dependsOn: 'filter-to-existing-services',
275
+ },
276
+ };
277
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../tool/utils/merge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Port utilities for normalizing and documenting ports
3
+ */
4
+ import type { PortMetadata, NormalizedPort, PortsDocumentation, OverlayMetadata } from '../schema/types.js';
5
+ /**
6
+ * Normalize a port entry (number or PortMetadata) to NormalizedPort format
7
+ */
8
+ export declare function normalizePort(port: number | PortMetadata, offset: number, overlayId?: string): NormalizedPort;
9
+ /**
10
+ * Generate connection string from template
11
+ */
12
+ export declare function generateConnectionString(template: string, port: NormalizedPort, envVars?: Record<string, string>): string;
13
+ /**
14
+ * Get default connection string template for common services
15
+ */
16
+ export declare function getDefaultConnectionStringTemplate(service: string, protocol?: string): string | undefined;
17
+ /**
18
+ * Generate URL for HTTP/HTTPS services
19
+ */
20
+ export declare function generateUrl(port: NormalizedPort): string | undefined;
21
+ /**
22
+ * Generate ports documentation from overlays
23
+ */
24
+ export declare function generatePortsDocumentation(overlays: OverlayMetadata[], portOffset: number, envVars?: Record<string, string>): PortsDocumentation;
25
+ /**
26
+ * Extract all unique ports from overlays (for offset calculation)
27
+ */
28
+ export declare function extractPorts(overlays: OverlayMetadata[]): number[];
29
+ //# sourceMappingURL=port-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-utils.d.ts","sourceRoot":"","sources":["../../../tool/utils/port-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,eAAe,EAClB,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,MAAM,GAAG,YAAY,EAC3B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACnB,cAAc,CAgBhB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACrC,MAAM,CAoBR;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CAC9C,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAiBpB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAMpE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACtC,QAAQ,EAAE,eAAe,EAAE,EAC3B,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACrC,kBAAkB,CAgDpB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,CAelE"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Port utilities for normalizing and documenting ports
3
+ */
4
+ /**
5
+ * Normalize a port entry (number or PortMetadata) to NormalizedPort format
6
+ */
7
+ export function normalizePort(port, offset, overlayId) {
8
+ if (typeof port === 'number') {
9
+ // Legacy format - just a number
10
+ return {
11
+ port,
12
+ actualPort: port + offset,
13
+ service: overlayId,
14
+ };
15
+ }
16
+ // Rich format - already an object
17
+ return {
18
+ ...port,
19
+ actualPort: port.port + offset,
20
+ service: port.service || overlayId,
21
+ };
22
+ }
23
+ /**
24
+ * Generate connection string from template
25
+ */
26
+ export function generateConnectionString(template, port, envVars = {}) {
27
+ let result = template;
28
+ // Replace port placeholder
29
+ result = result.replace(/\{port\}/g, String(port.actualPort));
30
+ // Replace other placeholders from environment variables or port metadata
31
+ const replacements = {
32
+ host: 'localhost',
33
+ service: port.service || 'unknown',
34
+ path: port.path || '',
35
+ ...envVars,
36
+ };
37
+ for (const [key, value] of Object.entries(replacements)) {
38
+ const regex = new RegExp(`\\{${key}\\}`, 'g');
39
+ result = result.replace(regex, value);
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * Get default connection string template for common services
45
+ */
46
+ export function getDefaultConnectionStringTemplate(service, protocol) {
47
+ const templates = {
48
+ postgres: 'postgresql://{user}:{password}@{host}:{port}/{database}',
49
+ postgresql: 'postgresql://{user}:{password}@{host}:{port}/{database}',
50
+ mysql: 'mysql://{user}:{password}@{host}:{port}/{database}',
51
+ mongodb: 'mongodb://{user}:{password}@{host}:{port}/{database}',
52
+ redis: 'redis://{host}:{port}',
53
+ rabbitmq: 'amqp://{user}:{password}@{host}:{port}',
54
+ nats: 'nats://{host}:{port}',
55
+ };
56
+ // For HTTP/HTTPS services, use URL format
57
+ if (protocol === 'http' || protocol === 'https') {
58
+ return `${protocol}://{host}:{port}{path}`;
59
+ }
60
+ return templates[service.toLowerCase()];
61
+ }
62
+ /**
63
+ * Generate URL for HTTP/HTTPS services
64
+ */
65
+ export function generateUrl(port) {
66
+ if (port.protocol === 'http' || port.protocol === 'https') {
67
+ const path = port.path || '';
68
+ return `${port.protocol}://localhost:${port.actualPort}${path}`;
69
+ }
70
+ return undefined;
71
+ }
72
+ /**
73
+ * Generate ports documentation from overlays
74
+ */
75
+ export function generatePortsDocumentation(overlays, portOffset, envVars = {}) {
76
+ const allPorts = [];
77
+ const connectionStrings = {};
78
+ for (const overlay of overlays) {
79
+ if (!overlay.ports || overlay.ports.length === 0) {
80
+ continue;
81
+ }
82
+ for (const portEntry of overlay.ports) {
83
+ const normalizedPort = normalizePort(portEntry, portOffset, overlay.id);
84
+ allPorts.push(normalizedPort);
85
+ // Generate connection string if template is provided or can be inferred
86
+ const template = typeof portEntry === 'object' && portEntry.connectionStringTemplate
87
+ ? portEntry.connectionStringTemplate
88
+ : getDefaultConnectionStringTemplate(normalizedPort.service || '', normalizedPort.protocol);
89
+ if (template) {
90
+ const connStr = generateConnectionString(template, normalizedPort, envVars);
91
+ // Use unique key for multi-port services by appending port number if service already exists
92
+ let key = normalizedPort.service || `port-${normalizedPort.port}`;
93
+ // If key already exists, make it unique by adding port number
94
+ if (connectionStrings[key]) {
95
+ key = `${key}-${normalizedPort.port}`;
96
+ }
97
+ connectionStrings[key] = connStr;
98
+ }
99
+ // Generate URL for HTTP/HTTPS services
100
+ const url = generateUrl(normalizedPort);
101
+ if (url && normalizedPort.service) {
102
+ connectionStrings[`${normalizedPort.service}-url`] = url;
103
+ }
104
+ }
105
+ }
106
+ return {
107
+ portOffset,
108
+ ports: allPorts,
109
+ connectionStrings,
110
+ };
111
+ }
112
+ /**
113
+ * Extract all unique ports from overlays (for offset calculation)
114
+ */
115
+ export function extractPorts(overlays) {
116
+ const ports = new Set();
117
+ for (const overlay of overlays) {
118
+ if (!overlay.ports) {
119
+ continue;
120
+ }
121
+ for (const portEntry of overlay.ports) {
122
+ const port = typeof portEntry === 'number' ? portEntry : portEntry.port;
123
+ ports.add(port);
124
+ }
125
+ }
126
+ return Array.from(ports).sort((a, b) => a - b);
127
+ }
128
+ //# sourceMappingURL=port-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-utils.js","sourceRoot":"","sources":["../../../tool/utils/port-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH;;GAEG;AACH,MAAM,UAAU,aAAa,CACzB,IAA2B,EAC3B,MAAc,EACd,SAAkB;IAElB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3B,gCAAgC;QAChC,OAAO;YACH,IAAI;YACJ,UAAU,EAAE,IAAI,GAAG,MAAM;YACzB,OAAO,EAAE,SAAS;SACrB,CAAC;IACN,CAAC;IAED,kCAAkC;IAClC,OAAO;QACH,GAAG,IAAI;QACP,UAAU,EAAE,IAAI,CAAC,IAAI,GAAG,MAAM;QAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS;KACrC,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB,CACpC,QAAgB,EAChB,IAAoB,EACpB,UAAkC,EAAE;IAEpC,IAAI,MAAM,GAAG,QAAQ,CAAC;IAEtB,2BAA2B;IAC3B,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAE9D,yEAAyE;IACzE,MAAM,YAAY,GAA2B;QACzC,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS;QAClC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;QACrB,GAAG,OAAO;KACb,CAAC;IAEF,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC;QAC9C,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kCAAkC,CAC9C,OAAe,EACf,QAAiB;IAEjB,MAAM,SAAS,GAA2B;QACtC,QAAQ,EAAE,yDAAyD;QACnE,UAAU,EAAE,yDAAyD;QACrE,KAAK,EAAE,oDAAoD;QAC3D,OAAO,EAAE,sDAAsD;QAC/D,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,wCAAwC;QAClD,IAAI,EAAE,sBAAsB;KAC/B,CAAC;IAEF,0CAA0C;IAC1C,IAAI,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC9C,OAAO,GAAG,QAAQ,wBAAwB,CAAC;IAC/C,CAAC;IAED,OAAO,SAAS,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAoB;IAC5C,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAC7B,OAAO,GAAG,IAAI,CAAC,QAAQ,gBAAgB,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,CAAC;IACpE,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CACtC,QAA2B,EAC3B,UAAkB,EAClB,UAAkC,EAAE;IAEpC,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,MAAM,iBAAiB,GAA2B,EAAE,CAAC;IAErD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,SAAS;QACb,CAAC;QAED,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACpC,MAAM,cAAc,GAAG,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YACxE,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAE9B,wEAAwE;YACxE,MAAM,QAAQ,GACV,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,wBAAwB;gBAC/D,CAAC,CAAC,SAAS,CAAC,wBAAwB;gBACpC,CAAC,CAAC,kCAAkC,CAC9B,cAAc,CAAC,OAAO,IAAI,EAAE,EAC5B,cAAc,CAAC,QAAQ,CAC1B,CAAC;YAEZ,IAAI,QAAQ,EAAE,CAAC;gBACX,MAAM,OAAO,GAAG,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;gBAC5E,4FAA4F;gBAC5F,IAAI,GAAG,GAAG,cAAc,CAAC,OAAO,IAAI,QAAQ,cAAc,CAAC,IAAI,EAAE,CAAC;gBAElE,8DAA8D;gBAC9D,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzB,GAAG,GAAG,GAAG,GAAG,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC;gBAC1C,CAAC;gBAED,iBAAiB,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;YACrC,CAAC;YAED,uCAAuC;YACvC,MAAM,GAAG,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;YACxC,IAAI,GAAG,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;gBAChC,iBAAiB,CAAC,GAAG,cAAc,CAAC,OAAO,MAAM,CAAC,GAAG,GAAG,CAAC;YAC7D,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO;QACH,UAAU;QACV,KAAK,EAAE,QAAQ;QACf,iBAAiB;KACpB,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAA2B;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACjB,SAAS;QACb,CAAC;QAED,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;YACxE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Utility functions for version management
3
+ */
4
+ /**
5
+ * Get the current tool version from package.json
6
+ * Works in both source (TypeScript) and compiled (JavaScript) contexts
7
+ */
8
+ export declare function getToolVersion(): string;
9
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../tool/utils/version.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAmBvC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Utility functions for version management
3
+ */
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ // Get __dirname equivalent in ESM
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ /**
11
+ * Get the current tool version from package.json
12
+ * Works in both source (TypeScript) and compiled (JavaScript) contexts
13
+ */
14
+ export function getToolVersion() {
15
+ try {
16
+ // Try multiple paths to handle different execution contexts
17
+ const packageJsonCandidates = [
18
+ // From tool/utils/ (source)
19
+ path.join(__dirname, '..', '..', 'package.json'),
20
+ // From dist/tool/utils/ (compiled)
21
+ path.join(__dirname, '..', '..', '..', 'package.json'),
22
+ ];
23
+ const packageJsonPath = packageJsonCandidates.find((candidate) => fs.existsSync(candidate)) ??
24
+ packageJsonCandidates[0];
25
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
26
+ return packageJson.version || 'unknown';
27
+ }
28
+ catch (error) {
29
+ return 'unknown';
30
+ }
31
+ }
32
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../../tool/utils/version.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,kCAAkC;AAClC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC1B,IAAI,CAAC;QACD,4DAA4D;QAC5D,MAAM,qBAAqB,GAAG;YAC1B,4BAA4B;YAC5B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC;YAChD,mCAAmC;YACnC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC;SACzD,CAAC;QAEF,MAAM,eAAe,GACjB,qBAAqB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YACnE,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAE7B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1E,OAAO,WAAW,CAAC,OAAO,IAAI,SAAS,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACrB,CAAC;AACL,CAAC"}
@@ -74,29 +74,33 @@ scripts/
74
74
  5. **Merge .env.example** files from all selected overlays
75
75
  6. **Write merged configuration** to output path
76
76
 
77
- ### Deep Merge Logic
78
-
79
- ```typescript
80
- function deepMerge(base, overlay) {
81
- for each key in overlay:
82
- if key exists in base and both are objects:
83
- if both are arrays:
84
- concatenate and deduplicate
85
- else:
86
- recursively merge
87
- else:
88
- use overlay value
89
- return merged
90
- }
91
- ```
77
+ ### Merge Strategy
78
+
79
+ Container-superposition uses a **formal, deterministic merge strategy** for combining configurations from base templates and overlays.
80
+
81
+ **Core principles:**
82
+
83
+ - **Deterministic**: Same inputs always produce same output
84
+ - **Deep merge**: Objects are recursively merged, not replaced
85
+ - **Union by default**: Arrays and collections are merged (unioned), not replaced
86
+ - **Last writer wins**: For conflicting primitive values
87
+
88
+ **Key behaviors:**
89
+
90
+ - **Objects**: Recursively merged
91
+ - **Arrays**: Concatenated and deduplicated (union strategy)
92
+ - **Features**: Deep merged by feature key
93
+ - **remoteEnv**: Intelligent PATH variable merging
94
+ - **Package lists**: Space-separated strings merged with deduplication
95
+ - **depends_on**: Filtered to only existing services
96
+
97
+ For the complete specification with examples, see **[Merge Strategy Specification](merge-strategy.md)**.
92
98
 
93
- Special handling:
99
+ Implementation details:
94
100
 
95
- - **Arrays**: Concatenate and deduplicate (ports, packages)
96
- - **apt-get packages**: Merge space-separated lists
97
- - **Features**: Deep merge feature configs
98
- - **Environment variables**: Merge key-value pairs
99
- - **Port attributes**: Merge labeled port configurations
101
+ - Merge utilities: `tool/utils/merge.ts`
102
+ - Integration: `tool/questionnaire/composer.ts`
103
+ - Tests: `tool/__tests__/merge-strategy.test.ts`
100
104
 
101
105
  ### File Handling per Overlay
102
106