container-superposition 0.1.6 → 0.1.7

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 (143) hide show
  1. package/dist/scripts/init.js +7 -4
  2. package/dist/scripts/init.js.map +1 -1
  3. package/dist/tool/commands/adopt.d.ts.map +1 -1
  4. package/dist/tool/commands/adopt.js +1 -27
  5. package/dist/tool/commands/adopt.js.map +1 -1
  6. package/dist/tool/commands/doctor.d.ts +3 -0
  7. package/dist/tool/commands/doctor.d.ts.map +1 -1
  8. package/dist/tool/commands/doctor.js +932 -69
  9. package/dist/tool/commands/doctor.js.map +1 -1
  10. package/dist/tool/commands/explain.d.ts.map +1 -1
  11. package/dist/tool/commands/explain.js +9 -0
  12. package/dist/tool/commands/explain.js.map +1 -1
  13. package/dist/tool/questionnaire/composer.d.ts.map +1 -1
  14. package/dist/tool/questionnaire/composer.js +212 -11
  15. package/dist/tool/questionnaire/composer.js.map +1 -1
  16. package/dist/tool/schema/overlay-loader.d.ts.map +1 -1
  17. package/dist/tool/schema/overlay-loader.js +1 -0
  18. package/dist/tool/schema/overlay-loader.js.map +1 -1
  19. package/dist/tool/schema/project-config.d.ts +1 -1
  20. package/dist/tool/schema/project-config.d.ts.map +1 -1
  21. package/dist/tool/schema/project-config.js +94 -25
  22. package/dist/tool/schema/project-config.js.map +1 -1
  23. package/dist/tool/schema/types.d.ts +85 -11
  24. package/dist/tool/schema/types.d.ts.map +1 -1
  25. package/dist/tool/utils/merge.d.ts.map +1 -1
  26. package/dist/tool/utils/merge.js +9 -0
  27. package/dist/tool/utils/merge.js.map +1 -1
  28. package/docs/creating-overlays.md +151 -2
  29. package/docs/overlay-imports.md +125 -102
  30. package/docs/overlays.md +49 -6
  31. package/docs/quick-reference.md +99 -0
  32. package/docs/specs/003-mkdocs2-overlay/spec.md +114 -0
  33. package/docs/specs/004-doctor-fix/spec.md +70 -0
  34. package/docs/specs/005-cuda-overlay/spec.md +101 -0
  35. package/docs/specs/006-rocm-overlay/spec.md +109 -0
  36. package/overlays/.shared/README.md +80 -21
  37. package/overlays/.shared/compose/common-healthchecks.md +60 -0
  38. package/overlays/.shared/vscode/recommended-extensions.json +15 -11
  39. package/overlays/alertmanager/setup.sh +4 -19
  40. package/overlays/alertmanager/verify.sh +8 -9
  41. package/overlays/all/README.md +43 -0
  42. package/overlays/all/devcontainer.patch.json +6 -0
  43. package/overlays/all/overlay.yml +14 -0
  44. package/overlays/amp/setup.sh +5 -0
  45. package/overlays/bun/setup.sh +10 -1
  46. package/overlays/bun/verify.sh +6 -1
  47. package/overlays/claude-code/setup.sh +5 -0
  48. package/overlays/cloudflared/setup.sh +9 -12
  49. package/overlays/codex/README.md +9 -6
  50. package/overlays/codex/devcontainer.patch.json +7 -1
  51. package/overlays/codex/setup.sh +5 -0
  52. package/overlays/codex/verify.sh +8 -0
  53. package/overlays/commitlint/setup.sh +5 -0
  54. package/overlays/cuda/README.md +179 -0
  55. package/overlays/cuda/devcontainer.patch.json +7 -0
  56. package/overlays/cuda/overlay.yml +17 -0
  57. package/overlays/cuda/setup.sh +32 -0
  58. package/overlays/cuda/verify.sh +38 -0
  59. package/overlays/devcontainer-cli/README.md +50 -0
  60. package/overlays/devcontainer-cli/devcontainer.patch.json +13 -0
  61. package/overlays/devcontainer-cli/overlay.yml +16 -0
  62. package/overlays/devcontainer-cli/setup.sh +14 -0
  63. package/overlays/direnv/devcontainer.patch.json +6 -0
  64. package/overlays/direnv/setup.sh +7 -6
  65. package/overlays/dotnet/setup.sh +14 -7
  66. package/overlays/duckdb/devcontainer.patch.json +1 -2
  67. package/overlays/gcloud/devcontainer.patch.json +0 -6
  68. package/overlays/gcloud/setup.sh +51 -0
  69. package/overlays/gemini-cli/setup.sh +5 -0
  70. package/overlays/git-helpers/devcontainer.patch.json +2 -1
  71. package/overlays/go/setup.sh +15 -14
  72. package/overlays/jaeger/overlay.yml +2 -0
  73. package/overlays/just/setup.sh +5 -17
  74. package/overlays/keycloak/docker-compose.yml +6 -4
  75. package/overlays/keycloak/verify.sh +4 -3
  76. package/overlays/kind/devcontainer.patch.json +1 -2
  77. package/overlays/kind/setup.sh +8 -17
  78. package/overlays/minio/setup.sh +10 -18
  79. package/overlays/mkdocs/overlay.yml +2 -1
  80. package/overlays/mkdocs2/README.md +135 -0
  81. package/overlays/mkdocs2/devcontainer.patch.json +19 -0
  82. package/overlays/mkdocs2/overlay.yml +17 -0
  83. package/overlays/mkdocs2/setup.sh +67 -0
  84. package/overlays/mkdocs2/verify.sh +35 -0
  85. package/overlays/modern-cli-tools/devcontainer.patch.json +7 -1
  86. package/overlays/modern-cli-tools/setup.sh +21 -71
  87. package/overlays/mongodb/devcontainer.patch.json +0 -6
  88. package/overlays/mongodb/setup.sh +59 -0
  89. package/overlays/mysql/verify.sh +4 -3
  90. package/overlays/nats/.env.example +1 -1
  91. package/overlays/nats/README.md +1 -1
  92. package/overlays/nats/docker-compose.yml +1 -1
  93. package/overlays/ngrok/setup.sh +9 -6
  94. package/overlays/nodejs/setup.sh +5 -0
  95. package/overlays/openapi-tools/devcontainer.patch.json +1 -2
  96. package/overlays/openapi-tools/setup.sh +9 -8
  97. package/overlays/opencode/setup.sh +5 -0
  98. package/overlays/otel-collector/overlay.yml +2 -0
  99. package/overlays/otel-collector/setup.sh +3 -16
  100. package/overlays/otel-demo-nodejs/verify.sh +8 -9
  101. package/overlays/otel-demo-python/verify.sh +16 -10
  102. package/overlays/pandoc/README.md +22 -15
  103. package/overlays/pandoc/devcontainer.patch.json +6 -2
  104. package/overlays/pandoc/setup.sh +217 -18
  105. package/overlays/pandoc/verify.sh +16 -4
  106. package/overlays/playwright/devcontainer.patch.json +3 -1
  107. package/overlays/playwright/setup.sh +37 -0
  108. package/overlays/postgres/docker-compose.yml +6 -0
  109. package/overlays/powershell/setup.sh +49 -13
  110. package/overlays/pre-commit/setup.sh +12 -3
  111. package/overlays/prometheus/overlay.yml +2 -0
  112. package/overlays/promtail/verify.sh +16 -10
  113. package/overlays/pulumi/devcontainer.patch.json +1 -1
  114. package/overlays/python/setup.sh +28 -9
  115. package/overlays/python/verify.sh +4 -2
  116. package/overlays/redpanda/docker-compose.yml +3 -5
  117. package/overlays/rocm/README.md +227 -0
  118. package/overlays/rocm/devcontainer.patch.json +4 -0
  119. package/overlays/rocm/overlay.yml +17 -0
  120. package/overlays/rocm/setup.sh +45 -0
  121. package/overlays/rocm/verify.sh +47 -0
  122. package/overlays/rust/setup.sh +11 -18
  123. package/overlays/spec-kit/setup.sh +7 -3
  124. package/overlays/sqlite/setup.sh +14 -14
  125. package/overlays/sqlserver/docker-compose.yml +3 -3
  126. package/overlays/sqlserver/verify.sh +22 -5
  127. package/overlays/tempo/verify.sh +16 -10
  128. package/overlays/tilt/devcontainer.patch.json +1 -2
  129. package/overlays/tilt/setup.sh +14 -4
  130. package/overlays/windsurf-cli/setup.sh +27 -4
  131. package/overlays/windsurf-cli/verify.sh +13 -3
  132. package/package.json +2 -1
  133. package/templates/scripts/setup-utils.sh +228 -0
  134. package/tool/schema/config.schema.json +110 -8
  135. package/tool/schema/overlay-manifest.schema.json +5 -0
  136. package/overlays/.shared/compose/common-healthchecks.yml +0 -38
  137. /package/overlays/otel-demo-nodejs/{Dockerfile-otel-demo-nodejs → Dockerfile} +0 -0
  138. /package/overlays/otel-demo-nodejs/{package-otel-demo-nodejs.json → package.json} +0 -0
  139. /package/overlays/otel-demo-nodejs/{server-otel-demo-nodejs.js → server.js} +0 -0
  140. /package/overlays/otel-demo-nodejs/{tracing-otel-demo-nodejs.js → tracing.js} +0 -0
  141. /package/overlays/otel-demo-python/{Dockerfile-otel-demo-python → Dockerfile} +0 -0
  142. /package/overlays/otel-demo-python/{app-otel-demo-python.py → app.py} +0 -0
  143. /package/overlays/otel-demo-python/{requirements-otel-demo-python.txt → requirements.txt} +0 -0
