@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,112 @@
1
+ /**
2
+ * k6 Configuration
3
+ * Base configuration for performance tests
4
+ */
5
+
6
+ export const options = {
7
+ // VUs (Virtual Users)
8
+ vus: 10,
9
+ duration: "30s",
10
+
11
+ // Thresholds - Define SLAs
12
+ thresholds: {
13
+ // 95% of requests should be below 500ms
14
+ http_req_duration: ["p(95)<500"],
15
+
16
+ // Error rate should be less than 1%
17
+ http_req_failed: ["rate<0.01"],
18
+
19
+ // 90% of requests should be below 300ms
20
+ "http_req_duration{name:health}": ["p(90)<300"],
21
+ },
22
+
23
+ // Stages for ramping VUs
24
+ stages: [
25
+ { duration: "30s", target: 20 }, // Ramp up to 20 users
26
+ { duration: "1m", target: 20 }, // Stay at 20 users
27
+ { duration: "30s", target: 0 }, // Ramp down to 0 users
28
+ ],
29
+
30
+ // Tags - For organizing metrics
31
+ tags: {
32
+ test_type: "load",
33
+ },
34
+
35
+ // Summaries
36
+ summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"],
37
+
38
+ // HTTP settings
39
+ http: {
40
+ timeout: "30s",
41
+ },
42
+
43
+ // Rate limiting
44
+ // rps: 100, // Max requests per second
45
+
46
+ // Scenarios - Advanced load patterns
47
+ scenarios: {
48
+ // Constant load
49
+ constant_load: {
50
+ executor: "constant-vus",
51
+ vus: 10,
52
+ duration: "1m",
53
+ tags: { scenario: "constant" },
54
+ },
55
+
56
+ // Ramping load
57
+ ramping_load: {
58
+ executor: "ramping-vus",
59
+ startVUs: 0,
60
+ stages: [
61
+ { duration: "30s", target: 50 },
62
+ { duration: "1m", target: 50 },
63
+ { duration: "30s", target: 0 },
64
+ ],
65
+ tags: { scenario: "ramping" },
66
+ },
67
+
68
+ // Per-VU iterations
69
+ // per_vu_iterations: {
70
+ // executor: 'per-vu-iterations',
71
+ // vus: 10,
72
+ // iterations: 100,
73
+ // maxDuration: '5m',
74
+ // tags: { scenario: 'iterations' },
75
+ // },
76
+
77
+ // Constant arrival rate
78
+ // constant_arrival_rate: {
79
+ // executor: 'constant-arrival-rate',
80
+ // rate: 100, // 100 iterations per timeUnit
81
+ // timeUnit: '1s',
82
+ // duration: '1m',
83
+ // preAllocatedVUs: 50,
84
+ // maxVUs: 100,
85
+ // tags: { scenario: 'arrival' },
86
+ // },
87
+ },
88
+ };
89
+
90
+ /**
91
+ * Load environment configuration
92
+ */
93
+ export function getEnvironment() {
94
+ const env = __ENV.E2E_ENVIRONMENT || "local";
95
+ const configPath = `./config/environments/${env}.json`;
96
+
97
+ // In k6, we need to read config from environment variables
98
+ // since k6 doesn't support direct file reading in init context
99
+ return {
100
+ name: env,
101
+ baseUrl: __ENV.API_BASE_URL || "http://localhost:3000",
102
+ apiTimeout: parseInt(__ENV.API_TIMEOUT || "30000", 10),
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Get authentication token
108
+ * Note: In real tests, you'd get this via OAuth flow or from ENV
109
+ */
110
+ export function getAuthToken() {
111
+ return __ENV.AUTH_TOKEN || "";
112
+ }
@@ -0,0 +1,345 @@
1
+ import { spawn } from "child_process";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { parseArgs } from "util";
5
+ import { resolveManifestBaseUrl } from "../../shared/service-url.js";
6
+ import { getDistRoot } from "../../shared/paths.js";
7
+ import type { Environment } from "../../shared/types/index.js";
8
+ import { findManifest, resolveRepoPath } from "../smoke/discover.js";
9
+ import { assertStorageAdminProbe } from "../smoke/admin-probe.js";
10
+ import {
11
+ acquireFixture,
12
+ releaseFixturesForConsumer,
13
+ } from "../smoke/fixture-manager.js";
14
+ import { resolvePerformanceStorageFixture } from "../smoke/fixtures.js";
15
+ import {
16
+ PERF_CURRENT_REPORT_DIR,
17
+ getPerfSummaryArtifactPath,
18
+ } from "../../shared/perf/report-paths.js";
19
+ import {
20
+ writeCurrentPerfRunReport,
21
+ resetCurrentPerfRunReport,
22
+ } from "../../shared/perf/report-store.js";
23
+
24
+ import { product } from "../../shared/product.js";
25
+ export interface PerfRunModule {
26
+ repoName: string;
27
+ moduleName: string;
28
+ }
29
+
30
+ export interface RunPerfModulesOptions {
31
+ modules: PerfRunModule[];
32
+ environment: Environment;
33
+ baseUrl: string;
34
+ useLocalCheckout?: boolean;
35
+ env?: NodeJS.ProcessEnv;
36
+ }
37
+
38
+ function spawnK6Process(
39
+ testFile: string,
40
+ env: NodeJS.ProcessEnv,
41
+ summaryExportPath?: string,
42
+ ): Promise<{ success: boolean; exitCode: number }> {
43
+ const args = ["run"];
44
+ if (summaryExportPath) {
45
+ args.push("--summary-export", summaryExportPath);
46
+ }
47
+ args.push(testFile);
48
+ return new Promise((resolve) => {
49
+ const proc = spawn("k6", args, {
50
+ env,
51
+ stdio: "inherit",
52
+ });
53
+
54
+ proc.on("close", (code) => {
55
+ resolve({
56
+ success: code === 0,
57
+ exitCode: code ?? 1,
58
+ });
59
+ });
60
+
61
+ proc.on("error", (error) => {
62
+ console.error(`Failed to execute k6: ${error.message}`);
63
+ resolve({
64
+ success: false,
65
+ exitCode: 1,
66
+ });
67
+ });
68
+ });
69
+ }
70
+
71
+ function logPerfTeardownFailure(error: unknown): void {
72
+ if (error instanceof AggregateError) {
73
+ console.error("Performance storage fixture teardown failed:");
74
+ for (const [index, err] of error.errors.entries()) {
75
+ console.error(` [${index + 1}/${error.errors.length}]`, err);
76
+ }
77
+ return;
78
+ }
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ console.error(`Performance storage fixture teardown failed: ${message}`);
81
+ }
82
+
83
+ export async function runPerfModules(
84
+ options: RunPerfModulesOptions,
85
+ ): Promise<{ success: boolean; exitCode: number }> {
86
+ const env: NodeJS.ProcessEnv = {
87
+ ...process.env,
88
+ ...(options.env ?? {}),
89
+ E2E_ENVIRONMENT: options.environment,
90
+ BASE_URL: options.baseUrl,
91
+ BASE_API_URL: options.baseUrl,
92
+ };
93
+ const releases: Array<() => Promise<void>> = [];
94
+
95
+ let k6Outcome = { success: true, exitCode: 0 };
96
+ let caughtError: unknown = undefined;
97
+ let teardownFailed = false;
98
+ let disabledSkipCount = 0;
99
+ const completedModuleKeys: string[] = [];
100
+
101
+ // Den-root anchored: this child inherits its spawner's (arbitrary) cwd.
102
+ fs.mkdirSync(path.resolve(getDistRoot(), PERF_CURRENT_REPORT_DIR), {
103
+ recursive: true,
104
+ });
105
+ // Clear the prior run's report so a stale one is never served.
106
+ resetCurrentPerfRunReport();
107
+
108
+ try {
109
+ for (const moduleRef of options.modules) {
110
+ const discovered = findManifest(moduleRef.repoName);
111
+ if (!discovered) {
112
+ throw new Error(
113
+ `Performance repo "${moduleRef.repoName}" is not registered with ${product().cli}`,
114
+ );
115
+ }
116
+
117
+ const repoPath = resolveRepoPath(
118
+ discovered,
119
+ options.environment,
120
+ options.useLocalCheckout ?? false,
121
+ );
122
+ const moduleConfig =
123
+ discovered.manifest.performance?.[moduleRef.moduleName];
124
+ if (!moduleConfig) {
125
+ throw new Error(
126
+ `Performance module "${moduleRef.moduleName}" not found in ${moduleRef.repoName}/${product().manifestYml}`,
127
+ );
128
+ }
129
+
130
+ if (moduleConfig.enabled === false) {
131
+ console.warn(
132
+ `Skipping disabled performance module "${moduleRef.moduleName}" (${moduleRef.repoName})`,
133
+ );
134
+ disabledSkipCount += 1;
135
+ continue;
136
+ }
137
+
138
+ const moduleEnv = {
139
+ ...env,
140
+ PERF_TEST_ID: `${moduleRef.repoName}/${moduleRef.moduleName}`,
141
+ PERF_RUN_ID:
142
+ env.PERF_RUN_ID ??
143
+ `${moduleRef.repoName}-${moduleRef.moduleName}-${Date.now()}`,
144
+ };
145
+
146
+ if (moduleConfig.fixtures?.storage?.[options.environment]) {
147
+ const lifecycle = resolvePerformanceStorageFixture(
148
+ discovered.manifest,
149
+ moduleRef.moduleName,
150
+ options.environment,
151
+ moduleEnv,
152
+ );
153
+ if (lifecycle.adminProbePath) {
154
+ await assertStorageAdminProbe({
155
+ baseUrl: options.baseUrl,
156
+ adminProbePath: lifecycle.adminProbePath,
157
+ label: `${moduleRef.repoName} / ${moduleRef.moduleName} (${moduleConfig.service})`,
158
+ ...(lifecycle.adminProbe403Hint
159
+ ? { hintOn403: () => lifecycle.adminProbe403Hint! }
160
+ : {}),
161
+ });
162
+ }
163
+ const fixtureEnv = { ...moduleEnv, ...lifecycle.envVars };
164
+ const release = await acquireFixture({
165
+ fixtureKey: lifecycle.fixtureKey,
166
+ resolvedPayload: lifecycle.resolvedPayload as unknown as Record<
167
+ string,
168
+ unknown
169
+ >,
170
+ cwd: repoPath,
171
+ env: fixtureEnv,
172
+ setup: lifecycle.setup,
173
+ teardown: lifecycle.teardown,
174
+ });
175
+ releases.push(release);
176
+ Object.assign(moduleEnv, lifecycle.envVars);
177
+ }
178
+
179
+ const testFile = path.resolve(
180
+ repoPath,
181
+ moduleConfig.testDir,
182
+ moduleConfig.testMatch,
183
+ );
184
+ const moduleKey = `${moduleRef.repoName}/${moduleRef.moduleName}`;
185
+ // Absolute: k6 resolves relative paths against its inherited cwd.
186
+ const summaryPath = path.resolve(
187
+ getDistRoot(),
188
+ getPerfSummaryArtifactPath(moduleKey),
189
+ );
190
+ const result = await spawnK6Process(testFile, moduleEnv, summaryPath);
191
+ completedModuleKeys.push(moduleKey);
192
+ k6Outcome = result;
193
+ if (!result.success) {
194
+ break;
195
+ }
196
+ }
197
+
198
+ if (
199
+ disabledSkipCount === options.modules.length &&
200
+ options.modules.length > 0
201
+ ) {
202
+ throw new Error(
203
+ `All requested performance modules are disabled in manifest: ${options.modules
204
+ .map((m) => `${m.repoName}/${m.moduleName}`)
205
+ .join(", ")}`,
206
+ );
207
+ }
208
+ } catch (e) {
209
+ caughtError = e;
210
+ } finally {
211
+ try {
212
+ await releaseFixturesForConsumer(releases);
213
+ } catch (e) {
214
+ teardownFailed = true;
215
+ logPerfTeardownFailure(e);
216
+ }
217
+ console.log(" Storage fixture teardown completed.");
218
+ }
219
+
220
+ if (completedModuleKeys.length > 0) {
221
+ try {
222
+ writeCurrentPerfRunReport({
223
+ environment: options.environment,
224
+ modules: completedModuleKeys,
225
+ finishedAt: new Date().toISOString(),
226
+ });
227
+ } catch (e) {
228
+ const msg = e instanceof Error ? e.message : String(e);
229
+ console.warn(`Failed to generate perf report: ${msg}`);
230
+ }
231
+ }
232
+
233
+ if (caughtError !== undefined) {
234
+ throw caughtError;
235
+ }
236
+ if (teardownFailed) {
237
+ return {
238
+ success: false,
239
+ exitCode: k6Outcome.success ? 1 : k6Outcome.exitCode,
240
+ };
241
+ }
242
+ return k6Outcome;
243
+ }
244
+
245
+ function cliArgs() {
246
+ return parseArgs({
247
+ options: {
248
+ env: { type: "string" },
249
+ repo: { type: "string", multiple: true },
250
+ module: { type: "string", multiple: true },
251
+ "base-url": { type: "string" },
252
+ "use-local-checkout": { type: "boolean" },
253
+ },
254
+ }).values;
255
+ }
256
+
257
+ /**
258
+ * Auto-resolve a base URL for perf runs from the manifest's
259
+ * `services.*.health.<env>` entry, falling back to
260
+ * `config/environments/<env>.json → baseUrls.api`.
261
+ */
262
+ export function resolveBaseUrlFromManifest(
263
+ repoName: string,
264
+ moduleName: string,
265
+ environment: Environment,
266
+ ): string | null {
267
+ const discovered = findManifest(repoName);
268
+ if (!discovered) return null;
269
+
270
+ const moduleConfig = discovered.manifest.performance?.[moduleName];
271
+ if (!moduleConfig) return null;
272
+
273
+ const serviceName = moduleConfig.service ?? repoName;
274
+ const baseUrlKey = moduleConfig.baseUrlKey ?? "api";
275
+
276
+ const manifestUrl = resolveManifestBaseUrl({
277
+ manifest: discovered.manifest,
278
+ repoName,
279
+ serviceName,
280
+ environment,
281
+ baseUrlKey,
282
+ });
283
+ if (manifestUrl) return manifestUrl;
284
+
285
+ try {
286
+ const configPath = path.resolve(
287
+ `./config/environments/${environment}.json`,
288
+ );
289
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
290
+ return config.baseUrls?.services?.[serviceName] ?? null;
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+
296
+ export async function main(): Promise<number> {
297
+ const values = cliArgs();
298
+ const repos = values.repo ?? [];
299
+ const moduleNames = values.module ?? [];
300
+ if (repos.length !== moduleNames.length || repos.length === 0) {
301
+ throw new Error(
302
+ "Performance runner requires matching --repo and --module arguments",
303
+ );
304
+ }
305
+
306
+ const environment = (values.env ??
307
+ process.env.E2E_ENVIRONMENT ??
308
+ "local") as Environment;
309
+
310
+ const baseUrl =
311
+ resolveBaseUrlFromManifest(repos[0], moduleNames[0], environment) ??
312
+ values["base-url"] ??
313
+ process.env.BASE_URL ??
314
+ process.env.BASE_API_URL;
315
+ if (!baseUrl) {
316
+ throw new Error(
317
+ "Could not determine base URL. Provide --base-url, set BASE_URL, " +
318
+ `or add a services.*.health.<env> entry in ${product().manifestYml}`,
319
+ );
320
+ }
321
+
322
+ const result = await runPerfModules({
323
+ modules: repos.map((repoName, index) => ({
324
+ repoName,
325
+ moduleName: moduleNames[index],
326
+ })),
327
+ environment,
328
+ baseUrl,
329
+ useLocalCheckout: values["use-local-checkout"] ?? false,
330
+ env: process.env,
331
+ });
332
+ return result.exitCode;
333
+ }
334
+
335
+ if (process.argv[1]?.endsWith("/run.ts")) {
336
+ main()
337
+ .then((exitCode) => {
338
+ process.exitCode = exitCode;
339
+ })
340
+ .catch((error: unknown) => {
341
+ const message = error instanceof Error ? error.message : String(error);
342
+ console.error(message);
343
+ process.exitCode = 1;
344
+ });
345
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Load Test Scenarios
3
+ * Different load patterns for various testing needs
4
+ */
5
+
6
+ /**
7
+ * Baseline Load Test
8
+ * Constant load to establish performance baseline
9
+ */
10
+ export const baselineLoad = {
11
+ executor: "constant-vus",
12
+ vus: 10,
13
+ duration: "2m",
14
+ tags: { test_type: "baseline" },
15
+ };
16
+
17
+ /**
18
+ * Stress Test
19
+ * Gradually increase load to find breaking point
20
+ */
21
+ export const stressTest = {
22
+ executor: "ramping-vus",
23
+ startVUs: 0,
24
+ stages: [
25
+ { duration: "2m", target: 50 }, // Ramp up to 50
26
+ { duration: "5m", target: 100 }, // Ramp up to 100
27
+ { duration: "2m", target: 200 }, // Ramp up to 200
28
+ { duration: "5m", target: 200 }, // Stay at 200
29
+ { duration: "2m", target: 0 }, // Ramp down
30
+ ],
31
+ tags: { test_type: "stress" },
32
+ };
33
+
34
+ /**
35
+ * Spike Test
36
+ * Sudden spike in traffic
37
+ */
38
+ export const spikeTest = {
39
+ executor: "ramping-vus",
40
+ startVUs: 0,
41
+ stages: [
42
+ { duration: "30s", target: 10 }, // Normal load
43
+ { duration: "10s", target: 200 }, // Spike!
44
+ { duration: "1m", target: 200 }, // Sustain spike
45
+ { duration: "10s", target: 10 }, // Back to normal
46
+ { duration: "30s", target: 10 }, // Stay at normal
47
+ { duration: "10s", target: 0 }, // Ramp down
48
+ ],
49
+ tags: { test_type: "spike" },
50
+ };
51
+
52
+ /**
53
+ * Soak Test (Endurance Test)
54
+ * Sustained load over extended period
55
+ */
56
+ export const soakTest = {
57
+ executor: "constant-vus",
58
+ vus: 50,
59
+ duration: "30m", // Run for 30 minutes
60
+ tags: { test_type: "soak" },
61
+ };
62
+
63
+ /**
64
+ * Breakpoint Test
65
+ * Incrementally increase load until system breaks
66
+ */
67
+ export const breakpointTest = {
68
+ executor: "ramping-arrival-rate",
69
+ startRate: 10,
70
+ timeUnit: "1s",
71
+ preAllocatedVUs: 50,
72
+ maxVUs: 500,
73
+ stages: [
74
+ { duration: "1m", target: 10 },
75
+ { duration: "1m", target: 20 },
76
+ { duration: "1m", target: 40 },
77
+ { duration: "1m", target: 80 },
78
+ { duration: "1m", target: 160 },
79
+ { duration: "1m", target: 320 },
80
+ ],
81
+ tags: { test_type: "breakpoint" },
82
+ };
83
+
84
+ /**
85
+ * Peak Traffic Test
86
+ * Simulate peak business hours
87
+ */
88
+ export const peakTrafficTest = {
89
+ executor: "ramping-vus",
90
+ startVUs: 0,
91
+ stages: [
92
+ { duration: "5m", target: 100 }, // Morning ramp up
93
+ { duration: "15m", target: 100 }, // Peak hours
94
+ { duration: "5m", target: 0 }, // Ramp down
95
+ ],
96
+ tags: { test_type: "peak" },
97
+ };
@@ -0,0 +1,73 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ export interface StagePerfTreeResult {
5
+ copiedFiles: number;
6
+ matchedTestFiles: number;
7
+ }
8
+
9
+ function normalizePath(value: string): string {
10
+ return value.split(path.sep).join("/");
11
+ }
12
+
13
+ function matchesTestPath(relativePath: string, testMatch: string): boolean {
14
+ const normalizedPath = normalizePath(relativePath);
15
+
16
+ if (normalizedPath === testMatch) {
17
+ return true;
18
+ }
19
+
20
+ if (testMatch.startsWith("**/*.")) {
21
+ return normalizedPath.endsWith(testMatch.slice("**/*".length));
22
+ }
23
+
24
+ if (testMatch.startsWith("**/")) {
25
+ const suffix = testMatch.slice(3);
26
+ return normalizedPath === suffix || normalizedPath.endsWith(`/${suffix}`);
27
+ }
28
+
29
+ if (!testMatch.includes("/")) {
30
+ return path.basename(normalizedPath) === testMatch;
31
+ }
32
+
33
+ return false;
34
+ }
35
+
36
+ export function stagePerfTree(
37
+ sourceDir: string,
38
+ destinationDir: string,
39
+ testMatch: string,
40
+ ): StagePerfTreeResult {
41
+ let copiedFiles = 0;
42
+ let matchedTestFiles = 0;
43
+
44
+ function copyRecursive(src: string, dst: string, relativeDir = ""): void {
45
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
46
+ const sourcePath = path.join(src, entry.name);
47
+ const destinationPath = path.join(dst, entry.name);
48
+ const relativePath = relativeDir
49
+ ? path.join(relativeDir, entry.name)
50
+ : entry.name;
51
+
52
+ if (entry.isDirectory()) {
53
+ fs.mkdirSync(destinationPath, { recursive: true });
54
+ copyRecursive(sourcePath, destinationPath, relativePath);
55
+ continue;
56
+ }
57
+
58
+ fs.copyFileSync(sourcePath, destinationPath);
59
+ copiedFiles += 1;
60
+ if (matchesTestPath(relativePath, testMatch)) {
61
+ matchedTestFiles += 1;
62
+ }
63
+ }
64
+ }
65
+
66
+ fs.mkdirSync(destinationDir, { recursive: true });
67
+ copyRecursive(sourceDir, destinationDir);
68
+
69
+ return {
70
+ copiedFiles,
71
+ matchedTestFiles,
72
+ };
73
+ }