@quiltdata/benchling-webhook 0.7.7 → 0.7.8-20251115T063729Z

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 (81) hide show
  1. package/README.md +24 -11
  2. package/dist/bin/cli.js +77 -3
  3. package/dist/bin/cli.js.map +1 -1
  4. package/dist/bin/commands/config-show.d.ts +14 -0
  5. package/dist/bin/commands/config-show.d.ts.map +1 -0
  6. package/dist/bin/commands/config-show.js +24 -0
  7. package/dist/bin/commands/config-show.js.map +1 -0
  8. package/dist/bin/commands/deploy.d.ts +14 -0
  9. package/dist/bin/commands/deploy.d.ts.map +1 -1
  10. package/dist/bin/commands/deploy.js +1 -0
  11. package/dist/bin/commands/deploy.js.map +1 -1
  12. package/dist/bin/commands/infer-quilt-config.d.ts +3 -0
  13. package/dist/bin/commands/infer-quilt-config.d.ts.map +1 -1
  14. package/dist/bin/commands/infer-quilt-config.js +74 -8
  15. package/dist/bin/commands/infer-quilt-config.js.map +1 -1
  16. package/dist/bin/commands/install.d.ts.map +1 -1
  17. package/dist/bin/commands/install.js +40 -7
  18. package/dist/bin/commands/install.js.map +1 -1
  19. package/dist/bin/commands/manifest.d.ts +20 -3
  20. package/dist/bin/commands/manifest.d.ts.map +1 -1
  21. package/dist/bin/commands/manifest.js +23 -7
  22. package/dist/bin/commands/manifest.js.map +1 -1
  23. package/dist/bin/commands/setup-wizard.d.ts +67 -13
  24. package/dist/bin/commands/setup-wizard.d.ts.map +1 -1
  25. package/dist/bin/commands/setup-wizard.js +239 -742
  26. package/dist/bin/commands/setup-wizard.js.map +1 -1
  27. package/dist/bin/commands/sync-secrets.d.ts +7 -7
  28. package/dist/bin/commands/sync-secrets.d.ts.map +1 -1
  29. package/dist/bin/commands/sync-secrets.js +55 -15
  30. package/dist/bin/commands/sync-secrets.js.map +1 -1
  31. package/dist/bin/commands/validate.d.ts +18 -3
  32. package/dist/bin/commands/validate.d.ts.map +1 -1
  33. package/dist/bin/commands/validate.js +103 -69
  34. package/dist/bin/commands/validate.js.map +1 -1
  35. package/dist/lib/types/config.d.ts +13 -0
  36. package/dist/lib/types/config.d.ts.map +1 -1
  37. package/dist/lib/types/config.js +1 -0
  38. package/dist/lib/types/config.js.map +1 -1
  39. package/dist/lib/utils/config.d.ts +14 -17
  40. package/dist/lib/utils/config.d.ts.map +1 -1
  41. package/dist/lib/utils/config.js +6 -88
  42. package/dist/lib/utils/config.js.map +1 -1
  43. package/dist/lib/wizard/phase1-catalog-discovery.d.ts +41 -0
  44. package/dist/lib/wizard/phase1-catalog-discovery.d.ts.map +1 -0
  45. package/dist/lib/wizard/phase1-catalog-discovery.js +206 -0
  46. package/dist/lib/wizard/phase1-catalog-discovery.js.map +1 -0
  47. package/dist/lib/wizard/phase2-stack-query.d.ts +35 -0
  48. package/dist/lib/wizard/phase2-stack-query.d.ts.map +1 -0
  49. package/dist/lib/wizard/phase2-stack-query.js +99 -0
  50. package/dist/lib/wizard/phase2-stack-query.js.map +1 -0
  51. package/dist/lib/wizard/phase3-parameter-collection.d.ts +24 -0
  52. package/dist/lib/wizard/phase3-parameter-collection.d.ts.map +1 -0
  53. package/dist/lib/wizard/phase3-parameter-collection.js +399 -0
  54. package/dist/lib/wizard/phase3-parameter-collection.js.map +1 -0
  55. package/dist/lib/wizard/phase4-validation.d.ts +22 -0
  56. package/dist/lib/wizard/phase4-validation.d.ts.map +1 -0
  57. package/dist/lib/wizard/phase4-validation.js +294 -0
  58. package/dist/lib/wizard/phase4-validation.js.map +1 -0
  59. package/dist/lib/wizard/phase5-mode-decision.d.ts +22 -0
  60. package/dist/lib/wizard/phase5-mode-decision.d.ts.map +1 -0
  61. package/dist/lib/wizard/phase5-mode-decision.js +65 -0
  62. package/dist/lib/wizard/phase5-mode-decision.js.map +1 -0
  63. package/dist/lib/wizard/phase6-integrated-mode.d.ts +24 -0
  64. package/dist/lib/wizard/phase6-integrated-mode.d.ts.map +1 -0
  65. package/dist/lib/wizard/phase6-integrated-mode.js +131 -0
  66. package/dist/lib/wizard/phase6-integrated-mode.js.map +1 -0
  67. package/dist/lib/wizard/phase7-standalone-mode.d.ts +25 -0
  68. package/dist/lib/wizard/phase7-standalone-mode.d.ts.map +1 -0
  69. package/dist/lib/wizard/phase7-standalone-mode.js +210 -0
  70. package/dist/lib/wizard/phase7-standalone-mode.js.map +1 -0
  71. package/dist/lib/wizard/types.d.ts +175 -0
  72. package/dist/lib/wizard/types.d.ts.map +1 -0
  73. package/dist/lib/wizard/types.js +11 -0
  74. package/dist/lib/wizard/types.js.map +1 -0
  75. package/dist/package.json +3 -4
  76. package/package.json +3 -4
  77. package/dist/bin/commands/get-env.d.ts +0 -16
  78. package/dist/bin/commands/get-env.d.ts.map +0 -1
  79. package/dist/bin/commands/get-env.js +0 -210
  80. package/dist/bin/commands/get-env.js.map +0 -1
  81. package/env.template +0 -79