@@ -4,18 +4,23 @@
4
4
  set -e
5
5
 
6
6
  echo "📦 Refreshing font cache..."
7
- sudo fc-cache -fv
7
+ sudo fc-cache -f
8
8
 
9
9
  echo "📦 Installing Pandoc (latest release)..."
10
10
  PANDOC_VERSION="3.6.4"
11
11
 
12
+ # Source shared setup utilities
13
+ # shellcheck source=setup-utils.sh
14
+ source "$(dirname "${BASH_SOURCE[0]}")/setup-utils.sh"
15
+ load_nvm
16
+
12
17
  if command -v apt-get > /dev/null 2>&1; then
13
18
  # Debian/Ubuntu — install official .deb from GitHub releases
14
19
  ARCH=$(dpkg --print-architecture)
15
20
  PANDOC_DEB="pandoc-${PANDOC_VERSION}-1-${ARCH}.deb"
16
21
  curl -fsSL "https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/${PANDOC_DEB}" \
17
22
  -o "/tmp/${PANDOC_DEB}"
18
- sudo dpkg -i "/tmp/${PANDOC_DEB}"
23
+ with_apt_lock sudo dpkg -i "/tmp/${PANDOC_DEB}"
19
24
  rm "/tmp/${PANDOC_DEB}"
