@monster-orange/den 0.0.0-stage → 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 +166 -2
  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 +104 -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,175 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { getInvokeCwd } from "../../shared/paths.js";
4
+ import type {
5
+ ResolvedService,
6
+ ServiceCatalog,
7
+ WorkspaceConfig,
8
+ } from "../../shared/types/index.js";
9
+ import {
10
+ listWorkspaceServices,
11
+ parseAddress,
12
+ } from "../../shared/environment-manager/workspaces.js";
13
+ import { readManifest } from "../../shared/manifest/read-manifest.js";
14
+ import { getGitHubCoords } from "../../shared/smoke/git-remote.js";
15
+ import type { AliasResolver } from "../../shared/environment-manager/derivation/alias.js";
16
+ import { loadAliasResolver } from "../../shared/environment-manager/derivation/load-alias.js";
17
+
18
+ import { product } from "../../shared/product.js";
19
+ export interface InvocationTarget {
20
+ repoRoot: string;
21
+ repoName: string;
22
+ addresses: string[];
23
+ }
24
+
25
+ function realPathSafe(input: string): string {
26
+ try {
27
+ return fs.realpathSync.native(input);
28
+ } catch {
29
+ return path.resolve(input);
30
+ }
31
+ }
32
+
33
+ function hasRepoMarker(dir: string): boolean {
34
+ return (
35
+ fs.existsSync(path.join(dir, `${product().manifestYml}`)) ||
36
+ fs.existsSync(path.join(dir, `${product().manifestJson}`)) ||
37
+ fs.existsSync(path.join(dir, ".git"))
38
+ );
39
+ }
40
+
41
+ export function findInvocationRepoRoot(
42
+ cwd: string = getInvokeCwd(),
43
+ ): string | null {
44
+ let dir = path.resolve(cwd);
45
+ while (dir.length > 0) {
46
+ if (hasRepoMarker(dir)) return dir;
47
+ const parent = path.dirname(dir);
48
+ if (parent === dir) return null;
49
+ dir = parent;
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * The canonical repo-system the cwd belongs to (and its repo root), identified by
56
+ * IDENTITY not exact path — so it resolves whether you're in a sibling checkout
57
+ * or a managed clone. Tries, in order: the repo's `.den.yml` name, the
58
+ * folder name, the parent folder name (managed clones live at `<repo>/<branch>/`).
59
+ * Returns null when the cwd isn't inside a known repo.
60
+ */
61
+ export function resolveCwdRepo(
62
+ alias: AliasResolver = loadAliasResolver(),
63
+ ): { repo: string; root: string } | null {
64
+ const root = findInvocationRepoRoot();
65
+ if (!root) return null;
66
+ const candidates = [
67
+ readManifest(root)?.name,
68
+ path.basename(root),
69
+ path.basename(path.dirname(root)),
70
+ ];
71
+ for (const c of candidates) {
72
+ if (!c) continue;
73
+ const resolved = alias.resolve(c);
74
+ const node =
75
+ resolved && alias.knownRepos.has(resolved)
76
+ ? resolved
77
+ : alias.knownRepos.has(c)
78
+ ? c
79
+ : undefined;
80
+ if (node) return { repo: node, root };
81
+ }
82
+ return null;
83
+ }
84
+
85
+ function uniqueServices(services: ResolvedService[]): ResolvedService[] {
86
+ const seen = new Set<string>();
87
+ const result: ResolvedService[] = [];
88
+ for (const svc of services) {
89
+ if (seen.has(svc.address)) continue;
90
+ seen.add(svc.address);
91
+ result.push(svc);
92
+ }
93
+ return result;
94
+ }
95
+
96
+ function servicesForRepoPath(
97
+ repoRoot: string,
98
+ services: ResolvedService[],
99
+ ): ResolvedService[] {
100
+ const target = realPathSafe(repoRoot);
101
+ return services.filter((svc) => realPathSafe(svc.repoPath) === target);
102
+ }
103
+
104
+ function servicesForGitRemote(
105
+ repoRoot: string,
106
+ services: ResolvedService[],
107
+ catalog: ServiceCatalog,
108
+ ): ResolvedService[] {
109
+ const coords = getGitHubCoords(repoRoot);
110
+ if (!coords) return [];
111
+
112
+ const slug = `${coords.owner}/${coords.repo}`.toLowerCase();
113
+ return services.filter((svc) => {
114
+ const catalogSlug = catalog[svc.repoName]?.gh?.toLowerCase();
115
+ return catalogSlug === slug || svc.repoName === coords.repo;
116
+ });
117
+ }
118
+
119
+ function selectAddresses(
120
+ repoRoot: string,
121
+ repoName: string,
122
+ services: ResolvedService[],
123
+ ): string[] {
124
+ const unique = uniqueServices(services);
125
+ const manifest = readManifest(repoRoot);
126
+ const serviceKeys = Object.keys(manifest?.services ?? {});
127
+
128
+ if (serviceKeys.length <= 1 || unique.length <= 1) {
129
+ return [unique[0].address];
130
+ }
131
+
132
+ if (manifest?.entrypoint) {
133
+ const entrypointAddress = `${repoName}/${manifest.entrypoint}`;
134
+ const matched = unique.find(
135
+ (svc) =>
136
+ svc.address === entrypointAddress ||
137
+ svc.subsystem === manifest.entrypoint,
138
+ );
139
+ return [matched?.address ?? entrypointAddress];
140
+ }
141
+
142
+ return unique.map((svc) => svc.address);
143
+ }
144
+
145
+ export function resolveInvocationTarget(
146
+ workspaceName: string,
147
+ workspace: WorkspaceConfig,
148
+ catalog: ServiceCatalog,
149
+ cwd: string = getInvokeCwd(),
150
+ ): InvocationTarget | null {
151
+ const repoRoot = findInvocationRepoRoot(cwd);
152
+ if (!repoRoot) return null;
153
+
154
+ const workspaceServices = listWorkspaceServices(
155
+ workspace,
156
+ catalog,
157
+ workspaceName,
158
+ );
159
+ const matched = uniqueServices([
160
+ ...servicesForRepoPath(repoRoot, workspaceServices),
161
+ ...servicesForGitRemote(repoRoot, workspaceServices, catalog),
162
+ ]);
163
+ if (matched.length === 0) return null;
164
+
165
+ const repoName =
166
+ matched[0].repoName ||
167
+ parseAddress(matched[0].address, catalog).repoName ||
168
+ path.basename(repoRoot);
169
+
170
+ return {
171
+ repoRoot,
172
+ repoName,
173
+ addresses: selectAddresses(repoRoot, repoName, matched),
174
+ };
175
+ }
@@ -0,0 +1,104 @@
1
+ import chalk from "chalk";
2
+ import type { AliasResolver } from "../../shared/environment-manager/derivation/alias.js";
3
+ import {
4
+ followTailSources,
5
+ resolveTailSources,
6
+ } from "../../shared/environment-manager/log-tail.js";
7
+ import type { ResolvedService } from "../../shared/types/index.js";
8
+ import { resolveCwdRepo } from "./invocation-target.js";
9
+
10
+ import { product } from "../../shared/product.js";
11
+ export type LogsTarget =
12
+ | { kind: "repo"; target: string }
13
+ | { kind: "address"; target: string }
14
+ | { kind: "unknown-repo"; arg: string }
15
+ | { kind: "no-cwd-repo" };
16
+
17
+ /**
18
+ * `den logs [repo]` target resolution: an explicit arg is alias-resolved
19
+ * against the known repo-systems (a `repo/subsystem` address passes through
20
+ * untouched); with no arg, the cwd repo is used.
21
+ */
22
+ export function resolveLogsTarget(
23
+ args: string[],
24
+ alias: AliasResolver,
25
+ cwdRepo: () => { repo: string; root: string } | null = () =>
26
+ resolveCwdRepo(alias),
27
+ ): LogsTarget {
28
+ const arg = args.find((a) => !a.startsWith("-"));
29
+ if (arg) {
30
+ if (arg.includes("/")) return { kind: "address", target: arg };
31
+ const resolved = alias.resolve(arg);
32
+ const repo =
33
+ resolved && alias.knownRepos.has(resolved)
34
+ ? resolved
35
+ : alias.knownRepos.has(arg)
36
+ ? arg
37
+ : undefined;
38
+ return repo
39
+ ? { kind: "repo", target: repo }
40
+ : { kind: "unknown-repo", arg };
41
+ }
42
+ const cwd = cwdRepo();
43
+ return cwd ? { kind: "repo", target: cwd.repo } : { kind: "no-cwd-repo" };
44
+ }
45
+
46
+ /**
47
+ * Tail the service's live logs to stdout until the stream ends or the user
48
+ * hits Ctrl-C. Returns the exit code the CLI should finish with.
49
+ * `waitForSourceMs` keeps polling for a log source to appear — used right
50
+ * after starting a service, when its container/daemon log may not exist yet
51
+ * (e.g. tab-runtime ups write no daemon file until the container is up).
52
+ */
53
+ export async function tailServiceLogs(
54
+ resolved: ResolvedService,
55
+ opts: { waitForSourceMs?: number } = {},
56
+ ): Promise<number> {
57
+ let candidates = resolveTailSources(resolved);
58
+ if (candidates.length === 0 && opts.waitForSourceMs) {
59
+ const deadline = Date.now() + opts.waitForSourceMs;
60
+ while (candidates.length === 0 && Date.now() < deadline) {
61
+ await new Promise((resolve) => setTimeout(resolve, 1_000));
62
+ candidates = resolveTailSources(resolved);
63
+ }
64
+ }
65
+ if (candidates.length === 0) {
66
+ console.log(
67
+ chalk.yellow(`\n No local log source found for ${resolved.address}.`),
68
+ );
69
+ console.log(
70
+ chalk.gray(
71
+ ` (no manifest logs.local command, no running container, and no ${product().cli}`,
72
+ ),
73
+ );
74
+ console.log(
75
+ chalk.gray(
76
+ ` daemon log — services started outside ${product().cli} don't get one; '${product().cli} up ${resolved.repoName}' does.)\n`,
77
+ ),
78
+ );
79
+ return 1;
80
+ }
81
+
82
+ console.log(
83
+ chalk.gray(`\n Tailing logs for ${resolved.address} — Ctrl-C to stop.`),
84
+ );
85
+ const handle = followTailSources(candidates, {
86
+ onLine: (line) => console.log(line),
87
+ onMeta: (line) => console.log(chalk.gray(` ${line}`)),
88
+ });
89
+ // Ctrl-C stops the tail (the child shares our process group); SIGTERM/SIGHUP
90
+ // land only on this process, so without stop() they'd orphan a live
91
+ // `docker logs -f`/`tail -F` child.
92
+ const onSignal = (): void => handle.stop();
93
+ const signals: NodeJS.Signals[] = ["SIGINT", "SIGTERM", "SIGHUP"];
94
+ for (const sig of signals) process.once(sig, onSignal);
95
+ // `den logs x | head` closes stdout mid-stream: without a handler the
96
+ // next console.log crashes node with an unhandled EPIPE and orphans the
97
+ // detached tail child. Standard pipe semantics: stop tailing, exit clean.
98
+ const onStdoutError = (): void => handle.stop();
99
+ process.stdout.on("error", onStdoutError);
100
+ const code = await handle.done;
101
+ for (const sig of signals) process.removeListener(sig, onSignal);
102
+ process.stdout.removeListener("error", onStdoutError);
103
+ return code;
104
+ }
@@ -0,0 +1,31 @@
1
+ interface NamedModule {
2
+ name: string;
3
+ }
4
+
5
+ interface ResolvePerformanceModulesOptions {
6
+ availableModules: NamedModule[];
7
+ selectedModule?: string | null;
8
+ repoName?: string | null;
9
+ }
10
+
11
+ export function resolvePerformanceModules({
12
+ availableModules,
13
+ selectedModule,
14
+ repoName,
15
+ }: ResolvePerformanceModulesOptions): string[] {
16
+ if (selectedModule) {
17
+ return [selectedModule];
18
+ }
19
+
20
+ if (repoName) {
21
+ const repoModules = availableModules
22
+ .map((module) => module.name)
23
+ .filter((name) => name.startsWith(`${repoName}/`));
24
+ if (repoModules.length === 0) {
25
+ throw new Error(`No performance modules found for repo "${repoName}"`);
26
+ }
27
+ return repoModules;
28
+ }
29
+
30
+ return availableModules.map((module) => module.name);
31
+ }