@@ -1,735 +1,223 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  /**
4
- * Interactive Configuration Wizard (v0.7.0)
4
+ * Interactive Configuration Wizard (v0.8.0)
5
5
  *
6
- * Complete setup wizard that orchestrates:
7
- * 1. Quilt configuration inference
8
- * 2. Interactive configuration prompts
9
- * 3. Configuration validation
10
- * 4. Profile persistence via XDGConfig
6
+ * Phase-based modular setup wizard that orchestrates:
7
+ * 1. Catalog discovery and confirmation
8
+ * 2. Stack query for infrastructure details
9
+ * 3. Parameter collection from user
10
+ * 4. Configuration validation
11
+ * 5. Deployment mode decision (integrated vs standalone)
12
+ * 6. Mode-specific setup (integrated or standalone)
11
13
  *
12
- * Consolidated from scripts/install-wizard.ts, scripts/config/wizard.ts,
13
- * and scripts/config/validator.ts.
14
+ * Architecture:
15
+ * - Each phase is a separate, testable module
16
+ * - Explicit data flow between phases via TypeScript types
17
+ * - Cannot skip phases or execute out of order
18
+ * - Integrated mode has explicit return (no deployment)
14
19
  *
15
20
  * @module commands/setup-wizard
21
+ * @version 0.8.0
16
22
  */
17
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- var desc = Object.getOwnPropertyDescriptor(m, k);
20
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
- desc = { enumerable: true, get: function() { return m[k]; } };
22
- }
23
- Object.defineProperty(o, k2, desc);
24
- }) : (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- o[k2] = m[k];
27
- }));
28
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
- Object.defineProperty(o, "default", { enumerable: true, value: v });
30
- }) : function(o, v) {
31
- o["default"] = v;
32
- });
33
- var __importStar = (this && this.__importStar) || (function () {
34
- var ownKeys = function(o) {
35
- ownKeys = Object.getOwnPropertyNames || function (o) {
36
- var ar = [];
37
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
- return ar;
39
- };
40
- return ownKeys(o);
41
- };
42
- return function (mod) {
43
- if (mod && mod.__esModule) return mod;
44
- var result = {};
45
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
- __setModuleDefault(result, mod);
47
- return result;
48
- };
49
- })();
50
23
  var __importDefault = (this && this.__importDefault) || function (mod) {
51
24
  return (mod && mod.__esModule) ? mod : { "default": mod };
52
25
  };
53
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.runSetupWizard = runSetupWizard;
54
28
  exports.setupWizardCommand = setupWizardCommand;
55
- const https = __importStar(require("https"));
56
- const inquirer_1 = __importDefault(require("inquirer"));
57
29
  const chalk_1 = __importDefault(require("chalk"));
58
- const client_s3_1 = require("@aws-sdk/client-s3");
30
+ const inquirer_1 = __importDefault(require("inquirer"));
59
31
  const xdg_config_1 = require("../../lib/xdg-config");
60
- const infer_quilt_config_1 = require("../commands/infer-quilt-config");
61
- const sqs_1 = require("../../lib/utils/sqs");
62
- const manifest_1 = require("./manifest");
63
- const next_steps_generator_1 = require("../../lib/next-steps-generator");
64
- // =============================================================================
65
- // VALIDATION FUNCTIONS (from scripts/config/validator.ts)
66
- // =============================================================================
32
+ const fs_1 = require("fs");
33
+ const path_1 = require("path");
34
+ // Phase modules
35
+ const phase1_catalog_discovery_1 = require("../../lib/wizard/phase1-catalog-discovery");
36
+ const phase2_stack_query_1 = require("../../lib/wizard/phase2-stack-query");
37
+ const phase3_parameter_collection_1 = require("../../lib/wizard/phase3-parameter-collection");
38
+ const phase4_validation_1 = require("../../lib/wizard/phase4-validation");
39
+ const phase5_mode_decision_1 = require("../../lib/wizard/phase5-mode-decision");
40
+ const phase6_integrated_mode_1 = require("../../lib/wizard/phase6-integrated-mode");
41
+ const phase7_standalone_mode_1 = require("../../lib/wizard/phase7-standalone-mode");
67
42
  /**
68
- * Detects the actual region of an S3 bucket
69
- *
70
- * @param bucketName - Name of the S3 bucket
71
- * @param awsProfile - Optional AWS profile to use
72
- * @returns The bucket's actual region, or null if detection fails
43
+ * Step titles for the wizard phases
44
+ * This is the single source of truth for step numbering and titles
73
45
  */