20
25
  elif command -v apk > /dev/null 2>&1; then
21
26
  # Alpine Linux — install via apk (version may differ from pinned release)
@@ -26,7 +31,50 @@ else
26
31
  exit 1
27
32
  fi
28
33
 
29
- echo "✓ pandoc $(pandoc --version | head -1)"
34
+ if [ -x /usr/bin/pandoc ]; then
35
+ PANDOC_REAL_BIN="/usr/bin/pandoc"
36
+ elif [ -x /usr/local/bin/pandoc ] && [ ! -L /usr/local/bin/pandoc ]; then
37
+ PANDOC_REAL_BIN="/usr/local/bin/pandoc"
38
+ else
39
+ PANDOC_REAL_BIN="$(type -P pandoc || true)"
40
+ fi
41
+
42
+ if [ -z "$PANDOC_REAL_BIN" ] || [ ! -x "$PANDOC_REAL_BIN" ]; then
43
+ echo "❌ Could not resolve the real pandoc binary."
44
+ exit 1
45
+ fi
46
+
47
+ if [ "$PANDOC_REAL_BIN" = "/usr/local/bin/pandoc" ] && [ -f /usr/local/bin/pandoc ]; then
48
+ if grep -q 'DEFAULTS_FILE="\${HOME}/.pandoc/pandoc.yaml"' /usr/local/bin/pandoc 2>/dev/null; then
49
+ echo "❌ Refusing to wrap /usr/local/bin/pandoc because it is already the pandoc wrapper."
50
+ exit 1
51
+ fi
52
+ fi
53
+
54
+ echo "✓ pandoc $("$PANDOC_REAL_BIN" --version | head -1)"
55
+
56
+ echo "📦 Installing pandoc wrapper with default PDF settings..."
57
+ sudo tee /usr/local/bin/pandoc > /dev/null <<EOF
58
+ #!/bin/sh
59
+ REAL_PANDOC="${PANDOC_REAL_BIN}"
60
+ DEFAULTS_FILE="\${HOME}/.pandoc/pandoc.yaml"
61
+
62
+ for arg in "\$@"; do
63
+ case "\$arg" in
64
+ -d|--defaults|--defaults=*)
65
+ exec "\${REAL_PANDOC}" "\$@"
66
+ ;;
67
+ esac
68
+ done
69
+
70
+ if [ -f "\${DEFAULTS_FILE}" ]; then
71
+ exec "\${REAL_PANDOC}" --defaults "\${DEFAULTS_FILE}" "\$@"
72
+ fi
73
+
74
+ exec "\${REAL_PANDOC}" "\$@"
75
+ EOF
76
+ sudo chmod +x /usr/local/bin/pandoc
77
+ echo "✓ pandoc wrapper installed: /usr/local/bin/pandoc -> ${PANDOC_REAL_BIN}"
30
78
 
31
79
  echo "📦 Installing diagram.lua Lua filter..."
32
80
  mkdir -p "$HOME/.pandoc/filters"
@@ -34,17 +82,167 @@ curl -fsSL \
34
82
  "https://raw.githubusercontent.com/pandoc-ext/diagram/main/_extensions/diagram/diagram.lua" \
35
83
  -o "$HOME/.pandoc/filters/diagram.lua"
36
84
 
