@wraps.dev/cli 2.5.1 → 2.6.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/dist/cli.js
CHANGED
|
@@ -737,7 +737,9 @@ var init_events = __esm({
|
|
|
737
737
|
import * as clack from "@clack/prompts";
|
|
738
738
|
import pc2 from "picocolors";
|
|
739
739
|
function isAWSError(error) {
|
|
740
|
-
if (!(error instanceof Error))
|
|
740
|
+
if (!(error instanceof Error)) {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
741
743
|
const awsErrorNames = [
|
|
742
744
|
"ExpiredTokenException",
|
|
743
745
|
"InvalidClientTokenId",
|
|
@@ -754,7 +756,9 @@ function isAWSError(error) {
|
|
|
754
756
|
return awsErrorNames.includes(error.name) || "$metadata" in error;
|
|
755
757
|
}
|
|
756
758
|
function isPulumiError(error) {
|
|
757
|
-
if (!(error instanceof Error))
|
|
759
|
+
if (!(error instanceof Error)) {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
758
762
|
return error.message?.includes("pulumi") || error.message?.includes("Pulumi") || error.message?.includes("resource") || error.message?.includes("creating") || error.message?.includes("AccessDenied");
|
|
759
763
|
}
|
|
760
764
|
function parseAWSError(error) {
|
|
@@ -805,7 +809,9 @@ function parsePulumiError(error) {
|
|
|
805
809
|
return { code: "PULUMI_ERROR" };
|
|
806
810
|
}
|
|
807
811
|
function sanitizeErrorMessage(error) {
|
|
808
|
-
if (!error)
|
|
812
|
+
if (!error) {
|
|
813
|
+
return "Unknown error";
|
|
814
|
+
}
|
|
809
815
|
let message = error instanceof Error ? error.message : String(error);
|
|
810
816
|
message = message.replace(/\b\d{12}\b/g, "[ACCOUNT_ID]");
|
|
811
817
|
message = message.replace(
|
|
@@ -1022,7 +1028,7 @@ To remove: wraps destroy --stack ${stackName}`,
|
|
|
1022
1028
|
stackLocked: () => new WrapsError(
|
|
1023
1029
|
"The Pulumi stack is locked from a previous run",
|
|
1024
1030
|
"STACK_LOCKED",
|
|
1025
|
-
"This happens when a previous deployment was interrupted.\n\
|
|
1031
|
+
"This happens when a previous deployment was interrupted.\n\nFor local state, run:\n rm -rf ~/.wraps/pulumi/.pulumi/locks\n\nFor S3 state, delete the lock object in your wraps-state-* bucket under .pulumi/locks/\n\nThen try your command again.",
|
|
1026
1032
|
"https://wraps.dev/docs/guides/aws-setup/permissions/troubleshooting"
|
|
1027
1033
|
),
|
|
1028
1034
|
// SMS-specific errors
|
|
@@ -1162,6 +1168,24 @@ View required SES permissions:
|
|
|
1162
1168
|
"ROUTE53_PERMISSION_DENIED",
|
|
1163
1169
|
"Your IAM user/role needs Route53 permissions for automatic DNS management.\nRequired actions: ChangeResourceRecordSets, ListHostedZones\n\nThis is optional - you can add DNS records manually instead.",
|
|
1164
1170
|
"https://wraps.dev/docs/guides/aws-setup/permissions"
|
|
1171
|
+
),
|
|
1172
|
+
s3StateBucketCreationFailed: (bucketName) => new WrapsError(
|
|
1173
|
+
`Failed to create S3 state bucket: ${bucketName}`,
|
|
1174
|
+
"S3_STATE_BUCKET_CREATION_FAILED",
|
|
1175
|
+
"Ensure your IAM user/role has s3:CreateBucket, s3:PutBucketEncryption, s3:PutBucketVersioning permissions.\n\nTo use local-only state instead:\n export WRAPS_LOCAL_ONLY=1",
|
|
1176
|
+
"https://wraps.dev/docs/guides/aws-setup/permissions"
|
|
1177
|
+
),
|
|
1178
|
+
s3StateAccessDenied: () => new WrapsError(
|
|
1179
|
+
"Access denied to S3 state bucket",
|
|
1180
|
+
"S3_STATE_ACCESS_DENIED",
|
|
1181
|
+
"Ensure your IAM user/role has s3:GetObject, s3:PutObject, s3:ListBucket permissions on wraps-state-* buckets.\n\nTo use local-only state instead:\n export WRAPS_LOCAL_ONLY=1",
|
|
1182
|
+
"https://wraps.dev/docs/guides/aws-setup/permissions"
|
|
1183
|
+
),
|
|
1184
|
+
stateMigrationFailed: () => new WrapsError(
|
|
1185
|
+
"Failed to migrate Pulumi state to S3",
|
|
1186
|
+
"STATE_MIGRATION_FAILED",
|
|
1187
|
+
"The migration from local to S3 state storage failed.\nYour local state is still intact.\n\nTo skip migration and use local-only state:\n export WRAPS_LOCAL_ONLY=1",
|
|
1188
|
+
"https://wraps.dev/docs/guides/aws-setup/permissions"
|
|
1165
1189
|
)
|
|
1166
1190
|
};
|
|
1167
1191
|
}
|
|
@@ -3308,31 +3332,285 @@ var init_route53 = __esm({
|
|
|
3308
3332
|
}
|
|
3309
3333
|
});
|
|
3310
3334
|
|
|
3311
|
-
// src/utils/shared/
|
|
3335
|
+
// src/utils/shared/s3-state.ts
|
|
3336
|
+
var s3_state_exports = {};
|
|
3337
|
+
__export(s3_state_exports, {
|
|
3338
|
+
downloadMetadata: () => downloadMetadata,
|
|
3339
|
+
ensureStateBucket: () => ensureStateBucket,
|
|
3340
|
+
getS3BackendUrl: () => getS3BackendUrl,
|
|
3341
|
+
getStateBucketName: () => getStateBucketName,
|
|
3342
|
+
migrateLocalPulumiState: () => migrateLocalPulumiState,
|
|
3343
|
+
needsMigration: () => needsMigration,
|
|
3344
|
+
stateBucketExists: () => stateBucketExists,
|
|
3345
|
+
uploadMetadata: () => uploadMetadata
|
|
3346
|
+
});
|
|
3312
3347
|
import { existsSync as existsSync2 } from "fs";
|
|
3348
|
+
import { readdir, writeFile } from "fs/promises";
|
|
3349
|
+
import { join as join2 } from "path";
|
|
3350
|
+
function getStateBucketName(accountId, region) {
|
|
3351
|
+
return `wraps-state-${accountId}-${region}`;
|
|
3352
|
+
}
|
|
3353
|
+
function getS3BackendUrl(accountId, region) {
|
|
3354
|
+
return `s3://${getStateBucketName(accountId, region)}`;
|
|
3355
|
+
}
|
|
3356
|
+
async function stateBucketExists(accountId, region) {
|
|
3357
|
+
const { S3Client, HeadBucketCommand } = await import("@aws-sdk/client-s3");
|
|
3358
|
+
const client = new S3Client({ region });
|
|
3359
|
+
const bucketName = getStateBucketName(accountId, region);
|
|
3360
|
+
try {
|
|
3361
|
+
await client.send(new HeadBucketCommand({ Bucket: bucketName }));
|
|
3362
|
+
return true;
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
if (error.name === "NotFound" || error.name === "NoSuchBucket" || error.$metadata?.httpStatusCode === 404) {
|
|
3365
|
+
return false;
|
|
3366
|
+
}
|
|
3367
|
+
throw error;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
async function ensureStateBucket(accountId, region) {
|
|
3371
|
+
const {
|
|
3372
|
+
S3Client,
|
|
3373
|
+
HeadBucketCommand,
|
|
3374
|
+
CreateBucketCommand,
|
|
3375
|
+
PutBucketEncryptionCommand,
|
|
3376
|
+
PutBucketVersioningCommand,
|
|
3377
|
+
PutPublicAccessBlockCommand,
|
|
3378
|
+
PutBucketTaggingCommand
|
|
3379
|
+
} = await import("@aws-sdk/client-s3");
|
|
3380
|
+
const client = new S3Client({ region });
|
|
3381
|
+
const bucketName = getStateBucketName(accountId, region);
|
|
3382
|
+
try {
|
|
3383
|
+
await client.send(new HeadBucketCommand({ Bucket: bucketName }));
|
|
3384
|
+
return bucketName;
|
|
3385
|
+
} catch (error) {
|
|
3386
|
+
if (error.name !== "NotFound" && error.name !== "NoSuchBucket" && error.$metadata?.httpStatusCode !== 404) {
|
|
3387
|
+
throw error;
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
const createParams = { Bucket: bucketName };
|
|
3391
|
+
if (region !== "us-east-1") {
|
|
3392
|
+
createParams.CreateBucketConfiguration = {
|
|
3393
|
+
LocationConstraint: region
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
await client.send(new CreateBucketCommand(createParams));
|
|
3397
|
+
await client.send(
|
|
3398
|
+
new PutBucketEncryptionCommand({
|
|
3399
|
+
Bucket: bucketName,
|
|
3400
|
+
ServerSideEncryptionConfiguration: {
|
|
3401
|
+
Rules: [
|
|
3402
|
+
{
|
|
3403
|
+
ApplyServerSideEncryptionByDefault: {
|
|
3404
|
+
SSEAlgorithm: "AES256"
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
]
|
|
3408
|
+
}
|
|
3409
|
+
})
|
|
3410
|
+
);
|
|
3411
|
+
await client.send(
|
|
3412
|
+
new PutBucketVersioningCommand({
|
|
3413
|
+
Bucket: bucketName,
|
|
3414
|
+
VersioningConfiguration: {
|
|
3415
|
+
Status: "Enabled"
|
|
3416
|
+
}
|
|
3417
|
+
})
|
|
3418
|
+
);
|
|
3419
|
+
await client.send(
|
|
3420
|
+
new PutPublicAccessBlockCommand({
|
|
3421
|
+
Bucket: bucketName,
|
|
3422
|
+
PublicAccessBlockConfiguration: {
|
|
3423
|
+
BlockPublicAcls: true,
|
|
3424
|
+
BlockPublicPolicy: true,
|
|
3425
|
+
IgnorePublicAcls: true,
|
|
3426
|
+
RestrictPublicBuckets: true
|
|
3427
|
+
}
|
|
3428
|
+
})
|
|
3429
|
+
);
|
|
3430
|
+
await client.send(
|
|
3431
|
+
new PutBucketTaggingCommand({
|
|
3432
|
+
Bucket: bucketName,
|
|
3433
|
+
Tagging: {
|
|
3434
|
+
TagSet: [
|
|
3435
|
+
{ Key: "ManagedBy", Value: "wraps-cli" },
|
|
3436
|
+
{ Key: "Purpose", Value: "state" }
|
|
3437
|
+
]
|
|
3438
|
+
}
|
|
3439
|
+
})
|
|
3440
|
+
);
|
|
3441
|
+
return bucketName;
|
|
3442
|
+
}
|
|
3443
|
+
async function uploadMetadata(bucketName, metadata) {
|
|
3444
|
+
const { S3Client, PutObjectCommand } = await import("@aws-sdk/client-s3");
|
|
3445
|
+
const client = new S3Client({ region: metadata.region });
|
|
3446
|
+
const key = `metadata/${metadata.accountId}-${metadata.region}.json`;
|
|
3447
|
+
await client.send(
|
|
3448
|
+
new PutObjectCommand({
|
|
3449
|
+
Bucket: bucketName,
|
|
3450
|
+
Key: key,
|
|
3451
|
+
Body: JSON.stringify(metadata, null, 2),
|
|
3452
|
+
ContentType: "application/json"
|
|
3453
|
+
})
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
async function downloadMetadata(bucketName, accountId, region) {
|
|
3457
|
+
const { S3Client, GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
3458
|
+
const client = new S3Client({ region });
|
|
3459
|
+
const key = `metadata/${accountId}-${region}.json`;
|
|
3460
|
+
try {
|
|
3461
|
+
const response = await client.send(
|
|
3462
|
+
new GetObjectCommand({
|
|
3463
|
+
Bucket: bucketName,
|
|
3464
|
+
Key: key
|
|
3465
|
+
})
|
|
3466
|
+
);
|
|
3467
|
+
const body = await response.Body?.transformToString();
|
|
3468
|
+
if (!body) {
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3471
|
+
return JSON.parse(body);
|
|
3472
|
+
} catch (error) {
|
|
3473
|
+
if (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404) {
|
|
3474
|
+
return null;
|
|
3475
|
+
}
|
|
3476
|
+
throw error;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
async function needsMigration(localPulumiDir, accountId, region) {
|
|
3480
|
+
const markerPath = join2(localPulumiDir, `.migrated-${accountId}-${region}`);
|
|
3481
|
+
if (existsSync2(markerPath)) {
|
|
3482
|
+
return false;
|
|
3483
|
+
}
|
|
3484
|
+
const stacksDir = join2(localPulumiDir, ".pulumi", "stacks");
|
|
3485
|
+
if (!existsSync2(stacksDir)) {
|
|
3486
|
+
return false;
|
|
3487
|
+
}
|
|
3488
|
+
try {
|
|
3489
|
+
const files = await readdir(stacksDir);
|
|
3490
|
+
const matchingStacks = files.filter(
|
|
3491
|
+
(f) => f.includes(accountId) && f.includes(region) && f.endsWith(".json")
|
|
3492
|
+
);
|
|
3493
|
+
return matchingStacks.length > 0;
|
|
3494
|
+
} catch {
|
|
3495
|
+
return false;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
async function migrateLocalPulumiState(localPulumiDir, bucketName, accountId, region) {
|
|
3499
|
+
const pulumi28 = await import("@pulumi/pulumi/automation/index.js");
|
|
3500
|
+
const stacksDir = join2(localPulumiDir, ".pulumi", "stacks");
|
|
3501
|
+
const files = await readdir(stacksDir);
|
|
3502
|
+
const stackFiles = files.filter(
|
|
3503
|
+
(f) => f.includes(accountId) && f.includes(region) && f.endsWith(".json")
|
|
3504
|
+
);
|
|
3505
|
+
for (const stackFile of stackFiles) {
|
|
3506
|
+
const stackName = stackFile.replace(".json", "");
|
|
3507
|
+
let projectName = "wraps-email";
|
|
3508
|
+
if (stackName.startsWith("wraps-sms-")) {
|
|
3509
|
+
projectName = "wraps-sms";
|
|
3510
|
+
} else if (stackName.startsWith("wraps-cdn-")) {
|
|
3511
|
+
projectName = "wraps-cdn";
|
|
3512
|
+
}
|
|
3513
|
+
try {
|
|
3514
|
+
const localStack = await pulumi28.LocalWorkspace.selectStack({
|
|
3515
|
+
stackName,
|
|
3516
|
+
workDir: localPulumiDir
|
|
3517
|
+
});
|
|
3518
|
+
const state = await localStack.exportStack();
|
|
3519
|
+
const s3Stack = await pulumi28.LocalWorkspace.createOrSelectStack(
|
|
3520
|
+
{
|
|
3521
|
+
stackName,
|
|
3522
|
+
projectName,
|
|
3523
|
+
program: async () => ({})
|
|
3524
|
+
},
|
|
3525
|
+
{
|
|
3526
|
+
workDir: localPulumiDir,
|
|
3527
|
+
envVars: {
|
|
3528
|
+
PULUMI_BACKEND_URL: `s3://${bucketName}`,
|
|
3529
|
+
PULUMI_CONFIG_PASSPHRASE: ""
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
);
|
|
3533
|
+
await s3Stack.importStack(state);
|
|
3534
|
+
} catch (error) {
|
|
3535
|
+
console.error(
|
|
3536
|
+
`Warning: Failed to migrate stack ${stackName}: ${error.message}`
|
|
3537
|
+
);
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
const markerPath = join2(localPulumiDir, `.migrated-${accountId}-${region}`);
|
|
3541
|
+
await writeFile(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
3542
|
+
}
|
|
3543
|
+
var init_s3_state = __esm({
|
|
3544
|
+
"src/utils/shared/s3-state.ts"() {
|
|
3545
|
+
"use strict";
|
|
3546
|
+
init_esm_shims();
|
|
3547
|
+
}
|
|
3548
|
+
});
|
|
3549
|
+
|
|
3550
|
+
// src/utils/shared/fs.ts
|
|
3551
|
+
import { existsSync as existsSync3 } from "fs";
|
|
3313
3552
|
import { mkdir } from "fs/promises";
|
|
3314
3553
|
import { homedir as homedir2 } from "os";
|
|
3315
|
-
import { join as
|
|
3554
|
+
import { join as join3 } from "path";
|
|
3316
3555
|
function getWrapsDir() {
|
|
3317
|
-
return
|
|
3556
|
+
return join3(homedir2(), ".wraps");
|
|
3318
3557
|
}
|
|
3319
3558
|
function getPulumiWorkDir() {
|
|
3320
|
-
return
|
|
3559
|
+
return join3(getWrapsDir(), "pulumi");
|
|
3321
3560
|
}
|
|
3322
3561
|
async function ensureWrapsDir() {
|
|
3323
3562
|
const wrapsDir = getWrapsDir();
|
|
3324
|
-
if (!
|
|
3563
|
+
if (!existsSync3(wrapsDir)) {
|
|
3325
3564
|
await mkdir(wrapsDir, { recursive: true });
|
|
3326
3565
|
}
|
|
3327
3566
|
}
|
|
3328
|
-
async function ensurePulumiWorkDir() {
|
|
3567
|
+
async function ensurePulumiWorkDir(options) {
|
|
3329
3568
|
await ensureWrapsDir();
|
|
3330
3569
|
const pulumiDir = getPulumiWorkDir();
|
|
3331
|
-
if (!
|
|
3570
|
+
if (!existsSync3(pulumiDir)) {
|
|
3332
3571
|
await mkdir(pulumiDir, { recursive: true });
|
|
3333
3572
|
}
|
|
3334
|
-
process.env.PULUMI_BACKEND_URL = `file://${pulumiDir}`;
|
|
3335
3573
|
process.env.PULUMI_CONFIG_PASSPHRASE = "";
|
|
3574
|
+
const useS3 = options?.accountId && options?.region && process.env.WRAPS_LOCAL_ONLY !== "1";
|
|
3575
|
+
if (useS3) {
|
|
3576
|
+
try {
|
|
3577
|
+
const {
|
|
3578
|
+
ensureStateBucket: ensureStateBucket2,
|
|
3579
|
+
getS3BackendUrl: getS3BackendUrl2,
|
|
3580
|
+
needsMigration: needsMigration2,
|
|
3581
|
+
migrateLocalPulumiState: migrateLocalPulumiState2
|
|
3582
|
+
} = await Promise.resolve().then(() => (init_s3_state(), s3_state_exports));
|
|
3583
|
+
const bucketName = await ensureStateBucket2(
|
|
3584
|
+
options.accountId,
|
|
3585
|
+
options.region
|
|
3586
|
+
);
|
|
3587
|
+
const shouldMigrate = await needsMigration2(
|
|
3588
|
+
pulumiDir,
|
|
3589
|
+
options.accountId,
|
|
3590
|
+
options.region
|
|
3591
|
+
);
|
|
3592
|
+
if (shouldMigrate) {
|
|
3593
|
+
process.env.PULUMI_BACKEND_URL = `file://${pulumiDir}`;
|
|
3594
|
+
await migrateLocalPulumiState2(
|
|
3595
|
+
pulumiDir,
|
|
3596
|
+
bucketName,
|
|
3597
|
+
options.accountId,
|
|
3598
|
+
options.region
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
process.env.PULUMI_BACKEND_URL = getS3BackendUrl2(
|
|
3602
|
+
options.accountId,
|
|
3603
|
+
options.region
|
|
3604
|
+
);
|
|
3605
|
+
return;
|
|
3606
|
+
} catch (error) {
|
|
3607
|
+
const clack38 = await import("@clack/prompts");
|
|
3608
|
+
clack38.log.warn(
|
|
3609
|
+
`S3 state backend unavailable (${error.message}). Using local state.`
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
process.env.PULUMI_BACKEND_URL = `file://${pulumiDir}`;
|
|
3336
3614
|
}
|
|
3337
3615
|
var init_fs = __esm({
|
|
3338
3616
|
"src/utils/shared/fs.ts"() {
|
|
@@ -3346,6 +3624,7 @@ var metadata_exports = {};
|
|
|
3346
3624
|
__export(metadata_exports, {
|
|
3347
3625
|
addServiceToConnection: () => addServiceToConnection,
|
|
3348
3626
|
applyConfigUpdates: () => applyConfigUpdates,
|
|
3627
|
+
buildEmailStackConfig: () => buildEmailStackConfig,
|
|
3349
3628
|
connectionExists: () => connectionExists,
|
|
3350
3629
|
createConnectionMetadata: () => createConnectionMetadata,
|
|
3351
3630
|
deleteConnectionMetadata: () => deleteConnectionMetadata,
|
|
@@ -3362,19 +3641,19 @@ __export(metadata_exports, {
|
|
|
3362
3641
|
updateServiceConfig: () => updateServiceConfig
|
|
3363
3642
|
});
|
|
3364
3643
|
import { randomBytes } from "crypto";
|
|
3365
|
-
import { existsSync as
|
|
3366
|
-
import { readFile, writeFile } from "fs/promises";
|
|
3367
|
-
import { join as
|
|
3644
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3645
|
+
import { readFile, writeFile as writeFile2 } from "fs/promises";
|
|
3646
|
+
import { join as join4 } from "path";
|
|
3368
3647
|
function getConnectionsDir() {
|
|
3369
|
-
return
|
|
3648
|
+
return join4(getWrapsDir(), "connections");
|
|
3370
3649
|
}
|
|
3371
3650
|
function getMetadataPath(accountId, region) {
|
|
3372
|
-
return
|
|
3651
|
+
return join4(getConnectionsDir(), `${accountId}-${region}.json`);
|
|
3373
3652
|
}
|
|
3374
3653
|
async function ensureConnectionsDir() {
|
|
3375
3654
|
await ensureWrapsDir();
|
|
3376
3655
|
const connectionsDir = getConnectionsDir();
|
|
3377
|
-
if (!
|
|
3656
|
+
if (!existsSync4(connectionsDir)) {
|
|
3378
3657
|
const { mkdir: mkdir2 } = await import("fs/promises");
|
|
3379
3658
|
await mkdir2(connectionsDir, { recursive: true });
|
|
3380
3659
|
}
|
|
@@ -3402,57 +3681,121 @@ function isLegacyMetadata(data) {
|
|
|
3402
3681
|
}
|
|
3403
3682
|
async function loadConnectionMetadata(accountId, region) {
|
|
3404
3683
|
const metadataPath = getMetadataPath(accountId, region);
|
|
3405
|
-
|
|
3406
|
-
|
|
3684
|
+
let localData = null;
|
|
3685
|
+
if (existsSync4(metadataPath)) {
|
|
3686
|
+
try {
|
|
3687
|
+
const content = await readFile(metadataPath, "utf-8");
|
|
3688
|
+
const data = JSON.parse(content);
|
|
3689
|
+
if (isLegacyMetadata(data)) {
|
|
3690
|
+
const migrated = migrateLegacyMetadata(data);
|
|
3691
|
+
await saveConnectionMetadataLocal(migrated);
|
|
3692
|
+
localData = migrated;
|
|
3693
|
+
} else {
|
|
3694
|
+
if (!data.version) {
|
|
3695
|
+
data.version = "1.0.0";
|
|
3696
|
+
await saveConnectionMetadataLocal(data);
|
|
3697
|
+
}
|
|
3698
|
+
localData = data;
|
|
3699
|
+
}
|
|
3700
|
+
} catch (error) {
|
|
3701
|
+
console.error("Error loading connection metadata:", error.message);
|
|
3702
|
+
}
|
|
3407
3703
|
}
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3704
|
+
if (process.env.WRAPS_LOCAL_ONLY !== "1") {
|
|
3705
|
+
try {
|
|
3706
|
+
const {
|
|
3707
|
+
stateBucketExists: stateBucketExists2,
|
|
3708
|
+
downloadMetadata: downloadMetadata2,
|
|
3709
|
+
uploadMetadata: uploadMetadata2,
|
|
3710
|
+
getStateBucketName: getStateBucketName2
|
|
3711
|
+
} = await Promise.resolve().then(() => (init_s3_state(), s3_state_exports));
|
|
3712
|
+
const bucketExists = await stateBucketExists2(accountId, region);
|
|
3713
|
+
if (bucketExists) {
|
|
3714
|
+
const bucketName = getStateBucketName2(accountId, region);
|
|
3715
|
+
const remoteData = await downloadMetadata2(
|
|
3716
|
+
bucketName,
|
|
3717
|
+
accountId,
|
|
3718
|
+
region
|
|
3719
|
+
);
|
|
3720
|
+
if (remoteData && localData) {
|
|
3721
|
+
if (remoteData.timestamp > localData.timestamp) {
|
|
3722
|
+
await saveConnectionMetadataLocal(remoteData);
|
|
3723
|
+
return remoteData;
|
|
3724
|
+
}
|
|
3725
|
+
if (localData.timestamp > remoteData.timestamp) {
|
|
3726
|
+
await uploadMetadata2(bucketName, localData).catch(() => {
|
|
3727
|
+
});
|
|
3728
|
+
return localData;
|
|
3729
|
+
}
|
|
3730
|
+
return localData;
|
|
3731
|
+
}
|
|
3732
|
+
if (remoteData && !localData) {
|
|
3733
|
+
await saveConnectionMetadataLocal(remoteData);
|
|
3734
|
+
return remoteData;
|
|
3735
|
+
}
|
|
3736
|
+
if (localData && !remoteData) {
|
|
3737
|
+
await uploadMetadata2(bucketName, localData).catch(() => {
|
|
3738
|
+
});
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
} catch {
|
|
3742
|
+
}
|
|
3424
3743
|
}
|
|
3744
|
+
return localData;
|
|
3745
|
+
}
|
|
3746
|
+
async function saveConnectionMetadataLocal(metadata) {
|
|
3747
|
+
await ensureConnectionsDir();
|
|
3748
|
+
const metadataPath = getMetadataPath(metadata.accountId, metadata.region);
|
|
3749
|
+
const content = JSON.stringify(metadata, null, 2);
|
|
3750
|
+
await writeFile2(metadataPath, content, "utf-8");
|
|
3425
3751
|
}
|
|
3426
3752
|
async function saveConnectionMetadata(metadata) {
|
|
3427
3753
|
await ensureConnectionsDir();
|
|
3428
3754
|
const metadataPath = getMetadataPath(metadata.accountId, metadata.region);
|
|
3429
3755
|
try {
|
|
3430
3756
|
const content = JSON.stringify(metadata, null, 2);
|
|
3431
|
-
await
|
|
3757
|
+
await writeFile2(metadataPath, content, "utf-8");
|
|
3432
3758
|
} catch (error) {
|
|
3433
3759
|
console.error("Error saving connection metadata:", error.message);
|
|
3434
3760
|
throw error;
|
|
3435
3761
|
}
|
|
3762
|
+
if (process.env.WRAPS_LOCAL_ONLY !== "1") {
|
|
3763
|
+
try {
|
|
3764
|
+
const { stateBucketExists: stateBucketExists2, uploadMetadata: uploadMetadata2, getStateBucketName: getStateBucketName2 } = await Promise.resolve().then(() => (init_s3_state(), s3_state_exports));
|
|
3765
|
+
const bucketExists = await stateBucketExists2(
|
|
3766
|
+
metadata.accountId,
|
|
3767
|
+
metadata.region
|
|
3768
|
+
);
|
|
3769
|
+
if (bucketExists) {
|
|
3770
|
+
const bucketName = getStateBucketName2(
|
|
3771
|
+
metadata.accountId,
|
|
3772
|
+
metadata.region
|
|
3773
|
+
);
|
|
3774
|
+
await uploadMetadata2(bucketName, metadata);
|
|
3775
|
+
}
|
|
3776
|
+
} catch {
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3436
3779
|
}
|
|
3437
3780
|
async function deleteConnectionMetadata(accountId, region) {
|
|
3438
3781
|
const metadataPath = getMetadataPath(accountId, region);
|
|
3439
|
-
if (
|
|
3782
|
+
if (existsSync4(metadataPath)) {
|
|
3440
3783
|
const { unlink } = await import("fs/promises");
|
|
3441
3784
|
await unlink(metadataPath);
|
|
3442
3785
|
}
|
|
3443
3786
|
}
|
|
3444
3787
|
async function listConnections() {
|
|
3445
3788
|
const connectionsDir = getConnectionsDir();
|
|
3446
|
-
if (!
|
|
3789
|
+
if (!existsSync4(connectionsDir)) {
|
|
3447
3790
|
return [];
|
|
3448
3791
|
}
|
|
3449
3792
|
try {
|
|
3450
|
-
const { readdir } = await import("fs/promises");
|
|
3451
|
-
const files = await
|
|
3793
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
3794
|
+
const files = await readdir2(connectionsDir);
|
|
3452
3795
|
const connections = [];
|
|
3453
3796
|
for (const file of files) {
|
|
3454
3797
|
if (file.endsWith(".json")) {
|
|
3455
|
-
const content = await readFile(
|
|
3798
|
+
const content = await readFile(join4(connectionsDir, file), "utf-8");
|
|
3456
3799
|
try {
|
|
3457
3800
|
const metadata = JSON.parse(content);
|
|
3458
3801
|
connections.push(metadata);
|
|
@@ -3469,7 +3812,7 @@ async function listConnections() {
|
|
|
3469
3812
|
}
|
|
3470
3813
|
async function connectionExists(accountId, region) {
|
|
3471
3814
|
const metadataPath = getMetadataPath(accountId, region);
|
|
3472
|
-
return
|
|
3815
|
+
return existsSync4(metadataPath);
|
|
3473
3816
|
}
|
|
3474
3817
|
function createConnectionMetadata(accountId, region, provider, emailConfig, preset) {
|
|
3475
3818
|
return {
|
|
@@ -3659,6 +4002,25 @@ async function findConnectionsWithService(accountId, service) {
|
|
|
3659
4002
|
const accountConnections = await findConnectionsForAccount(accountId);
|
|
3660
4003
|
return accountConnections.filter((conn) => hasService(conn, service));
|
|
3661
4004
|
}
|
|
4005
|
+
function buildEmailStackConfig(metadata, region, overrides) {
|
|
4006
|
+
const emailService = metadata.services.email;
|
|
4007
|
+
let webhook;
|
|
4008
|
+
if (overrides && "webhook" in overrides) {
|
|
4009
|
+
webhook = overrides.webhook;
|
|
4010
|
+
} else if (emailService?.webhookSecret) {
|
|
4011
|
+
webhook = {
|
|
4012
|
+
awsAccountNumber: metadata.accountId,
|
|
4013
|
+
webhookSecret: emailService.webhookSecret
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
return {
|
|
4017
|
+
provider: metadata.provider,
|
|
4018
|
+
region,
|
|
4019
|
+
vercel: metadata.vercel,
|
|
4020
|
+
emailConfig: overrides?.emailConfig ?? emailService.config,
|
|
4021
|
+
webhook
|
|
4022
|
+
};
|
|
4023
|
+
}
|
|
3662
4024
|
function generateWebhookSecret() {
|
|
3663
4025
|
return randomBytes(32).toString("hex");
|
|
3664
4026
|
}
|
|
@@ -3868,9 +4230,9 @@ __export(lambda_exports, {
|
|
|
3868
4230
|
getLambdaCode: () => getLambdaCode
|
|
3869
4231
|
});
|
|
3870
4232
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
3871
|
-
import { existsSync as
|
|
4233
|
+
import { existsSync as existsSync5, mkdirSync } from "fs";
|
|
3872
4234
|
import { tmpdir } from "os";
|
|
3873
|
-
import { dirname, join as
|
|
4235
|
+
import { dirname, join as join5 } from "path";
|
|
3874
4236
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3875
4237
|
import * as aws8 from "@pulumi/aws";
|
|
3876
4238
|
import * as pulumi11 from "@pulumi/pulumi";
|
|
@@ -3879,7 +4241,7 @@ function getPackageRoot() {
|
|
|
3879
4241
|
const currentFile = fileURLToPath2(import.meta.url);
|
|
3880
4242
|
let dir = dirname(currentFile);
|
|
3881
4243
|
while (dir !== dirname(dir)) {
|
|
3882
|
-
if (
|
|
4244
|
+
if (existsSync5(join5(dir, "package.json"))) {
|
|
3883
4245
|
return dir;
|
|
3884
4246
|
}
|
|
3885
4247
|
dir = dirname(dir);
|
|
@@ -3922,18 +4284,18 @@ async function findEventSourceMapping(functionName, queueArn) {
|
|
|
3922
4284
|
}
|
|
3923
4285
|
async function getLambdaCode(functionName) {
|
|
3924
4286
|
const packageRoot = getPackageRoot();
|
|
3925
|
-
const distLambdaPath =
|
|
3926
|
-
const distBundleMarker =
|
|
3927
|
-
if (
|
|
4287
|
+
const distLambdaPath = join5(packageRoot, "dist", "lambda", functionName);
|
|
4288
|
+
const distBundleMarker = join5(distLambdaPath, ".bundled");
|
|
4289
|
+
if (existsSync5(distBundleMarker)) {
|
|
3928
4290
|
return distLambdaPath;
|
|
3929
4291
|
}
|
|
3930
|
-
const lambdaPath =
|
|
3931
|
-
const lambdaBundleMarker =
|
|
3932
|
-
if (
|
|
4292
|
+
const lambdaPath = join5(packageRoot, "lambda", functionName);
|
|
4293
|
+
const lambdaBundleMarker = join5(lambdaPath, ".bundled");
|
|
4294
|
+
if (existsSync5(lambdaBundleMarker)) {
|
|
3933
4295
|
return lambdaPath;
|
|
3934
4296
|
}
|
|
3935
|
-
const sourcePath =
|
|
3936
|
-
if (!
|
|
4297
|
+
const sourcePath = join5(lambdaPath, "index.ts");
|
|
4298
|
+
if (!existsSync5(sourcePath)) {
|
|
3937
4299
|
throw new Error(
|
|
3938
4300
|
`Lambda source not found: ${sourcePath}
|
|
3939
4301
|
This usually means the build process didn't complete successfully.
|
|
@@ -3941,8 +4303,8 @@ Try running: pnpm build`
|
|
|
3941
4303
|
);
|
|
3942
4304
|
}
|
|
3943
4305
|
const buildId = randomBytes2(8).toString("hex");
|
|
3944
|
-
const outdir =
|
|
3945
|
-
if (!
|
|
4306
|
+
const outdir = join5(tmpdir(), `wraps-lambda-${buildId}`);
|
|
4307
|
+
if (!existsSync5(outdir)) {
|
|
3946
4308
|
mkdirSync(outdir, { recursive: true });
|
|
3947
4309
|
}
|
|
3948
4310
|
await build({
|
|
@@ -3951,7 +4313,7 @@ Try running: pnpm build`
|
|
|
3951
4313
|
platform: "node",
|
|
3952
4314
|
target: "node24",
|
|
3953
4315
|
format: "esm",
|
|
3954
|
-
outfile:
|
|
4316
|
+
outfile: join5(outdir, "index.mjs"),
|
|
3955
4317
|
external: ["@aws-sdk/*"],
|
|
3956
4318
|
// AWS SDK v3 is included in Lambda runtime
|
|
3957
4319
|
minify: true,
|
|
@@ -5059,12 +5421,22 @@ async function createDNSRecordsForProvider(credentials, data, selectedCategories
|
|
|
5059
5421
|
data.mailFromDomain
|
|
5060
5422
|
);
|
|
5061
5423
|
let recordsCreated = 0;
|
|
5062
|
-
if (categories.has("dkim"))
|
|
5063
|
-
|
|
5064
|
-
|
|
5424
|
+
if (categories.has("dkim")) {
|
|
5425
|
+
recordsCreated += data.dkimTokens.length;
|
|
5426
|
+
}
|
|
5427
|
+
if (categories.has("spf")) {
|
|
5428
|
+
recordsCreated += 1;
|
|
5429
|
+
}
|
|
5430
|
+
if (categories.has("dmarc")) {
|
|
5431
|
+
recordsCreated += 1;
|
|
5432
|
+
}
|
|
5065
5433
|
if (data.mailFromDomain) {
|
|
5066
|
-
if (categories.has("mailfrom_mx"))
|
|
5067
|
-
|
|
5434
|
+
if (categories.has("mailfrom_mx")) {
|
|
5435
|
+
recordsCreated += 1;
|
|
5436
|
+
}
|
|
5437
|
+
if (categories.has("mailfrom_spf")) {
|
|
5438
|
+
recordsCreated += 1;
|
|
5439
|
+
}
|
|
5068
5440
|
}
|
|
5069
5441
|
return {
|
|
5070
5442
|
success: true,
|
|
@@ -5366,10 +5738,18 @@ async function detectAvailableDNSProviders(domain, region) {
|
|
|
5366
5738
|
hint: "I'll add DNS records myself"
|
|
5367
5739
|
});
|
|
5368
5740
|
return providers.sort((a, b) => {
|
|
5369
|
-
if (a.provider === "manual")
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
if (
|
|
5741
|
+
if (a.provider === "manual") {
|
|
5742
|
+
return 1;
|
|
5743
|
+
}
|
|
5744
|
+
if (b.provider === "manual") {
|
|
5745
|
+
return -1;
|
|
5746
|
+
}
|
|
5747
|
+
if (a.detected && !b.detected) {
|
|
5748
|
+
return -1;
|
|
5749
|
+
}
|
|
5750
|
+
if (!a.detected && b.detected) {
|
|
5751
|
+
return 1;
|
|
5752
|
+
}
|
|
5373
5753
|
return 0;
|
|
5374
5754
|
});
|
|
5375
5755
|
}
|
|
@@ -5731,7 +6111,7 @@ var init_dynamodb_metrics = __esm({
|
|
|
5731
6111
|
// src/cli.ts
|
|
5732
6112
|
init_esm_shims();
|
|
5733
6113
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5734
|
-
import { dirname as dirname2, join as
|
|
6114
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
5735
6115
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5736
6116
|
import * as clack37 from "@clack/prompts";
|
|
5737
6117
|
import args from "args";
|
|
@@ -7316,7 +7696,7 @@ async function cdnDestroy(options) {
|
|
|
7316
7696
|
const previewResult = await progress.execute(
|
|
7317
7697
|
"Generating destruction preview",
|
|
7318
7698
|
async () => {
|
|
7319
|
-
await ensurePulumiWorkDir();
|
|
7699
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
7320
7700
|
const stackName = storedStackName || `wraps-cdn-${identity.accountId}-${region}`;
|
|
7321
7701
|
let stack;
|
|
7322
7702
|
try {
|
|
@@ -7394,7 +7774,7 @@ async function cdnDestroy(options) {
|
|
|
7394
7774
|
await progress.execute(
|
|
7395
7775
|
"Destroying CDN infrastructure (this may take 2-3 minutes)",
|
|
7396
7776
|
async () => {
|
|
7397
|
-
await ensurePulumiWorkDir();
|
|
7777
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
7398
7778
|
const stackName = storedStackName || `wraps-cdn-${identity.accountId}-${region}`;
|
|
7399
7779
|
let stack;
|
|
7400
7780
|
try {
|
|
@@ -7591,6 +7971,8 @@ async function createCdnBucket(config2) {
|
|
|
7591
7971
|
"https://*.wraps.dev",
|
|
7592
7972
|
"http://localhost:3000",
|
|
7593
7973
|
"http://localhost:3001",
|
|
7974
|
+
"http://localhost:3002",
|
|
7975
|
+
"http://localhost:3003",
|
|
7594
7976
|
"http://localhost:4000",
|
|
7595
7977
|
"http://localhost:5173",
|
|
7596
7978
|
"http://localhost:5174",
|
|
@@ -8649,7 +9031,7 @@ ${pc8.yellow(pc8.bold("Configuration Notes:"))}`);
|
|
|
8649
9031
|
const previewResult = await progress.execute(
|
|
8650
9032
|
"Generating infrastructure preview",
|
|
8651
9033
|
async () => {
|
|
8652
|
-
await ensurePulumiWorkDir();
|
|
9034
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
8653
9035
|
const stack = await pulumi4.automation.LocalWorkspace.createOrSelectStack(
|
|
8654
9036
|
{
|
|
8655
9037
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
@@ -8713,7 +9095,7 @@ ${pc8.yellow(pc8.bold("Configuration Notes:"))}`);
|
|
|
8713
9095
|
outputs = await progress.execute(
|
|
8714
9096
|
"Deploying CDN infrastructure (this may take 2-3 minutes)",
|
|
8715
9097
|
async () => {
|
|
8716
|
-
await ensurePulumiWorkDir();
|
|
9098
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
8717
9099
|
const stack = await pulumi4.automation.LocalWorkspace.createOrSelectStack(
|
|
8718
9100
|
{
|
|
8719
9101
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
@@ -9082,7 +9464,7 @@ async function cdnStatus(options) {
|
|
|
9082
9464
|
}
|
|
9083
9465
|
let stackOutputs = {};
|
|
9084
9466
|
try {
|
|
9085
|
-
await ensurePulumiWorkDir();
|
|
9467
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
9086
9468
|
const stack = await pulumi5.automation.LocalWorkspace.selectStack({
|
|
9087
9469
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
9088
9470
|
workDir: getPulumiWorkDir()
|
|
@@ -9260,7 +9642,7 @@ async function cdnSync(options) {
|
|
|
9260
9642
|
let existingCertArn;
|
|
9261
9643
|
if (cdnConfig.cdn.customDomain) {
|
|
9262
9644
|
try {
|
|
9263
|
-
await ensurePulumiWorkDir();
|
|
9645
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
9264
9646
|
const checkStack = await pulumi6.automation.LocalWorkspace.selectStack({
|
|
9265
9647
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
9266
9648
|
workDir: getPulumiWorkDir()
|
|
@@ -9297,7 +9679,7 @@ async function cdnSync(options) {
|
|
|
9297
9679
|
};
|
|
9298
9680
|
try {
|
|
9299
9681
|
await progress.execute("Syncing CDN infrastructure", async () => {
|
|
9300
|
-
await ensurePulumiWorkDir();
|
|
9682
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
9301
9683
|
const stack = await pulumi6.automation.LocalWorkspace.createOrSelectStack(
|
|
9302
9684
|
{
|
|
9303
9685
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
@@ -9414,7 +9796,7 @@ async function cdnUpgrade(options) {
|
|
|
9414
9796
|
const cdnConfig = cdnService.config;
|
|
9415
9797
|
let stackOutputs = {};
|
|
9416
9798
|
try {
|
|
9417
|
-
await ensurePulumiWorkDir();
|
|
9799
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
9418
9800
|
const stack = await pulumi7.automation.LocalWorkspace.selectStack({
|
|
9419
9801
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
9420
9802
|
workDir: getPulumiWorkDir()
|
|
@@ -9551,7 +9933,7 @@ Ready to add custom domain: ${pc11.cyan(pendingDomain)}`);
|
|
|
9551
9933
|
await progress.execute(
|
|
9552
9934
|
"Updating CloudFront distribution (this may take 2-3 minutes)",
|
|
9553
9935
|
async () => {
|
|
9554
|
-
await ensurePulumiWorkDir();
|
|
9936
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
9555
9937
|
const stack = await pulumi7.automation.LocalWorkspace.createOrSelectStack(
|
|
9556
9938
|
{
|
|
9557
9939
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
@@ -9721,7 +10103,7 @@ async function cdnVerify(options) {
|
|
|
9721
10103
|
}
|
|
9722
10104
|
let stackOutputs = {};
|
|
9723
10105
|
try {
|
|
9724
|
-
await ensurePulumiWorkDir();
|
|
10106
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
9725
10107
|
const stack = await pulumi8.automation.LocalWorkspace.selectStack({
|
|
9726
10108
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
9727
10109
|
workDir: getPulumiWorkDir()
|
|
@@ -14026,22 +14408,13 @@ ${pc14.bold("Current Configuration:")}
|
|
|
14026
14408
|
process.exit(0);
|
|
14027
14409
|
}
|
|
14028
14410
|
}
|
|
14029
|
-
|
|
14030
|
-
if (metadata.provider === "vercel" && metadata.vercel) {
|
|
14031
|
-
vercelConfig = metadata.vercel;
|
|
14032
|
-
}
|
|
14033
|
-
const stackConfig = {
|
|
14034
|
-
provider: metadata.provider,
|
|
14035
|
-
region,
|
|
14036
|
-
vercel: vercelConfig,
|
|
14037
|
-
emailConfig: config2
|
|
14038
|
-
};
|
|
14411
|
+
const stackConfig = buildEmailStackConfig(metadata, region);
|
|
14039
14412
|
if (options.preview) {
|
|
14040
14413
|
try {
|
|
14041
14414
|
const previewResult = await progress.execute(
|
|
14042
14415
|
"Generating update preview",
|
|
14043
14416
|
async () => {
|
|
14044
|
-
await ensurePulumiWorkDir();
|
|
14417
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
14045
14418
|
const stack = await pulumi12.automation.LocalWorkspace.createOrSelectStack(
|
|
14046
14419
|
{
|
|
14047
14420
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
@@ -14105,7 +14478,7 @@ ${pc14.bold("Current Configuration:")}
|
|
|
14105
14478
|
outputs = await progress.execute(
|
|
14106
14479
|
"Updating Wraps infrastructure (this may take 2-3 minutes)",
|
|
14107
14480
|
async () => {
|
|
14108
|
-
await ensurePulumiWorkDir();
|
|
14481
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
14109
14482
|
const stack = await pulumi12.automation.LocalWorkspace.createOrSelectStack(
|
|
14110
14483
|
{
|
|
14111
14484
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
@@ -14542,7 +14915,7 @@ async function connect2(options) {
|
|
|
14542
14915
|
const previewResult = await progress.execute(
|
|
14543
14916
|
"Generating infrastructure preview",
|
|
14544
14917
|
async () => {
|
|
14545
|
-
await ensurePulumiWorkDir();
|
|
14918
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
14546
14919
|
const stack = await pulumi13.automation.LocalWorkspace.createOrSelectStack(
|
|
14547
14920
|
{
|
|
14548
14921
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
@@ -14606,7 +14979,7 @@ async function connect2(options) {
|
|
|
14606
14979
|
outputs = await progress.execute(
|
|
14607
14980
|
"Deploying Wraps infrastructure (this may take 2-3 minutes)",
|
|
14608
14981
|
async () => {
|
|
14609
|
-
await ensurePulumiWorkDir();
|
|
14982
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
14610
14983
|
const stack = await pulumi13.automation.LocalWorkspace.createOrSelectStack(
|
|
14611
14984
|
{
|
|
14612
14985
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
@@ -14902,7 +15275,7 @@ async function emailDestroy(options) {
|
|
|
14902
15275
|
const previewResult = await progress.execute(
|
|
14903
15276
|
"Generating destruction preview",
|
|
14904
15277
|
async () => {
|
|
14905
|
-
await ensurePulumiWorkDir();
|
|
15278
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
14906
15279
|
const stackName = storedStackName || `wraps-${identity.accountId}-${region}`;
|
|
14907
15280
|
let stack;
|
|
14908
15281
|
try {
|
|
@@ -14973,7 +15346,7 @@ async function emailDestroy(options) {
|
|
|
14973
15346
|
await progress.execute(
|
|
14974
15347
|
"Destroying email infrastructure (this may take 2-3 minutes)",
|
|
14975
15348
|
async () => {
|
|
14976
|
-
await ensurePulumiWorkDir();
|
|
15349
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
14977
15350
|
const stackName = storedStackName || `wraps-${identity.accountId}-${region}`;
|
|
14978
15351
|
let stack;
|
|
14979
15352
|
try {
|
|
@@ -15517,7 +15890,11 @@ var CORE_IAM_ACTIONS = [
|
|
|
15517
15890
|
"ses:CreateEmailIdentity",
|
|
15518
15891
|
"ses:DeleteEmailIdentity",
|
|
15519
15892
|
"ses:GetEmailIdentity",
|
|
15520
|
-
"ses:PutEmailIdentityDkimAttributes"
|
|
15893
|
+
"ses:PutEmailIdentityDkimAttributes",
|
|
15894
|
+
"s3:HeadBucket",
|
|
15895
|
+
"s3:CreateBucket",
|
|
15896
|
+
"s3:GetObject",
|
|
15897
|
+
"s3:PutObject"
|
|
15521
15898
|
];
|
|
15522
15899
|
var EVENT_TRACKING_ACTIONS = [
|
|
15523
15900
|
"events:CreateEventBus",
|
|
@@ -15584,8 +15961,12 @@ async function checkIAMPermissions(userArn, actions, region) {
|
|
|
15584
15961
|
const actionName = result.EvalActionName;
|
|
15585
15962
|
const decision = result.EvalDecision;
|
|
15586
15963
|
if (decision === "allowed") {
|
|
15587
|
-
if (actionName)
|
|
15588
|
-
|
|
15964
|
+
if (actionName) {
|
|
15965
|
+
allowedActions.push(actionName);
|
|
15966
|
+
}
|
|
15967
|
+
} else if (actionName) {
|
|
15968
|
+
deniedActions.push(actionName);
|
|
15969
|
+
}
|
|
15589
15970
|
}
|
|
15590
15971
|
}
|
|
15591
15972
|
return {
|
|
@@ -15615,7 +15996,9 @@ async function checkIAMPermissions(userArn, actions, region) {
|
|
|
15615
15996
|
}
|
|
15616
15997
|
}
|
|
15617
15998
|
function formatDeniedActions(actions) {
|
|
15618
|
-
if (actions.length === 0)
|
|
15999
|
+
if (actions.length === 0) {
|
|
16000
|
+
return "";
|
|
16001
|
+
}
|
|
15619
16002
|
const byService = {};
|
|
15620
16003
|
for (const action of actions) {
|
|
15621
16004
|
const [service, actionName] = action.split(":");
|
|
@@ -15772,7 +16155,7 @@ ${pc18.yellow(pc18.bold("Configuration Warnings:"))}`);
|
|
|
15772
16155
|
const previewResult = await progress.execute(
|
|
15773
16156
|
"Generating infrastructure preview",
|
|
15774
16157
|
async () => {
|
|
15775
|
-
await ensurePulumiWorkDir();
|
|
16158
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
15776
16159
|
const stack = await pulumi15.automation.LocalWorkspace.createOrSelectStack(
|
|
15777
16160
|
{
|
|
15778
16161
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
@@ -15841,7 +16224,7 @@ ${pc18.yellow(pc18.bold("Configuration Warnings:"))}`);
|
|
|
15841
16224
|
outputs = await progress.execute(
|
|
15842
16225
|
"Deploying infrastructure (this may take 2-3 minutes)",
|
|
15843
16226
|
async () => {
|
|
15844
|
-
await ensurePulumiWorkDir();
|
|
16227
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
15845
16228
|
const stack = await pulumi15.automation.LocalWorkspace.createOrSelectStack(
|
|
15846
16229
|
{
|
|
15847
16230
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
@@ -16374,7 +16757,7 @@ async function emailStatus(options) {
|
|
|
16374
16757
|
}
|
|
16375
16758
|
let stackOutputs = {};
|
|
16376
16759
|
try {
|
|
16377
|
-
await ensurePulumiWorkDir();
|
|
16760
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
16378
16761
|
const stack = await pulumi17.automation.LocalWorkspace.selectStack({
|
|
16379
16762
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
16380
16763
|
workDir: getPulumiWorkDir()
|
|
@@ -17570,30 +17953,18 @@ ${pc21.bold("Cost Impact:")}`);
|
|
|
17570
17953
|
process.exit(0);
|
|
17571
17954
|
}
|
|
17572
17955
|
}
|
|
17573
|
-
let vercelConfig;
|
|
17574
17956
|
if (metadata.provider === "vercel" && !metadata.vercel) {
|
|
17575
|
-
|
|
17576
|
-
} else if (metadata.provider === "vercel") {
|
|
17577
|
-
vercelConfig = metadata.vercel;
|
|
17957
|
+
metadata.vercel = await promptVercelConfig();
|
|
17578
17958
|
}
|
|
17579
|
-
const stackConfig = {
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
vercel: vercelConfig,
|
|
17583
|
-
emailConfig: updatedConfig,
|
|
17584
|
-
// Include webhook config if Wraps Dashboard is connected
|
|
17585
|
-
webhook: metadata.services.email?.webhookSecret ? {
|
|
17586
|
-
awsAccountNumber: metadata.accountId,
|
|
17587
|
-
// User's 12-digit AWS account ID
|
|
17588
|
-
webhookSecret: metadata.services.email.webhookSecret
|
|
17589
|
-
} : void 0
|
|
17590
|
-
};
|
|
17959
|
+
const stackConfig = buildEmailStackConfig(metadata, region, {
|
|
17960
|
+
emailConfig: updatedConfig
|
|
17961
|
+
});
|
|
17591
17962
|
if (options.preview) {
|
|
17592
17963
|
try {
|
|
17593
17964
|
const previewResult = await progress.execute(
|
|
17594
17965
|
"Generating upgrade preview",
|
|
17595
17966
|
async () => {
|
|
17596
|
-
await ensurePulumiWorkDir();
|
|
17967
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
17597
17968
|
const stack = await pulumi18.automation.LocalWorkspace.createOrSelectStack(
|
|
17598
17969
|
{
|
|
17599
17970
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
@@ -17671,7 +18042,7 @@ ${pc21.bold("Cost Impact:")}`);
|
|
|
17671
18042
|
outputs = await progress.execute(
|
|
17672
18043
|
"Updating Wraps infrastructure (this may take 2-3 minutes)",
|
|
17673
18044
|
async () => {
|
|
17674
|
-
await ensurePulumiWorkDir();
|
|
18045
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
17675
18046
|
const stack = await pulumi18.automation.LocalWorkspace.createOrSelectStack(
|
|
17676
18047
|
{
|
|
17677
18048
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
@@ -18290,6 +18661,26 @@ function getS3Statements() {
|
|
|
18290
18661
|
}
|
|
18291
18662
|
];
|
|
18292
18663
|
}
|
|
18664
|
+
function getS3StateStatements() {
|
|
18665
|
+
return [
|
|
18666
|
+
{
|
|
18667
|
+
Sid: "S3StateManagement",
|
|
18668
|
+
Effect: "Allow",
|
|
18669
|
+
Action: [
|
|
18670
|
+
"s3:CreateBucket",
|
|
18671
|
+
"s3:HeadBucket",
|
|
18672
|
+
"s3:PutBucketEncryption",
|
|
18673
|
+
"s3:PutBucketVersioning",
|
|
18674
|
+
"s3:PutPublicAccessBlock",
|
|
18675
|
+
"s3:PutBucketTagging",
|
|
18676
|
+
"s3:GetObject",
|
|
18677
|
+
"s3:PutObject",
|
|
18678
|
+
"s3:ListBucket"
|
|
18679
|
+
],
|
|
18680
|
+
Resource: ["arn:aws:s3:::wraps-state-*", "arn:aws:s3:::wraps-state-*/*"]
|
|
18681
|
+
}
|
|
18682
|
+
];
|
|
18683
|
+
}
|
|
18293
18684
|
function getCloudFrontStatements() {
|
|
18294
18685
|
return [
|
|
18295
18686
|
{
|
|
@@ -18364,7 +18755,10 @@ function getSMSStatements() {
|
|
|
18364
18755
|
];
|
|
18365
18756
|
}
|
|
18366
18757
|
function buildPolicy(service, preset) {
|
|
18367
|
-
const statements = [
|
|
18758
|
+
const statements = [
|
|
18759
|
+
...getBaseStatements(),
|
|
18760
|
+
...getS3StateStatements()
|
|
18761
|
+
];
|
|
18368
18762
|
if (!service || service === "email") {
|
|
18369
18763
|
statements.push(...getSESStatements());
|
|
18370
18764
|
if (!preset || preset === "production" || preset === "enterprise") {
|
|
@@ -18413,6 +18807,9 @@ ${pc23.dim("Service:")} ${pc23.cyan(serviceLabel)}`);
|
|
|
18413
18807
|
console.log(` ${pc23.green("+")} ${pc23.bold("IAM")} - Role management`);
|
|
18414
18808
|
console.log(` ${pc23.green("+")} ${pc23.bold("STS")} - Credential validation`);
|
|
18415
18809
|
console.log(` ${pc23.green("+")} ${pc23.bold("CloudWatch")} - Metrics access`);
|
|
18810
|
+
console.log(
|
|
18811
|
+
` ${pc23.green("+")} ${pc23.bold("S3")} - State management ${pc23.dim("(wraps-state-* buckets)")}`
|
|
18812
|
+
);
|
|
18416
18813
|
if (!service || service === "email") {
|
|
18417
18814
|
console.log(
|
|
18418
18815
|
` ${pc23.green("+")} ${pc23.bold("SES")} - Email sending & configuration`
|
|
@@ -18725,9 +19122,9 @@ Run ${pc24.cyan("wraps email init")} or ${pc24.cyan("wraps sms init")} first.
|
|
|
18725
19122
|
let webhookSecret;
|
|
18726
19123
|
let needsDeployment = false;
|
|
18727
19124
|
if (hasEmail) {
|
|
18728
|
-
const emailConfig = metadata.services.email
|
|
18729
|
-
const existingSecret = metadata.services.email
|
|
18730
|
-
if (!emailConfig
|
|
19125
|
+
const emailConfig = metadata.services.email?.config;
|
|
19126
|
+
const existingSecret = metadata.services.email?.webhookSecret;
|
|
19127
|
+
if (!emailConfig?.eventTracking?.enabled) {
|
|
18731
19128
|
progress.stop();
|
|
18732
19129
|
log21.warn(
|
|
18733
19130
|
"Event tracking must be enabled to connect to the Wraps Platform."
|
|
@@ -18756,8 +19153,8 @@ Run ${pc24.cyan("wraps email init")} or ${pc24.cyan("wraps sms init")} first.
|
|
|
18756
19153
|
"BOUNCE",
|
|
18757
19154
|
"COMPLAINT"
|
|
18758
19155
|
],
|
|
18759
|
-
dynamoDBHistory: emailConfig
|
|
18760
|
-
archiveRetention: emailConfig
|
|
19156
|
+
dynamoDBHistory: emailConfig?.eventTracking?.dynamoDBHistory ?? false,
|
|
19157
|
+
archiveRetention: emailConfig?.eventTracking?.archiveRetention ?? "90days"
|
|
18761
19158
|
}
|
|
18762
19159
|
};
|
|
18763
19160
|
needsDeployment = true;
|
|
@@ -18817,25 +19214,15 @@ Run ${pc24.cyan("wraps email init")} or ${pc24.cyan("wraps sms init")} first.
|
|
|
18817
19214
|
}
|
|
18818
19215
|
}
|
|
18819
19216
|
if (needsDeployment && hasEmail) {
|
|
18820
|
-
let vercelConfig;
|
|
18821
19217
|
if (metadata.provider === "vercel" && !metadata.vercel) {
|
|
18822
19218
|
progress.stop();
|
|
18823
|
-
|
|
18824
|
-
} else if (metadata.provider === "vercel") {
|
|
18825
|
-
vercelConfig = metadata.vercel;
|
|
19219
|
+
metadata.vercel = await promptVercelConfig();
|
|
18826
19220
|
}
|
|
18827
|
-
const stackConfig = {
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
vercel: vercelConfig,
|
|
18831
|
-
emailConfig: metadata.services.email.config,
|
|
18832
|
-
webhook: webhookSecret ? {
|
|
18833
|
-
awsAccountNumber: metadata.accountId,
|
|
18834
|
-
webhookSecret
|
|
18835
|
-
} : void 0
|
|
18836
|
-
};
|
|
19221
|
+
const stackConfig = buildEmailStackConfig(metadata, region, {
|
|
19222
|
+
webhook: webhookSecret ? { awsAccountNumber: metadata.accountId, webhookSecret } : void 0
|
|
19223
|
+
});
|
|
18837
19224
|
await progress.execute("Configuring event streaming", async () => {
|
|
18838
|
-
await ensurePulumiWorkDir();
|
|
19225
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
18839
19226
|
const stack = await pulumi19.automation.LocalWorkspace.createOrSelectStack(
|
|
18840
19227
|
{
|
|
18841
19228
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
@@ -21827,7 +22214,7 @@ async function dashboard(options) {
|
|
|
21827
22214
|
let smsStackOutputs = {};
|
|
21828
22215
|
let storageStackOutputs = {};
|
|
21829
22216
|
try {
|
|
21830
|
-
await ensurePulumiWorkDir();
|
|
22217
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
21831
22218
|
try {
|
|
21832
22219
|
const emailStack = await pulumi20.automation.LocalWorkspace.selectStack({
|
|
21833
22220
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
@@ -22037,7 +22424,7 @@ async function status(_options) {
|
|
|
22037
22424
|
progress.info(`Region: ${pc29.cyan(region)}`);
|
|
22038
22425
|
const services = [];
|
|
22039
22426
|
try {
|
|
22040
|
-
await ensurePulumiWorkDir();
|
|
22427
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
22041
22428
|
const emailStack = await pulumi21.automation.LocalWorkspace.selectStack({
|
|
22042
22429
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
22043
22430
|
workDir: getPulumiWorkDir()
|
|
@@ -23083,7 +23470,7 @@ async function smsDestroy(options) {
|
|
|
23083
23470
|
const previewResult = await progress.execute(
|
|
23084
23471
|
"Generating destruction preview",
|
|
23085
23472
|
async () => {
|
|
23086
|
-
await ensurePulumiWorkDir();
|
|
23473
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
23087
23474
|
const stackName = storedStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
23088
23475
|
let stack;
|
|
23089
23476
|
try {
|
|
@@ -23140,7 +23527,7 @@ async function smsDestroy(options) {
|
|
|
23140
23527
|
await progress.execute(
|
|
23141
23528
|
"Destroying SMS infrastructure (this may take 2-3 minutes)",
|
|
23142
23529
|
async () => {
|
|
23143
|
-
await ensurePulumiWorkDir();
|
|
23530
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
23144
23531
|
const stackName = storedStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
23145
23532
|
let stack;
|
|
23146
23533
|
try {
|
|
@@ -23847,7 +24234,7 @@ ${pc31.yellow(pc31.bold("Important Notes:"))}`);
|
|
|
23847
24234
|
outputs = await progress.execute(
|
|
23848
24235
|
"Deploying SMS infrastructure (this may take 2-3 minutes)",
|
|
23849
24236
|
async () => {
|
|
23850
|
-
await ensurePulumiWorkDir();
|
|
24237
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
23851
24238
|
const stack = await pulumi24.automation.LocalWorkspace.createOrSelectStack(
|
|
23852
24239
|
{
|
|
23853
24240
|
stackName: `wraps-sms-${identity.accountId}-${region}`,
|
|
@@ -24257,7 +24644,7 @@ Run ${pc33.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
24257
24644
|
}
|
|
24258
24645
|
let stackOutputs = {};
|
|
24259
24646
|
try {
|
|
24260
|
-
await ensurePulumiWorkDir();
|
|
24647
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
24261
24648
|
const stackName = metadata.services.sms.pulumiStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
24262
24649
|
const stack = await pulumi25.automation.LocalWorkspace.selectStack({
|
|
24263
24650
|
stackName,
|
|
@@ -24354,7 +24741,7 @@ Run ${pc34.cyan("wraps sms init")} to deploy SMS infrastructure first.
|
|
|
24354
24741
|
let outputs;
|
|
24355
24742
|
try {
|
|
24356
24743
|
outputs = await progress.execute("Syncing SMS infrastructure", async () => {
|
|
24357
|
-
await ensurePulumiWorkDir();
|
|
24744
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
24358
24745
|
const stackName = storedStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
24359
24746
|
const stack = await pulumi26.automation.LocalWorkspace.createOrSelectStack(
|
|
24360
24747
|
{
|
|
@@ -25419,7 +25806,7 @@ ${pc36.bold("Cost Impact:")}`);
|
|
|
25419
25806
|
outputs = await progress.execute(
|
|
25420
25807
|
"Updating SMS infrastructure (this may take 2-3 minutes)",
|
|
25421
25808
|
async () => {
|
|
25422
|
-
await ensurePulumiWorkDir();
|
|
25809
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
25423
25810
|
const stack = await pulumi27.automation.LocalWorkspace.createOrSelectStack(
|
|
25424
25811
|
{
|
|
25425
25812
|
stackName: metadata.services.sms?.pulumiStackName || `wraps-sms-${identity.accountId}-${region}`,
|
|
@@ -26102,7 +26489,7 @@ if (nodeMajorVersion < 20) {
|
|
|
26102
26489
|
var __filename2 = fileURLToPath4(import.meta.url);
|
|
26103
26490
|
var __dirname3 = dirname2(__filename2);
|
|
26104
26491
|
var packageJson = JSON.parse(
|
|
26105
|
-
readFileSync2(
|
|
26492
|
+
readFileSync2(join6(__dirname3, "../package.json"), "utf-8")
|
|
26106
26493
|
);
|
|
26107
26494
|
var VERSION = packageJson.version;
|
|
26108
26495
|
setupTabCompletion();
|