74
- async function detectBucketRegion(bucketName, awsProfile) {
75
- try {
76
- const clientConfig = {
77
- region: "us-east-1", // Use us-east-1 as the API endpoint for GetBucketLocation
78
- };
79
- if (awsProfile) {
80
- const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
81
- clientConfig.credentials = fromIni({ profile: awsProfile });
82
- }
83
- const s3Client = new client_s3_1.S3Client(clientConfig);
84
- const command = new client_s3_1.GetBucketLocationCommand({ Bucket: bucketName });
85
- const response = await s3Client.send(command);
86
- // AWS returns null for us-east-1, otherwise returns the region constraint
87
- const region = response.LocationConstraint || "us-east-1";
88
- return region;
89
- }
90
- catch {
91
- // If we can't detect the region, return null and let validation proceed with the provided region
92
- return null;
93
- }
94
- }
95
- /**
96
- * Validates Benchling tenant accessibility
97
- */
98
- async function validateBenchlingTenant(tenant) {
99
- const result = {
100
- isValid: false,
101
- errors: [],
102
- warnings: [],
103
- };
104
- if (!tenant || tenant.trim().length === 0) {
105
- result.errors.push("Tenant name cannot be empty");
106
- return result;
107
- }
108
- // Basic format validation
109
- if (!/^[a-zA-Z0-9-_]+$/.test(tenant)) {
110
- result.errors.push("Tenant name contains invalid characters (only alphanumeric, dash, underscore allowed)");
111
- return result;
112
- }
113
- // Test tenant URL accessibility
114
- const tenantUrl = `https://${tenant}.benchling.com`;
115
- console.log(` Testing Benchling tenant URL: ${tenantUrl}`);
116
- return new Promise((resolve) => {
117
- https
118
- .get(tenantUrl, { timeout: 5000 }, (res) => {
119
- if (res.statusCode === 200 || res.statusCode === 302 || res.statusCode === 301) {
120
- result.isValid = true;
121
- console.log(` ✓ Tenant URL accessible: ${tenantUrl}`);
122
- }
123
- else {
124
- if (!result.warnings)
125
- result.warnings = [];
126
- result.warnings.push(`Tenant URL ${tenantUrl} returned status ${res.statusCode}`);
127
- result.isValid = true; // Consider this a warning, not an error
128
- }
129
- resolve(result);
130
- })
131
- .on("error", (error) => {
132
- if (!result.warnings)
133
- result.warnings = [];
134
- result.warnings.push(`Could not verify tenant URL ${tenantUrl}: ${error.message}`);
135
- result.isValid = true; // Allow proceeding with warning
136
- resolve(result);
137
- });
138
- });
139
- }
140
- /**
141
- * Validates Benchling OAuth credentials
142
- */
143
- async function validateBenchlingCredentials(tenant, clientId, clientSecret) {
144
- const result = {
145
- isValid: false,
146
- errors: [],
147
- warnings: [],
148
- };
149
- if (!clientId || clientId.trim().length === 0) {
150
- result.errors.push("Client ID cannot be empty");
151
- }
152
- if (!clientSecret || clientSecret.trim().length === 0) {
153
- result.errors.push("Client secret cannot be empty");
154
- }
155
- if (result.errors.length > 0) {
156
- return result;
157
- }
158
- // Test OAuth token endpoint
159
- const tokenUrl = `https://${tenant}.benchling.com/api/v2/token`;
160
- const authString = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
161
- console.log(` Testing OAuth credentials: ${tokenUrl} (Client ID: ${clientId.substring(0, 8)}...)`);
162
- return new Promise((resolve) => {
163
- const postData = "grant_type=client_credentials";
164
- const options = {
165
- method: "POST",
166
- headers: {
167
- "Authorization": `Basic ${authString}`,
168
- "Content-Type": "application/x-www-form-urlencoded",
169
- "Content-Length": postData.length,
170
- },
171
- timeout: 10000,
172
- };
173
- const req = https.request(tokenUrl, options, (res) => {
174
- let data = "";
175
- res.on("data", (chunk) => {
176
- data += chunk;
177
- });
178
- res.on("end", () => {
179
- if (res.statusCode === 200) {
180
- result.isValid = true;
181
- console.log(" ✓ OAuth credentials validated successfully");
182
- }
183
- else {
184
- let errorDetail = data.substring(0, 200);
185
- try {
186
- const parsed = JSON.parse(data);
187
- if (parsed.error_description) {
188
- errorDetail = parsed.error_description;
189
- }
190
- }
191
- catch {
192
- // Keep the raw data if not JSON
193
- }
194
- result.errors.push(`OAuth validation failed for tenant '${tenant}':\n` +
195
- ` Tested: POST ${tokenUrl}\n` +
196
- ` Status: ${res.statusCode}\n` +
197
- ` Error: ${errorDetail}\n` +
198
- " Hint: Verify Client ID and Secret are correct and match the app definition");
199
- }
200
- resolve(result);
201
- });
202
- });
203
- req.on("error", (error) => {
204
- if (!result.warnings)
205
- result.warnings = [];
206
- result.warnings.push(`Could not validate OAuth credentials at ${tokenUrl}: ${error.message}\n` +
207
- " This may be a network issue. Credentials will be validated during deployment.");
208
- result.isValid = true; // Allow proceeding with warning
209
- resolve(result);
210
- });
211
- req.write(postData);
212
- req.end();
213
- });
214
- }
46
+ const STEP_TITLES = {
47
+ catalogDiscovery: "Quilt Catalog Discovery",
48
+ stackQuery: "Quilt Stack Configuration",
49
+ parameterCollection: "Configuration Parameters",
50
+ validation: "Validation",
51
+ modeDecision: "Deployment Mode",
52
+ integratedMode: "Integrated Setup",
53
+ standaloneMode: "Standalone Setup",
54
+ };
215
55
  /**
216
- * Validates S3 bucket access
56
+ * Gets the package version from package.json
217
57
  */