85
+ echo "📦 Writing emoji fallback Lua filter..."
86
+ cat > "$HOME/.pandoc/filters/emoji-fallback.lua" <<'EOF'
87
+ -- Replace emoji and flag glyphs that XeLaTeX commonly cannot render.
88
+ --
89
+ -- * BMP symbols (Miscellaneous Symbols U+2600-U+26FF, Dingbats U+2700-U+27BF)
90
+ -- are routed to the fallback font via \textfallback{} so they render as
91
+ -- real glyphs using Noto Sans Symbols 2 (declared by pandoc's fallbackfont
92
+ -- variable in pandoc.yaml).
93
+ --
94
+ -- * High-plane emoji (U+1F000-U+1FAFF: 🎉 🚀 etc.) are replaced with the
95
+ -- plain-text marker [emoji] because no common PDF font covers them fully.
96
+ --
97
+ -- * Flag sequences (pairs of regional-indicator letters: 🇺🇸 etc.) are
98
+ -- replaced with the two-letter country code in brackets, e.g. [US].
99
+ --
100
+ -- The filter is a no-op for all non-LaTeX output formats.
101
+
102
+ local REGIONAL_INDICATOR_A = 0x1F1E6
103
+ local REGIONAL_INDICATOR_Z = 0x1F1FF
104
+
105
+ local function is_regional_indicator(cp)
106
+ return cp >= REGIONAL_INDICATOR_A and cp <= REGIONAL_INDICATOR_Z
107
+ end
108
+
109
+ -- High-plane emoji that no standard PDF font renders reliably.
110
+ local function is_high_plane_emoji(cp)
111
+ return cp >= 0x1F000 and cp <= 0x1FAFF
112
+ end
113
+
114
+ -- BMP symbol blocks absent from Carlito (and most body fonts).
115
+ -- Characters in these ranges are routed to the \textfallback font instead.
116
+ local function is_bmp_symbol(cp)
117
+ return (cp >= 0x2600 and cp <= 0x26FF) -- Miscellaneous Symbols (⚠ ☀ ⛔ …)
118
+ or (cp >= 0x2700 and cp <= 0x27BF) -- Dingbats (✅ ❌ ✓ …)
119
+ end
120
+
121
+ -- Parse text into a list of pandoc inline elements, applying substitutions.
122
+ -- Returns nil when no substitutions are needed.
123
+ local function text_to_inlines(text)
124
+ if FORMAT ~= 'latex' then return nil end
125
+
126
+ -- Quick scan: skip AST work when there is nothing to process.
127
+ local needs_processing = false
128
+ for _, cp in utf8.codes(text) do
129
+ if is_regional_indicator(cp) or is_high_plane_emoji(cp) or is_bmp_symbol(cp)
130
+ or cp == 0x200D or cp == 0x20E3 or cp == 0xFE0F then
131
+ needs_processing = true
132
+ break
133
+ end
134
+ end
135
+ if not needs_processing then return nil end
136
+
137
+ local codepoints = {}
138
+ for _, cp in utf8.codes(text) do table.insert(codepoints, cp) end
139
+
140
+ local inlines = {}
141
+ local buf = {}
142
+
143
+ local function flush_buf()
144
+ if #buf > 0 then
145
+ table.insert(inlines, pandoc.Str(table.concat(buf)))
146
+ buf = {}
147
+ end
148
+ end
149
+
150
+ local i = 1
151
+ while i <= #codepoints do
152
+ local cp = codepoints[i]
153
+
154
+ -- Variation selectors / zero-width joiners / combining enclosing keycap
155
+ if cp == 0x200D or cp == 0x20E3 or cp == 0xFE0F then
156
+ i = i + 1
157
+
158
+ -- Flag sequences: two consecutive regional-indicator letters → [XX]
159
+ elseif is_regional_indicator(cp)
160
+ and i < #codepoints
161
+ and is_regional_indicator(codepoints[i + 1]) then
162
+ flush_buf()
163
+ local a = string.char((cp - REGIONAL_INDICATOR_A) + string.byte('A'))
164
+ local b = string.char((codepoints[i + 1] - REGIONAL_INDICATOR_A) + string.byte('A'))
165
+ table.insert(inlines, pandoc.Str('[' .. a .. b .. ']'))
166
+ i = i + 2
167
+
168
+ -- High-plane emoji → [emoji] text marker
169
+ elseif is_high_plane_emoji(cp) then
170
+ flush_buf()
171
+ table.insert(inlines, pandoc.Str('[emoji]'))
172
+ i = i + 1
173
+ -- Collapse consecutive emoji into one marker
174
+ while i <= #codepoints
175
+ and (is_high_plane_emoji(codepoints[i])
176
+ or codepoints[i] == 0xFE0F
177
+ or codepoints[i] == 0x200D) do
178
+ i = i + 1
179
+ end
180
+
181
+ -- BMP symbols → \textfallback{char} (renders with Noto Sans Symbols 2)
182
+ elseif is_bmp_symbol(cp) then
183
+ flush_buf()
184
+ table.insert(inlines, pandoc.RawInline('latex',
185
+ '\\textfallback{' .. utf8.char(cp) .. '}'))
186
+ i = i + 1
187
+ -- Skip trailing variation selector-16 (e.g. U+FE0F after ⚠)
188
+ if i <= #codepoints and codepoints[i] == 0xFE0F then i = i + 1 end
189
+
190
+ else
191
+ table.insert(buf, utf8.char(cp))
192
+ i = i + 1
193
+ end
194
+ end
195
+ flush_buf()
196
+
197
+ -- If result is a single identical Str, nothing actually changed.
198
+ if #inlines == 1 and inlines[1].t == 'Str' and inlines[1].text == text then
199
+ return nil
200
+ end
201
+
202
+ return inlines
203
+ end
204
+
205
+ function Str(el)
206
+ local inlines = text_to_inlines(el.text)
207
+ if inlines == nil then return nil end
208
+ return inlines
209
+ end
210
+
211
+ -- In code spans / code blocks we cannot use \textfallback (verbatim context),
212
+ -- so we simply drop the offending characters to prevent XeLaTeX warnings.
213
+ local function strip_unsupported(el)
214
+ if FORMAT ~= 'latex' then return nil end
215
+ local changed = false
216
+ local out = {}
217
+ for _, cp in utf8.codes(el.text) do
218
+ if is_high_plane_emoji(cp) or is_bmp_symbol(cp)
219
+ or cp == 0x200D or cp == 0x20E3 or cp == 0xFE0F then
220
+ changed = true
221
+ else
222
+ table.insert(out, utf8.char(cp))
223
+ end
224
+ end
225
+ if not changed then return nil end
226
+ el.text = table.concat(out)
227
+ return el
228
+ end
229
+
230
+ function Code(el) return strip_unsupported(el) end
231
+ function CodeBlock(el) return strip_unsupported(el) end
232
+ EOF
233
+
37
234
  echo "📦 Installing Mermaid CLI (requires Node.js)..."
38
235
  if command -v npm &>/dev/null; then
