@monster-orange/den 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -1
  4. package/bin/den +61 -0
  5. package/docs/distributions.md +256 -0
  6. package/make/demo.mk +82 -0
  7. package/make/den.mk +80 -0
  8. package/make/journeys.mk +57 -0
  9. package/make/local_dev.mk +154 -0
  10. package/make/security.mk +59 -0
  11. package/make/test.mk +405 -0
  12. package/package.json +99 -4
  13. package/scripts/auth-configure.ts +415 -0
  14. package/scripts/heal-config.ts +92 -0
  15. package/scripts/jsonl-playwright-reporter.js +70 -0
  16. package/scripts/manifest-read.cjs +91 -0
  17. package/scripts/setup-1password.ts +570 -0
  18. package/scripts/setup-secrets.ts +149 -0
  19. package/scripts/view-results.js +156 -0
  20. package/service/install.sh +112 -0
  21. package/src/pillars/api/fixtures/index.ts +128 -0
  22. package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
  23. package/src/pillars/api/global-setup.ts +103 -0
  24. package/src/pillars/api/playwright.config.ts +105 -0
  25. package/src/pillars/api/schemas/example-api.json +110 -0
  26. package/src/pillars/api/tests/health/health.spec.ts +19 -0
  27. package/src/pillars/api/tests/health/manifest.yml +6 -0
  28. package/src/pillars/browser/fixtures/index.ts +232 -0
  29. package/src/pillars/browser/fixtures/package.json +10 -0
  30. package/src/pillars/browser/global-setup.ts +140 -0
  31. package/src/pillars/browser/global-teardown.ts +48 -0
  32. package/src/pillars/browser/playwright.config.ts +212 -0
  33. package/src/pillars/browser/setup.ts +219 -0
  34. package/src/pillars/journeys/app-config.ts +237 -0
  35. package/src/pillars/journeys/discover.ts +160 -0
  36. package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
  37. package/src/pillars/journeys/observability/events.ts +288 -0
  38. package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
  39. package/src/pillars/journeys/report/friction.ts +304 -0
  40. package/src/pillars/journeys/report/junit.ts +105 -0
  41. package/src/pillars/journeys/report/render.ts +273 -0
  42. package/src/pillars/journeys/report/results.ts +323 -0
  43. package/src/pillars/journeys/run.ts +673 -0
  44. package/src/pillars/journeys/runner.ts +1025 -0
  45. package/src/pillars/journeys/sandbox.ts +406 -0
  46. package/src/pillars/journeys/spec/loader.ts +152 -0
  47. package/src/pillars/journeys/spec/schema.ts +208 -0
  48. package/src/pillars/journeys/teardown.ts +98 -0
  49. package/src/pillars/journeys/tools/browser-session.ts +219 -0
  50. package/src/pillars/journeys/tools/browser-tool.ts +914 -0
  51. package/src/pillars/journeys/tools/http-tool.ts +564 -0
  52. package/src/pillars/journeys/tools/ledger.ts +278 -0
  53. package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
  54. package/src/pillars/journeys/verify/assertions.ts +502 -0
  55. package/src/pillars/journeys/verify/verdict.ts +71 -0
  56. package/src/pillars/perf/build-report.ts +36 -0
  57. package/src/pillars/perf/k6.config.js +112 -0
  58. package/src/pillars/perf/run.ts +345 -0
  59. package/src/pillars/perf/scenarios/load-profile.js +97 -0
  60. package/src/pillars/perf/staging.ts +73 -0
  61. package/src/pillars/security/discover.ts +108 -0
  62. package/src/pillars/security/findings.ts +151 -0
  63. package/src/pillars/security/plan.ts +355 -0
  64. package/src/pillars/security/run.ts +729 -0
  65. package/src/pillars/smoke/admin-probe.ts +68 -0
  66. package/src/pillars/smoke/base-url.ts +56 -0
  67. package/src/pillars/smoke/cleanup.ts +8 -0
  68. package/src/pillars/smoke/discover.ts +237 -0
  69. package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
  70. package/src/pillars/smoke/fixture-manager.ts +217 -0
  71. package/src/pillars/smoke/fixtures.ts +468 -0
  72. package/src/pillars/smoke/global-setup-all.ts +262 -0
  73. package/src/pillars/smoke/global-setup-global.ts +262 -0
  74. package/src/pillars/smoke/global-setup.ts +273 -0
  75. package/src/pillars/smoke/global-teardown-all.ts +8 -0
  76. package/src/pillars/smoke/global-teardown-global.ts +8 -0
  77. package/src/pillars/smoke/global-teardown.ts +139 -0
  78. package/src/pillars/smoke/playwright-all.config.ts +147 -0
  79. package/src/pillars/smoke/playwright-global.config.ts +141 -0
  80. package/src/pillars/smoke/playwright.config.ts +162 -0
  81. package/src/pillars/smoke/setup-env.ts +132 -0
  82. package/src/pillars/smoke/setup-global.ts +126 -0
  83. package/src/pillars/smoke/setup.ts +115 -0
  84. package/src/pillars/smoke/smoke-repo.ts +16 -0
  85. package/src/pillars/smoke/staging.ts +49 -0
  86. package/src/pillars/suites/run.ts +147 -0
  87. package/src/scripts/configure-aws-profiles.ts +114 -0
  88. package/src/scripts/register-repos.ts +307 -0
  89. package/src/scripts/setup-default-workspace.ts +203 -0
  90. package/src/shared/applications.ts +138 -0
  91. package/src/shared/auth/api-token.ts +416 -0
  92. package/src/shared/auth/auth0-helper.ts +284 -0
  93. package/src/shared/auth/headless-login.ts +265 -0
  94. package/src/shared/auth/jwt-helper.ts +186 -0
  95. package/src/shared/auth/sudo-vault.ts +69 -0
  96. package/src/shared/auth/test-accounts.ts +98 -0
  97. package/src/shared/auth/token-injection.ts +184 -0
  98. package/src/shared/auth/token-providers/auth0.ts +80 -0
  99. package/src/shared/auth/token-providers/index.ts +182 -0
  100. package/src/shared/auth/token-refresh.ts +273 -0
  101. package/src/shared/banner.ts +15 -0
  102. package/src/shared/brand.ts +79 -0
  103. package/src/shared/cli-tabs.ts +131 -0
  104. package/src/shared/constants.ts +128 -0
  105. package/src/shared/den-config.ts +566 -0
  106. package/src/shared/dev-metrics/emit.ts +354 -0
  107. package/src/shared/edge/config.ts +229 -0
  108. package/src/shared/edge/render.ts +221 -0
  109. package/src/shared/edge/runtime.ts +268 -0
  110. package/src/shared/edge/types.ts +62 -0
  111. package/src/shared/engine.ts +67 -0
  112. package/src/shared/environment/aws-profiles.ts +49 -0
  113. package/src/shared/environment/env-selector.ts +161 -0
  114. package/src/shared/environment/playwright-env.ts +82 -0
  115. package/src/shared/environment-manager/catalog.ts +77 -0
  116. package/src/shared/environment-manager/clone-guard.ts +163 -0
  117. package/src/shared/environment-manager/dag.ts +286 -0
  118. package/src/shared/environment-manager/default-workspace.ts +182 -0
  119. package/src/shared/environment-manager/derivation/alias.ts +90 -0
  120. package/src/shared/environment-manager/derivation/cache.ts +97 -0
  121. package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
  122. package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
  123. package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
  124. package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
  125. package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
  126. package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
  127. package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
  128. package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
  129. package/src/shared/environment-manager/derivation/scan.ts +160 -0
  130. package/src/shared/environment-manager/derivation/text.ts +40 -0
  131. package/src/shared/environment-manager/derivation/types.ts +69 -0
  132. package/src/shared/environment-manager/health.ts +589 -0
  133. package/src/shared/environment-manager/index.ts +6 -0
  134. package/src/shared/environment-manager/load-applications.ts +78 -0
  135. package/src/shared/environment-manager/log-tail.ts +212 -0
  136. package/src/shared/environment-manager/make-targets.ts +78 -0
  137. package/src/shared/environment-manager/opctl.ts +334 -0
  138. package/src/shared/environment-manager/readiness.ts +93 -0
  139. package/src/shared/environment-manager/register-local.ts +72 -0
  140. package/src/shared/environment-manager/secrets-check.ts +260 -0
  141. package/src/shared/environment-manager/service-env.ts +110 -0
  142. package/src/shared/environment-manager/service-runner.ts +1588 -0
  143. package/src/shared/environment-manager/up-plan.ts +302 -0
  144. package/src/shared/environment-manager/workspace-manager.ts +905 -0
  145. package/src/shared/environment-manager/workspaces.ts +860 -0
  146. package/src/shared/manifest/deps.ts +27 -0
  147. package/src/shared/manifest/migrate-manifest.ts +101 -0
  148. package/src/shared/manifest/read-manifest.ts +160 -0
  149. package/src/shared/manifest/schema.ts +299 -0
  150. package/src/shared/manifest/service-links.ts +93 -0
  151. package/src/shared/manifest/validate-manifest.ts +140 -0
  152. package/src/shared/manifest/workspace-manifests.ts +92 -0
  153. package/src/shared/open-url.ts +122 -0
  154. package/src/shared/org.ts +44 -0
  155. package/src/shared/paths.ts +178 -0
  156. package/src/shared/perf/optional-fields.ts +105 -0
  157. package/src/shared/perf/report-normalizer.ts +404 -0
  158. package/src/shared/perf/report-paths.ts +19 -0
  159. package/src/shared/perf/report-store.ts +110 -0
  160. package/src/shared/perf/report-types.ts +70 -0
  161. package/src/shared/perf/test-paths.ts +25 -0
  162. package/src/shared/process/log-rotation.ts +147 -0
  163. package/src/shared/process/spawn-streaming.ts +229 -0
  164. package/src/shared/product.ts +92 -0
  165. package/src/shared/profile.ts +141 -0
  166. package/src/shared/repo-kind.ts +32 -0
  167. package/src/shared/reporting/jsonl-reporter.ts +131 -0
  168. package/src/shared/reporting/slack-reporter.ts +340 -0
  169. package/src/shared/results/result-reader.ts +266 -0
  170. package/src/shared/results/result-writer.ts +168 -0
  171. package/src/shared/secrets/doppler.ts +126 -0
  172. package/src/shared/secrets/env.ts +48 -0
  173. package/src/shared/secrets/index.ts +197 -0
  174. package/src/shared/secrets/keys.ts +33 -0
  175. package/src/shared/secrets/onepassword.ts +75 -0
  176. package/src/shared/secrets/types.ts +52 -0
  177. package/src/shared/service-url.ts +119 -0
  178. package/src/shared/smoke/branch-resolver.ts +141 -0
  179. package/src/shared/smoke/git-remote.ts +70 -0
  180. package/src/shared/smoke/repo-cloner.ts +151 -0
  181. package/src/shared/stacks/config.ts +137 -0
  182. package/src/shared/stacks/manifest-commands.ts +88 -0
  183. package/src/shared/stacks/render.ts +253 -0
  184. package/src/shared/stacks/runtime.ts +334 -0
  185. package/src/shared/stacks/types.ts +53 -0
  186. package/src/shared/terminal/tabs.ts +212 -0
  187. package/src/shared/test-discovery/module-scanner.ts +569 -0
  188. package/src/shared/types/index.ts +678 -0
  189. package/src/shared/ui-marker.ts +19 -0
  190. package/src/ui/cli/applications-command.ts +116 -0
  191. package/src/ui/cli/config-command.ts +68 -0
  192. package/src/ui/cli/create-main.ts +9 -0
  193. package/src/ui/cli/create.ts +360 -0
  194. package/src/ui/cli/edge-command.ts +91 -0
  195. package/src/ui/cli/flag-parser.ts +246 -0
  196. package/src/ui/cli/graph-command.ts +524 -0
  197. package/src/ui/cli/health-display.ts +63 -0
  198. package/src/ui/cli/index.ts +3656 -0
  199. package/src/ui/cli/init.ts +855 -0
  200. package/src/ui/cli/invocation-target.ts +175 -0
  201. package/src/ui/cli/logs-command.ts +104 -0
  202. package/src/ui/cli/module-selection.ts +31 -0
  203. package/src/ui/cli/prompts.ts +767 -0
  204. package/src/ui/cli/results-viewer.ts +436 -0
  205. package/src/ui/cli/secrets-preflight.ts +91 -0
  206. package/src/ui/cli/stack-command.ts +144 -0
  207. package/src/ui/cli/stalled-setup-recovery.ts +129 -0
  208. package/src/ui/cli/test-runner.ts +1312 -0
  209. package/src/ui/cli/web-command.ts +1073 -0
  210. package/src/ui/server/changelog-parser.ts +94 -0
  211. package/src/ui/server/daemon-reaper.ts +129 -0
  212. package/src/ui/server/editor-manager.ts +301 -0
  213. package/src/ui/server/github-service.ts +223 -0
  214. package/src/ui/server/http-helpers.ts +98 -0
  215. package/src/ui/server/index.ts +3236 -0
  216. package/src/ui/server/log-history.ts +452 -0
  217. package/src/ui/server/perf-report.ts +42 -0
  218. package/src/ui/server/public/environments.html +494 -0
  219. package/src/ui/server/public/favicon.ico +0 -0
  220. package/src/ui/server/public/header.js +1371 -0
  221. package/src/ui/server/public/index.html +2595 -0
  222. package/src/ui/server/public/local-dev.html +7405 -0
  223. package/src/ui/server/public/logs.html +1489 -0
  224. package/src/ui/server/public/perf-report.js +359 -0
  225. package/src/ui/server/public/release.html +2111 -0
  226. package/src/ui/server/public/services.html +1098 -0
  227. package/src/ui/server/public/workspace-picker.js +69 -0
  228. package/src/ui/server/release-deploy-session.ts +246 -0
  229. package/src/ui/server/release-service.ts +1125 -0
  230. package/src/ui/server/sudo-store.ts +111 -0
  231. package/src/ui/server/workspace-routes.ts +2154 -0
