@quiltdata/benchling-webhook 0.6.3-20251104T170954Z → 0.7.1-20251106T100426Z
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/README.md +166 -5
- package/dist/bin/benchling-webhook.d.ts +2 -16
- package/dist/bin/benchling-webhook.d.ts.map +1 -1
- package/dist/bin/benchling-webhook.js +96 -158
- package/dist/bin/benchling-webhook.js.map +1 -1
- package/dist/bin/cli.js +96 -8
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/{config-profiles.d.ts → commands/config-profiles.d.ts} +10 -9
- package/dist/bin/commands/config-profiles.d.ts.map +1 -0
- package/dist/bin/{config-profiles.js → commands/config-profiles.js} +109 -102
- package/dist/bin/commands/config-profiles.js.map +1 -0
- package/dist/bin/commands/create-secret.d.ts.map +1 -0
- package/dist/bin/commands/create-secret.js.map +1 -0
- package/dist/bin/commands/deploy.d.ts +12 -1
- package/dist/bin/commands/deploy.d.ts.map +1 -1
- package/dist/bin/commands/deploy.js +117 -107
- package/dist/bin/commands/deploy.js.map +1 -1
- package/dist/bin/{get-env.d.ts → commands/get-env.d.ts} +1 -1
- package/dist/bin/commands/get-env.d.ts.map +1 -0
- package/dist/bin/{get-env.js → commands/get-env.js} +2 -2
- package/dist/bin/commands/get-env.js.map +1 -0
- package/dist/bin/commands/health-check.d.ts +47 -0
- package/dist/bin/commands/health-check.d.ts.map +1 -0
- package/dist/bin/commands/health-check.js +357 -0
- package/dist/bin/commands/health-check.js.map +1 -0
- package/dist/{scripts → bin/commands}/infer-quilt-config.d.ts +6 -15
- package/dist/bin/commands/infer-quilt-config.d.ts.map +1 -0
- package/dist/{scripts → bin/commands}/infer-quilt-config.js +37 -63
- package/dist/bin/commands/infer-quilt-config.js.map +1 -0
- package/dist/bin/commands/init.d.ts.map +1 -1
- package/dist/bin/commands/init.js +2 -32
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/publish.d.ts.map +1 -0
- package/dist/bin/{publish.js → commands/publish.js} +2 -2
- package/dist/bin/commands/publish.js.map +1 -0
- package/dist/bin/commands/setup-profile.d.ts +29 -0
- package/dist/bin/commands/setup-profile.d.ts.map +1 -0
- package/dist/bin/commands/setup-profile.js +218 -0
- package/dist/bin/commands/setup-profile.js.map +1 -0
- package/dist/bin/commands/setup-wizard.d.ts +24 -11
- package/dist/bin/commands/setup-wizard.d.ts.map +1 -1
- package/dist/bin/commands/setup-wizard.js +607 -51
- package/dist/bin/commands/setup-wizard.js.map +1 -1
- package/dist/{scripts → bin/commands}/sync-secrets.d.ts +6 -1
- package/dist/bin/commands/sync-secrets.d.ts.map +1 -0
- package/dist/{scripts → bin/commands}/sync-secrets.js +159 -55
- package/dist/bin/commands/sync-secrets.js.map +1 -0
- package/dist/bin/commands/validate.d.ts.map +1 -1
- package/dist/bin/commands/validate.js +2 -12
- package/dist/bin/commands/validate.js.map +1 -1
- package/dist/lib/alb-api-gateway.d.ts +7 -1
- package/dist/lib/alb-api-gateway.d.ts.map +1 -1
- package/dist/lib/alb-api-gateway.js +9 -6
- package/dist/lib/alb-api-gateway.js.map +1 -1
- package/dist/lib/benchling-webhook-stack.d.ts +13 -12
- package/dist/lib/benchling-webhook-stack.d.ts.map +1 -1
- package/dist/lib/benchling-webhook-stack.js +44 -31
- package/dist/lib/benchling-webhook-stack.js.map +1 -1
- package/dist/lib/configuration-saver.d.ts +4 -24
- package/dist/lib/configuration-saver.d.ts.map +1 -1
- package/dist/lib/configuration-saver.js +14 -71
- package/dist/lib/configuration-saver.js.map +1 -1
- package/dist/lib/fargate-service.d.ts +11 -21
- package/dist/lib/fargate-service.d.ts.map +1 -1
- package/dist/lib/fargate-service.js +79 -176
- package/dist/lib/fargate-service.js.map +1 -1
- package/dist/lib/types/config.d.ts +538 -232
- package/dist/lib/types/config.d.ts.map +1 -1
- package/dist/lib/types/config.js +133 -3
- package/dist/lib/types/config.js.map +1 -1
- package/dist/lib/utils/config-loader.d.ts.map +1 -1
- package/dist/lib/utils/config-loader.js +3 -2
- package/dist/lib/utils/config-loader.js.map +1 -1
- package/dist/lib/utils/config-resolver.d.ts +2 -2
- package/dist/lib/utils/config-resolver.d.ts.map +1 -1
- package/dist/lib/utils/config-resolver.js +14 -7
- package/dist/lib/utils/config-resolver.js.map +1 -1
- package/dist/lib/utils/config.d.ts +1 -1
- package/dist/lib/utils/config.d.ts.map +1 -1
- package/dist/lib/utils/config.js +7 -3
- package/dist/lib/utils/config.js.map +1 -1
- package/dist/lib/utils/sqs.d.ts +13 -0
- package/dist/lib/utils/sqs.d.ts.map +1 -0
- package/dist/lib/utils/sqs.js +22 -0
- package/dist/lib/utils/sqs.js.map +1 -0
- package/dist/lib/utils/stack-inference.d.ts.map +1 -1
- package/dist/lib/utils/stack-inference.js +8 -6
- package/dist/lib/utils/stack-inference.js.map +1 -1
- package/dist/lib/xdg-config.d.ts +224 -106
- package/dist/lib/xdg-config.d.ts.map +1 -1
- package/dist/lib/xdg-config.js +454 -387
- package/dist/lib/xdg-config.js.map +1 -1
- package/dist/package.json +19 -14
- package/dist/scripts/check-logs.d.ts +12 -0
- package/dist/scripts/check-logs.d.ts.map +1 -0
- package/dist/{bin → scripts}/check-logs.js +65 -15
- package/dist/scripts/check-logs.js.map +1 -0
- package/dist/scripts/check-webhook-verification.d.ts +3 -0
- package/dist/scripts/check-webhook-verification.d.ts.map +1 -0
- package/dist/{bin/test-invalid-signature.js → scripts/check-webhook-verification.js} +1 -1
- package/dist/scripts/check-webhook-verification.js.map +1 -0
- package/dist/scripts/get-dev-version.d.ts +16 -0
- package/dist/scripts/get-dev-version.d.ts.map +1 -0
- package/dist/scripts/get-dev-version.js +57 -0
- package/dist/scripts/get-dev-version.js.map +1 -0
- package/dist/scripts/send-event.d.ts.map +1 -0
- package/dist/scripts/send-event.js.map +1 -0
- package/dist/{bin → scripts}/version.d.ts +3 -1
- package/dist/scripts/version.d.ts.map +1 -0
- package/dist/{bin → scripts}/version.js +95 -9
- package/dist/scripts/version.js.map +1 -0
- package/package.json +19 -14
- package/dist/bin/check-logs.d.ts +0 -7
- package/dist/bin/check-logs.d.ts.map +0 -1
- package/dist/bin/check-logs.js.map +0 -1
- package/dist/bin/config-profiles.d.ts.map +0 -1
- package/dist/bin/config-profiles.js.map +0 -1
- package/dist/bin/create-secret.d.ts.map +0 -1
- package/dist/bin/create-secret.js.map +0 -1
- package/dist/bin/dev-deploy.d.ts +0 -20
- package/dist/bin/dev-deploy.d.ts.map +0 -1
- package/dist/bin/dev-deploy.js +0 -289
- package/dist/bin/dev-deploy.js.map +0 -1
- package/dist/bin/get-env.d.ts.map +0 -1
- package/dist/bin/get-env.js.map +0 -1
- package/dist/bin/publish.d.ts.map +0 -1
- package/dist/bin/publish.js.map +0 -1
- package/dist/bin/release.d.ts +0 -11
- package/dist/bin/release.d.ts.map +0 -1
- package/dist/bin/release.js +0 -141
- package/dist/bin/release.js.map +0 -1
- package/dist/bin/send-event.d.ts.map +0 -1
- package/dist/bin/send-event.js.map +0 -1
- package/dist/bin/test-invalid-signature.d.ts +0 -3
- package/dist/bin/test-invalid-signature.d.ts.map +0 -1
- package/dist/bin/test-invalid-signature.js.map +0 -1
- package/dist/bin/version.d.ts.map +0 -1
- package/dist/bin/version.js.map +0 -1
- package/dist/lib/xdg-cli-wrapper.d.ts +0 -113
- package/dist/lib/xdg-cli-wrapper.d.ts.map +0 -1
- package/dist/lib/xdg-cli-wrapper.js +0 -289
- package/dist/lib/xdg-cli-wrapper.js.map +0 -1
- package/dist/scripts/config-health-check.d.ts +0 -84
- package/dist/scripts/config-health-check.d.ts.map +0 -1
- package/dist/scripts/config-health-check.js +0 -659
- package/dist/scripts/config-health-check.js.map +0 -1
- package/dist/scripts/infer-quilt-config.d.ts.map +0 -1
- package/dist/scripts/infer-quilt-config.js.map +0 -1
- package/dist/scripts/install-wizard.d.ts +0 -34
- package/dist/scripts/install-wizard.d.ts.map +0 -1
- package/dist/scripts/install-wizard.js +0 -719
- package/dist/scripts/install-wizard.js.map +0 -1
- package/dist/scripts/sync-secrets.d.ts.map +0 -1
- package/dist/scripts/sync-secrets.js.map +0 -1
- /package/dist/bin/{create-secret.d.ts → commands/create-secret.d.ts} +0 -0
- /package/dist/bin/{create-secret.js → commands/create-secret.js} +0 -0
- /package/dist/bin/{publish.d.ts → commands/publish.d.ts} +0 -0
- /package/dist/{bin → scripts}/send-event.d.ts +0 -0
- /package/dist/{bin → scripts}/send-event.js +0 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Interactive Configuration Wizard (v0.7.0)
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
11
|
+
*
|
|
12
|
+
* Consolidated from scripts/install-wizard.ts, scripts/config/wizard.ts,
|
|
13
|
+
* and scripts/config/validator.ts.
|
|
8
14
|
*
|
|
9
15
|
* @module commands/setup-wizard
|
|
10
16
|
*/
|
|
@@ -46,58 +52,608 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
46
52
|
};
|
|
47
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
54
|
exports.setupWizardCommand = setupWizardCommand;
|
|
49
|
-
const
|
|
50
|
-
const
|
|
55
|
+
const https = __importStar(require("https"));
|
|
56
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
57
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
58
|
+
const xdg_config_1 = require("../../lib/xdg-config");
|
|
59
|
+
const infer_quilt_config_1 = require("../commands/infer-quilt-config");
|
|
60
|
+
const sqs_1 = require("../../lib/utils/sqs");
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// VALIDATION FUNCTIONS (from scripts/config/validator.ts)
|
|
63
|
+
// =============================================================================
|
|
51
64
|
/**
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
* Validates Benchling tenant accessibility
|
|
66
|
+
*/
|
|
67
|
+
async function validateBenchlingTenant(tenant) {
|
|
68
|
+
const result = {
|
|
69
|
+
isValid: false,
|
|
70
|
+
errors: [],
|
|
71
|
+
warnings: [],
|
|
72
|
+
};
|
|
73
|
+
if (!tenant || tenant.trim().length === 0) {
|
|
74
|
+
result.errors.push("Tenant name cannot be empty");
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
// Basic format validation
|
|
78
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(tenant)) {
|
|
79
|
+
result.errors.push("Tenant name contains invalid characters (only alphanumeric, dash, underscore allowed)");
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
// Test tenant URL accessibility
|
|
83
|
+
const tenantUrl = `https://${tenant}.benchling.com`;
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
https
|
|
86
|
+
.get(tenantUrl, { timeout: 5000 }, (res) => {
|
|
87
|
+
if (res.statusCode === 200 || res.statusCode === 302 || res.statusCode === 301) {
|
|
88
|
+
result.isValid = true;
|
|
89
|
+
console.log(` ✓ Tenant URL accessible: ${tenantUrl}`);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
if (!result.warnings)
|
|
93
|
+
result.warnings = [];
|
|
94
|
+
result.warnings.push(`Tenant URL returned status ${res.statusCode}`);
|
|
95
|
+
result.isValid = true; // Consider this a warning, not an error
|
|
96
|
+
}
|
|
97
|
+
resolve(result);
|
|
98
|
+
})
|
|
99
|
+
.on("error", (error) => {
|
|
100
|
+
if (!result.warnings)
|
|
101
|
+
result.warnings = [];
|
|
102
|
+
result.warnings.push(`Could not verify tenant URL: ${error.message}`);
|
|
103
|
+
result.isValid = true; // Allow proceeding with warning
|
|
104
|
+
resolve(result);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Validates Benchling OAuth credentials
|
|
61
110
|
*/
|
|
62
|
-
async function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
async function validateBenchlingCredentials(tenant, clientId, clientSecret) {
|
|
112
|
+
const result = {
|
|
113
|
+
isValid: false,
|
|
114
|
+
errors: [],
|
|
115
|
+
warnings: [],
|
|
116
|
+
};
|
|
117
|
+
if (!clientId || clientId.trim().length === 0) {
|
|
118
|
+
result.errors.push("Client ID cannot be empty");
|
|
119
|
+
}
|
|
120
|
+
if (!clientSecret || clientSecret.trim().length === 0) {
|
|
121
|
+
result.errors.push("Client secret cannot be empty");
|
|
122
|
+
}
|
|
123
|
+
if (result.errors.length > 0) {
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
// Test OAuth token endpoint
|
|
127
|
+
const tokenUrl = `https://${tenant}.benchling.com/api/v2/token`;
|
|
128
|
+
const authString = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
const postData = "grant_type=client_credentials";
|
|
131
|
+
const options = {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: {
|
|
134
|
+
"Authorization": `Basic ${authString}`,
|
|
135
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
136
|
+
"Content-Length": postData.length,
|
|
137
|
+
},
|
|
138
|
+
timeout: 10000,
|
|
139
|
+
};
|
|
140
|
+
const req = https.request(tokenUrl, options, (res) => {
|
|
141
|
+
let data = "";
|
|
142
|
+
res.on("data", (chunk) => {
|
|
143
|
+
data += chunk;
|
|
144
|
+
});
|
|
145
|
+
res.on("end", () => {
|
|
146
|
+
if (res.statusCode === 200) {
|
|
147
|
+
result.isValid = true;
|
|
148
|
+
console.log(" ✓ OAuth credentials validated successfully");
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
result.errors.push(`OAuth validation failed with status ${res.statusCode}: ${data.substring(0, 100)}`);
|
|
152
|
+
}
|
|
153
|
+
resolve(result);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
req.on("error", (error) => {
|
|
157
|
+
if (!result.warnings)
|
|
158
|
+
result.warnings = [];
|
|
159
|
+
result.warnings.push(`Could not validate OAuth credentials: ${error.message}`);
|
|
160
|
+
result.isValid = true; // Allow proceeding with warning
|
|
161
|
+
resolve(result);
|
|
162
|
+
});
|
|
163
|
+
req.write(postData);
|
|
164
|
+
req.end();
|
|
74
165
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Validates S3 bucket access
|
|
169
|
+
*/
|
|
170
|
+
async function validateS3BucketAccess(bucketName, region, awsProfile) {
|
|
171
|
+
const result = {
|
|
172
|
+
isValid: false,
|
|
173
|
+
errors: [],
|
|
174
|
+
warnings: [],
|
|
175
|
+
};
|
|
176
|
+
if (!bucketName || bucketName.trim().length === 0) {
|
|
177
|
+
result.errors.push("Bucket name cannot be empty");
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const clientConfig = { region };
|
|
182
|
+
if (awsProfile) {
|
|
183
|
+
const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
|
|
184
|
+
clientConfig.credentials = fromIni({ profile: awsProfile });
|
|
185
|
+
}
|
|
186
|
+
const s3Client = new client_s3_1.S3Client(clientConfig);
|
|
187
|
+
// Test HeadBucket (verify bucket exists and we have access)
|
|
188
|
+
const headCommand = new client_s3_1.HeadBucketCommand({ Bucket: bucketName });
|
|
189
|
+
await s3Client.send(headCommand);
|
|
190
|
+
console.log(` ✓ S3 bucket accessible: ${bucketName}`);
|
|
191
|
+
// Test ListObjects (verify we can list objects)
|
|
192
|
+
const listCommand = new client_s3_1.ListObjectsV2Command({
|
|
193
|
+
Bucket: bucketName,
|
|
194
|
+
MaxKeys: 1,
|
|
195
|
+
});
|
|
196
|
+
await s3Client.send(listCommand);
|
|
197
|
+
console.log(" ✓ S3 bucket list permission confirmed");
|
|
198
|
+
result.isValid = true;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
const err = error;
|
|
202
|
+
result.errors.push(`S3 bucket validation failed: ${err.message}`);
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Validates complete ProfileConfig
|
|
208
|
+
*/
|
|
209
|
+
async function validateConfig(config, options = {}) {
|
|
210
|
+
const result = {
|
|
211
|
+
isValid: true,
|
|
212
|
+
errors: [],
|
|
213
|
+
warnings: [],
|
|
214
|
+
};
|
|
215
|
+
if (options.skipValidation) {
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
// Validate Benchling tenant
|
|
219
|
+
const tenantValidation = await validateBenchlingTenant(config.benchling.tenant);
|
|
220
|
+
if (!tenantValidation.isValid) {
|
|
221
|
+
result.isValid = false;
|
|
222
|
+
result.errors.push(...tenantValidation.errors);
|
|
223
|
+
}
|
|
224
|
+
if (tenantValidation.warnings && tenantValidation.warnings.length > 0) {
|
|
225
|
+
if (!result.warnings)
|
|
226
|
+
result.warnings = [];
|
|
227
|
+
result.warnings.push(...tenantValidation.warnings);
|
|
228
|
+
}
|
|
229
|
+
// Validate OAuth credentials (if secret is provided)
|
|
230
|
+
if (config.benchling.clientSecret) {
|
|
231
|
+
const credValidation = await validateBenchlingCredentials(config.benchling.tenant, config.benchling.clientId, config.benchling.clientSecret);
|
|
232
|
+
if (!credValidation.isValid) {
|
|
233
|
+
result.isValid = false;
|
|
234
|
+
result.errors.push(...credValidation.errors);
|
|
235
|
+
}
|
|
236
|
+
if (credValidation.warnings && credValidation.warnings.length > 0) {
|
|
237
|
+
if (!result.warnings)
|
|
238
|
+
result.warnings = [];
|
|
239
|
+
result.warnings.push(...credValidation.warnings);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Validate S3 bucket access
|
|
243
|
+
const bucketValidation = await validateS3BucketAccess(config.packages.bucket, config.deployment.region, options.awsProfile);
|
|
244
|
+
if (!bucketValidation.isValid) {
|
|
245
|
+
result.isValid = false;
|
|
246
|
+
result.errors.push(...bucketValidation.errors);
|
|
247
|
+
}
|
|
248
|
+
if (bucketValidation.warnings && bucketValidation.warnings.length > 0) {
|
|
249
|
+
if (!result.warnings)
|
|
250
|
+
result.warnings = [];
|
|
251
|
+
result.warnings.push(...bucketValidation.warnings);
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Runs interactive configuration wizard
|
|
257
|
+
*/
|
|
258
|
+
async function runConfigWizard(options = {}) {
|
|
259
|
+
const { existingConfig = {}, nonInteractive = false, inheritFrom } = options;
|
|
260
|
+
console.log("╔═══════════════════════════════════════════════════════════╗");
|
|
261
|
+
console.log("║ Benchling Webhook Configuration Wizard ║");
|
|
262
|
+
console.log("╚═══════════════════════════════════════════════════════════╝\n");
|
|
263
|
+
if (inheritFrom) {
|
|
264
|
+
console.log(`Creating profile inheriting from: ${inheritFrom}\n`);
|
|
265
|
+
}
|
|
266
|
+
const config = { ...existingConfig };
|
|
267
|
+
let awsAccountId;
|
|
268
|
+
// If non-interactive, validate that all required fields are present
|
|
269
|
+
if (nonInteractive) {
|
|
270
|
+
if (!config.benchling?.tenant || !config.benchling?.clientId || !config.benchling?.clientSecret) {
|
|
271
|
+
throw new Error("Non-interactive mode requires benchlingTenant, benchlingClientId, and benchlingClientSecret to be already configured");
|
|
272
|
+
}
|
|
273
|
+
// Add metadata and inheritance marker before returning
|
|
274
|
+
const now = new Date().toISOString();
|
|
275
|
+
const finalConfig = config;
|
|
276
|
+
finalConfig._metadata = {
|
|
277
|
+
version: "0.7.0",
|
|
278
|
+
createdAt: config._metadata?.createdAt || now,
|
|
279
|
+
updatedAt: now,
|
|
280
|
+
source: "wizard",
|
|
281
|
+
};
|
|
282
|
+
if (inheritFrom) {
|
|
283
|
+
finalConfig._inherits = inheritFrom;
|
|
284
|
+
}
|
|
285
|
+
return finalConfig;
|
|
286
|
+
}
|
|
287
|
+
// Prompt for Quilt configuration (if not inherited)
|
|
288
|
+
if (!inheritFrom) {
|
|
289
|
+
console.log("Step 1: Quilt Configuration\n");
|
|
290
|
+
const quiltAnswers = await inquirer_1.default.prompt([
|
|
291
|
+
{
|
|
292
|
+
type: "input",
|
|
293
|
+
name: "stackArn",
|
|
294
|
+
message: "Quilt Stack ARN:",
|
|
295
|
+
default: config.quilt?.stackArn,
|
|
296
|
+
validate: (input) => input.trim().length > 0 && input.startsWith("arn:aws:cloudformation:") ||
|
|
297
|
+
"Stack ARN is required and must start with arn:aws:cloudformation:",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
type: "input",
|
|
301
|
+
name: "catalog",
|
|
302
|
+
message: "Quilt Catalog URL (domain or full URL):",
|
|
303
|
+
default: config.quilt?.catalog,
|
|
304
|
+
validate: (input) => {
|
|
305
|
+
const trimmed = input.trim();
|
|
306
|
+
if (trimmed.length === 0) {
|
|
307
|
+
return "Catalog URL is required";
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
310
|
+
},
|
|
311
|
+
filter: (input) => {
|
|
312
|
+
// Strip protocol if present, store only domain
|
|
313
|
+
return input.trim().replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
type: "input",
|
|
318
|
+
name: "database",
|
|
319
|
+
message: "Quilt Athena Database:",
|
|
320
|
+
default: config.quilt?.database || "quilt_catalog",
|
|
321
|
+
validate: (input) => input.trim().length > 0 || "Database name is required",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
type: "input",
|
|
325
|
+
name: "queueUrl",
|
|
326
|
+
message: "SQS Queue URL:",
|
|
327
|
+
default: config.quilt?.queueUrl,
|
|
328
|
+
validate: (input) => {
|
|
329
|
+
return (0, sqs_1.isQueueUrl)(input) ||
|
|
330
|
+
"Queue URL is required and must look like https://sqs.<region>.amazonaws.com/<account>/<queue>";
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
]);
|
|
334
|
+
// Extract region and account ID from stack ARN
|
|
335
|
+
// ARN format: arn:aws:cloudformation:REGION:ACCOUNT_ID:stack/STACK_NAME/STACK_ID
|
|
336
|
+
const arnMatch = quiltAnswers.stackArn.match(/^arn:aws:cloudformation:([^:]+):(\d{12}):/);
|
|
337
|
+
const quiltRegion = arnMatch ? arnMatch[1] : "us-east-1";
|
|
338
|
+
awsAccountId = arnMatch ? arnMatch[2] : undefined;
|
|
339
|
+
config.quilt = {
|
|
340
|
+
stackArn: quiltAnswers.stackArn,
|
|
341
|
+
catalog: quiltAnswers.catalog,
|
|
342
|
+
database: quiltAnswers.database,
|
|
343
|
+
queueUrl: quiltAnswers.queueUrl,
|
|
344
|
+
region: quiltRegion,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Prompt for Benchling configuration
|
|
348
|
+
console.log("\nStep 2: Benchling Configuration\n");
|
|
349
|
+
const benchlingAnswers = await inquirer_1.default.prompt([
|
|
350
|
+
{
|
|
351
|
+
type: "input",
|
|
352
|
+
name: "tenant",
|
|
353
|
+
message: "Benchling Tenant:",
|
|
354
|
+
default: config.benchling?.tenant,
|
|
355
|
+
validate: (input) => input.trim().length > 0 || "Tenant is required",
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
type: "input",
|
|
359
|
+
name: "clientId",
|
|
360
|
+
message: "Benchling OAuth Client ID:",
|
|
361
|
+
default: config.benchling?.clientId,
|
|
362
|
+
validate: (input) => input.trim().length > 0 || "Client ID is required",
|
|
363
|
+
},
|
|
79
364
|
{
|
|
80
|
-
type: "
|
|
81
|
-
name: "
|
|
82
|
-
message:
|
|
83
|
-
|
|
365
|
+
type: "password",
|
|
366
|
+
name: "clientSecret",
|
|
367
|
+
message: config.benchling?.clientSecret
|
|
368
|
+
? "Benchling OAuth Client Secret (press Enter to keep existing):"
|
|
369
|
+
: "Benchling OAuth Client Secret:",
|
|
370
|
+
validate: (input) => {
|
|
371
|
+
// If there's an existing secret and input is empty, we'll keep the existing one
|
|
372
|
+
if (config.benchling?.clientSecret && input.trim().length === 0) {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
return input.trim().length > 0 || "Client secret is required";
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
type: "input",
|
|
380
|
+
name: "appDefinitionId",
|
|
381
|
+
message: "Benchling App Definition ID:",
|
|
382
|
+
default: config.benchling?.appDefinitionId,
|
|
383
|
+
validate: (input) => input.trim().length > 0 || "App definition ID is required",
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
type: "input",
|
|
387
|
+
name: "testEntryId",
|
|
388
|
+
message: "Benchling Test Entry ID (optional):",
|
|
389
|
+
default: config.benchling?.testEntryId || "",
|
|
390
|
+
},
|
|
391
|
+
]);
|
|
392
|
+
// Handle empty password input - keep existing secret if user pressed Enter
|
|
393
|
+
if (benchlingAnswers.clientSecret.trim().length === 0 && config.benchling?.clientSecret) {
|
|
394
|
+
benchlingAnswers.clientSecret = config.benchling.clientSecret;
|
|
395
|
+
}
|
|
396
|
+
config.benchling = {
|
|
397
|
+
tenant: benchlingAnswers.tenant,
|
|
398
|
+
clientId: benchlingAnswers.clientId,
|
|
399
|
+
clientSecret: benchlingAnswers.clientSecret,
|
|
400
|
+
appDefinitionId: benchlingAnswers.appDefinitionId,
|
|
401
|
+
};
|
|
402
|
+
if (benchlingAnswers.testEntryId && benchlingAnswers.testEntryId.trim() !== "") {
|
|
403
|
+
config.benchling.testEntryId = benchlingAnswers.testEntryId;
|
|
404
|
+
}
|
|
405
|
+
// Prompt for package configuration
|
|
406
|
+
console.log("\nStep 3: Package Configuration\n");
|
|
407
|
+
const packageAnswers = await inquirer_1.default.prompt([
|
|
408
|
+
{
|
|
409
|
+
type: "input",
|
|
410
|
+
name: "bucket",
|
|
411
|
+
message: "Package S3 Bucket:",
|
|
412
|
+
default: config.packages?.bucket,
|
|
413
|
+
validate: (input) => input.trim().length > 0 || "Bucket name is required",
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
type: "input",
|
|
417
|
+
name: "prefix",
|
|
418
|
+
message: "Package S3 prefix:",
|
|
419
|
+
default: config.packages?.prefix || "benchling",
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
type: "input",
|
|
423
|
+
name: "metadataKey",
|
|
424
|
+
message: "Package metadata key:",
|
|
425
|
+
default: config.packages?.metadataKey || "experiment_id",
|
|
426
|
+
},
|
|
427
|
+
]);
|
|
428
|
+
config.packages = {
|
|
429
|
+
bucket: packageAnswers.bucket,
|
|
430
|
+
prefix: packageAnswers.prefix,
|
|
431
|
+
metadataKey: packageAnswers.metadataKey,
|
|
432
|
+
};
|
|
433
|
+
// Prompt for deployment configuration
|
|
434
|
+
console.log("\nStep 4: Deployment Configuration\n");
|
|
435
|
+
const deploymentAnswers = await inquirer_1.default.prompt([
|
|
436
|
+
{
|
|
437
|
+
type: "input",
|
|
438
|
+
name: "region",
|
|
439
|
+
message: "AWS Deployment Region:",
|
|
440
|
+
default: config.deployment?.region || config.quilt?.region || "us-east-1",
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
type: "input",
|
|
444
|
+
name: "account",
|
|
445
|
+
message: "AWS Account ID:",
|
|
446
|
+
default: config.deployment?.account || awsAccountId || config.quilt?.stackArn?.match(/:(\d{12}):/)?.[1],
|
|
447
|
+
validate: (input) => {
|
|
448
|
+
if (!input || input.trim().length === 0) {
|
|
449
|
+
return "AWS Account ID is required";
|
|
450
|
+
}
|
|
451
|
+
if (!/^\d{12}$/.test(input.trim())) {
|
|
452
|
+
return "AWS Account ID must be a 12-digit number";
|
|
453
|
+
}
|
|
454
|
+
return true;
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
]);
|
|
458
|
+
config.deployment = {
|
|
459
|
+
region: deploymentAnswers.region,
|
|
460
|
+
account: deploymentAnswers.account,
|
|
461
|
+
};
|
|
462
|
+
// Optional: Logging configuration
|
|
463
|
+
console.log("\nStep 5: Optional Configuration\n");
|
|
464
|
+
const optionalAnswers = await inquirer_1.default.prompt([
|
|
465
|
+
{
|
|
466
|
+
type: "list",
|
|
467
|
+
name: "logLevel",
|
|
468
|
+
message: "Log level:",
|
|
469
|
+
choices: ["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
470
|
+
default: config.logging?.level || "INFO",
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
type: "input",
|
|
474
|
+
name: "webhookAllowList",
|
|
475
|
+
message: "Webhook IP allowlist (comma-separated, empty for none):",
|
|
476
|
+
default: config.security?.webhookAllowList || "",
|
|
84
477
|
},
|
|
85
478
|
]);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
479
|
+
config.logging = {
|
|
480
|
+
level: optionalAnswers.logLevel,
|
|
481
|
+
};
|
|
482
|
+
config.security = {
|
|
483
|
+
enableVerification: true,
|
|
484
|
+
webhookAllowList: optionalAnswers.webhookAllowList,
|
|
485
|
+
};
|
|
486
|
+
// Add metadata
|
|
487
|
+
const now = new Date().toISOString();
|
|
488
|
+
config._metadata = {
|
|
489
|
+
version: "0.7.0",
|
|
490
|
+
createdAt: config._metadata?.createdAt || now,
|
|
491
|
+
updatedAt: now,
|
|
492
|
+
source: "wizard",
|
|
493
|
+
};
|
|
494
|
+
// Add inheritance marker if specified
|
|
495
|
+
if (inheritFrom) {
|
|
496
|
+
config._inherits = inheritFrom;
|
|
497
|
+
}
|
|
498
|
+
return config;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Main install wizard function
|
|
502
|
+
*
|
|
503
|
+
* Orchestrates the complete configuration workflow:
|
|
504
|
+
* 1. Load existing configuration (if any)
|
|
505
|
+
* 2. Infer Quilt configuration from AWS
|
|
506
|
+
* 3. Run interactive prompts for missing fields
|
|
507
|
+
* 4. Validate configuration
|
|
508
|
+
* 5. Save to XDG config directory
|
|
509
|
+
*/
|
|
510
|
+
async function runInstallWizard(options = {}) {
|
|
511
|
+
const { profile = "default", inheritFrom, nonInteractive = false, skipValidation = false, awsProfile, awsRegion = "us-east-1", } = options;
|
|
512
|
+
const xdg = new xdg_config_1.XDGConfig();
|
|
513
|
+
console.log("\n╔═══════════════════════════════════════════════════════════╗");
|
|
514
|
+
console.log("║ Benchling Webhook Setup (v0.7.0) ║");
|
|
515
|
+
console.log("╚═══════════════════════════════════════════════════════════╝\n");
|
|
516
|
+
// Step 1: Load existing configuration (if profile exists)
|
|
517
|
+
let existingConfig;
|
|
518
|
+
// Determine if we should inherit from 'default' when profile is not 'default'
|
|
519
|
+
const shouldInheritFromDefault = profile !== "default" && !inheritFrom;
|
|
520
|
+
const effectiveInheritFrom = inheritFrom || (shouldInheritFromDefault ? "default" : undefined);
|
|
521
|
+
if (xdg.profileExists(profile)) {
|
|
522
|
+
console.log(`Loading existing configuration for profile: ${profile}\n`);
|
|
523
|
+
try {
|
|
524
|
+
existingConfig = effectiveInheritFrom
|
|
525
|
+
? xdg.readProfileWithInheritance(profile, effectiveInheritFrom)
|
|
526
|
+
: xdg.readProfile(profile);
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
console.warn(`Warning: Could not load existing config: ${error.message}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else if (effectiveInheritFrom) {
|
|
533
|
+
// If profile doesn't exist but we should inherit, load base profile
|
|
534
|
+
console.log(`Creating new profile '${profile}' inheriting from '${effectiveInheritFrom}'\n`);
|
|
535
|
+
try {
|
|
536
|
+
existingConfig = xdg.readProfile(effectiveInheritFrom);
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
throw new Error(`Base profile '${effectiveInheritFrom}' not found: ${error.message}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Step 2: Infer Quilt configuration (unless inheriting from another profile)
|
|
543
|
+
let quiltConfig = existingConfig?.quilt || {};
|
|
544
|
+
let inferredAccountId;
|
|
545
|
+
if (!effectiveInheritFrom || !existingConfig?.quilt) {
|
|
546
|
+
console.log("Step 1: Inferring Quilt configuration from AWS...\n");
|
|
547
|
+
try {
|
|
548
|
+
const inferenceResult = await (0, infer_quilt_config_1.inferQuiltConfig)({
|
|
549
|
+
region: awsRegion,
|
|
550
|
+
profile: awsProfile,
|
|
551
|
+
interactive: !nonInteractive,
|
|
552
|
+
});
|
|
553
|
+
quiltConfig = inferenceResult;
|
|
554
|
+
inferredAccountId = inferenceResult.account;
|
|
555
|
+
console.log("✓ Quilt configuration inferred\n");
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
console.error(`Failed to infer Quilt configuration: ${error.message}`);
|
|
559
|
+
if (nonInteractive) {
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
const { continueManually } = await inquirer_1.default.prompt([
|
|
563
|
+
{
|
|
564
|
+
type: "confirm",
|
|
565
|
+
name: "continueManually",
|
|
566
|
+
message: "Continue and enter Quilt configuration manually?",
|
|
567
|
+
default: true,
|
|
568
|
+
},
|
|
569
|
+
]);
|
|
570
|
+
if (!continueManually) {
|
|
571
|
+
throw new Error("Setup aborted by user");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// Merge inferred Quilt config with existing config
|
|
576
|
+
const partialConfig = {
|
|
577
|
+
...existingConfig,
|
|
578
|
+
quilt: {
|
|
579
|
+
...existingConfig?.quilt,
|
|
580
|
+
...quiltConfig,
|
|
581
|
+
},
|
|
582
|
+
// Pass through inferred account ID for deployment config
|
|
583
|
+
deployment: {
|
|
584
|
+
...existingConfig?.deployment,
|
|
585
|
+
account: existingConfig?.deployment?.account || inferredAccountId,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
// Step 3: Run interactive wizard for remaining configuration
|
|
589
|
+
const config = await runConfigWizard({
|
|
590
|
+
existingConfig: partialConfig,
|
|
591
|
+
nonInteractive,
|
|
592
|
+
inheritFrom: effectiveInheritFrom,
|
|
593
|
+
});
|
|
594
|
+
// Step 4: Validate configuration
|
|
595
|
+
if (!skipValidation) {
|
|
596
|
+
console.log("\nValidating configuration...\n");
|
|
597
|
+
const validation = await validateConfig(config, {
|
|
598
|
+
skipValidation,
|
|
599
|
+
awsProfile,
|
|
600
|
+
});
|
|
601
|
+
if (!validation.isValid) {
|
|
602
|
+
console.error("\n❌ Configuration validation failed:");
|
|
603
|
+
validation.errors.forEach((err) => console.error(` - ${err}`));
|
|
604
|
+
if (nonInteractive) {
|
|
605
|
+
throw new Error("Configuration validation failed");
|
|
606
|
+
}
|
|
607
|
+
const { proceed } = await inquirer_1.default.prompt([
|
|
608
|
+
{
|
|
609
|
+
type: "confirm",
|
|
610
|
+
name: "proceed",
|
|
611
|
+
message: "Save configuration anyway?",
|
|
612
|
+
default: false,
|
|
613
|
+
},
|
|
614
|
+
]);
|
|
615
|
+
if (!proceed) {
|
|
616
|
+
throw new Error("Setup aborted by user");
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
console.log("✓ Configuration validated successfully\n");
|
|
621
|
+
}
|
|
622
|
+
if (validation.warnings && validation.warnings.length > 0) {
|
|
623
|
+
console.warn("\n⚠ Warnings:");
|
|
624
|
+
validation.warnings.forEach((warn) => console.warn(` - ${warn}`));
|
|
625
|
+
console.log("");
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Step 5: Save configuration
|
|
629
|
+
console.log(`Saving configuration to profile: ${profile}...\n`);
|
|
630
|
+
try {
|
|
631
|
+
xdg.writeProfile(profile, config);
|
|
632
|
+
console.log(`✓ Configuration saved to: ~/.config/benchling-webhook/${profile}/config.json\n`);
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
throw new Error(`Failed to save configuration: ${error.message}`);
|
|
636
|
+
}
|
|
637
|
+
// Step 6: Display next steps
|
|
638
|
+
console.log("╔═══════════════════════════════════════════════════════════╗");
|
|
639
|
+
console.log("║ Setup Complete! ║");
|
|
640
|
+
console.log("╚═══════════════════════════════════════════════════════════╝\n");
|
|
641
|
+
console.log("Next steps:");
|
|
642
|
+
console.log(" 1. Sync secrets to AWS: npm run setup:sync-secrets");
|
|
643
|
+
console.log(" 2. Deploy to AWS: npm run deploy:dev");
|
|
644
|
+
console.log(" 3. Test integration: npm run test:dev\n");
|
|
645
|
+
return config;
|
|
646
|
+
}
|
|
647
|
+
// =============================================================================
|
|
648
|
+
// CLI COMMAND EXPORT
|
|
649
|
+
// =============================================================================
|
|
650
|
+
/**
|
|
651
|
+
* Setup wizard command handler
|
|
652
|
+
*
|
|
653
|
+
* @param options - Wizard options
|
|
654
|
+
* @returns Promise that resolves when wizard completes
|
|
655
|
+
*/
|
|
656
|
+
async function setupWizardCommand(options = {}) {
|
|
657
|
+
await runInstallWizard(options);
|
|
102
658
|
}
|
|
103
659
|
//# sourceMappingURL=setup-wizard.js.map
|