39
- npm install -g @mermaid-js/mermaid-cli
40
- # Point mmdc at the system Chromium (avoids Puppeteer downloading its own)
41
- mkdir -p "$HOME/.config/mermaid"
42
- cat > "$HOME/.config/mermaid/puppeteer-config.json" <<'EOF'
43
- {
44
- "executablePath": "/usr/bin/chromium",
45
- "args": ["--no-sandbox", "--disable-setuid-sandbox"]
46
- }
47
- EOF
236
+ run_spinner "Mermaid CLI" npm install -g @mermaid-js/mermaid-cli
237
+ # Create a chromium wrapper that always passes --no-sandbox (required in containers).
238
+ # This is more robust than configuring mmdc/puppeteer individually — any tool
239
+ # that launches chromium via PUPPETEER_EXECUTABLE_PATH gets the sandbox flags.
240
+ sudo tee /usr/local/bin/chromium-no-sandbox > /dev/null <<'WRAPPER'
241
+ #!/bin/sh
242
+ exec /usr/bin/chromium --no-sandbox --disable-setuid-sandbox "$@"
243
+ WRAPPER
244
+ sudo chmod +x /usr/local/bin/chromium-no-sandbox
245
+ echo "✓ chromium-no-sandbox wrapper installed"
48
246
  if command -v mmdc >/dev/null 2>&1; then
49
247
  echo "✓ Mermaid CLI installed: $(command -v mmdc)"
50
248
  else
@@ -56,8 +254,12 @@ fi
56
254
 
57
255
  echo "📦 Writing default pandoc.yaml..."
58
256
  mkdir -p "$HOME/.pandoc"
257
+ PANDOC_FILTERS_DIR="$HOME/.pandoc/filters"
59
258
  cat > "$HOME/.pandoc/pandoc.yaml" <<'EOF'
60
259
  pdf-engine: xelatex
260
+ filters:
261
+ - __PANDOC_FILTERS_DIR__/emoji-fallback.lua
262
+ - __PANDOC_FILTERS_DIR__/diagram.lua
61
263
 
62
264
  variables:
63
265
  mainfont: "Carlito"
@@ -82,13 +284,10 @@ variables:
82
284
  # toc: true
83
285
  # toc-depth: 3
84
286
  # number-sections: true
85
-
86
- # Uncomment to enable Mermaid/diagram rendering (requires nodejs overlay):
87
- # lua-filter:
88
- # - ~/.pandoc/filters/diagram.lua
89
287
  EOF
288
+ sed -i "s|__PANDOC_FILTERS_DIR__|${PANDOC_FILTERS_DIR}|g" "$HOME/.pandoc/pandoc.yaml"
90
289
 
91
290
  echo ""
92
291
  echo "✓ pandoc overlay setup complete"
93
- echo "ℹ️ Build a PDF: pandoc -d ~/.pandoc/pandoc.yaml doc.md -o doc.pdf"
94
- echo "ℹ️ With Mermaid: pandoc -d ~/.pandoc/pandoc.yaml --lua-filter ~/.pandoc/filters/diagram.lua doc.md -o doc.pdf"
292
+ echo "ℹ️ Build a PDF: pandoc doc.md -o doc.pdf"
293
+ echo "ℹ️ With Mermaid diagrams: pandoc doc.md -o doc.pdf (diagram.lua enabled by default)"
@@ -3,11 +3,23 @@ set -e
3
3
  command -v pandoc || { echo "✗ pandoc not found"; exit 1; }
4
4
  command -v xelatex || { echo "✗ xelatex not found"; exit 1; }
5
5
  [ -f "$HOME/.pandoc/filters/diagram.lua" ] || { echo "✗ diagram.lua not found"; exit 1; }
6
- [ -f "$HOME/.pandoc/pandoc.yaml" ] || { echo "✗ pandoc.yaml not found"; exit 1; }
6
+ [ -f "$HOME/.pandoc/filters/emoji-fallback.lua" ] || { echo "✗ emoji-fallback.lua not found"; exit 1; }
7
+ [ -f "$HOME/.pandoc/pandoc.yaml" ] || { echo "✗ pandoc.yaml not found"; exit 1; }
7
8
 
8
- # Smoke test: render a trivial PDF
9
- echo "# Test" | pandoc --pdf-engine=xelatex -o /tmp/pandoc-verify-test.pdf && \
10
- echo "✓ pandoc PDF smoke test passed" || { echo "✗ pandoc PDF render failed"; exit 1; }
9
+ # Smoke test: render a PDF that includes generic emoji and flag sequences
10
+ # which XeLaTeX can reject without the fallback filter.
11
+ printf '%s\n' \
12
+ '# Unicode Smoke Test' \
13
+ '' \
14
+ 'Status icons: ✅ ⚠️ ❌' \
15
+ 'Activity emoji: 🎉 🚀 🧪' \
16
+ 'Flag emoji: 🇺🇸 🇯🇵 🇧🇷' \
17
+ 'Mixed sample: Unicode check 😀' |
18
+ pandoc -o /tmp/pandoc-verify-test.pdf &&
19
+ echo "✓ pandoc Unicode PDF smoke test passed" || {
20
+ echo "✗ pandoc Unicode PDF render failed"
21
+ exit 1
22
+ }
11
23
 
12
24
  rm -f /tmp/pandoc-verify-test.pdf
