@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,767 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Prompts
|
|
3
|
+
* Interactive prompts for user input
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import * as fs from "fs/promises";
|
|
9
|
+
import { existsSync, type Dirent } from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import type { Environment, Pillar } from "../../shared/types/index.js";
|
|
12
|
+
import { getReposRoot } from "../../shared/den-config.js";
|
|
13
|
+
import { envSelector } from "../../shared/environment/env-selector.js";
|
|
14
|
+
import { moduleScanner } from "../../shared/test-discovery/module-scanner.js";
|
|
15
|
+
import * as yaml from "yaml";
|
|
16
|
+
import { readManifestAsync } from "../../shared/manifest/read-manifest.js";
|
|
17
|
+
|
|
18
|
+
import { product } from "../../shared/product.js";
|
|
19
|
+
import { getDistRoot } from "../../shared/paths.js";
|
|
20
|
+
// Linked repos (.<name>/<repo>) live at the distribution root, wherever the
|
|
21
|
+
// CLI was launched from.
|
|
22
|
+
const linkedRepo = (...parts: string[]): string =>
|
|
23
|
+
path.join(getDistRoot(), `.${product().name}`, ...parts);
|
|
24
|
+
|
|
25
|
+
export class CLIPrompts {
|
|
26
|
+
/**
|
|
27
|
+
* Prompt for environment selection
|
|
28
|
+
*/
|
|
29
|
+
async promptForEnvironment(): Promise<Environment> {
|
|
30
|
+
const availableEnvs = await envSelector.getAvailableEnvironments();
|
|
31
|
+
|
|
32
|
+
const { environment } = await inquirer.prompt<{ environment: Environment }>(
|
|
33
|
+
[
|
|
34
|
+
{
|
|
35
|
+
type: "list",
|
|
36
|
+
name: "environment",
|
|
37
|
+
message: "Select environment:",
|
|
38
|
+
choices: availableEnvs.map((env) => ({
|
|
39
|
+
name: this.formatEnvironmentName(env),
|
|
40
|
+
value: env,
|
|
41
|
+
})),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return environment;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Prompt for test pillar selection
|
|
51
|
+
*/
|
|
52
|
+
async promptForPillar(): Promise<Pillar> {
|
|
53
|
+
const { pillar } = await inquirer.prompt<{ pillar: Pillar }>([
|
|
54
|
+
{
|
|
55
|
+
type: "list",
|
|
56
|
+
name: "pillar",
|
|
57
|
+
message: "Select test pillar:",
|
|
58
|
+
choices: [
|
|
59
|
+
{
|
|
60
|
+
name: "๐ Synthetic (Browser Tests)",
|
|
61
|
+
value: "synthetic",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "๐ Integration (API Tests)",
|
|
65
|
+
value: "integration",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "๐จ Smoke (Service-Owned Tests)",
|
|
69
|
+
value: "smoke",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "โก Performance (Load Tests)",
|
|
73
|
+
value: "performance",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "๐งญ Journeys (Agent E2E)",
|
|
77
|
+
value: "journeys",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "๐ก Security (OWASP ZAP)",
|
|
81
|
+
value: "security",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "๐งช Suites (repo-owned tests)",
|
|
85
|
+
value: "suites",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
return pillar;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Prompt for test module selection (works for all pillars)
|
|
96
|
+
* Auto-discovers modules from filesystem
|
|
97
|
+
*/
|
|
98
|
+
async promptForModule(pillar: Pillar): Promise<string | null | "back"> {
|
|
99
|
+
// Scan for available modules
|
|
100
|
+
const modules = await moduleScanner.scanModules(pillar);
|
|
101
|
+
|
|
102
|
+
// Pillar-specific labels
|
|
103
|
+
const labels: Record<Pillar, { all: string; message: string }> = {
|
|
104
|
+
synthetic: {
|
|
105
|
+
all: "๐ฏ All Modules (complete test suite)",
|
|
106
|
+
message: "Select test module:",
|
|
107
|
+
},
|
|
108
|
+
integration: {
|
|
109
|
+
all: "๐ฏ All APIs (complete test suite)",
|
|
110
|
+
message: "Select API module:",
|
|
111
|
+
},
|
|
112
|
+
smoke: {
|
|
113
|
+
all: "๐ฏ All Services (complete smoke suite)",
|
|
114
|
+
message: "Select service:",
|
|
115
|
+
},
|
|
116
|
+
performance: {
|
|
117
|
+
all: "๐ฏ All Performance Tests (complete suite)",
|
|
118
|
+
message: "Select performance test:",
|
|
119
|
+
},
|
|
120
|
+
journeys: {
|
|
121
|
+
all: "๐ฏ All Journeys (complete suite)",
|
|
122
|
+
message: "Select journey:",
|
|
123
|
+
},
|
|
124
|
+
security: {
|
|
125
|
+
all: "๐ฏ All Security Targets (baseline scans)",
|
|
126
|
+
message: "Select security target:",
|
|
127
|
+
},
|
|
128
|
+
suites: {
|
|
129
|
+
all: "๐ฏ Every repo suite",
|
|
130
|
+
message: "Select suite:",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Build choices dynamically
|
|
135
|
+
const choices: Array<
|
|
136
|
+
| { name: string; value: string | null }
|
|
137
|
+
| InstanceType<typeof inquirer.Separator>
|
|
138
|
+
> = [
|
|
139
|
+
{
|
|
140
|
+
name: labels[pillar].all,
|
|
141
|
+
value: "all",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "โฌ
๏ธ Back to pillar selection",
|
|
145
|
+
value: "back",
|
|
146
|
+
},
|
|
147
|
+
new inquirer.Separator("โ".repeat(40)),
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// Add discovered modules (show count and tags)
|
|
151
|
+
for (const module of modules) {
|
|
152
|
+
// For performance/journeys/security: no count (a journeys module IS
|
|
153
|
+
// one journey; a security module IS one scan target). For smoke: count files not test cases.
|
|
154
|
+
const unit = pillar === "smoke" ? "file" : "test";
|
|
155
|
+
const countStr =
|
|
156
|
+
pillar === "performance" ||
|
|
157
|
+
pillar === "journeys" ||
|
|
158
|
+
pillar === "security"
|
|
159
|
+
? ""
|
|
160
|
+
: module.testCount === 0
|
|
161
|
+
? chalk.gray(`(0 ${unit}s)`)
|
|
162
|
+
: chalk.cyan(
|
|
163
|
+
`(${module.testCount} ${unit}${module.testCount > 1 ? "s" : ""})`,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Show tags if present
|
|
167
|
+
const tagsStr =
|
|
168
|
+
module.tags.length > 0
|
|
169
|
+
? chalk.gray(` [${module.tags.join(", ")}]`)
|
|
170
|
+
: "";
|
|
171
|
+
|
|
172
|
+
choices.push({
|
|
173
|
+
name: `${module.icon} ${module.displayName} ${countStr}${tagsStr} - ${module.description}`,
|
|
174
|
+
value: module.name,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const { module } = await inquirer.prompt<{ module: string }>([
|
|
179
|
+
{
|
|
180
|
+
type: "list",
|
|
181
|
+
name: "module",
|
|
182
|
+
message: labels[pillar].message,
|
|
183
|
+
choices,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
if (module === "back") return "back";
|
|
188
|
+
return module === "all" ? null : module;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Prompt for a smoke collection (suite) within a service
|
|
193
|
+
* moduleName format: "shop/api"
|
|
194
|
+
*/
|
|
195
|
+
async promptForSmokeCollection(
|
|
196
|
+
moduleName: string,
|
|
197
|
+
): Promise<string | null | "back"> {
|
|
198
|
+
const [repo, service] = moduleName.split("/");
|
|
199
|
+
|
|
200
|
+
let testDir = "";
|
|
201
|
+
try {
|
|
202
|
+
const manifest = await readManifestAsync(linkedRepo(repo));
|
|
203
|
+
testDir = (manifest as Record<string, unknown> | null)
|
|
204
|
+
? ((manifest as { smoke?: Record<string, { testDir?: string }> })
|
|
205
|
+
.smoke?.[service]?.testDir ?? "")
|
|
206
|
+
: "";
|
|
207
|
+
} catch {
|
|
208
|
+
// manifest unreadable โ fall through with empty testDir
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const collectionsDir = linkedRepo(repo, testDir);
|
|
212
|
+
|
|
213
|
+
// Discover collection subdirectories
|
|
214
|
+
const collections: Array<{
|
|
215
|
+
name: string;
|
|
216
|
+
icon: string;
|
|
217
|
+
displayName: string;
|
|
218
|
+
testCount: number;
|
|
219
|
+
}> = [];
|
|
220
|
+
try {
|
|
221
|
+
const entries = await fs.readdir(collectionsDir, { withFileTypes: true });
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
if (!entry.isDirectory()) continue;
|
|
224
|
+
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
225
|
+
|
|
226
|
+
// Count .smoke.ts files in this collection
|
|
227
|
+
let testCount = 0;
|
|
228
|
+
try {
|
|
229
|
+
const files = await fs.readdir(path.join(collectionsDir, entry.name));
|
|
230
|
+
testCount = files.filter((f) => f.endsWith(".smoke.ts")).length;
|
|
231
|
+
} catch {
|
|
232
|
+
/* ignore */
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Load manifest.yml for icon/name
|
|
236
|
+
let icon = "๐ฆ";
|
|
237
|
+
let displayName = entry.name;
|
|
238
|
+
try {
|
|
239
|
+
const mYaml = await fs.readFile(
|
|
240
|
+
path.join(collectionsDir, entry.name, "manifest.yml"),
|
|
241
|
+
"utf-8",
|
|
242
|
+
);
|
|
243
|
+
const m = yaml.parse(mYaml);
|
|
244
|
+
if (m?.icon) icon = m.icon;
|
|
245
|
+
if (m?.name) displayName = m.name;
|
|
246
|
+
} catch {
|
|
247
|
+
/* no manifest */
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
collections.push({ name: entry.name, icon, displayName, testCount });
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
/* dir unreadable */
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
collections.sort((a, b) => a.name.localeCompare(b.name));
|
|
257
|
+
|
|
258
|
+
const choices: Array<
|
|
259
|
+
| { name: string; value: string | null }
|
|
260
|
+
| InstanceType<typeof inquirer.Separator>
|
|
261
|
+
> = [
|
|
262
|
+
{ name: `๐ฏ All collections in ${service}`, value: null },
|
|
263
|
+
{ name: "โฌ
๏ธ Back to service selection", value: "back" },
|
|
264
|
+
new inquirer.Separator("โ".repeat(40)),
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
for (const col of collections) {
|
|
268
|
+
const countStr =
|
|
269
|
+
col.testCount === 0
|
|
270
|
+
? chalk.gray("(0 files)")
|
|
271
|
+
: chalk.cyan(
|
|
272
|
+
`(${col.testCount} file${col.testCount !== 1 ? "s" : ""})`,
|
|
273
|
+
);
|
|
274
|
+
choices.push({
|
|
275
|
+
name: `${col.icon} ${col.displayName} ${countStr}`,
|
|
276
|
+
value: col.name,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const { collection } = await inquirer.prompt<{ collection: string | null }>(
|
|
281
|
+
[
|
|
282
|
+
{
|
|
283
|
+
type: "list",
|
|
284
|
+
name: "collection",
|
|
285
|
+
message: `Select collection in ${repo}/${service}:`,
|
|
286
|
+
choices,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
return collection === "back" ? "back" : collection;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Prompt for credentials setup
|
|
296
|
+
*/
|
|
297
|
+
async promptForCredentials(environment: Environment): Promise<{
|
|
298
|
+
username: string;
|
|
299
|
+
password: string;
|
|
300
|
+
auth0ClientId?: string;
|
|
301
|
+
}> {
|
|
302
|
+
console.log(
|
|
303
|
+
`\n๐ Setup credentials for: ${this.formatEnvironmentName(environment)}`,
|
|
304
|
+
);
|
|
305
|
+
console.log("โ".repeat(50));
|
|
306
|
+
|
|
307
|
+
const answers = await inquirer.prompt([
|
|
308
|
+
{
|
|
309
|
+
type: "input",
|
|
310
|
+
name: "username",
|
|
311
|
+
message: "Username/Email:",
|
|
312
|
+
validate: (input: string) => {
|
|
313
|
+
if (!input || input.trim().length === 0) {
|
|
314
|
+
return "Username is required";
|
|
315
|
+
}
|
|
316
|
+
return true;
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: "password",
|
|
321
|
+
name: "password",
|
|
322
|
+
message: "Password:",
|
|
323
|
+
mask: "*",
|
|
324
|
+
validate: (input: string) => {
|
|
325
|
+
if (!input || input.trim().length === 0) {
|
|
326
|
+
return "Password is required";
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
type: "input",
|
|
333
|
+
name: "auth0ClientId",
|
|
334
|
+
message: "Auth0 Client ID (optional):",
|
|
335
|
+
},
|
|
336
|
+
]);
|
|
337
|
+
|
|
338
|
+
return answers;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Prompt for elevated credentials
|
|
343
|
+
*/
|
|
344
|
+
async promptForElevatedCredentials(environment: Environment): Promise<{
|
|
345
|
+
username: string;
|
|
346
|
+
password: string;
|
|
347
|
+
} | null> {
|
|
348
|
+
const { needElevated } = await inquirer.prompt<{ needElevated: boolean }>([
|
|
349
|
+
{
|
|
350
|
+
type: "confirm",
|
|
351
|
+
name: "needElevated",
|
|
352
|
+
message: "Setup elevated/admin credentials?",
|
|
353
|
+
default: false,
|
|
354
|
+
},
|
|
355
|
+
]);
|
|
356
|
+
|
|
357
|
+
if (!needElevated) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
console.log(
|
|
362
|
+
`\n๐ Setup elevated credentials for: ${this.formatEnvironmentName(environment)}`,
|
|
363
|
+
);
|
|
364
|
+
console.log("โ".repeat(50));
|
|
365
|
+
|
|
366
|
+
const answers = await inquirer.prompt([
|
|
367
|
+
{
|
|
368
|
+
type: "input",
|
|
369
|
+
name: "username",
|
|
370
|
+
message: "Admin Username/Email:",
|
|
371
|
+
validate: (input: string) => {
|
|
372
|
+
if (!input || input.trim().length === 0) {
|
|
373
|
+
return "Username is required";
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
type: "password",
|
|
380
|
+
name: "password",
|
|
381
|
+
message: "Admin Password:",
|
|
382
|
+
mask: "*",
|
|
383
|
+
validate: (input: string) => {
|
|
384
|
+
if (!input || input.trim().length === 0) {
|
|
385
|
+
return "Password is required";
|
|
386
|
+
}
|
|
387
|
+
return true;
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
]);
|
|
391
|
+
|
|
392
|
+
return answers;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Confirm action
|
|
397
|
+
*/
|
|
398
|
+
async confirm(
|
|
399
|
+
message: string,
|
|
400
|
+
defaultValue: boolean = true,
|
|
401
|
+
): Promise<boolean> {
|
|
402
|
+
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
|
|
403
|
+
{
|
|
404
|
+
type: "confirm",
|
|
405
|
+
name: "confirmed",
|
|
406
|
+
message,
|
|
407
|
+
default: defaultValue,
|
|
408
|
+
},
|
|
409
|
+
]);
|
|
410
|
+
|
|
411
|
+
return confirmed;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Prompt for main menu action
|
|
416
|
+
*/
|
|
417
|
+
async promptForMainAction(): Promise<
|
|
418
|
+
"run" | "results" | "environment" | "exit"
|
|
419
|
+
> {
|
|
420
|
+
const { action } = await inquirer.prompt<{
|
|
421
|
+
action: "run" | "results" | "environment" | "exit";
|
|
422
|
+
}>([
|
|
423
|
+
{
|
|
424
|
+
type: "list",
|
|
425
|
+
name: "action",
|
|
426
|
+
message: "What would you like to do?",
|
|
427
|
+
choices: [
|
|
428
|
+
{
|
|
429
|
+
name: "โถ๏ธ Run Tests",
|
|
430
|
+
value: "run",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: "๐ View Results",
|
|
434
|
+
value: "results",
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "โ Exit",
|
|
438
|
+
value: "exit",
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
]);
|
|
443
|
+
|
|
444
|
+
return action;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Prompt for number of days for results
|
|
449
|
+
*/
|
|
450
|
+
async promptForResultsDays(): Promise<number> {
|
|
451
|
+
const { days } = await inquirer.prompt<{ days: string }>([
|
|
452
|
+
{
|
|
453
|
+
type: "list",
|
|
454
|
+
name: "days",
|
|
455
|
+
message: "Show results from:",
|
|
456
|
+
choices: [
|
|
457
|
+
{ name: "Last 24 hours", value: "1" },
|
|
458
|
+
{ name: "Last 7 days", value: "7" },
|
|
459
|
+
{ name: "Last 30 days", value: "30" },
|
|
460
|
+
{ name: "All time", value: "365" },
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
]);
|
|
464
|
+
|
|
465
|
+
return parseInt(days, 10);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async promptForWorkspaceAction(): Promise<
|
|
469
|
+
| "list"
|
|
470
|
+
| "use"
|
|
471
|
+
| "create"
|
|
472
|
+
| "add-service"
|
|
473
|
+
| "remove-service"
|
|
474
|
+
| "delete"
|
|
475
|
+
| "back"
|
|
476
|
+
> {
|
|
477
|
+
const { action } = await inquirer.prompt<{
|
|
478
|
+
action:
|
|
479
|
+
| "list"
|
|
480
|
+
| "use"
|
|
481
|
+
| "create"
|
|
482
|
+
| "add-service"
|
|
483
|
+
| "remove-service"
|
|
484
|
+
| "delete"
|
|
485
|
+
| "back";
|
|
486
|
+
}>([
|
|
487
|
+
{
|
|
488
|
+
type: "list",
|
|
489
|
+
name: "action",
|
|
490
|
+
message: "Workspaces:",
|
|
491
|
+
choices: [
|
|
492
|
+
{ name: "๐ List Workspaces", value: "list" },
|
|
493
|
+
{ name: "๐ Switch Workspace", value: "use" },
|
|
494
|
+
{ name: "โ Create Workspace", value: "create" },
|
|
495
|
+
{ name: "โ Add Service to Workspace", value: "add-service" },
|
|
496
|
+
{ name: "โ Remove Service from Workspace", value: "remove-service" },
|
|
497
|
+
{ name: "๐ Delete Workspace", value: "delete" },
|
|
498
|
+
{ name: "โ Back", value: "back" },
|
|
499
|
+
],
|
|
500
|
+
},
|
|
501
|
+
]);
|
|
502
|
+
return action;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async promptForRegistrationSource(): Promise<"local" | "github"> {
|
|
506
|
+
const { source } = await inquirer.prompt<{ source: "local" | "github" }>([
|
|
507
|
+
{
|
|
508
|
+
type: "list",
|
|
509
|
+
name: "source",
|
|
510
|
+
message: "Where is this repo?",
|
|
511
|
+
choices: [
|
|
512
|
+
{
|
|
513
|
+
name: `Local checkout โ I already have it on disk (${product().cli} reads it, never modifies)`,
|
|
514
|
+
value: "local",
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: `GitHub repo โ ${product().cli} clones and manages it for me`,
|
|
518
|
+
value: "github",
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
},
|
|
522
|
+
]);
|
|
523
|
+
return source;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async promptForGhOrgRepo(
|
|
527
|
+
repos: { name: string; nameWithOwner: string; defaultBranch: string }[],
|
|
528
|
+
): Promise<{ name: string; nameWithOwner: string; defaultBranch: string }> {
|
|
529
|
+
const { selected } = await inquirer.prompt<{ selected: string }>([
|
|
530
|
+
{
|
|
531
|
+
type: "list",
|
|
532
|
+
name: "selected",
|
|
533
|
+
message: "Select GitHub repo:",
|
|
534
|
+
choices: repos.map((r) => ({
|
|
535
|
+
name: r.nameWithOwner,
|
|
536
|
+
value: r.nameWithOwner,
|
|
537
|
+
})),
|
|
538
|
+
pageSize: 20,
|
|
539
|
+
},
|
|
540
|
+
]);
|
|
541
|
+
return repos.find((r) => r.nameWithOwner === selected)!;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async promptForRepoPath(alreadyRegistered: string[] = []): Promise<string> {
|
|
545
|
+
const reposRoot = getReposRoot();
|
|
546
|
+
const MANUAL_ENTRY = "__manual__";
|
|
547
|
+
|
|
548
|
+
// Recursively discover repos under reposRoot (dirs with .den.yml), up to depth 4.
|
|
549
|
+
type RepoCandidate = {
|
|
550
|
+
relPath: string;
|
|
551
|
+
absPath: string;
|
|
552
|
+
registered: boolean;
|
|
553
|
+
};
|
|
554
|
+
const candidates: RepoCandidate[] = [];
|
|
555
|
+
|
|
556
|
+
const walkDir = async (
|
|
557
|
+
dir: string,
|
|
558
|
+
relPrefix: string,
|
|
559
|
+
depth: number,
|
|
560
|
+
): Promise<void> => {
|
|
561
|
+
if (depth > 4) return;
|
|
562
|
+
let entries: Dirent[];
|
|
563
|
+
try {
|
|
564
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
565
|
+
} catch {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
await Promise.all(
|
|
569
|
+
entries.map(async (e) => {
|
|
570
|
+
if (!e.isDirectory() && !e.isSymbolicLink()) return;
|
|
571
|
+
if (e.name.startsWith(".")) return;
|
|
572
|
+
const absPath = path.join(dir, e.name);
|
|
573
|
+
if (absPath === getDistRoot()) return; // skip this distribution itself
|
|
574
|
+
const relPath = relPrefix ? `${relPrefix}/${e.name}` : e.name;
|
|
575
|
+
const hasManifest = await fs
|
|
576
|
+
.access(path.join(absPath, `${product().manifestYml}`))
|
|
577
|
+
.then(() => true)
|
|
578
|
+
.catch(() => false);
|
|
579
|
+
if (hasManifest) {
|
|
580
|
+
candidates.push({
|
|
581
|
+
relPath,
|
|
582
|
+
absPath,
|
|
583
|
+
registered: alreadyRegistered.includes(relPath),
|
|
584
|
+
});
|
|
585
|
+
} else {
|
|
586
|
+
await walkDir(absPath, relPath, depth + 1);
|
|
587
|
+
}
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
};
|
|
591
|
+
await walkDir(reposRoot, "", 0);
|
|
592
|
+
|
|
593
|
+
const choices: { name: string; value: string }[] = candidates
|
|
594
|
+
.sort((a, b) => a.relPath.localeCompare(b.relPath))
|
|
595
|
+
.map((c) => {
|
|
596
|
+
const reg = c.registered ? chalk.gray(" (already registered)") : "";
|
|
597
|
+
return { name: `${chalk.green(c.relPath)}${reg}`, value: c.absPath };
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
choices.push({
|
|
601
|
+
name: chalk.gray("Enter path manuallyโฆ"),
|
|
602
|
+
value: MANUAL_ENTRY,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const { selected } = await inquirer.prompt<{ selected: string }>([
|
|
606
|
+
{
|
|
607
|
+
type: "list",
|
|
608
|
+
name: "selected",
|
|
609
|
+
message: "Select repo:",
|
|
610
|
+
choices,
|
|
611
|
+
pageSize: 15,
|
|
612
|
+
},
|
|
613
|
+
]);
|
|
614
|
+
|
|
615
|
+
if (selected !== MANUAL_ENTRY) return selected;
|
|
616
|
+
|
|
617
|
+
const { repoPath } = await inquirer.prompt<{ repoPath: string }>([
|
|
618
|
+
{
|
|
619
|
+
type: "input",
|
|
620
|
+
name: "repoPath",
|
|
621
|
+
message: "Absolute path to repo checkout:",
|
|
622
|
+
validate: (v: string) => {
|
|
623
|
+
const p = v.trim();
|
|
624
|
+
if (!p) return "Path is required";
|
|
625
|
+
if (!existsSync(p)) return `Path does not exist: ${p}`;
|
|
626
|
+
return true;
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
]);
|
|
630
|
+
return repoPath.trim();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async promptForBranch(defaultValue = "main"): Promise<string> {
|
|
634
|
+
const { branch } = await inquirer.prompt<{ branch: string }>([
|
|
635
|
+
{
|
|
636
|
+
type: "input",
|
|
637
|
+
name: "branch",
|
|
638
|
+
message: `Branch to track (${product().cli} will clone/pull this branch):`,
|
|
639
|
+
default: defaultValue,
|
|
640
|
+
validate: (v: string) => (v.trim() ? true : "Branch name is required"),
|
|
641
|
+
},
|
|
642
|
+
]);
|
|
643
|
+
return branch.trim();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async promptForLinkedPath(defaultPath: string): Promise<string> {
|
|
647
|
+
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
|
|
648
|
+
{
|
|
649
|
+
type: "confirm",
|
|
650
|
+
name: "confirmed",
|
|
651
|
+
message: `Use this path for the linked checkout?\n ${defaultPath}`,
|
|
652
|
+
default: true,
|
|
653
|
+
},
|
|
654
|
+
]);
|
|
655
|
+
if (confirmed) return defaultPath;
|
|
656
|
+
|
|
657
|
+
const { p } = await inquirer.prompt<{ p: string }>([
|
|
658
|
+
{
|
|
659
|
+
type: "input",
|
|
660
|
+
name: "p",
|
|
661
|
+
message: "Absolute path to your local checkout:",
|
|
662
|
+
validate: (v: string) => {
|
|
663
|
+
const trimmed = v.trim();
|
|
664
|
+
if (!trimmed) return "Path is required";
|
|
665
|
+
if (!existsSync(trimmed)) return `Path does not exist: ${trimmed}`;
|
|
666
|
+
return true;
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
]);
|
|
670
|
+
return p.trim();
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async promptForGhSlug(defaultValue?: string): Promise<string> {
|
|
674
|
+
const { slug } = await inquirer.prompt<{ slug: string }>([
|
|
675
|
+
{
|
|
676
|
+
type: "input",
|
|
677
|
+
name: "slug",
|
|
678
|
+
message: "GitHub repo slug (owner/repo):",
|
|
679
|
+
default: defaultValue,
|
|
680
|
+
validate: (v: string) =>
|
|
681
|
+
/^[\w.-]+\/[\w.-]+$/.test(v.trim())
|
|
682
|
+
? true
|
|
683
|
+
: "Expected format: owner/repo",
|
|
684
|
+
},
|
|
685
|
+
]);
|
|
686
|
+
return slug.trim();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async promptSelectWorkspace(
|
|
690
|
+
workspaces: string[],
|
|
691
|
+
current: string,
|
|
692
|
+
): Promise<string> {
|
|
693
|
+
const { name } = await inquirer.prompt<{ name: string }>([
|
|
694
|
+
{
|
|
695
|
+
type: "list",
|
|
696
|
+
name: "name",
|
|
697
|
+
message: "Select workspace:",
|
|
698
|
+
choices: workspaces.map((w) => ({
|
|
699
|
+
name: w === current ? `${w} (active)` : w,
|
|
700
|
+
value: w,
|
|
701
|
+
})),
|
|
702
|
+
},
|
|
703
|
+
]);
|
|
704
|
+
return name;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async promptForWorkspaceName(existing: string[]): Promise<string> {
|
|
708
|
+
const { name } = await inquirer.prompt<{ name: string }>([
|
|
709
|
+
{
|
|
710
|
+
type: "input",
|
|
711
|
+
name: "name",
|
|
712
|
+
message: "Workspace name:",
|
|
713
|
+
validate: (v: string) => {
|
|
714
|
+
const n = v.trim();
|
|
715
|
+
if (!n) return "Name is required";
|
|
716
|
+
if (existing.includes(n)) return `Workspace "${n}" already exists`;
|
|
717
|
+
// eslint-disable-next-line no-control-regex
|
|
718
|
+
if (/["'\\/<>\x00-\x1f]/.test(n))
|
|
719
|
+
return "Name must not contain quotes, slashes, or control characters";
|
|
720
|
+
return true;
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
]);
|
|
724
|
+
return name.trim();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async promptSelectServices(
|
|
728
|
+
addresses: string[],
|
|
729
|
+
message: string,
|
|
730
|
+
defaultAll = true,
|
|
731
|
+
): Promise<string[]> {
|
|
732
|
+
const { selected } = await inquirer.prompt<{ selected: string[] }>([
|
|
733
|
+
{
|
|
734
|
+
type: "checkbox",
|
|
735
|
+
name: "selected",
|
|
736
|
+
message,
|
|
737
|
+
choices: addresses.map((a) => ({
|
|
738
|
+
name: a,
|
|
739
|
+
value: a,
|
|
740
|
+
checked: defaultAll,
|
|
741
|
+
})),
|
|
742
|
+
pageSize: 20,
|
|
743
|
+
},
|
|
744
|
+
]);
|
|
745
|
+
return selected;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Format environment name for display
|
|
750
|
+
*/
|
|
751
|
+
private formatEnvironmentName(env: Environment): string {
|
|
752
|
+
const names: Record<Environment, string> = {
|
|
753
|
+
local: "๐ Local",
|
|
754
|
+
dev: "๐ง Development",
|
|
755
|
+
sandbox: "๐๏ธ Sandbox",
|
|
756
|
+
staging: "๐ญ Staging",
|
|
757
|
+
beta: "๐งช Beta",
|
|
758
|
+
prod: "๐ Prod",
|
|
759
|
+
production: "๐ Production",
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
return names[env] || env;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Export singleton instance
|
|
767
|
+
export const cliPrompts = new CLIPrompts();
|