@@ -0,0 +1,678 @@
1
+ /**
2
+ * Shared TypeScript types for Den
3
+ */
4
+
5
+ export interface EnvironmentConfig {
6
+ name: string;
7
+ baseUrls: {
8
+ web: string;
9
+ api: string;
10
+ services: Record<string, string>;
11
+ };
12
+ /**
13
+ * Which identity provider mints test tokens for this environment.
14
+ * Strategy, not code: see src/shared/auth/token-providers.
15
+ * Defaults to "auth0" when an `auth0` block is present, else "none".
16
+ */
17
+ auth?: AuthConfig;
18
+ /** Auth0 settings — required only when auth.provider is "auth0". */
19
+ auth0?: {
20
+ domain: string;
21
+ clientId: string;
22
+ audience: string;
23
+ /**
24
+ * Optional dotenv file (relative to the den root, e.g. an app
25
+ * checkout's `.env.local`) whose AUTH0_CLIENT_SECRET is the local-dev
26
+ * fallback for the client secret.
27
+ */
28
+ clientSecretEnvFile?: string;
29
+ };
30
+ timeouts: {
31
+ api: number;
32
+ pageLoad: number;
33
+ };
34
+ }
35
+
36
+ export type AuthProviderName = "auth0" | "cognito" | "local-jwt" | "none";
37
+
38
+ export interface AuthConfig {
39
+ provider: AuthProviderName;
40
+ /**
41
+ * How the headless browser login reaches the IdP and recognises success.
42
+ * Defaults: start at "/" (the app redirects to its IdP) and treat any
43
+ * page back on the app origin as signed in.
44
+ */
45
+ browser?: BrowserLoginConfig;
46
+ /** AWS Cognito user pool app client (USER_PASSWORD_AUTH flow). */
47
+ cognito?: {
48
+ region: string;
49
+ clientId: string;
50
+ /** Use the ID token instead of the access token as the bearer. */
51
+ useIdToken?: boolean;
52
+ };
53
+ /** Self-signed HS256 JWT for local stacks that verify a shared secret. */
54
+ localJwt?: {
55
+ /** Env var / secrets flat key holding the signing secret. */
56
+ secretKey: string;
57
+ issuer?: string;
58
+ audience?: string;
59
+ ttlSeconds?: number;
60
+ /** Extra claims merged into every token. */
61
+ claims?: Record<string, unknown>;
62
+ };
63
+ }
64
+
65
+ export interface BrowserLoginConfig {
66
+ /** App path that starts the login redirect (e.g. "/auth/login"). Default "/". */
67
+ loginPath?: string;
68
+ /**
69
+ * App path a signed-in session can open (session checks navigate here and
70
+ * expect to stay on it). Default "/".
71
+ */
72
+ signedInPath?: string;
73
+ }
74
+
75
+ export interface Credentials {
76
+ regular: {
77
+ username: string;
78
+ password: string;
79
+ auth0ClientId?: string;
80
+ };
81
+ elevated?: {
82
+ username: string;
83
+ password: string;
84
+ };
85
+ lastUpdated: string;
86
+ }
87
+
88
+ export interface TestResult {
89
+ timestamp: string;
90
+ pillar: Pillar;
91
+ environment: string;
92
+ test: string;
93
+ status: "passed" | "failed" | "skipped";
94
+ duration: number;
95
+ user: string;
96
+ error?: string;
97
+ screenshot?: string;
98
+ trace?: string;
99
+ metadata?: Record<string, unknown>;
100
+ }
101
+
102
+ export interface SlackConfig {
103
+ webhookUrl: string;
104
+ enabled: boolean;
105
+ notifyOn: {
106
+ testComplete: boolean;
107
+ testFailure: boolean;
108
+ thresholdExceeded: boolean;
109
+ };
110
+ thresholds: {
111
+ failurePercentage: number;
112
+ };
113
+ channel: string;
114
+ }
115
+
116
+ export interface TestSummary {
117
+ pillar: string;
118
+ environment: string;
119
+ total: number;
120
+ passed: number;
121
+ failed: number;
122
+ skipped: number;
123
+ duration: number;
124
+ passRate: number;
125
+ }
126
+
127
+ export interface FlakyTest {
128
+ test: string;
129
+ runs: number;
130
+ failures: number;
131
+ flakyRate: number;
132
+ }
133
+
134
+ export interface TrendData {
135
+ date: string;
136
+ passRate: number;
137
+ avgDuration: number;
138
+ totalTests: number;
139
+ }
140
+
141
+ export type Environment =
142
+ | "local"
143
+ | "dev"
144
+ | "sandbox"
145
+ | "staging"
146
+ | "beta"
147
+ | "prod"
148
+ | "production";
149
+ export type Pillar =
150
+ | "synthetic"
151
+ | "integration"
152
+ | "performance"
153
+ | "smoke"
154
+ | "journeys"
155
+ | "security"
156
+ | "suites";
157
+
158
+ // ── Schema validation types ───────────────────────────────────────────────────
159
+
160
+ export interface ValidationError {
161
+ /** Dot-separated path to the offending field, e.g. "services.api.health.local". Empty string for top-level parse errors. */
162
+ path: string;
163
+ /** Human-readable description of the problem. */
164
+ message: string;
165
+ /** Optional remediation hint, e.g. "Did you mean X?" */
166
+ suggestion?: string;
167
+ }
168
+
169
+ export interface ValidationResult {
170
+ /** True when the manifest passes all checks with no errors. Warnings do not affect validity. */
171
+ valid: boolean;
172
+ /** Detected schema version (defaults to 1 when no version field is present). */
173
+ version: number;
174
+ /** Fatal validation errors — must be fixed before the manifest can be used. */
175
+ errors: ValidationError[];
176
+ /** Non-fatal warnings — e.g. unknown future version, deprecated patterns. */
177
+ warnings: ValidationError[];
178
+ }
179
+
180
+ // ── .den.json manifest types ─────────────────────────────────────────────
181
+
182
+ export type LogSource =
183
+ | { type: "process"; distinct?: boolean }
184
+ | { type: "shell"; cmd: string; distinct?: boolean }
185
+ | {
186
+ type: "cloudwatch";
187
+ logGroup: string;
188
+ logStream?: string;
189
+ region?: string;
190
+ /**
191
+ * Marks a SUBSYSTEM's log as its own listed source in the /logs view
192
+ * (e.g. shop/db runs shop's own postgres, not infra-stack's shared
193
+ * one). Without it, only a system's primary service log is listed.
194
+ */
195
+ distinct?: boolean;
196
+ };
197
+
198
+ /**
199
+ * Health check config for a service in a given environment.
200
+ *
201
+ * HTTP (default):
202
+ * url: "https://api.example.com/healthz"
203
+ * -- or explicitly --
204
+ * type: http
205
+ * url: "https://api.example.com/healthz"
206
+ *
207
+ * Shell command (for services without an HTTP endpoint, e.g. Postgres):
208
+ * type: shell
209
+ * cmd: "docker exec $(docker ps --filter ancestor=postgres:16 --format '{{.ID}}' | head -1) pg_isready -U postgres"
210
+ * label: "postgres" # optional — shown in UI instead of cmd
211
+ */
212
+ export type ServiceHealthConfig =
213
+ | { type?: "http"; url: string; label?: string }
214
+ | { type: "shell"; cmd: string; label?: string };
215
+
216
+ /**
217
+ * A command can be a plain shell string (detached by default) or an object
218
+ * that explicitly controls behaviour:
219
+ *
220
+ * up: make up # string — detached: true (default)
221
+ * seed:
222
+ * cmd: make db-seed
223
+ * detachable: false # wait for exit, stream output until done
224
+ * setup:
225
+ * cmd: make auth-login
226
+ * detachable: false
227
+ * interactive: true # hand full stdin/stdout/stderr to the user's TTY
228
+ * # required when the command prompts for input (HITL)
229
+ */
230
+ export type CommandDef =
231
+ | string
232
+ | { cmd: string; detachable?: boolean; interactive?: boolean };
233
+
234
+ /**
235
+ * A `depends_on` entry. String form for the common case; object form to opt
236
+ * into blocking startup on the dep's health check (`wait: true`).
237
+ *
238
+ * depends_on:
239
+ * - tasks-runner # string — fire-and-forget (default)
240
+ * - { service: shop/db, wait: true } # block this service's start until shop/db is healthy
241
+ */
242
+ export type DependsOnEntry = string | { service: string; wait?: boolean };
243
+
244
+ export interface ServiceManagementConfig {
245
+ /** env → command-name → command definition. Omit env key if not supported. */
246
+ commands?: Partial<Record<Environment, Record<string, CommandDef>>>;
247
+ /** env → log source. Omit env key if not supported. */
248
+ logs?: Partial<Record<Environment, LogSource>>;
249
+ /** env → health check URL. Omit env key if not supported. */
250
+ health?: Partial<Record<Environment, ServiceHealthConfig>>;
251
+ /**
252
+ * env → where the app opens in a browser (the UI's open-in-new-tab link).
253
+ * Unset: the local edge route, else the origin of the env's HTTP health URL.
254
+ */
255
+ urls?: Partial<Record<Environment, string>>;
256
+ /** Extra named links per env (docs, admin, API explorer…). */
257
+ links?: ServiceLink[];
258
+ /**
259
+ * Other managed services this service depends on.
260
+ * Use service addresses: "tasks-runner", "shop/api", etc.
261
+ * Den will start dependencies before this service.
262
+ *
263
+ * Default is fire-and-forget (5-second detach). To block on a dep's health
264
+ * check before starting this service, use the object form: { service, wait: true }.
265
+ */
266
+ depends_on?: DependsOnEntry[];
267
+ /** OWASP ZAP options: `openapi` per env turns on the API scan; `skip` leaves it out. */
268
+ security?: { openapi?: Partial<Record<Environment, string>>; skip?: boolean };
269
+ }
270
+
271
+ export interface ServiceLink {
272
+ label: string;
273
+ urls: Partial<Record<Environment, string>>;
274
+ }
275
+
276
+ /**
277
+ * Where the deployed commit for one environment is read from. The first that
278
+ * answers wins: `version` (JSON endpoint; `field` holds the SHA, default
279
+ * "build"), then `deployment` (a GitHub environment), then the newest
280
+ * successful run of the repo's release `workflow` on main.
281
+ */
282
+ export interface ReleaseSourceConfig {
283
+ version?: string;
284
+ field?: string;
285
+ deployment?: string;
286
+ }
287
+
288
+ export interface ReleaseManifestConfig {
289
+ workflow?: string;
290
+ environments?: Partial<Record<Environment, ReleaseSourceConfig>>;
291
+ }
292
+
293
+ /** A repo-owned test suite Den can run from the checkout. */
294
+ export interface TestSuiteConfig {
295
+ cmd: string;
296
+ cwd?: string;
297
+ label?: string;
298
+ /** Den environments it may run against (default: local only). */
299
+ environments?: Environment[];
300
+ }
301
+
302
+ export interface SmokeStorageFixtureConfig {
303
+ endpoint?: string;
304
+ endpointFromEnv?: string;
305
+ apiEndpoint?: string;
306
+ apiEndpointFromEnv?: string;
307
+ bucket?: string;
308
+ bucketFromEnv?: string;
309
+ accessKey?: string;
310
+ accessKeyFromEnv?: string;
311
+ secretKey?: string;
312
+ secretKeyFromEnv?: string;
313
+ cleanupContext?: SmokeCleanupContextConfig;
314
+ setup?: CommandDef;
315
+ teardown?: CommandDef;
316
+ /**
317
+ * If set, Den GETs `{baseUrl}{adminProbePath}` before tests when this storage fixture is active.
318
+ * Use a lightweight admin route that shares the same guard as teardown (e.g. files-api
319
+ * `GET /api/v1/admin/ingest-queue/status`). Omit when teardown does not call admin HTTP APIs.
320
+ */
321
+ adminProbePath?: string;
322
+ /**
323
+ * Service-authored remediation text appended to the error when `adminProbePath` returns 403.
324
+ * Den does not interpret this string — it is passed verbatim to the operator. Example:
325
+ * "Set ENABLE_ADMIN_ENDPOINTS in stack.yml for this env, run make infra-sync, and redeploy."
326
+ * Omit to fall back to the raw 403 response body in the error message.
327
+ */
328
+ adminProbe403Hint?: string;
329
+ /**
330
+ * Extra env vars merged into the fixture process env for `setup` / `teardown`
331
+ * (e.g. `PG_*` for repo-owned cleanup scripts). Values should be strings in
332
+ * `.den.yml`; YAML numbers are coerced to strings.
333
+ */
334
+ teardownEnv?: Record<string, string | number | boolean>;
335
+ }
336
+
337
+ export interface SmokeObjectStoreCleanupTargetConfig {
338
+ endpoint?: string;
339
+ endpointFromEnv?: string;
340
+ bucket?: string;
341
+ bucketFromEnv?: string;
342
+ accessKey?: string;
343
+ accessKeyFromEnv?: string;
344
+ secretKey?: string;
345
+ secretKeyFromEnv?: string;
346
+ }
347
+
348
+ export interface SmokeCleanupContextConfig {
349
+ objectStores?: Record<string, SmokeObjectStoreCleanupTargetConfig>;
350
+ }
351
+
352
+ /**
353
+ * Teardown-only smoke fixture: runs a repo-owned sweep command after the
354
+ * suite, with no object-storage payload. Unlike the storage fixture, it is
355
+ * acquired AFTER JWT warming, so the teardown env includes SMOKE_*_TOKEN —
356
+ * required by repos (e.g. shop) whose sweep goes through JWT-authed APIs.
357
+ */
358
+ export interface SmokeCleanupFixtureConfig {
359
+ setup?: CommandDef;
360
+ teardown: CommandDef;
361
+ /** Same semantics as the storage fixture's adminProbePath. */
362
+ adminProbePath?: string;
363
+ /** Same semantics as the storage fixture's adminProbe403Hint. */
364
+ adminProbe403Hint?: string;
365
+ /**
366
+ * Extra env vars merged into the fixture process env for `setup` /
367
+ * `teardown` (e.g. `ENVIRONMENT` so the script can refuse production).
368
+ */
369
+ teardownEnv?: Record<string, string | number | boolean>;
370
+ }
371
+
372
+ export interface SmokeFixturesConfig {
373
+ storage?: Partial<Record<Environment, SmokeStorageFixtureConfig>>;
374
+ cleanup?: Partial<Record<Environment, SmokeCleanupFixtureConfig>>;
375
+ }
376
+
377
+ /** Smoke test config for a single service (lives under `smoke.<name>` in the manifest). */
378
+ export interface SmokeServiceConfig {
379
+ testDir: string;
380
+ testMatch: string;
381
+ baseUrlKey: "api" | "web";
382
+ auth: ("admin" | "user" | "beta")[];
383
+ fixtures?: SmokeFixturesConfig;
384
+ }
385
+
386
+ /**
387
+ * Declarative launch-time option for a perf test, surfaced as a form control in
388
+ * the web UI and resolved into an env var on THIS test's k6 process. Tests
389
+ * declare these in their co-located `manifest.yml` (Den-local) or under
390
+ * `performance.<name>.optionalFields` in `.den.yml` (repo-owned).
391
+ */
392
+ export type PerfOptionalFieldType = "boolean" | "select" | "string" | "number";
393
+
394
+ export interface PerfOptionalField {
395
+ /** Field id, unique within a test. Used as the values map key + localStorage key. */
396
+ name: string;
397
+ /** Form label shown in the UI. */
398
+ label: string;
399
+ /** Control type → which form widget renders + how the value is resolved. */
400
+ type: PerfOptionalFieldType;
401
+ /** Env var set on THIS test's k6 process when the field resolves to a value. */
402
+ env: string;
403
+ /** Seed value when the user has not chosen one. */
404
+ default?: string | number | boolean;
405
+ /** Tooltip / help text. */
406
+ description?: string;
407
+ /** Allowed values for `type: select`. */
408
+ options?: Array<{ value: string; label: string }>;
409
+ /** For `type: boolean`: env value emitted when true (default "1"); env omitted when false. */
410
+ emit?: string;
411
+ /** Greys out the control (and skips the env at runtime) when the referenced field equals the given value. */
412
+ disabledWhen?: { field: string; equals: string | number | boolean };
413
+ }
414
+
415
+ /** Performance test config for a single module (lives under `performance.<name>` in the manifest). */
416
+ export interface PerformanceServiceConfig {
417
+ /** When false, Den skips this module (no k6 run, no storage fixture acquire). Default: enabled. */
418
+ enabled?: boolean;
419
+ testDir: string;
420
+ testMatch: string;
421
+ baseUrlKey: "api" | "web";
422
+ service: string;
423
+ tags?: string[];
424
+ fixtures?: SmokeFixturesConfig;
425
+ /** Declarative launch-time options for repo-owned perf tests (via .den.yml). */
426
+ optionalFields?: PerfOptionalField[];
427
+ }
428
+
429
+ /**
430
+ * Alias for {@link PerformanceServiceConfig}. The module scanner historically
431
+ * imports this name; kept as an alias so both spellings resolve to one shape.
432
+ */
433
+ export type PerformanceModuleConfig = PerformanceServiceConfig;
434
+
435
+ /** Synthetics config (lives under `synthetics` in the manifest). */
436
+ export interface SyntheticsConfig {
437
+ testDir: string;
438
+ testMatch: string;
439
+ baseUrlKey: "api" | "web";
440
+ auth: ("admin" | "user" | "beta")[];
441
+ }
442
+
443
+ /**
444
+ * Journeys config (lives under top-level `journeys` in the manifest).
445
+ * Deliberately thinner than SyntheticsConfig: no baseUrlKey (the journeys
446
+ * runner resolves both web and api base URLs from env config) and no auth
447
+ * array (each journey spec's persona.role declares its own role).
448
+ */
449
+ export interface JourneysConfig {
450
+ testDir: string;
451
+ testMatch: string;
452
+ /**
453
+ * The app under test (resources, session token, web-origin API feed).
454
+ * Raw here; parsed and validated by the journeys pillar
455
+ * (src/pillars/journeys/app-config.ts).
456
+ */
457
+ app?: unknown;
458
+ }
459
+
460
+ /**
461
+ * Optional GitHub metadata in .den.yml.
462
+ * Owner/repo are auto-derived from git remote, so this section is only
463
+ * needed when the den environment name differs from the GitHub environment
464
+ * name (rare — by convention they match).
465
+ */
466
+ export interface GitHubManifestConfig {
467
+ /**
468
+ * Map den environment names → GitHub environment names.
469
+ * Only needed for overrides; unspecified envs default to the den name.
470
+ * e.g. production: prod
471
+ */
472
+ environments?: Partial<Record<Environment, string>>;
473
+ }
474
+
475
+ /** ZAP scan modes, least to most intrusive: passive-only, API active, full active. */
476
+ export type SecurityMode = "baseline" | "api" | "full";
477
+
478
+ /**
479
+ * Security (ZAP) scan target for a single service (lives under
480
+ * `security.<name>` in the manifest). Either `baseUrlKey` or `hosts` must be
481
+ * present; explicit `hosts` win when both are given for an environment.
482
+ */
483
+ export interface SecurityServiceConfig {
484
+ /** Resolve the target from the den environment config (baseUrls.api / baseUrls.web). */
485
+ baseUrlKey?: "api" | "web";
486
+ /** Explicit env → target base URLs; wins over baseUrlKey for that env. */
487
+ hosts?: Partial<Record<Environment, string[]>>;
488
+ /** Repo-relative OpenAPI spec path, or a URL template (`{baseUrl}` expands). */
489
+ openapi?: string;
490
+ /** Roles to authenticate as; empty/omitted = unauthenticated scan. */
491
+ auth?: ("admin" | "user" | "beta")[];
492
+ /** Regexes (no whitespace) over URLs ZAP must not touch. */
493
+ exclude?: string[];
494
+ /** Allowed modes per env; capped by the pillar's per-env ceiling regardless. */
495
+ modes?: Partial<Record<Environment, SecurityMode[]>>;
496
+ }
497
+
498
+ /** Parsed .den.yml/.den.json manifest — top-level shape. */
499
+ export interface RawManifest {
500
+ name: string;
501
+ /**
502
+ * Front-door service for cross-repo depends_on.
503
+ * When another repo lists this multi-service repo by bare name in its
504
+ * depends_on, den resolves it to "repoName/entrypoint".
505
+ * Single-service repos need no declaration — bare name is always correct.
506
+ */
507
+ entrypoint?: string;
508
+ smoke?: Record<string, SmokeServiceConfig>;
509
+ performance?: Record<string, PerformanceServiceConfig>;
510
+ synthetics?: SyntheticsConfig;
511
+ journeys?: JourneysConfig;
512
+ security?: Record<string, SecurityServiceConfig>;
513
+ services?: Record<string, ServiceManagementConfig>;
514
+ github?: GitHubManifestConfig;
515
+ release?: ReleaseManifestConfig;
516
+ tests?: Record<string, TestSuiteConfig>;
517
+ /** The application (config/den.yml `applications:`) this repo belongs to. */
518
+ application?: string;
519
+ /**
520
+ * What the repo is. `service` (the default) runs on its own and has a
521
+ * status. `module` is code a host service loads or invokes (a bolt-on);
522
+ * `library` is code other repos import. Neither has a runtime of its own:
523
+ * den never starts or health-checks them and shows what they
524
+ * contribute to instead.
525
+ */
526
+ kind?: RepoKind;
527
+ /** For a module or library: the repos it contributes to. */
528
+ contributes_to?: string[];
529
+ /**
530
+ * The repo is a Docker Compose project: den runs it with no host ports
531
+ * behind the edge, and supplies up/down/restart/logs/health unless the
532
+ * entrypoint service declares its own.
533
+ */
534
+ compose?: ManifestComposeConfig;
535
+ }
536
+
537
+ export type RepoKind = "service" | "module" | "library";
538
+
539
+ export interface ManifestComposeConfig {
540
+ /** Stack name, used in hostnames; defaults to the manifest name. */
541
+ name?: string;
542
+ dir?: string;
543
+ files?: string[];
544
+ routes?: Record<string, string>;
545
+ generate?: string[];
546
+ /** Other stacks (by stack or repo name) this one calls. */
547
+ uses?: string[];
548
+ env?: Record<string, string | number | boolean>;
549
+ profiles?: string[];
550
+ /** service → replacement healthcheck `test` (written to den's override, not the repo). */
551
+ healthchecks?: Record<string, string[]>;
552
+ }
553
+
554
+ export interface MigrationResult {
555
+ /** The migrated manifest, ready to write back to disk. */
556
+ manifest: RawManifest;
557
+ /** Schema version of the input manifest (1 when no version field was present). */
558
+ fromVersion: number;
559
+ /** Schema version of the output manifest. */
560
+ toVersion: number;
561
+ /** Human-readable descriptions of each transformation applied. Empty when nothing changed. */
562
+ changes: string[];
563
+ }
564
+
565
+ /** A repo discovered under .den/<name>/ with its parsed manifest. */
566
+ export interface DiscoveredRepo {
567
+ repoName: string;
568
+ repoPath: string;
569
+ manifest: RawManifest;
570
+ }
571
+
572
+ // ── Service catalog & workspace types ────────────────────────────────────────
573
+
574
+ export interface ServiceCatalogEntry {
575
+ /** Full GitHub slug: "Globex-Corp/repo-name" — used when cloning managed repos */
576
+ gh: string;
577
+ }
578
+
579
+ /** Keyed by GH repo name (e.g. "shop", "tasks-runner") */
580
+ export type ServiceCatalog = Record<string, ServiceCatalogEntry>;
581
+
582
+ export interface WorkspaceServiceEntry {
583
+ /**
584
+ * Auto-start toggle: absent (or true) = the system starts with workspace-wide
585
+ * ups (web master ▶ Up, `den up` closure walks). false = skipped by the
586
+ * STARTING half of workspace-wide ops (up/restart) — but workspace down still
587
+ * stops it, and explicit targeting (card ⋮ up, `den up <repo>`) always
588
+ * wins. Toggled per system from the web ⋮ menu.
589
+ */
590
+ autostart?: boolean;
591
+ /**
592
+ * How this service is managed in this workspace.
593
+ * dev-managed — developer's own checkout; den never modifies it
594
+ * managed — den clones and tracks a branch in .den-services/<repo>/
595
+ */
596
+ mode: "dev-managed" | "managed";
597
+ /**
598
+ * For linked repos: absolute path to the local checkout in this workspace.
599
+ */
600
+ path?: string;
601
+ /**
602
+ * For managed repos: branch to track. Defaults to "main".
603
+ */
604
+ branch?: string;
605
+ }
606
+
607
+ export interface WorkspaceConfig {
608
+ /**
609
+ * Service entries keyed by service address.
610
+ * Single-service repos: "tasks-runner"
611
+ * Mono-repo subsystems: "shop/api", "shop/web"
612
+ */
613
+ services: Record<string, WorkspaceServiceEntry>;
614
+ /**
615
+ * Feature workspace: path to the directory containing managed clones.
616
+ * Relative to den root. Only managed repos are cloned here —
617
+ * linked repos always stay in their own location on disk.
618
+ */
619
+ directory?: string;
620
+ /**
621
+ * Managed repos to clone into the workspace directory, keyed by
622
+ * GH repo name. Only valid when directory is set.
623
+ */
624
+ repos?: Record<string, string>; // repoName → branch
625
+ /**
626
+ * Optional editor override for this workspace. When set, opening the workspace
627
+ * directory uses this editor instead of the user's global default.
628
+ * Per-language overrides (from stack.yml) still take precedence at repo level.
629
+ */
630
+ editor?: string;
631
+ /**
632
+ * Blueprint repo list (`defaults.workspace.repos`) as last applied to this
633
+ * workspace — `default` workspace only. Top-up converges on blueprint
634
+ * DELTAS against this list, so a standard repo the user removed stays
635
+ * removed instead of resurrecting on the next command; only repos that
636
+ * newly JOIN the blueprint are added.
637
+ */
638
+ seeded_repos?: string[];
639
+ }
640
+
641
+ export interface WorkspacesFile {
642
+ /**
643
+ * Workspaces currently active (may be more than one). Services may run
644
+ * under any active workspace; see daemon ownership / conflict rules.
645
+ */
646
+ active: string[];
647
+ /**
648
+ * UI/CLI default target workspace. Must be a member of `active` when set.
649
+ * Resolved via DEN_WORKSPACE_OVERRIDE → focus → active[0].
650
+ */
651
+ focus?: string;
652
+ workspaces: Record<string, WorkspaceConfig>;
653
+ }
654
+
655
+ /** Runtime-resolved service info — not persisted */
656
+ export interface ResolvedService {
657
+ /** Service address as used in workspace config: "shop/api" or "tasks-runner" */
658
+ address: string;
659
+ /** GH repo name (catalog key): "shop" or "tasks-runner" */
660
+ repoName: string;
661
+ /** Subsystem key from .den.yml services block: "api", "web", or same as repoName */
662
+ subsystem: string;
663
+ /** Absolute path to the repo root for this workspace context */
664
+ repoPath: string;
665
+ /** Branch currently targeted (null for linked repos — den doesn't control their branch) */
666
+ branch: string | null;
667
+ mode: "dev-managed" | "managed";
668
+ }
669
+
670
+ export interface TokenSet {
671
+ access_token: string;
672
+ refresh_token?: string;
673
+ id_token?: string;
674
+ expires_in?: number;
675
+ token_type?: string;
676
+ scope?: string;
677
+ [key: string]: unknown;
678
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A fixed marker the web server adds to every HTML page it serves, so the CLI
3
+ * can tell its own UI is up without matching any product or brand text
4
+ * (a brand may differ from the product title, even in case).
5
+ */
6
+ export const DEN_UI_MARKER = '<meta name="den-ui" content="1">';
7
+
8
+ /** Insert the marker right after <head> (or at the start when there is none). */
9
+ export function addDenUiMarker(html: string): string {
10
+ if (html.includes(DEN_UI_MARKER)) return html;
11
+ const head = /<head(\s[^>]*)?>/i.exec(html);
12
+ if (!head) return DEN_UI_MARKER + html;
13
+ const at = head.index + head[0].length;
14
+ return html.slice(0, at) + DEN_UI_MARKER + html.slice(at);
15
+ }
16
+
17
+ export function hasDenUiMarker(html: string): boolean {
18
+ return html.includes(DEN_UI_MARKER);
19
+ }