13
25
  echo "✓ pandoc overlay: OK"
@@ -5,5 +5,7 @@
5
5
  "version": "lts"
6
6
  }
7
7
  },
8
- "postCreateCommand": "npx -y playwright install --with-deps chromium"
8
+ "postCreateCommand": {
9
+ "setup-playwright": "bash .devcontainer/scripts/setup-playwright.sh"
10
+ }
9
11
  }
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # Playwright setup script — Install Chromium browser + system dependencies
3
+
4
+ set -e
5
+
6
+ # Source shared setup utilities (provides load_nvm, acquire_apt_lock)
7
+ # shellcheck source=setup-utils.sh
8
+ source "$(dirname "${BASH_SOURCE[0]}")/setup-utils.sh"
9
+ load_nvm
10
+
11
+ echo "🎭 Setting up Playwright..."
12
+
13
+ # Suppress Playwright's "WARNING: no project dependencies" banner, apt output,
14
+ # and download progress bars.
15
+ export DEBIAN_FRONTEND=noninteractive
16
+ export CI=1 # suppresses interactive warnings inside playwright install
17
+
18
+ # Install Chromium and its system dependencies.
19
+ # playwright install --with-deps calls apt-get internally; we must hold the
20
+ # shared apt lock so it does not race with other parallel setup scripts.
21
+ acquire_apt_lock
22
+ npx -y playwright install --with-deps chromium 2>&1 \
23
+ | grep -vEe '^[╔║╚]' \
24
+ -e '^\|[[:space:]■]*\|' \
25
+ -e '^(Get:|Fetched|Reading|Building dependency|Processing triggers|Setting up|Preparing to|Selecting previously|Unpacking|Need to get|After this operation|The following )' \
26
+ -e '(is already the newest version|set to manually installed|npm notice|debconf:|update-alternatives:)' \
27
+ -e '^\(Reading database'
28
+ rc=${PIPESTATUS[0]}
29
+ release_apt_lock
30
+
31
+ if [ "$rc" -ne 0 ]; then
32
+ echo "❌ Playwright browser installation failed (exit $rc)"
33
+ exit "$rc"
34
+ fi
35
+
36
+ echo "✓ Playwright setup complete"
37
+ echo "ℹ️ Run 'npx playwright test' to execute tests"
@@ -14,6 +14,12 @@ services:
14
14
  - '${POSTGRES_PORT:-5432}:5432'
15
15
  networks:
16
16
  - devnet
17
+ healthcheck:
18
+ test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-devdb}']
19
+ interval: 10s
20
+ timeout: 5s
21
+ retries: 5
22
+ start_period: 10s
17
23
 
18
24
  volumes:
19
25
  postgres-data:
@@ -5,25 +5,61 @@ set -e
5
5
 
6
6
  echo "🔧 Setting up PowerShell development environment..."
7
7
 
8
- # Verify PowerShell is installed
9
- if command -v pwsh &> /dev/null; then
10
- PWSH_VERSION=$(pwsh -NoProfile -Command '$PSVersionTable.PSVersion.ToString()')
11
- echo "✓ PowerShell found: v$PWSH_VERSION"
12
- else
8
+ # Verify PowerShell is installed and responds within 10 s.
9
+ # If pwsh doesn't respond quickly (e.g. shared library issue, slow startup),
10
+ # there is no point proceeding — all subsequent pwsh calls would also hang.
11
+ if ! command -v pwsh &>/dev/null; then
13
12
  echo "⚠️ PowerShell not found"
14
13
  exit 1
15
14
  fi
16
15
 
17
- # Install common PowerShell modules
18
- echo "📦 Installing PowerShell modules..."
16
+ # --kill-after ensures pwsh child processes are SIGKILL-ed after grace period.
17
+ PWSH_VERSION=$(timeout --kill-after=5s 10s \
18
+ pwsh -NoProfile -NonInteractive -Command '$PSVersionTable.PSVersion.ToString()' \
19
+ 2>/dev/null) || true
20
+
21
+ if [ -z "$PWSH_VERSION" ]; then
22
+ echo "⚠️ pwsh did not respond within 10 s — skipping module installation"
23
+ echo "✓ PowerShell setup complete (modules skipped)"
24
+ exit 0
25
+ fi
19
26
 
20
- # PSScriptAnalyzer (Linting and best practices)
21
- pwsh -NoProfile -Command 'Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser -AllowClobber' || echo "⚠️ PSScriptAnalyzer installation failed"
27
+ echo "✓ PowerShell found: v$PWSH_VERSION"
28
+
29
+ # Trust PSGallery non-interactively.
30
+ # PowerShell 7+ bundles the NuGet provider — Install-PackageProvider is not
31
+ # needed and will fail with "No match found" on PS7. Call it only on PS5.
32
+ echo "🔧 Configuring PSGallery..."
33
+ timeout --kill-after=5s 60s \
34
+ pwsh -NoProfile -NonInteractive -Command '
35
+ $major = $PSVersionTable.PSVersion.Major
36
+ if ($major -lt 7) {
37
+ Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser | Out-Null
38
+ }
39
+ Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
40
+ ' || echo "⚠️ Failed to configure PSGallery (network may be unavailable) — skipping modules"
41
+
42
+ # Install common PowerShell modules.
43
+ # Each module runs in its own pwsh call with a per-module timeout so a slow
44
+ # PSGallery download doesn't block the remaining modules.
45
+ echo "📦 Installing PowerShell modules..."
22
46
 
