@monster-orange/den 0.0.0 → 0.2.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.
- package/CHANGELOG.md +204 -0
- package/LICENSE +21 -0
- package/README.md +165 -1
- package/bin/den +68 -0
- package/docs/distributions.md +499 -0
- package/make/demo.mk +82 -0
- package/make/den.mk +80 -0
- package/make/journeys.mk +57 -0
- package/make/local_dev.mk +154 -0
- package/make/security.mk +59 -0
- package/make/test.mk +405 -0
- package/package.json +101 -4
- package/scripts/auth-configure.ts +415 -0
- package/scripts/heal-config.ts +92 -0
- package/scripts/jsonl-playwright-reporter.js +70 -0
- package/scripts/manifest-read.cjs +91 -0
- package/scripts/setup-1password.ts +570 -0
- package/scripts/setup-secrets.ts +149 -0
- package/scripts/view-results.js +156 -0
- package/service/install.sh +112 -0
- package/src/pillars/api/fixtures/index.ts +128 -0
- package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
- package/src/pillars/api/global-setup.ts +103 -0
- package/src/pillars/api/playwright.config.ts +105 -0
- package/src/pillars/api/schemas/example-api.json +110 -0
- package/src/pillars/api/tests/health/health.spec.ts +19 -0
- package/src/pillars/api/tests/health/manifest.yml +6 -0
- package/src/pillars/browser/fixtures/index.ts +232 -0
- package/src/pillars/browser/fixtures/package.json +10 -0
- package/src/pillars/browser/global-setup.ts +140 -0
- package/src/pillars/browser/global-teardown.ts +48 -0
- package/src/pillars/browser/playwright.config.ts +212 -0
- package/src/pillars/browser/setup.ts +219 -0
- package/src/pillars/journeys/app-config.ts +237 -0
- package/src/pillars/journeys/discover.ts +160 -0
- package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
- package/src/pillars/journeys/observability/events.ts +288 -0
- package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
- package/src/pillars/journeys/report/friction.ts +304 -0
- package/src/pillars/journeys/report/junit.ts +105 -0
- package/src/pillars/journeys/report/render.ts +273 -0
- package/src/pillars/journeys/report/results.ts +323 -0
- package/src/pillars/journeys/run.ts +673 -0
- package/src/pillars/journeys/runner.ts +1025 -0
- package/src/pillars/journeys/sandbox.ts +403 -0
- package/src/pillars/journeys/spec/loader.ts +152 -0
- package/src/pillars/journeys/spec/schema.ts +208 -0
- package/src/pillars/journeys/teardown.ts +98 -0
- package/src/pillars/journeys/tools/browser-session.ts +219 -0
- package/src/pillars/journeys/tools/browser-tool.ts +914 -0
- package/src/pillars/journeys/tools/http-tool.ts +564 -0
- package/src/pillars/journeys/tools/ledger.ts +278 -0
- package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
- package/src/pillars/journeys/verify/assertions.ts +502 -0
- package/src/pillars/journeys/verify/verdict.ts +71 -0
- package/src/pillars/perf/build-report.ts +36 -0
- package/src/pillars/perf/k6.config.js +112 -0
- package/src/pillars/perf/run.ts +345 -0
- package/src/pillars/perf/scenarios/load-profile.js +97 -0
- package/src/pillars/perf/staging.ts +73 -0
- package/src/pillars/security/discover.ts +124 -0
- package/src/pillars/security/findings.ts +151 -0
- package/src/pillars/security/plan.ts +355 -0
- package/src/pillars/security/run.ts +757 -0
- package/src/pillars/smoke/admin-probe.ts +68 -0
- package/src/pillars/smoke/base-url.ts +56 -0
- package/src/pillars/smoke/cleanup.ts +8 -0
- package/src/pillars/smoke/discover.ts +237 -0
- package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
- package/src/pillars/smoke/fixture-manager.ts +217 -0
- package/src/pillars/smoke/fixtures.ts +468 -0
- package/src/pillars/smoke/global-setup-all.ts +262 -0
- package/src/pillars/smoke/global-setup-global.ts +262 -0
- package/src/pillars/smoke/global-setup.ts +273 -0
- package/src/pillars/smoke/global-teardown-all.ts +8 -0
- package/src/pillars/smoke/global-teardown-global.ts +8 -0
- package/src/pillars/smoke/global-teardown.ts +139 -0
- package/src/pillars/smoke/playwright-all.config.ts +147 -0
- package/src/pillars/smoke/playwright-global.config.ts +141 -0
- package/src/pillars/smoke/playwright.config.ts +162 -0
- package/src/pillars/smoke/setup-env.ts +132 -0
- package/src/pillars/smoke/setup-global.ts +126 -0
- package/src/pillars/smoke/setup.ts +115 -0
- package/src/pillars/smoke/smoke-repo.ts +16 -0
- package/src/pillars/smoke/staging.ts +49 -0
- package/src/pillars/suites/run.ts +147 -0
- package/src/scripts/configure-aws-profiles.ts +114 -0
- package/src/scripts/register-repos.ts +307 -0
- package/src/scripts/setup-default-workspace.ts +203 -0
- package/src/shared/agent-actor.ts +77 -0
- package/src/shared/applications.ts +138 -0
- package/src/shared/auth/api-token.ts +416 -0
- package/src/shared/auth/auth0-helper.ts +284 -0
- package/src/shared/auth/headless-login.ts +265 -0
- package/src/shared/auth/jwt-helper.ts +186 -0
- package/src/shared/auth/sudo-vault.ts +69 -0
- package/src/shared/auth/test-accounts.ts +98 -0
- package/src/shared/auth/token-injection.ts +184 -0
- package/src/shared/auth/token-providers/auth0.ts +80 -0
- package/src/shared/auth/token-providers/index.ts +182 -0
- package/src/shared/auth/token-refresh.ts +273 -0
- package/src/shared/banner.ts +15 -0
- package/src/shared/brand.ts +79 -0
- package/src/shared/cli-tabs.ts +131 -0
- package/src/shared/constants.ts +128 -0
- package/src/shared/den-config.ts +688 -0
- package/src/shared/dev-metrics/emit.ts +354 -0
- package/src/shared/edge/config.ts +229 -0
- package/src/shared/edge/render.ts +221 -0
- package/src/shared/edge/runtime.ts +268 -0
- package/src/shared/edge/status.ts +74 -0
- package/src/shared/edge/types.ts +62 -0
- package/src/shared/engine.ts +67 -0
- package/src/shared/environment/aws-profiles.ts +49 -0
- package/src/shared/environment/env-selector.ts +161 -0
- package/src/shared/environment/playwright-env.ts +82 -0
- package/src/shared/environment-manager/catalog.ts +77 -0
- package/src/shared/environment-manager/clone-guard.ts +163 -0
- package/src/shared/environment-manager/dag.ts +286 -0
- package/src/shared/environment-manager/default-workspace.ts +182 -0
- package/src/shared/environment-manager/derivation/alias.ts +90 -0
- package/src/shared/environment-manager/derivation/cache.ts +97 -0
- package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
- package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
- package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
- package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
- package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
- package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
- package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
- package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
- package/src/shared/environment-manager/derivation/scan.ts +160 -0
- package/src/shared/environment-manager/derivation/text.ts +40 -0
- package/src/shared/environment-manager/derivation/types.ts +69 -0
- package/src/shared/environment-manager/health.ts +635 -0
- package/src/shared/environment-manager/index.ts +6 -0
- package/src/shared/environment-manager/load-applications.ts +108 -0
- package/src/shared/environment-manager/log-tail.ts +213 -0
- package/src/shared/environment-manager/make-targets.ts +78 -0
- package/src/shared/environment-manager/opctl.ts +335 -0
- package/src/shared/environment-manager/readiness.ts +93 -0
- package/src/shared/environment-manager/register-local.ts +72 -0
- package/src/shared/environment-manager/secrets-check.ts +260 -0
- package/src/shared/environment-manager/service-container.ts +47 -0
- package/src/shared/environment-manager/service-env.ts +110 -0
- package/src/shared/environment-manager/service-runner.ts +1603 -0
- package/src/shared/environment-manager/sync-targets.ts +66 -0
- package/src/shared/environment-manager/up-plan.ts +326 -0
- package/src/shared/environment-manager/workspace-manager.ts +905 -0
- package/src/shared/environment-manager/workspaces.ts +860 -0
- package/src/shared/manifest/deps.ts +27 -0
- package/src/shared/manifest/merge-override.ts +75 -0
- package/src/shared/manifest/migrate-manifest.ts +101 -0
- package/src/shared/manifest/placement.ts +321 -0
- package/src/shared/manifest/read-manifest.ts +278 -0
- package/src/shared/manifest/schema.ts +313 -0
- package/src/shared/manifest/service-links.ts +93 -0
- package/src/shared/manifest/validate-layers.ts +229 -0
- package/src/shared/manifest/validate-manifest.ts +200 -0
- package/src/shared/manifest/workspace-manifests.ts +92 -0
- package/src/shared/open-url.ts +122 -0
- package/src/shared/org.ts +44 -0
- package/src/shared/paths.ts +190 -0
- package/src/shared/perf/optional-fields.ts +105 -0
- package/src/shared/perf/report-normalizer.ts +404 -0
- package/src/shared/perf/report-paths.ts +19 -0
- package/src/shared/perf/report-store.ts +110 -0
- package/src/shared/perf/report-types.ts +70 -0
- package/src/shared/perf/test-paths.ts +25 -0
- package/src/shared/process/log-rotation.ts +147 -0
- package/src/shared/process/spawn-streaming.ts +229 -0
- package/src/shared/product.ts +92 -0
- package/src/shared/profile.ts +141 -0
- package/src/shared/redact.ts +107 -0
- package/src/shared/repo-kind.ts +32 -0
- package/src/shared/reporting/jsonl-reporter.ts +131 -0
- package/src/shared/reporting/slack-reporter.ts +340 -0
- package/src/shared/results/result-reader.ts +266 -0
- package/src/shared/results/result-writer.ts +168 -0
- package/src/shared/secrets/doppler.ts +126 -0
- package/src/shared/secrets/env.ts +48 -0
- package/src/shared/secrets/index.ts +197 -0
- package/src/shared/secrets/keys.ts +33 -0
- package/src/shared/secrets/onepassword.ts +75 -0
- package/src/shared/secrets/types.ts +52 -0
- package/src/shared/service-url.ts +119 -0
- package/src/shared/smoke/branch-resolver.ts +141 -0
- package/src/shared/smoke/git-remote.ts +70 -0
- package/src/shared/smoke/repo-cloner.ts +151 -0
- package/src/shared/stacks/config.ts +137 -0
- package/src/shared/stacks/manifest-commands.ts +118 -0
- package/src/shared/stacks/render.ts +253 -0
- package/src/shared/stacks/runtime.ts +334 -0
- package/src/shared/stacks/status.ts +99 -0
- package/src/shared/stacks/types.ts +53 -0
- package/src/shared/terminal/tabs.ts +212 -0
- package/src/shared/test-discovery/module-scanner.ts +569 -0
- package/src/shared/types/index.ts +701 -0
- package/src/shared/ui-marker.ts +19 -0
- package/src/ui/cli/applications-command.ts +148 -0
- package/src/ui/cli/config-command.ts +68 -0
- package/src/ui/cli/create-main.ts +9 -0
- package/src/ui/cli/create.ts +360 -0
- package/src/ui/cli/edge-command.ts +81 -0
- package/src/ui/cli/flag-parser.ts +246 -0
- package/src/ui/cli/graph-command.ts +570 -0
- package/src/ui/cli/health-display.ts +63 -0
- package/src/ui/cli/index.ts +3742 -0
- package/src/ui/cli/init.ts +855 -0
- package/src/ui/cli/invocation-target.ts +175 -0
- package/src/ui/cli/logs-command.ts +104 -0
- package/src/ui/cli/mcp-main.ts +13 -0
- package/src/ui/cli/mcp-stdout-guard.ts +10 -0
- package/src/ui/cli/module-selection.ts +31 -0
- package/src/ui/cli/prompts.ts +767 -0
- package/src/ui/cli/results-viewer.ts +436 -0
- package/src/ui/cli/secrets-preflight.ts +91 -0
- package/src/ui/cli/stack-command.ts +117 -0
- package/src/ui/cli/stalled-setup-recovery.ts +129 -0
- package/src/ui/cli/test-runner.ts +1319 -0
- package/src/ui/cli/web-command.ts +1073 -0
- package/src/ui/mcp/api.ts +284 -0
- package/src/ui/mcp/guards.ts +141 -0
- package/src/ui/mcp/server.ts +147 -0
- package/src/ui/mcp/tools.ts +565 -0
- package/src/ui/server/audit.ts +71 -0
- package/src/ui/server/changelog-parser.ts +105 -0
- package/src/ui/server/daemon-reaper.ts +129 -0
- package/src/ui/server/editor-manager.ts +301 -0
- package/src/ui/server/github-service.ts +223 -0
- package/src/ui/server/graph-prefs.ts +89 -0
- package/src/ui/server/health-row.ts +15 -0
- package/src/ui/server/http-helpers.ts +98 -0
- package/src/ui/server/index.ts +3350 -0
- package/src/ui/server/log-history.ts +453 -0
- package/src/ui/server/perf-report.ts +42 -0
- package/src/ui/server/public/environments.html +496 -0
- package/src/ui/server/public/favicon.ico +0 -0
- package/src/ui/server/public/graph-model.js +416 -0
- package/src/ui/server/public/graph.css +976 -0
- package/src/ui/server/public/graph.js +986 -0
- package/src/ui/server/public/header.js +1373 -0
- package/src/ui/server/public/index.html +2595 -0
- package/src/ui/server/public/local-dev.html +5344 -0
- package/src/ui/server/public/logs.html +1496 -0
- package/src/ui/server/public/panel.js +787 -0
- package/src/ui/server/public/perf-report.js +359 -0
- package/src/ui/server/public/release.html +2111 -0
- package/src/ui/server/public/services.html +1103 -0
- package/src/ui/server/public/workspace-picker.js +69 -0
- package/src/ui/server/readonly-routes.ts +200 -0
- package/src/ui/server/release-deploy-session.ts +246 -0
- package/src/ui/server/release-service.ts +1125 -0
- package/src/ui/server/sudo-store.ts +111 -0
- package/src/ui/server/ui-assets.ts +73 -0
- package/src/ui/server/workspace-routes.ts +2185 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke Test Global Setup
|
|
3
|
+
*
|
|
4
|
+
* Runs once before any smoke test worker starts:
|
|
5
|
+
* 1. Checks API reachability (same VPN-aware logic as integration global-setup)
|
|
6
|
+
* 2. Pre-warms JWT tokens for all roles declared in the service manifest
|
|
7
|
+
* 3. Exports tokens to process.env so workers inherit them at fork time
|
|
8
|
+
*
|
|
9
|
+
* Environment variables read:
|
|
10
|
+
* E2E_ENVIRONMENT — which env config to load (default: local)
|
|
11
|
+
* SMOKE_SERVICE — service name from the manifest (default: api)
|
|
12
|
+
* SMOKE_REPO — repo name from .den/ (required)
|
|
13
|
+
* BASE_API_URL — override the API base URL
|
|
14
|
+
*
|
|
15
|
+
* Environment variables written (inherited by workers):
|
|
16
|
+
* SMOKE_ADMIN_TOKEN — API JWT for admin role (required)
|
|
17
|
+
* SMOKE_USER_TOKEN — API JWT for user role (required)
|
|
18
|
+
* SMOKE_BETA_TOKEN — API JWT for beta role (optional — warns if missing)
|
|
19
|
+
* SMOKE_ROOT_ADMIN_TOKEN — API JWT for root_admin role (optional — warns if missing)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { type FullConfig } from "@playwright/test";
|
|
23
|
+
import { envSelector } from "../../shared/environment/env-selector.js";
|
|
24
|
+
import { getApiToken } from "../../shared/auth/api-token.js";
|
|
25
|
+
import { findManifest, resolveRepoPath, resolveService } from "./discover.js";
|
|
26
|
+
import type { Environment } from "../../shared/types/index.js";
|
|
27
|
+
import { resolveSmokeBaseUrlForService } from "./base-url.js";
|
|
28
|
+
import { assertStorageAdminProbe } from "./admin-probe.js";
|
|
29
|
+
import {
|
|
30
|
+
resolveSmokeCleanupFixture,
|
|
31
|
+
resolveSmokeStorageFixture,
|
|
32
|
+
} from "./fixtures.js";
|
|
33
|
+
import { acquireFixture, fixtureInstanceId } from "./cleanup.js";
|
|
34
|
+
import {
|
|
35
|
+
registerSmokeFixtureReleaseHandles,
|
|
36
|
+
smokeFixtureConsumerKey,
|
|
37
|
+
writeSmokeFixtureHandoff,
|
|
38
|
+
type SmokeFixtureHandoffPayload,
|
|
39
|
+
} from "./global-teardown.js";
|
|
40
|
+
import { smokeRepoName } from "./smoke-repo.js";
|
|
41
|
+
|
|
42
|
+
import { product } from "../../shared/product.js";
|
|
43
|
+
async function globalSetup(_config: FullConfig) {
|
|
44
|
+
const environment = (process.env.E2E_ENVIRONMENT as Environment) || "local";
|
|
45
|
+
const serviceName = process.env.SMOKE_SERVICE || "api";
|
|
46
|
+
const repoName = smokeRepoName();
|
|
47
|
+
|
|
48
|
+
const envConfig = await envSelector.loadEnvironment(environment);
|
|
49
|
+
|
|
50
|
+
// Resolve the service manifest to get baseUrlKey and auth roles
|
|
51
|
+
const discovered = findManifest(repoName);
|
|
52
|
+
if (!discovered) {
|
|
53
|
+
console.error(
|
|
54
|
+
`\n❌ No manifest found for repo "${repoName}" in .${product().name}/`,
|
|
55
|
+
);
|
|
56
|
+
console.error(` Run: make smoke-link repo=${repoName}\n`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const svc = resolveService(discovered, serviceName);
|
|
61
|
+
if (!svc) {
|
|
62
|
+
const available = Object.keys(discovered.manifest.smoke ?? {}).join(", ");
|
|
63
|
+
console.error(
|
|
64
|
+
`\n❌ Service "${serviceName}" not found in ${repoName}/${product().manifestJson}`,
|
|
65
|
+
);
|
|
66
|
+
console.error(` Available: ${available}\n`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const baseUrl = resolveSmokeBaseUrlForService(
|
|
71
|
+
discovered.manifest,
|
|
72
|
+
repoName,
|
|
73
|
+
serviceName,
|
|
74
|
+
svc,
|
|
75
|
+
environment,
|
|
76
|
+
envConfig,
|
|
77
|
+
process.env.BASE_API_URL,
|
|
78
|
+
);
|
|
79
|
+
const needsVpn = baseUrl.includes(".internal.");
|
|
80
|
+
|
|
81
|
+
console.log(`\n💨 Smoke Test Setup`);
|
|
82
|
+
console.log(` Repo: ${repoName}`);
|
|
83
|
+
console.log(` Service: ${serviceName}`);
|
|
84
|
+
console.log(` Environment: ${environment}`);
|
|
85
|
+
console.log(` Base URL: ${baseUrl}\n`);
|
|
86
|
+
|
|
87
|
+
// ── Reachability check ──────────────────────────────────────────────────
|
|
88
|
+
try {
|
|
89
|
+
let response: Response;
|
|
90
|
+
try {
|
|
91
|
+
response = await fetch(`${baseUrl}/healthz`, {
|
|
92
|
+
signal: AbortSignal.timeout(5000),
|
|
93
|
+
});
|
|
94
|
+
} catch {
|
|
95
|
+
response = await fetch(baseUrl, { signal: AbortSignal.timeout(5000) });
|
|
96
|
+
}
|
|
97
|
+
console.log(`✅ Service is reachable (status: ${response.status})`);
|
|
98
|
+
} catch (error: unknown) {
|
|
99
|
+
console.error(`❌ Cannot reach service: ${baseUrl}`);
|
|
100
|
+
console.error(` Error: ${(error as Error).message}`);
|
|
101
|
+
if (needsVpn) {
|
|
102
|
+
console.error(
|
|
103
|
+
`\n💡 VPN-protected URL. Check that your VPN is connected.`,
|
|
104
|
+
);
|
|
105
|
+
console.error(
|
|
106
|
+
` Synthetic (browser) tests do NOT require VPN — only smoke/API tests do.\n`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handoff bookkeeping shared by the storage + cleanup fixture blocks; the
|
|
113
|
+
// handoff file is written once, after every fixture has been acquired.
|
|
114
|
+
const consumersInAcquireOrder: string[] = [];
|
|
115
|
+
const bindings: SmokeFixtureHandoffPayload["bindings"] = [];
|
|
116
|
+
|
|
117
|
+
// ── Storage smoke fixtures (acquire + env for workers) ─────────────────────
|
|
118
|
+
if (svc.fixtures?.storage?.[environment]) {
|
|
119
|
+
const workerCount = _config.workers;
|
|
120
|
+
if (!Number.isInteger(workerCount) || workerCount <= 0) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Smoke fixture teardown requires a positive Playwright worker count",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const useLocalCheckout = process.env.USE_LOCAL_CHECKOUT === "1";
|
|
126
|
+
const lifecycle = resolveSmokeStorageFixture(
|
|
127
|
+
discovered.manifest,
|
|
128
|
+
serviceName,
|
|
129
|
+
environment,
|
|
130
|
+
);
|
|
131
|
+
if (lifecycle.adminProbePath) {
|
|
132
|
+
await assertStorageAdminProbe({
|
|
133
|
+
baseUrl,
|
|
134
|
+
adminProbePath: lifecycle.adminProbePath,
|
|
135
|
+
label: `${repoName} / ${serviceName}`,
|
|
136
|
+
...(lifecycle.adminProbe403Hint
|
|
137
|
+
? { hintOn403: () => lifecycle.adminProbe403Hint! }
|
|
138
|
+
: {}),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const cwd = resolveRepoPath(discovered, environment, useLocalCheckout);
|
|
142
|
+
const resolvedBaseUrl = baseUrl;
|
|
143
|
+
const fixtureEnv = {
|
|
144
|
+
...process.env,
|
|
145
|
+
BASE_API_URL: resolvedBaseUrl,
|
|
146
|
+
SMOKE_BASE_URL: resolvedBaseUrl,
|
|
147
|
+
...lifecycle.envVars,
|
|
148
|
+
SMOKE_WORKER_COUNT: String(workerCount),
|
|
149
|
+
};
|
|
150
|
+
const release = await acquireFixture({
|
|
151
|
+
fixtureKey: lifecycle.fixtureKey,
|
|
152
|
+
resolvedPayload: lifecycle.resolvedPayload as unknown as Record<
|
|
153
|
+
string,
|
|
154
|
+
unknown
|
|
155
|
+
>,
|
|
156
|
+
cwd,
|
|
157
|
+
env: fixtureEnv,
|
|
158
|
+
setup: lifecycle.setup,
|
|
159
|
+
teardown: lifecycle.teardown,
|
|
160
|
+
});
|
|
161
|
+
Object.assign(process.env, fixtureEnv);
|
|
162
|
+
const consumerKey = smokeFixtureConsumerKey(repoName, serviceName);
|
|
163
|
+
registerSmokeFixtureReleaseHandles(consumerKey, [release]);
|
|
164
|
+
consumersInAcquireOrder.push(consumerKey);
|
|
165
|
+
bindings.push({
|
|
166
|
+
consumerKey,
|
|
167
|
+
fixtureInstanceId: fixtureInstanceId(
|
|
168
|
+
lifecycle.fixtureKey,
|
|
169
|
+
lifecycle.resolvedPayload as unknown as Record<string, unknown>,
|
|
170
|
+
),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Pre-warm JWT tokens ─────────────────────────────────────────────────
|
|
175
|
+
// Tokens are written to process.env here. Worker processes inherit env at
|
|
176
|
+
// fork time (after globalSetup completes), so they all get cache hits.
|
|
177
|
+
// Required roles: admin + user — fatal if credentials or token fetch fails.
|
|
178
|
+
// Optional roles: beta + root_admin — warn and continue (accounts may not exist in all envs).
|
|
179
|
+
const optionalRoles = new Set(["beta", "root_admin"]);
|
|
180
|
+
const roles = (svc.auth ?? ["admin", "user"]) as (
|
|
181
|
+
| "admin"
|
|
182
|
+
| "user"
|
|
183
|
+
| "beta"
|
|
184
|
+
| "root_admin"
|
|
185
|
+
)[];
|
|
186
|
+
|
|
187
|
+
if (roles.length > 0) {
|
|
188
|
+
console.log(`\n🔑 Warming JWT token cache...`);
|
|
189
|
+
for (const role of roles) {
|
|
190
|
+
const isOptional = optionalRoles.has(role);
|
|
191
|
+
try {
|
|
192
|
+
const token = await getApiToken(role, environment);
|
|
193
|
+
process.env[`SMOKE_${role.toUpperCase()}_TOKEN`] = token;
|
|
194
|
+
} catch (err: unknown) {
|
|
195
|
+
const msg = (err as Error).message ?? String(err);
|
|
196
|
+
if (isOptional) {
|
|
197
|
+
console.warn(
|
|
198
|
+
` ⚠ No token for ${role} — ${role} tests will be skipped`,
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
console.error(`\n❌ Failed to fetch ${role} JWT: ${msg}\n`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Cleanup smoke fixture (teardown-only sweep) ─────────────────────────────
|
|
209
|
+
// Acquired AFTER token warming on purpose: the fixture env is snapshotted at
|
|
210
|
+
// acquire time, so the teardown command inherits SMOKE_*_TOKEN — required by
|
|
211
|
+
// repos whose sweep authenticates against JWT-guarded APIs.
|
|
212
|
+
const cleanupLifecycle = resolveSmokeCleanupFixture(
|
|
213
|
+
discovered.manifest,
|
|
214
|
+
serviceName,
|
|
215
|
+
environment,
|
|
216
|
+
);
|
|
217
|
+
if (cleanupLifecycle) {
|
|
218
|
+
const workerCount = _config.workers;
|
|
219
|
+
if (!Number.isInteger(workerCount) || workerCount <= 0) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
"Smoke fixture teardown requires a positive Playwright worker count",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const useLocalCheckout = process.env.USE_LOCAL_CHECKOUT === "1";
|
|
225
|
+
if (cleanupLifecycle.adminProbePath) {
|
|
226
|
+
await assertStorageAdminProbe({
|
|
227
|
+
baseUrl,
|
|
228
|
+
adminProbePath: cleanupLifecycle.adminProbePath,
|
|
229
|
+
label: `${repoName} / ${serviceName}`,
|
|
230
|
+
...(cleanupLifecycle.adminProbe403Hint
|
|
231
|
+
? { hintOn403: () => cleanupLifecycle.adminProbe403Hint! }
|
|
232
|
+
: {}),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const cwd = resolveRepoPath(discovered, environment, useLocalCheckout);
|
|
236
|
+
const fixtureEnv = {
|
|
237
|
+
...process.env,
|
|
238
|
+
BASE_API_URL: baseUrl,
|
|
239
|
+
SMOKE_BASE_URL: baseUrl,
|
|
240
|
+
...cleanupLifecycle.envVars,
|
|
241
|
+
SMOKE_WORKER_COUNT: String(workerCount),
|
|
242
|
+
};
|
|
243
|
+
const release = await acquireFixture({
|
|
244
|
+
fixtureKey: cleanupLifecycle.fixtureKey,
|
|
245
|
+
resolvedPayload: cleanupLifecycle.resolvedPayload,
|
|
246
|
+
cwd,
|
|
247
|
+
env: fixtureEnv,
|
|
248
|
+
setup: cleanupLifecycle.setup,
|
|
249
|
+
teardown: cleanupLifecycle.teardown,
|
|
250
|
+
});
|
|
251
|
+
const consumerKey = smokeFixtureConsumerKey(repoName, serviceName);
|
|
252
|
+
registerSmokeFixtureReleaseHandles(consumerKey, [release]);
|
|
253
|
+
consumersInAcquireOrder.push(consumerKey);
|
|
254
|
+
bindings.push({
|
|
255
|
+
consumerKey,
|
|
256
|
+
fixtureInstanceId: fixtureInstanceId(
|
|
257
|
+
cleanupLifecycle.fixtureKey,
|
|
258
|
+
cleanupLifecycle.resolvedPayload,
|
|
259
|
+
),
|
|
260
|
+
});
|
|
261
|
+
console.log(
|
|
262
|
+
`🧹 Cleanup fixture armed for ${repoName}/${serviceName} (${environment})`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (consumersInAcquireOrder.length > 0) {
|
|
267
|
+
writeSmokeFixtureHandoff({ consumersInAcquireOrder, bindings });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.log();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export default globalSetup;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FullConfig } from "@playwright/test";
|
|
2
|
+
import { releaseSmokeFixturesFromHandoff } from "./global-teardown.js";
|
|
3
|
+
|
|
4
|
+
async function globalTeardownAll(_config: FullConfig) {
|
|
5
|
+
await releaseSmokeFixturesFromHandoff();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default globalTeardownAll;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FullConfig } from "@playwright/test";
|
|
2
|
+
import { releaseSmokeFixturesFromHandoff } from "./global-teardown.js";
|
|
3
|
+
|
|
4
|
+
async function globalTeardownGlobal(_config: FullConfig) {
|
|
5
|
+
await releaseSmokeFixturesFromHandoff();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default globalTeardownGlobal;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { FullConfig } from "@playwright/test";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { releaseFixturesForConsumer } from "./cleanup.js";
|
|
5
|
+
import { getDistRoot } from "../../shared/paths.js";
|
|
6
|
+
|
|
7
|
+
import { product } from "../../shared/product.js";
|
|
8
|
+
// Den-root anchored: writes the fixture-handoff file that
|
|
9
|
+
// ensure-smoke-teardown.ts re-derives — the two must agree from any cwd.
|
|
10
|
+
const PROJECT_ROOT = getDistRoot();
|
|
11
|
+
const SMOKE_RUN_DIR = path.resolve(PROJECT_ROOT, ".smoke-run");
|
|
12
|
+
|
|
13
|
+
/** Persisted between global setup and teardown (same Playwright parent process). */
|
|
14
|
+
export const SMOKE_FIXTURE_HANDOFF_PATH = path.join(
|
|
15
|
+
SMOKE_RUN_DIR,
|
|
16
|
+
"smoke-fixture-handoff.json",
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const smokeFixtureReleaseHandles = new Map<
|
|
20
|
+
string,
|
|
21
|
+
Array<() => Promise<void>>
|
|
22
|
+
>();
|
|
23
|
+
|
|
24
|
+
export function smokeFixtureConsumerKey(
|
|
25
|
+
repoName: string,
|
|
26
|
+
serviceName: string,
|
|
27
|
+
): string {
|
|
28
|
+
return `${repoName}__${serviceName}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function registerSmokeFixtureReleaseHandles(
|
|
32
|
+
consumerKey: string,
|
|
33
|
+
handles: ReadonlyArray<() => Promise<void>>,
|
|
34
|
+
): void {
|
|
35
|
+
if (handles.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const prev = smokeFixtureReleaseHandles.get(consumerKey) ?? [];
|
|
39
|
+
smokeFixtureReleaseHandles.set(consumerKey, [...prev, ...handles]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SmokeFixtureHandoffPayload = {
|
|
43
|
+
version: 1;
|
|
44
|
+
/** Acquisition order; teardown releases in reverse (LIFO across consumers). */
|
|
45
|
+
consumersInAcquireOrder: string[];
|
|
46
|
+
bindings: Array<{
|
|
47
|
+
consumerKey: string;
|
|
48
|
+
fixtureInstanceId: string;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function writeSmokeFixtureHandoff(
|
|
53
|
+
payload: Omit<SmokeFixtureHandoffPayload, "version">,
|
|
54
|
+
): void {
|
|
55
|
+
const full: SmokeFixtureHandoffPayload = { version: 1, ...payload };
|
|
56
|
+
fs.mkdirSync(SMOKE_RUN_DIR, { recursive: true });
|
|
57
|
+
fs.writeFileSync(SMOKE_FIXTURE_HANDOFF_PATH, JSON.stringify(full, null, 2));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function releaseAllRegisteredFixtures(): Promise<void> {
|
|
61
|
+
const handlesInReverseRegistrationOrder = Array.from(
|
|
62
|
+
smokeFixtureReleaseHandles.values(),
|
|
63
|
+
).reverse();
|
|
64
|
+
for (const handles of handlesInReverseRegistrationOrder) {
|
|
65
|
+
await releaseFixturesForConsumer(handles);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Releases fixtures registered for this run, using the persisted consumer order
|
|
71
|
+
* (release-driven: refcount / shared instances, not per-repo shell hooks).
|
|
72
|
+
*/
|
|
73
|
+
export async function releaseSmokeFixturesFromHandoff(): Promise<void> {
|
|
74
|
+
try {
|
|
75
|
+
if (!fs.existsSync(SMOKE_FIXTURE_HANDOFF_PATH)) {
|
|
76
|
+
await releaseAllRegisteredFixtures();
|
|
77
|
+
console.log(
|
|
78
|
+
`[${product().cli}] smoke global teardown: storage fixture releases completed`,
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let payload: SmokeFixtureHandoffPayload;
|
|
84
|
+
try {
|
|
85
|
+
payload = JSON.parse(
|
|
86
|
+
fs.readFileSync(SMOKE_FIXTURE_HANDOFF_PATH, "utf-8"),
|
|
87
|
+
) as SmokeFixtureHandoffPayload;
|
|
88
|
+
} catch {
|
|
89
|
+
await releaseAllRegisteredFixtures();
|
|
90
|
+
console.log(
|
|
91
|
+
`[${product().cli}] smoke global teardown: storage fixture releases completed (handoff parse fallback)`,
|
|
92
|
+
);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const bindingConsumerKeys = new Set(
|
|
97
|
+
(payload.bindings ?? []).map((binding) => binding.consumerKey),
|
|
98
|
+
);
|
|
99
|
+
const order = (payload.consumersInAcquireOrder ?? []).filter((key) =>
|
|
100
|
+
bindingConsumerKeys.has(key),
|
|
101
|
+
);
|
|
102
|
+
for (let i = order.length - 1; i >= 0; i -= 1) {
|
|
103
|
+
const key = order[i];
|
|
104
|
+
const handles = smokeFixtureReleaseHandles.get(key);
|
|
105
|
+
if (handles?.length) {
|
|
106
|
+
await releaseFixturesForConsumer(handles);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Safety net: any consumer registered in-memory but missing from handoff
|
|
110
|
+
// order still must release (release fns are idempotent if already run).
|
|
111
|
+
await releaseAllRegisteredFixtures();
|
|
112
|
+
console.log(
|
|
113
|
+
`[${product().cli}] smoke global teardown: storage fixture releases completed`,
|
|
114
|
+
);
|
|
115
|
+
} finally {
|
|
116
|
+
smokeFixtureReleaseHandles.clear();
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
fs.unlinkSync(SMOKE_FIXTURE_HANDOFF_PATH);
|
|
120
|
+
} catch {
|
|
121
|
+
// ignore
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function resetSmokeFixtureBridgeForTests(): void {
|
|
127
|
+
smokeFixtureReleaseHandles.clear();
|
|
128
|
+
try {
|
|
129
|
+
fs.unlinkSync(SMOKE_FIXTURE_HANDOFF_PATH);
|
|
130
|
+
} catch {
|
|
131
|
+
// ignore
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function globalTeardown(_config: FullConfig) {
|
|
136
|
+
await releaseSmokeFixturesFromHandoff();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default globalTeardown;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright Configuration — All Smoke Services
|
|
3
|
+
*
|
|
4
|
+
* Used by: make test-smk-all
|
|
5
|
+
*
|
|
6
|
+
* Creates one Playwright project per service in the linked repo's manifest.
|
|
7
|
+
* Each project gets its own testDir (.smoke-run/<repo>-<service>/) and
|
|
8
|
+
* baseURL, so api and web tests can coexist in a single run and produce
|
|
9
|
+
* a single combined report.
|
|
10
|
+
*
|
|
11
|
+
* Environment variables:
|
|
12
|
+
* SMOKE_REPO — repo name in .den/ (required)
|
|
13
|
+
* E2E_ENVIRONMENT — env config to load (default: local)
|
|
14
|
+
* BASE_API_URL — override API base URL
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { defineConfig } from "@playwright/test";
|
|
18
|
+
import * as fs from "fs";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
import { findManifest } from "./discover.js";
|
|
21
|
+
import {
|
|
22
|
+
resolveManifestSmokeBaseUrl,
|
|
23
|
+
resolveSmokeBaseUrl,
|
|
24
|
+
} from "./base-url.js";
|
|
25
|
+
import type { Environment } from "../../shared/types/index.js";
|
|
26
|
+
import { getDistRoot } from "../../shared/paths.js";
|
|
27
|
+
import { distPath, enginePath } from "../../shared/engine.js";
|
|
28
|
+
import { smokeRepoName } from "./smoke-repo.js";
|
|
29
|
+
|
|
30
|
+
import { product } from "../../shared/product.js";
|
|
31
|
+
// Den-root anchored in lockstep with setup.ts (the .smoke-run writer).
|
|
32
|
+
const PROJECT_ROOT = getDistRoot();
|
|
33
|
+
|
|
34
|
+
const repoName = smokeRepoName();
|
|
35
|
+
const environment = (process.env.E2E_ENVIRONMENT || "local") as Environment;
|
|
36
|
+
|
|
37
|
+
const discovered = findManifest(repoName);
|
|
38
|
+
if (!discovered) {
|
|
39
|
+
console.error(
|
|
40
|
+
`\n❌ playwright-all.config: no manifest found for repo "${repoName}" in .${product().name}/`,
|
|
41
|
+
);
|
|
42
|
+
console.error(` Run: make smoke-link repo=${repoName}\n`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const configPath = path.resolve(
|
|
47
|
+
PROJECT_ROOT,
|
|
48
|
+
`config/environments/${environment}.json`,
|
|
49
|
+
);
|
|
50
|
+
let envConfig: { baseUrls: { api: string; web: string } };
|
|
51
|
+
try {
|
|
52
|
+
envConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
53
|
+
} catch {
|
|
54
|
+
console.error(
|
|
55
|
+
`\n❌ playwright-all.config: no env config found for "${environment}"`,
|
|
56
|
+
);
|
|
57
|
+
console.error(` Expected: config/environments/${environment}.json\n`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const smokeServicesFilter = process.env.SMOKE_SERVICES
|
|
62
|
+
? new Set(process.env.SMOKE_SERVICES.split(",").map((s) => s.trim()))
|
|
63
|
+
: null;
|
|
64
|
+
|
|
65
|
+
const projects = Object.entries(discovered.manifest.smoke ?? {})
|
|
66
|
+
.filter(
|
|
67
|
+
([svcName]) => !smokeServicesFilter || smokeServicesFilter.has(svcName),
|
|
68
|
+
)
|
|
69
|
+
.map(([svcName, svc]) => {
|
|
70
|
+
const stagingDir = path.resolve(
|
|
71
|
+
PROJECT_ROOT,
|
|
72
|
+
`.smoke-run/${repoName}-${svcName}`,
|
|
73
|
+
);
|
|
74
|
+
if (!fs.existsSync(stagingDir)) {
|
|
75
|
+
console.error(
|
|
76
|
+
`\n❌ Staging dir not found: .smoke-run/${repoName}-${svcName}/`,
|
|
77
|
+
);
|
|
78
|
+
console.error(
|
|
79
|
+
` Run setup first: SMOKE_SERVICE=${svcName} SMOKE_REPO=${repoName} npx tsx src/pillars/smoke/setup.ts\n`,
|
|
80
|
+
);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const configuredUrl =
|
|
85
|
+
svc.baseUrlKey === "api"
|
|
86
|
+
? envConfig.baseUrls.api
|
|
87
|
+
: envConfig.baseUrls.web;
|
|
88
|
+
const manifestUrl = resolveManifestSmokeBaseUrl({
|
|
89
|
+
manifest: discovered.manifest,
|
|
90
|
+
repoName,
|
|
91
|
+
serviceName: svcName,
|
|
92
|
+
environment,
|
|
93
|
+
baseUrlKey: svc.baseUrlKey,
|
|
94
|
+
});
|
|
95
|
+
const baseUrl = resolveSmokeBaseUrl({
|
|
96
|
+
baseUrlKey: svc.baseUrlKey,
|
|
97
|
+
configuredUrl,
|
|
98
|
+
manifestUrl,
|
|
99
|
+
overrideUrl: process.env.BASE_API_URL,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
name: svcName,
|
|
104
|
+
testDir: stagingDir,
|
|
105
|
+
testMatch: svc.testMatch,
|
|
106
|
+
use: {
|
|
107
|
+
baseURL: baseUrl,
|
|
108
|
+
timeout: 30000,
|
|
109
|
+
...(svc.baseUrlKey === "api"
|
|
110
|
+
? {
|
|
111
|
+
extraHTTPHeaders: {
|
|
112
|
+
Accept: "application/json",
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
: {}),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export default defineConfig({
|
|
122
|
+
globalSetup: enginePath("src/pillars/smoke/global-setup-all.ts"),
|
|
123
|
+
globalTeardown: enginePath("src/pillars/smoke/global-teardown-all.ts"),
|
|
124
|
+
|
|
125
|
+
fullyParallel: true,
|
|
126
|
+
forbidOnly: !!process.env.CI,
|
|
127
|
+
retries: process.env.CI ? 2 : 0,
|
|
128
|
+
workers: process.env.CI ? 1 : undefined,
|
|
129
|
+
|
|
130
|
+
reporter: [
|
|
131
|
+
[
|
|
132
|
+
"html",
|
|
133
|
+
{
|
|
134
|
+
outputFolder:
|
|
135
|
+
process.env.PLAYWRIGHT_HTML_REPORT || distPath("playwright-report"),
|
|
136
|
+
open: "never",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
["list"],
|
|
140
|
+
["junit", { outputFile: distPath("reports", "junit-smoke-results.xml") }],
|
|
141
|
+
],
|
|
142
|
+
|
|
143
|
+
projects,
|
|
144
|
+
|
|
145
|
+
timeout: 60000,
|
|
146
|
+
expect: { timeout: 10000 },
|
|
147
|
+
});
|