218
- async function validateS3BucketAccess(bucketName, region, awsProfile) {
219
- const result = {
220
- isValid: false,
221
- errors: [],
222
- warnings: [],
223
- };
224
- if (!bucketName || bucketName.trim().length === 0) {
225
- result.errors.push("Bucket name cannot be empty");
226
- return result;
227
- }
228
- // First, try to detect the bucket's actual region
229
- console.log(` Detecting region for bucket: ${bucketName}`);
230
- const actualRegion = await detectBucketRegion(bucketName, awsProfile);
231
- let regionToUse = region;
232
- if (actualRegion && actualRegion !== region) {
233
- console.log(` ⚠ Bucket is in ${actualRegion}, not ${region} - using detected region`);
234
- regionToUse = actualRegion;
235
- }
236
- else if (actualRegion) {
237
- console.log(` ✓ Bucket region confirmed: ${actualRegion}`);
238
- }
58
+ function getVersion() {
239
59
  try {
240
- const clientConfig = { region: regionToUse };
241
- if (awsProfile) {
242
- const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
243
- clientConfig.credentials = fromIni({ profile: awsProfile });
244
- }
245
- const s3Client = new client_s3_1.S3Client(clientConfig);
246
- // Test HeadBucket (verify bucket exists and we have access)
247
- console.log(` Testing S3 bucket access: ${bucketName} (region: ${regionToUse}${awsProfile ? `, profile: ${awsProfile}` : ""})`);
248
- const headCommand = new client_s3_1.HeadBucketCommand({ Bucket: bucketName });
249
- await s3Client.send(headCommand);
250
- console.log(` ✓ S3 bucket accessible: ${bucketName}`);
251
- // Test ListObjects (verify we can list objects)
252
- const listCommand = new client_s3_1.ListObjectsV2Command({
253
- Bucket: bucketName,
254
- MaxKeys: 1,
255
- });
256
- await s3Client.send(listCommand);
257
- console.log(" ✓ S3 bucket list permission confirmed");
258
- result.isValid = true;
60
+ const packagePath = (0, path_1.join)(__dirname, "../../package.json");
61
+ const packageJson = JSON.parse((0, fs_1.readFileSync)(packagePath, "utf-8"));
62
+ return packageJson.version;
259
63
  }
260
- catch (error) {
261
- const err = error;
262
- const errorCode = err.Code || err.name || "UnknownError";
263
- const errorMsg = err.message || "Unknown error occurred";
264
- const statusCode = err.$metadata?.httpStatusCode;
265
- // Provide specific guidance based on error type
266
- let hint = "Verify bucket exists, region is correct, and you have s3:GetBucketLocation and s3:ListBucket permissions";
267
- if (errorCode === "NoSuchBucket" || errorMsg.includes("does not exist")) {
268
- hint = "The bucket does not exist. Verify the bucket name is correct.";
269
- }
270
- else if (errorCode === "AccessDenied" || errorCode === "403" || statusCode === 403) {
271
- hint = "Access denied. Verify your AWS credentials have s3:GetBucketLocation and s3:ListBucket permissions for this bucket.";
272
- }
273
- else if (errorCode === "PermanentRedirect" || errorCode === "301" || statusCode === 301) {
274
- hint = `The bucket exists but is in a different region. Try specifying the correct region for bucket '${bucketName}'.`;
275
- }
276
- result.errors.push(`S3 bucket validation failed for '${bucketName}' in region '${regionToUse}'${awsProfile ? ` (AWS profile: ${awsProfile})` : ""}:\n` +
277
- ` Error: ${errorCode}${statusCode ? ` (HTTP ${statusCode})` : ""}\n` +
278
- ` Message: ${errorMsg}\n` +
279
- " Tested: HeadBucket operation\n" +
280
- ` Hint: ${hint}`);
64
+ catch {
65
+ return "unknown";
281
66
  }
282
- return result;
283
67
  }
284
68
  /**
285
- * Validates complete ProfileConfig
69
+ * Prints the wizard welcome banner
286
70
  */
287
- async function validateConfig(config, options = {}) {
288
- const result = {
289
- isValid: true,
290
- errors: [],
291
- warnings: [],
292
- };
293
- if (options.skipValidation) {
294
- return result;
295
- }
296
- // Validate Benchling tenant
297
- const tenantValidation = await validateBenchlingTenant(config.benchling.tenant);
298
- if (!tenantValidation.isValid) {
299
- result.isValid = false;
300
- result.errors.push(...tenantValidation.errors);
301
- }
302
- if (tenantValidation.warnings && tenantValidation.warnings.length > 0) {
303
- if (!result.warnings)
304
- result.warnings = [];
305
- result.warnings.push(...tenantValidation.warnings);
306
- }
307
- // Validate OAuth credentials (if secret is provided)
308
- if (config.benchling.clientSecret) {
309
- const credValidation = await validateBenchlingCredentials(config.benchling.tenant, config.benchling.clientId, config.benchling.clientSecret);
310
- if (!credValidation.isValid) {
311
- result.isValid = false;
312
- result.errors.push(...credValidation.errors);
313
- }
314
- if (credValidation.warnings && credValidation.warnings.length > 0) {
315
- if (!result.warnings)
316
- result.warnings = [];
317
- result.warnings.push(...credValidation.warnings);
318
- }
319
- }
320
- // Validate S3 bucket access
321
- const bucketValidation = await validateS3BucketAccess(config.packages.bucket, config.deployment.region, options.awsProfile);
322
- if (!bucketValidation.isValid) {
323
- result.isValid = false;
324
- result.errors.push(...bucketValidation.errors);
325
- }
326
- if (bucketValidation.warnings && bucketValidation.warnings.length > 0) {
327
- if (!result.warnings)
328
- result.warnings = [];
329
- result.warnings.push(...bucketValidation.warnings);
330
- }
331
- return result;
71
+ function printWelcomeBanner() {
72
+ const version = getVersion();
73
+ const prefix = " Benchling Webhook Setup (v";
74
+ const suffix = ")";
75
+ const totalWidth = 63; // Width between the ║ symbols
76
+ const contentLength = prefix.length + version.length + suffix.length;
77
+ const padding = " ".repeat(totalWidth - contentLength);
78
+ console.log("\n╔═══════════════════════════════════════════════════════════╗");
79
+ console.log(`║${prefix}${version}${suffix}${padding}║`);
80
+ console.log("╚═══════════════════════════════════════════════════════════╝");
332
81
  }
333
82
  /**
334
- * Runs interactive configuration wizard
83
+ * Prints a step header with proper numbering
84
+ * @param stepNumber - The current step number
85
+ * @param title - The step title
335
86
  */
336
- async function runConfigWizard(options = {}) {
337
- const { existingConfig = {}, yes = false, inheritFrom } = options;
338
- console.log("╔═══════════════════════════════════════════════════════════╗");
339
- console.log("║ Benchling Webhook Configuration Wizard ║");
340
- console.log("╚═══════════════════════════════════════════════════════════╝\n");
341
- if (inheritFrom) {
342
- console.log(`Creating profile inheriting from: ${inheritFrom}\n`);
343
- }
344
- const config = { ...existingConfig };
345
- let awsAccountId;
346
- // If non-interactive, validate that all required fields are present
347
- if (yes) {
348
- if (!config.benchling?.tenant || !config.benchling?.clientId || !config.benchling?.clientSecret) {
349
- throw new Error("Non-interactive mode requires benchlingTenant, benchlingClientId, and benchlingClientSecret to be already configured");
350
- }
351
- // Add metadata and inheritance marker before returning
352
- const now = new Date().toISOString();
353
- const finalConfig = config;
354
- finalConfig._metadata = {
355
- version: "0.7.0",
356
- createdAt: config._metadata?.createdAt || now,
357
- updatedAt: now,
358
- source: "wizard",
359
- };
360
- if (inheritFrom) {
361
- finalConfig._inherits = inheritFrom;
362
- }
363
- return finalConfig;
364
- }
365
- // Always prompt for Quilt configuration (use existing/inferred values as defaults)
366
- console.log("Step 1: Quilt Configuration\n");
367
- const quiltAnswers = await inquirer_1.default.prompt([
368
- {
369
- type: "input",
370
- name: "stackArn",
371
- message: "Quilt Stack ARN:",
372
- default: config.quilt?.stackArn,
373
- validate: (input) => input.trim().length > 0 && input.startsWith("arn:aws:cloudformation:") ||
374
- "Stack ARN is required and must start with arn:aws:cloudformation:",
375
- },
376
- {
377
- type: "input",
378
- name: "catalog",
379
- message: "Quilt Catalog URL (domain or full URL):",
380
- default: config.quilt?.catalog,
381
- validate: (input) => {
382
- const trimmed = input.trim();
383
- if (trimmed.length === 0) {
384
- return "Catalog URL is required";
385
- }
386
- return true;
387
- },
388
- filter: (input) => {
389
- // Strip protocol if present, store only domain
390
- return input.trim().replace(/^https?:\/\//, "").replace(/\/$/, "");
391
- },
392
- },
393
- {
394
- type: "input",
395
- name: "database",
396
- message: "Quilt Athena Database:",
397
- // NOTE: "quilt_catalog" is a prompt default ONLY, NOT an OPTIONAL preset
398
- // This field is REQUIRED - users must provide a value or it must be inferred from Quilt stack
399
- // With --yes flag, this prompt should NOT be skipped even though it has a default here
400
- default: config.quilt?.database || "quilt_catalog",
401
- validate: (input) => input.trim().length > 0 || "Database name is required",
402
- },
403
- {
404
- type: "input",
405
- name: "queueUrl",
406
- message: "SQS Queue URL:",
407
- default: config.quilt?.queueUrl,
408
- validate: (input) => {
409
- return (0, sqs_1.isQueueUrl)(input) ||
410
- "Queue URL is required and must look like https://sqs.<region>.amazonaws.com/<account>/<queue>";
411
- },
412
- },
413
- ]);
414
- // Extract region and account ID from stack ARN
415
- // ARN format: arn:aws:cloudformation:REGION:ACCOUNT_ID:stack/STACK_NAME/STACK_ID
416
- const arnMatch = quiltAnswers.stackArn.match(/^arn:aws:cloudformation:([^:]+):(\d{12}):/);
417
- const quiltRegion = arnMatch ? arnMatch[1] : "us-east-1";
418
- awsAccountId = arnMatch ? arnMatch[2] : undefined;
419
- config.quilt = {
420
- stackArn: quiltAnswers.stackArn,
421
- catalog: quiltAnswers.catalog,
422
- database: quiltAnswers.database,
423
- queueUrl: quiltAnswers.queueUrl,
424
- region: quiltRegion,
425
- };
426
- // Prompt for Benchling configuration
427
- console.log("\nStep 2: Benchling Configuration\n");
428
- // First, get tenant
429
- const tenantAnswer = await inquirer_1.default.prompt([
430
- {
431
- type: "input",
432
- name: "tenant",
433
- message: "Benchling Tenant:",
434
- default: config.benchling?.tenant,
435
- validate: (input) => input.trim().length > 0 || "Tenant is required",
436
- },
437
- ]);
438
- // Ask if they have an app_definition_id BEFORE asking for credentials
439
- const hasAppDefId = await inquirer_1.default.prompt([
440
- {
441
- type: "confirm",
442
- name: "hasIt",
443
- message: "Do you have a Benchling App Definition ID for this app?",
444
- default: !!config.benchling?.appDefinitionId,
445
- },
446
- ]);
447
- let appDefinitionId;
448
- if (hasAppDefId.hasIt) {
449
- // They have it, ask for it
450
- const appDefAnswer = await inquirer_1.default.prompt([
451
- {
452
- type: "input",
453
- name: "appDefinitionId",
454
- message: "Benchling App Definition ID:",
455
- default: config.benchling?.appDefinitionId,
456
- validate: (input) => input.trim().length > 0 || "App definition ID is required",
457
- },
458
- ]);
459
- appDefinitionId = appDefAnswer.appDefinitionId;
460
- }
461
- else {
462
- // They don't have it, create the manifest and show instructions
463
- console.log("\n" + chalk_1.default.blue("Creating app manifest...") + "\n");
464
- // Create manifest using the existing command
465
- await (0, manifest_1.manifestCommand)({
466
- catalog: config.quilt?.catalog,
467
- output: "app-manifest.yaml",
468
- });
469
- console.log("\n" + chalk_1.default.yellow("After you have installed the app in Benchling and have the App Definition ID, you can continue.") + "\n");
470
- // Now ask for the app definition ID
471
- const appDefAnswer = await inquirer_1.default.prompt([
472
- {
473
- type: "input",
474
- name: "appDefinitionId",
475
- message: "Benchling App Definition ID:",
476
- validate: (input) => input.trim().length > 0 || "App definition ID is required",
477
- },
478
- ]);
479
- appDefinitionId = appDefAnswer.appDefinitionId;
480
- }
481
- // Now ask for OAuth credentials (which must come from the app)
482
- const credentialAnswers = await inquirer_1.default.prompt([
483
- {
484
- type: "input",
485
- name: "clientId",
486
- message: "Benchling OAuth Client ID (from the app above):",
487
- default: config.benchling?.clientId,
488
- validate: (input) => input.trim().length > 0 || "Client ID is required",
489
- },
490
- {
491
- type: "password",
492
- name: "clientSecret",
493
- message: config.benchling?.clientSecret
494
- ? "Benchling OAuth Client Secret (press Enter to keep existing):"
495
- : "Benchling OAuth Client Secret (from the app above):",
496
- validate: (input) => {
497
- // If there's an existing secret and input is empty, we'll keep the existing one
498
- if (config.benchling?.clientSecret && input.trim().length === 0) {
499
- return true;
500
- }
501
- return input.trim().length > 0 || "Client secret is required";
502
- },
503
- },
504
- ]);
505
- // Ask for optional test entry ID
506
- const testEntryAnswer = await inquirer_1.default.prompt([
507
- {
508
- type: "input",
509
- name: "testEntryId",
510
- message: "Benchling Test Entry ID (optional):",
511
- default: config.benchling?.testEntryId || "",
512
- },
513
- ]);
514
- // Handle empty password input - keep existing secret if user pressed Enter
515
- if (credentialAnswers.clientSecret.trim().length === 0 && config.benchling?.clientSecret) {
516
- credentialAnswers.clientSecret = config.benchling.clientSecret;
517
- }
518
- config.benchling = {
519
- tenant: tenantAnswer.tenant,
520
- clientId: credentialAnswers.clientId,
521
- clientSecret: credentialAnswers.clientSecret,
522
- appDefinitionId: appDefinitionId,
523
- };
524
- if (testEntryAnswer.testEntryId && testEntryAnswer.testEntryId.trim() !== "") {
525
- config.benchling.testEntryId = testEntryAnswer.testEntryId;
526
- }
527
- // Prompt for package configuration
528
- console.log("\nStep 3: Package Configuration\n");
529
- const packageAnswers = await inquirer_1.default.prompt([
530
- {
531
- type: "input",
532
- name: "bucket",
533
- message: "Package S3 Bucket:",
534
- default: config.packages?.bucket,
535
- validate: (input) => input.trim().length > 0 || "Bucket name is required",
536
- },
537
- {
538
- type: "input",
539
- name: "prefix",
540
- message: "Package S3 prefix:",
541
- // NOTE: "benchling" is an OPTIONAL preset - can be auto-applied with --yes flag
542
- default: config.packages?.prefix || "benchling",
543
- },
544
- {
545
- type: "input",
546
- name: "metadataKey",
547
- message: "Package metadata key:",
548
- // NOTE: "experiment_id" is an OPTIONAL preset - can be auto-applied with --yes flag
549
- default: config.packages?.metadataKey || "experiment_id",
550
- },
551
- ]);
552
- config.packages = {
553
- bucket: packageAnswers.bucket,
554
- prefix: packageAnswers.prefix,
555
- metadataKey: packageAnswers.metadataKey,
556
- };
557
- // Prompt for deployment configuration
558
- console.log("\nStep 4: Deployment Configuration\n");
559
- const deploymentAnswers = await inquirer_1.default.prompt([
560
- {
561
- type: "input",
562
- name: "region",
563
- message: "AWS Deployment Region:",
564
- // Prefer inferred region from Quilt stack, then existing deployment config, then fallback
565
- default: config.quilt?.region || config.deployment?.region || "us-east-1",
566
- },
567
- {
568
- type: "input",
569
- name: "account",
570
- message: "AWS Account ID:",
571
- default: config.deployment?.account || awsAccountId || config.quilt?.stackArn?.match(/:(\d{12}):/)?.[1],
572
- validate: (input) => {
573
- if (!input || input.trim().length === 0) {
574
- return "AWS Account ID is required";
575
- }
576
- if (!/^\d{12}$/.test(input.trim())) {
577
- return "AWS Account ID must be a 12-digit number";
578
- }
579
- return true;
580
- },
581
- },
582
- ]);
583
- config.deployment = {
584
- region: deploymentAnswers.region,
585
- account: deploymentAnswers.account,
586
- };
587
- // Optional: Logging configuration
588
- console.log("\nStep 5: Optional Configuration\n");
589
- const optionalAnswers = await inquirer_1.default.prompt([
590
- {
591
- type: "list",
592
- name: "logLevel",
593
- message: "Log level:",
594
- choices: ["DEBUG", "INFO", "WARNING", "ERROR"],
595
- default: config.logging?.level || "INFO",
596
- },
597
- {
598
- type: "input",
599
- name: "webhookAllowList",
600
- message: "Webhook IP allowlist (comma-separated, empty for none):",
601
- default: config.security?.webhookAllowList || "",
602
- },
603
- ]);
604
- config.logging = {
605
- level: optionalAnswers.logLevel,
606
- };
607
- config.security = {
608
- enableVerification: true,
609
- webhookAllowList: optionalAnswers.webhookAllowList,
610
- };
611
- // Add metadata
612
- const now = new Date().toISOString();
613
- config._metadata = {
614
- version: "0.7.0",
615
- createdAt: config._metadata?.createdAt || now,
616
- updatedAt: now,
617
- source: "wizard",
618
- };
619
- // Add inheritance marker if specified
620
- if (inheritFrom) {
621
- config._inherits = inheritFrom;
622
- }
623
- return config;
87
+ function printStepHeader(stepNumber, title) {
88
+ console.log(chalk_1.default.bold(`\nStep ${stepNumber}: ${title}\n`));
624
89
  }
625
90
  /**
626
- * Main install wizard function
91
+ * Main setup wizard orchestrator
92
+ *
93
+ * This function orchestrates the 7 phases of the setup wizard in sequence.
94
+ * Each phase is isolated and testable. The flow is enforced by the code
95
+ * structure - integrated mode explicitly returns, preventing fall-through
96
+ * to deployment.
627
97
  *
628
- * Orchestrates the complete configuration workflow:
629
- * 1. Load existing configuration (if any)
630
- * 2. Infer Quilt configuration from AWS
631
- * 3. Run interactive prompts for missing fields
632
- * 4. Validate configuration
633
- * 5. Save to XDG config directory
634
- * 6. Sync secrets to AWS Secrets Manager
98
+ * Flow:
99
+ * 1. Phase 1: Catalog Discovery (local config only, no AWS)
100
+ * 2. Phase 2: Stack Query (query CloudFormation for confirmed catalog)
101
+ * 3. Phase 3: Parameter Collection (collect user inputs)
102
+ * 4. Phase 4: Validation (validate all parameters)
103
+ * 5. Phase 5: Mode Decision (choose integrated vs standalone)
104
+ * 6a. Phase 6: Integrated Mode (update secret, EXIT) OR
105
+ * 6b. Phase 7: Standalone Mode (create secret, optionally deploy, EXIT)
106
+ *
107
+ * The orchestrator prints step headers before each phase to ensure
108
+ * consistent numbering regardless of execution path.
109
+ *
110
+ * @param options - Setup wizard options
111
+ * @returns Setup wizard result
635
112
  */
636
- async function runInstallWizard(options = {}) {
637
- const { profile = "default", inheritFrom, yes = false, skipValidation = false, skipSecretsSync = false, awsProfile, awsRegion, // NO DEFAULT - let inferQuiltConfig fetch region from catalog's config.json
638
- isPartOfInstall = false, // NEW: Default to false for backward compatibility
639
- } = options;
640
- const xdg = new xdg_config_1.XDGConfig();
641
- console.log("\n╔═══════════════════════════════════════════════════════════╗");
642
- console.log("║ Benchling Webhook Setup (v0.7.0) ║");
643
- console.log("╚═══════════════════════════════════════════════════════════╝\n");
644
- // Step 1: Load existing configuration (if profile exists) - for suggestions only
645
- let existingConfig;
646
- if (xdg.profileExists(profile)) {
647
- console.log(`Loading existing configuration for profile: ${profile}\n`);
648
- try {
649
- existingConfig = xdg.readProfile(profile);
650
- }
651
- catch (error) {
652
- console.warn(`Warning: Could not load existing config: ${error.message}`);
653
- }
654
- }
655
- else if (inheritFrom) {
656
- // Only use explicit inheritFrom if specified (for suggestions)
657
- console.log(`Creating new profile '${profile}' with suggestions from '${inheritFrom}'\n`);
658
- try {
659
- existingConfig = xdg.readProfile(inheritFrom);
660
- }
661
- catch (error) {
662
- throw new Error(`Base profile '${inheritFrom}' not found: ${error.message}`);
663
- }
664
- }
665
- // Step 2: Always infer Quilt configuration from AWS (provides suggestions)
666
- let quiltConfig = existingConfig?.quilt || {};
667
- let inferredAccountId;
668
- console.log("Step 1: Inferring Quilt configuration from AWS...\n");
113
+ async function runSetupWizard(options = {}) {
114
+ const { profile = "default", yes = false, skipValidation = false, awsProfile, awsRegion, setupOnly = false, configStorage, } = options;
115
+ const xdg = configStorage || new xdg_config_1.XDGConfig();
116
+ printWelcomeBanner();
117
+ // Load existing configuration if it exists
118
+ let existingConfig = null;
119
+ let inheritFrom = null;
669
120
  try {
670
- const inferenceResult = await (0, infer_quilt_config_1.inferQuiltConfig)({
671
- region: awsRegion,
672
- profile: awsProfile,
673
- interactive: !yes,
674
- });
675
- // Merge inferred config with existing (inferred takes precedence as fresher data)
676
- quiltConfig = {
677
- ...quiltConfig,
678
- ...inferenceResult,
679
- };
680
- inferredAccountId = inferenceResult.account;
681
- console.log("✓ Quilt configuration inferred\n");
121
+ existingConfig = xdg.readProfile(profile);
122
+ console.log(chalk_1.default.dim(`\nLoading existing configuration for profile: ${profile}\n`));
682
123
  }
683
124
  catch (error) {
684
- console.error(`Failed to infer Quilt configuration: ${error.message}`);
685
- if (yes) {
686
- throw error;
125
+ // Profile doesn't exist - offer to copy from default
126
+ if (profile !== "default" && !yes) {
127
+ try {
128
+ const defaultConfig = xdg.readProfile("default");
129
+ const { copy } = await inquirer_1.default.prompt([
130
+ {
131
+ type: "confirm",
132
+ name: "copy",
133
+ message: `Profile '${profile}' doesn't exist. Copy configuration from 'default'?`,
134
+ default: true,
135
+ },
136
+ ]);
137
+ if (copy) {
138
+ existingConfig = defaultConfig;
139
+ inheritFrom = "default";
140
+ console.log(chalk_1.default.dim(`\nCopying configuration from profile: default\n`));
141
+ }
142
+ else {
143
+ console.log(chalk_1.default.dim(`\nCreating new configuration for profile: ${profile}\n`));
144
+ }
145
+ }
146
+ catch {
147
+ // No default profile either - fresh setup
148
+ console.log(chalk_1.default.dim(`\nCreating new configuration for profile: ${profile}\n`));
149
+ }
687
150
  }
688
- const { continueManually } = await inquirer_1.default.prompt([
689
- {
690
- type: "confirm",
691
- name: "continueManually",
692
- message: "Continue and enter Quilt configuration manually?",
693
- default: true,
694
- },
695
- ]);
696
- if (!continueManually) {
697
- throw new Error("Setup aborted by user");
151
+ else {
152
+ // Creating default profile or in --yes mode
153
+ console.log(chalk_1.default.dim(`\nCreating new configuration for profile: ${profile}\n`));
698
154
  }
699
155
  }
700
- // Merge inferred/existing config as suggestions for the wizard
701
- const partialConfig = {
702
- ...existingConfig,
703
- quilt: quiltConfig,
704
- // Pass through inferred account ID for deployment config
705
- deployment: {
706
- ...existingConfig?.deployment,
707
- account: existingConfig?.deployment?.account || inferredAccountId,
708
- },
709
- };
710
- // Step 3: Run interactive wizard for all configuration (with inferred/existing values as suggestions)
711
- const config = await runConfigWizard({
712
- existingConfig: partialConfig,
156
+ // =========================================================================
157
+ // PHASE 1: CATALOG DISCOVERY
158
+ // =========================================================================
159
+ // Detects and confirms catalog DNS (local config only, NO AWS queries)
160
+ // Priority: CLI arg > existing config > quilt3 detection > manual entry
161
+ printStepHeader(1, STEP_TITLES.catalogDiscovery);
162
+ const catalogResult = await (0, phase1_catalog_discovery_1.runCatalogDiscovery)({
163
+ yes,
164
+ catalogUrl: options.catalogUrl,
165
+ existingCatalog: existingConfig?.quilt?.catalog,
166
+ });
167
+ // =========================================================================
168
+ // PHASE 2: STACK QUERY
169
+ // =========================================================================
170
+ // NOW query AWS for the CONFIRMED catalog
171
+ // This extracts ALL parameters including BenchlingSecret ARN
172
+ printStepHeader(2, STEP_TITLES.stackQuery);
173
+ const stackQuery = await (0, phase2_stack_query_1.runStackQuery)(catalogResult.catalogDns, {
174
+ awsProfile,
175
+ awsRegion,
176
+ yes,
177
+ });
178
+ // Handle stack query failure
179
+ if (!stackQuery.stackQuerySucceeded) {
180
+ console.error(chalk_1.default.red("\n❌ Stack query failed. Cannot continue setup."));
181
+ console.error(chalk_1.default.yellow("Please verify:"));
182
+ console.error(chalk_1.default.yellow(" 1. The catalog DNS is correct"));
183
+ console.error(chalk_1.default.yellow(" 2. You have AWS credentials configured"));
184
+ console.error(chalk_1.default.yellow(" 3. The CloudFormation stack exists for this catalog\n"));
185
+ throw new Error("Stack query failed");
186
+ }
187
+ // =========================================================================
188
+ // PHASE 3: PARAMETER COLLECTION
189
+ // =========================================================================
190
+ // Collect user inputs, using existing config and stack query results as defaults
191
+ printStepHeader(3, STEP_TITLES.parameterCollection);
192
+ const parameters = await (0, phase3_parameter_collection_1.runParameterCollection)({
193
+ stackQuery,
194
+ existingConfig,
713
195
  yes,
714
- inheritFrom, // Only pass explicit inheritFrom, not auto-derived
196
+ benchlingTenant: options.benchlingTenant,
197
+ benchlingClientId: options.benchlingClientId,
198
+ benchlingClientSecret: options.benchlingClientSecret,
199
+ benchlingAppDefinitionId: options.benchlingAppDefinitionId,
200
+ benchlingTestEntryId: options.benchlingTestEntryId,
201
+ userBucket: options.userBucket,
202
+ pkgPrefix: options.pkgPrefix,
203
+ pkgKey: options.pkgKey,
204
+ logLevel: options.logLevel,
205
+ webhookAllowList: options.webhookAllowList,
715
206
  });
716
- // Step 4: Validate configuration
207
+ // =========================================================================
208
+ // PHASE 4: VALIDATION
209
+ // =========================================================================
210
+ // Validate all collected parameters BEFORE making mode decision
717
211
  if (!skipValidation) {
718
- console.log("\nValidating configuration...\n");
719
- const validation = await validateConfig(config, {
720
- skipValidation,
212
+ printStepHeader(4, STEP_TITLES.validation);
213
+ const validation = await (0, phase4_validation_1.runValidation)({
214
+ stackQuery,
215
+ parameters,
721
216
  awsProfile,
722
217
  });
723
- if (!validation.isValid) {
724
- console.error("\n❌ Configuration validation failed:");
725
- console.error(chalk_1.default.gray(" The following validations were performed:"));
726
- console.error(chalk_1.default.gray(` - Benchling tenant: ${config.benchling.tenant}`));
727
- console.error(chalk_1.default.gray(` - OAuth credentials: Client ID ${config.benchling.clientId.substring(0, 8)}...`));
728
- console.error(chalk_1.default.gray(` - S3 bucket: ${config.packages.bucket} (region: ${config.deployment.region})`));
729
- console.error("");
730
- validation.errors.forEach((err) => console.error(`${err}\n`));
218
+ if (!validation.success) {
731
219
  if (yes) {
732
- throw new Error("Configuration validation failed");
220
+ throw new Error(`Validation failed: ${validation.errors.join(", ")}`);
733
221
  }
734
222
  const { proceed } = await inquirer_1.default.prompt([
735
223
  {
@@ -743,74 +231,83 @@ async function runInstallWizard(options = {}) {
743
231
  throw new Error("Setup aborted by user");
744
232
  }
745
233
  }
746
- else {
747
- console.log("✓ Configuration validated successfully\n");
748
- }
749
- if (validation.warnings && validation.warnings.length > 0) {
750
- console.warn("\n⚠ Warnings:");
751
- validation.warnings.forEach((warn) => console.warn(` ${warn}\n`));
752
- }
753
- }
754
- // Step 5: Save configuration
755
- console.log(`Saving configuration to profile: ${profile}...\n`);
756
- try {
757
- xdg.writeProfile(profile, config);
758
- console.log(`✓ Configuration saved to: ~/.config/benchling-webhook/${profile}/config.json\n`);
759
- }
760
- catch (error) {
761
- throw new Error(`Failed to save configuration: ${error.message}`);
762
234
  }
763
- // Step 6: Sync secrets to AWS Secrets Manager
764
- if (!skipSecretsSync) {
765
- console.log("Syncing secrets to AWS Secrets Manager...\n");
766
- try {
767
- const { syncSecretsToAWS } = await Promise.resolve().then(() => __importStar(require("./sync-secrets")));
768
- await syncSecretsToAWS({
769
- profile,
770
- awsProfile,
771
- // Use the deployment region from config (which defaults to Quilt stack region)
772
- region: config.deployment?.region,
773
- force: true,
774
- });
775
- console.log("✓ Secrets synced to AWS Secrets Manager\n");
776
- }
777
- catch (error) {
778
- console.warn(chalk_1.default.yellow(`⚠️ Failed to sync secrets: ${error.message}`));
779
- console.warn(chalk_1.default.yellow(" You can sync secrets manually later with:"));
780
- console.warn(chalk_1.default.cyan(` npm run setup:sync-secrets -- --profile ${profile}\n`));
781
- }
235
+ // =========================================================================
236
+ // PHASE 5: MODE DECISION
237
+ // =========================================================================
238
+ // Decide between integrated mode (use existing BenchlingSecret) or
239
+ // standalone mode (create new dedicated secret)
240
+ printStepHeader(5, STEP_TITLES.modeDecision);
241
+ const modeDecision = await (0, phase5_mode_decision_1.runModeDecision)({
242
+ stackQuery,
243
+ yes,
244
+ });
245
+ // =========================================================================
246
+ // PHASE 6 OR 7: MODE-SPECIFIC EXECUTION
247
+ // =========================================================================
248
+ if (modeDecision.mode === "integrated") {
249
+ // =====================================================================
250
+ // PHASE 6: INTEGRATED MODE
251
+ // =====================================================================
252
+ // Update existing BenchlingSecret in Quilt stack
253
+ // NO deployment - Quilt stack handles webhook
254
+ // MUST return here to prevent fall-through
255
+ printStepHeader(6, STEP_TITLES.integratedMode);
256
+ await (0, phase6_integrated_mode_1.runIntegratedMode)({
257
+ profile,
258
+ catalogDns: catalogResult.catalogDns,
259
+ stackQuery,
260
+ parameters,
261
+ benchlingSecretArn: modeDecision.benchlingSecretArn,
262
+ configStorage: xdg,
263
+ awsProfile,
264
+ });
265
+ // CRITICAL: Explicit return for integrated mode
266
+ // Cannot fall through to deployment
267
+ const finalConfig = xdg.readProfile(profile);
268
+ return {
269
+ success: true,
270
+ profile,
271
+ config: finalConfig,
272
+ };
782
273
  }
783
- // Step 7: Display next steps (only if NOT part of install command)
784
- if (!isPartOfInstall) {
785
- console.log("╔═══════════════════════════════════════════════════════════╗");
786
- console.log("║ Setup Complete! ║");
787
- console.log("╚═══════════════════════════════════════════════════════════╝\n");
788
- // Use next steps generator (Phase 2: with context detection)
789
- const nextSteps = (0, next_steps_generator_1.generateNextSteps)({
274
+ else {
275
+ // =====================================================================
276
+ // PHASE 7: STANDALONE MODE
277
+ // =====================================================================
278
+ // Create new dedicated secret
279
+ // Optionally deploy as separate stack
280
+ printStepHeader(6, STEP_TITLES.standaloneMode);
281
+ await (0, phase7_standalone_mode_1.runStandaloneMode)({
790
282
  profile,
791
- stage: profile === "prod" ? "prod" : "dev",
283
+ catalogDns: catalogResult.catalogDns,
284
+ stackQuery,
285
+ parameters,
286
+ configStorage: xdg,
287
+ yes,
288
+ setupOnly,
289
+ awsProfile,
792
290
  });
793
- console.log(nextSteps + "\n");
291
+ // CRITICAL: Explicit return for standalone mode
292
+ const finalConfig = xdg.readProfile(profile);
293
+ return {
294
+ success: true,
295
+ profile,
296
+ config: finalConfig,
297
+ };
794
298
  }
795
- // Return result for install command orchestration
796
- return {
797
- success: true,
798
- profile,
799
- config,
800
- };
801
299
  }
802
- // =============================================================================
803
- // CLI COMMAND EXPORT
804
- // =============================================================================
805
300
  /**
806
301
  * Setup wizard command handler
807
302
  *
303
+ * Wraps runSetupWizard with error handling for graceful user cancellation.
304
+ *
808
305
  * @param options - Wizard options
809
306
  * @returns Promise that resolves with setup result
810
307
  */
811
308
  async function setupWizardCommand(options = {}) {
812
309
  try {
813
- return await runInstallWizard(options);
310
+ return await runSetupWizard(options);
814
311
  }
815
312
  catch (error) {
816
313
  // Handle user cancellation (Ctrl+C) gracefully