23
- # Pester (Testing framework)
24
- pwsh -NoProfile -Command 'Install-Module -Name Pester -Force -Scope CurrentUser -AllowClobber -SkipPublisherCheck' || echo "⚠️ Pester installation failed"
47
+ _install_psmodule() {
48
+ local name="$1"; shift # remaining args passed as extra Install-Module params
49
+ local extra="$*"
50
+ timeout --kill-after=5s 90s \
51
+ pwsh -NoProfile -NonInteractive -Command "
52
+ try {
53
+ Install-Module -Name '$name' -Force -Scope CurrentUser -AllowClobber -Repository PSGallery $extra -ErrorAction Stop
54
+ Write-Host ' ✓ $name'
55
+ } catch {
56
+ Write-Host ' ⚠️ $name failed: ' + \$_
57
+ }
58
+ " 2>/dev/null || echo " ⚠️ $name timed out"
59
+ }
25
60
 
26
- # PowerShellGet (Module management)
27
- pwsh -NoProfile -Command 'Install-Module -Name PowerShellGet -Force -Scope CurrentUser -AllowClobber' || echo "⚠️ PowerShellGet installation failed"
61
+ _install_psmodule PSScriptAnalyzer
62
+ _install_psmodule Pester -SkipPublisherCheck
63
+ _install_psmodule PowerShellGet
28
64
 
29
65
  echo "✓ PowerShell setup complete"
@@ -5,10 +5,19 @@ set -e
5
5
 
6
6
  echo "🔍 Setting up pre-commit framework..."
7
7
 
8
- # Install pre-commit using pip
9
- pip install --user pre-commit
8
+ # Install pre-commit prefer pipx (avoids --user conflicts inside virtualenvs)
9
+ if command -v pipx &> /dev/null; then
10
+ pipx install pre-commit
11
+ elif command -v pip3 &> /dev/null; then
12
+ pip3 install pre-commit 2>/dev/null || pip3 install --break-system-packages pre-commit
13
+ elif command -v pip &> /dev/null; then
14
+ pip install pre-commit 2>/dev/null || pip install --break-system-packages pre-commit
15
+ else
16
+ echo "✗ No pip/pipx found — cannot install pre-commit"
17
+ exit 1
18
+ fi
10
19
 
11
- # Add to PATH if not already there
20
+ # pipx installs to ~/.local/bin
12
21
  export PATH="$HOME/.local/bin:$PATH"
13
22
 
14
23
  # Verify installation
@@ -20,3 +20,5 @@ ports:
20
20
  path: /
21
21
  onAutoForward: openBrowser
22
22
  order: 1
23
+ imports:
24
+ - .shared/otel/instrumentation.env
@@ -6,19 +6,25 @@ echo "🔍 Verifying Promtail installation..."
6
6
  # Track overall success
7
7
  ALL_CHECKS_PASSED=true
8
8
 
9
- # Check if Promtail service is running
10
- if docker ps --format '{{.Names}}' | grep -q promtail; then
11
- echo "✓ Promtail service is running"
12
- else
13
- echo " Promtail service is not running"
9
+ # Wait for Promtail /ready endpoint (primary health signal).
10
+ # docker ps is informational only not reliably accessible in all devcontainers.
11
+ PROMTAIL_READY=false
12
+ for i in {1..40}; do
13
+ if curl -s -o /dev/null -w "%{http_code}" http://promtail:3101/ready 2>/dev/null | grep -q "200"; then
14
+ echo "✓ Promtail is ready (HTTP /ready)"
15
+ PROMTAIL_READY=true
16
+ break
17
+ fi
18
+ sleep 3
19
+ done
20
+ if [ "$PROMTAIL_READY" = false ]; then
21
+ echo "✗ Promtail /ready endpoint not responding after 120 s (http://promtail:3101/ready)"
14
22
  ALL_CHECKS_PASSED=false
15
23
  fi
16
24
 
17
- # Check if Promtail can access Docker socket
18
- if docker exec promtail test -S /var/run/docker.sock 2>/dev/null; then
19
- echo "✓ Promtail has access to Docker socket"
20
- else
21
- echo "⚠️ Promtail cannot access Docker socket"
25
+ # Informational: check via docker ps if available.
26
+ if docker ps --format '{{.Names}}' 2>/dev/null | grep -q promtail; then
27
+ echo "✓ Promtail container visible in docker ps"
22
28
  fi
23
29
 
24
30
  # Final result
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.base.schema.json",
3
3
  "features": {
4
- "ghcr.io/devcontainers/features/pulumi:1": {
4
+ "ghcr.io/devcontainers-extra/features/pulumi:1": {
5
5
  "version": "latest"
6
6
  }
7
7
  },
@@ -13,6 +13,23 @@ WORKSPACE_ROOT="${PWD}"
13
13
  VENV_DIR="${WORKSPACE_ROOT}/.venv"
14
14
 
15
15
  # Create virtual environment if it doesn't exist
16
+ # Helper: validate that the venv's Python interpreter is actually executable.
17
+ # A stale .venv (e.g., leftover from a previous container build) can have a
18
+ # bin/python that is a dangling symlink, causing "cannot execute: required
19
+ # file not found" when pip or other venv scripts are invoked.
20
+ venv_is_valid() {
21
+ "${VENV_DIR}/bin/python" -c "import sys" &>/dev/null
22
+ }
23
+
24
+ if [ -d "${VENV_DIR}" ]; then
25
+ if venv_is_valid; then
26
+ echo "✓ Virtual environment already exists at .venv"
27
+ else
28
+ echo "⚠️ Existing .venv is invalid (stale interpreter), recreating..."
29
+ rm -rf "${VENV_DIR}"
30
+ fi
31
+ fi
32
+
16
33
  if [ ! -d "${VENV_DIR}" ]; then
17
34
  echo "📦 Creating virtual environment at .venv..."
18
35
  if ! command -v python3 >/dev/null 2>&1; then
@@ -21,49 +38,51 @@ if [ ! -d "${VENV_DIR}" ]; then
21
38
  fi
22
39
  python3 -m venv "${VENV_DIR}"
23
40
  echo "✓ Virtual environment created"
24
- else
25
- echo "✓ Virtual environment already exists at .venv"
26
41
  fi
27
42
 
28
- # Activate virtual environment
43
+ # Use the venv's Python directly to invoke pip — this is more robust than
44
+ # calling the pip wrapper script, whose shebang can point to a stale path.
45
+ PYTHON="${VENV_DIR}/bin/python"
46
+
47
+ # Activate virtual environment for PATH and VIRTUAL_ENV
29
48
  # shellcheck source=/dev/null
30
49
  source "${VENV_DIR}/bin/activate"
31
50
 
32
51
  # Upgrade pip, setuptools, and wheel inside the venv
33
52
  echo "⬆️ Upgrading pip, setuptools, and wheel..."
34
- pip install --upgrade pip setuptools wheel
53
+ "${PYTHON}" -m pip install --upgrade pip setuptools wheel
35
54
  echo "✓ pip, setuptools, and wheel upgraded"
36
55
 
37
56
  # Install overlay-specific packages (if requirements-overlay.txt exists)
38
57
  if [ -f ".devcontainer/requirements-overlay-${OVERLAY_NAME}.txt" ]; then
39
58
  echo "📦 Installing overlay packages from requirements-overlay-${OVERLAY_NAME}.txt..."
40
- pip install -r ".devcontainer/requirements-overlay-${OVERLAY_NAME}.txt"
59
+ "${PYTHON}" -m pip install -r ".devcontainer/requirements-overlay-${OVERLAY_NAME}.txt"
41
60
  echo "✓ Overlay packages installed"
42
61
  fi
43
62
 
44
63
  # Install from root requirements.txt (project production dependencies)
45
64
  if [ -f "requirements.txt" ]; then
46
65
  echo "📦 Installing dependencies from requirements.txt..."
47
- pip install -r requirements.txt
66
+ "${PYTHON}" -m pip install -r requirements.txt
48
67
  echo "✓ Dependencies installed from requirements.txt"
49
68
  fi
50
69
 
51
70
  # Install from requirements-dev.txt (project development dependencies)
52
71
  if [ -f "requirements-dev.txt" ]; then
53
72
  echo "📦 Installing dev dependencies from requirements-dev.txt..."
54
- pip install -r requirements-dev.txt
73
+ "${PYTHON}" -m pip install -r requirements-dev.txt
55
74
  echo "✓ Dev dependencies installed from requirements-dev.txt"
56
75
  fi
57
76
 
58
77
  # Install project in editable mode if pyproject.toml exists (modern Python projects)
59
78
  if [ -f "pyproject.toml" ]; then
60
79
  echo "📦 Found pyproject.toml, installing project in editable mode..."
61
- pip install -e .
80
+ "${PYTHON}" -m pip install -e .
62
81
  echo "✓ Project installed in editable mode"
63
82
  elif [ -f "setup.py" ]; then
64
83
  # Fallback for legacy Python projects
65
84
  echo "📦 Found setup.py, installing project in editable mode..."
66
- pip install -e .
85
+ "${PYTHON}" -m pip install -e .
67
86
  echo "✓ Project installed in editable mode"
68
87
  fi
69
88
 
@@ -18,10 +18,12 @@ else
18
18
  fi
19
19
 
20
20
  # Check pip is installed
21
+ # Use `python3 -m pip` instead of calling pip3 directly — the wrapper script
22
+ # inside a .venv has a shebang that can point to a stale interpreter path.
21
23
  echo ""
22
24
  echo "2️⃣ Checking pip..."
23
- if command -v pip3 &> /dev/null; then
24
- pip3 --version
25
+ if python3 -m pip --version &> /dev/null; then
26
+ python3 -m pip --version
25
27
  echo " ✅ pip found"
26
28
  else
27
29
  echo " ❌ pip not found"
@@ -43,15 +43,13 @@ services:
43
43
  CONSOLE_CONFIG_FILE: |
44
44
  kafka:
45
45
  brokers: ["redpanda:9092"]
46
- schemaRegistry:
47
- enabled: true
48
- urls: ["http://redpanda:8081"]
46
+ schemaRegistry:
47
+ enabled: true
48
+ urls: ["http://redpanda:8081"]
49
49
  redpanda:
50
50
  adminApi:
51
51
  enabled: true
52
52
  urls: ["http://redpanda:9644"]
53
- connect:
54
- enabled: false
55
53
  ports:
56
54
  - '${REDPANDA_CONSOLE_PORT:-8080}:8080'
57
55
  networks: