@wraps.dev/cli 2.18.3 → 2.18.5
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
|
@@ -1855,6 +1855,7 @@ You may need to merge your existing rules into the wraps rule set.`,
|
|
|
1855
1855
|
// src/utils/shared/aws.ts
|
|
1856
1856
|
var aws_exports = {};
|
|
1857
1857
|
__export(aws_exports, {
|
|
1858
|
+
SES_REGIONS: () => SES_REGIONS,
|
|
1858
1859
|
checkRegion: () => checkRegion,
|
|
1859
1860
|
getACMCertificateStatus: () => getACMCertificateStatus,
|
|
1860
1861
|
getAWSRegion: () => getAWSRegion,
|
|
@@ -1933,31 +1934,7 @@ async function validateAWSCredentialsWithDetails() {
|
|
|
1933
1934
|
}
|
|
1934
1935
|
}
|
|
1935
1936
|
async function checkRegion(region) {
|
|
1936
|
-
|
|
1937
|
-
"us-east-1",
|
|
1938
|
-
"us-east-2",
|
|
1939
|
-
"us-west-1",
|
|
1940
|
-
"us-west-2",
|
|
1941
|
-
"af-south-1",
|
|
1942
|
-
"ap-east-1",
|
|
1943
|
-
"ap-south-1",
|
|
1944
|
-
"ap-northeast-1",
|
|
1945
|
-
"ap-northeast-2",
|
|
1946
|
-
"ap-northeast-3",
|
|
1947
|
-
"ap-southeast-1",
|
|
1948
|
-
"ap-southeast-2",
|
|
1949
|
-
"ap-southeast-3",
|
|
1950
|
-
"ca-central-1",
|
|
1951
|
-
"eu-central-1",
|
|
1952
|
-
"eu-west-1",
|
|
1953
|
-
"eu-west-2",
|
|
1954
|
-
"eu-west-3",
|
|
1955
|
-
"eu-south-1",
|
|
1956
|
-
"eu-north-1",
|
|
1957
|
-
"me-south-1",
|
|
1958
|
-
"sa-east-1"
|
|
1959
|
-
];
|
|
1960
|
-
return validRegions.includes(region);
|
|
1937
|
+
return SES_REGIONS.includes(region);
|
|
1961
1938
|
}
|
|
1962
1939
|
async function getAWSRegion() {
|
|
1963
1940
|
if (process.env.AWS_REGION) {
|
|
@@ -2042,12 +2019,37 @@ async function getACMCertificateStatus(certificateArn) {
|
|
|
2042
2019
|
return null;
|
|
2043
2020
|
}
|
|
2044
2021
|
}
|
|
2022
|
+
var SES_REGIONS;
|
|
2045
2023
|
var init_aws = __esm({
|
|
2046
2024
|
"src/utils/shared/aws.ts"() {
|
|
2047
2025
|
"use strict";
|
|
2048
2026
|
init_esm_shims();
|
|
2049
2027
|
init_aws_detection();
|
|
2050
2028
|
init_errors();
|
|
2029
|
+
SES_REGIONS = [
|
|
2030
|
+
"us-east-1",
|
|
2031
|
+
"us-east-2",
|
|
2032
|
+
"us-west-1",
|
|
2033
|
+
"us-west-2",
|
|
2034
|
+
"af-south-1",
|
|
2035
|
+
"ap-east-1",
|
|
2036
|
+
"ap-south-1",
|
|
2037
|
+
"ap-northeast-1",
|
|
2038
|
+
"ap-northeast-2",
|
|
2039
|
+
"ap-northeast-3",
|
|
2040
|
+
"ap-southeast-1",
|
|
2041
|
+
"ap-southeast-2",
|
|
2042
|
+
"ap-southeast-3",
|
|
2043
|
+
"ca-central-1",
|
|
2044
|
+
"eu-central-1",
|
|
2045
|
+
"eu-west-1",
|
|
2046
|
+
"eu-west-2",
|
|
2047
|
+
"eu-west-3",
|
|
2048
|
+
"eu-south-1",
|
|
2049
|
+
"eu-north-1",
|
|
2050
|
+
"me-south-1",
|
|
2051
|
+
"sa-east-1"
|
|
2052
|
+
];
|
|
2051
2053
|
}
|
|
2052
2054
|
});
|
|
2053
2055
|
|
|
@@ -8123,6 +8125,9 @@ async function scanSESIdentities(region) {
|
|
|
8123
8125
|
}
|
|
8124
8126
|
return identities;
|
|
8125
8127
|
} catch (error) {
|
|
8128
|
+
if (error instanceof Error && (error.name === "AccessDeniedException" || error.name === "AccessDenied")) {
|
|
8129
|
+
throw error;
|
|
8130
|
+
}
|
|
8126
8131
|
console.error(
|
|
8127
8132
|
"Error scanning SES identities:",
|
|
8128
8133
|
error instanceof Error ? error.message : error
|
|
@@ -8303,27 +8308,37 @@ async function scanIAMRoles(region) {
|
|
|
8303
8308
|
}
|
|
8304
8309
|
async function scanAWSResources(region) {
|
|
8305
8310
|
const [
|
|
8306
|
-
|
|
8311
|
+
identityResult,
|
|
8307
8312
|
configurationSets,
|
|
8308
8313
|
snsTopics,
|
|
8309
8314
|
dynamoTables,
|
|
8310
8315
|
lambdaFunctions,
|
|
8311
8316
|
iamRoles
|
|
8312
8317
|
] = await Promise.all([
|
|
8313
|
-
scanSESIdentities(region)
|
|
8318
|
+
scanSESIdentities(region).catch(
|
|
8319
|
+
(error) => error instanceof Error ? error : new Error(String(error))
|
|
8320
|
+
),
|
|
8314
8321
|
scanSESConfigurationSets(region),
|
|
8315
8322
|
scanSNSTopics(region),
|
|
8316
8323
|
scanDynamoTables(region),
|
|
8317
8324
|
scanLambdaFunctions(region),
|
|
8318
8325
|
scanIAMRoles(region)
|
|
8319
8326
|
]);
|
|
8327
|
+
let identities = [];
|
|
8328
|
+
let scanErrors;
|
|
8329
|
+
if (identityResult instanceof Error) {
|
|
8330
|
+
scanErrors = { identities: identityResult.name };
|
|
8331
|
+
} else {
|
|
8332
|
+
identities = identityResult;
|
|
8333
|
+
}
|
|
8320
8334
|
return {
|
|
8321
8335
|
identities,
|
|
8322
8336
|
configurationSets,
|
|
8323
8337
|
snsTopics,
|
|
8324
8338
|
dynamoTables,
|
|
8325
8339
|
lambdaFunctions,
|
|
8326
|
-
iamRoles
|
|
8340
|
+
iamRoles,
|
|
8341
|
+
scanErrors
|
|
8327
8342
|
};
|
|
8328
8343
|
}
|
|
8329
8344
|
function filterWrapsResources(scan) {
|
|
@@ -8978,9 +8993,8 @@ Run ${pc27.cyan("wraps email init")} to deploy email infrastructure.
|
|
|
8978
8993
|
process.exit(1);
|
|
8979
8994
|
return;
|
|
8980
8995
|
}
|
|
8981
|
-
const
|
|
8982
|
-
|
|
8983
|
-
if (!domain) {
|
|
8996
|
+
const trackedDomains = getAllTrackedDomains(metadata);
|
|
8997
|
+
if (trackedDomains.length === 0) {
|
|
8984
8998
|
progress.stop();
|
|
8985
8999
|
clack25.log.error("No domain configured in email infrastructure");
|
|
8986
9000
|
console.log(`
|
|
@@ -8989,6 +9003,24 @@ Run ${pc27.cyan("wraps email init")} to set up a domain.
|
|
|
8989
9003
|
process.exit(1);
|
|
8990
9004
|
return;
|
|
8991
9005
|
}
|
|
9006
|
+
let domain;
|
|
9007
|
+
if (trackedDomains.length === 1) {
|
|
9008
|
+
domain = trackedDomains[0].domain;
|
|
9009
|
+
} else {
|
|
9010
|
+
const selected = await clack25.select({
|
|
9011
|
+
message: "Which domain do you want to send from?",
|
|
9012
|
+
options: trackedDomains.map((d) => ({
|
|
9013
|
+
value: d.domain,
|
|
9014
|
+
label: d.domain,
|
|
9015
|
+
hint: d.isPrimary ? "primary" : d.purpose
|
|
9016
|
+
}))
|
|
9017
|
+
});
|
|
9018
|
+
if (clack25.isCancel(selected)) {
|
|
9019
|
+
clack25.cancel("Operation cancelled.");
|
|
9020
|
+
process.exit(0);
|
|
9021
|
+
}
|
|
9022
|
+
domain = selected;
|
|
9023
|
+
}
|
|
8992
9024
|
let toEmail = options.to;
|
|
8993
9025
|
if (!toEmail) {
|
|
8994
9026
|
if (isJsonMode() && !options.scenario) {
|
|
@@ -17714,12 +17746,7 @@ async function createSMTPCredentials(config2) {
|
|
|
17714
17746
|
{
|
|
17715
17747
|
Effect: "Allow",
|
|
17716
17748
|
Action: "ses:SendRawEmail",
|
|
17717
|
-
Resource: "*"
|
|
17718
|
-
Condition: {
|
|
17719
|
-
StringEquals: {
|
|
17720
|
-
"ses:ConfigurationSetName": config2.configSetName
|
|
17721
|
-
}
|
|
17722
|
-
}
|
|
17749
|
+
Resource: "*"
|
|
17723
17750
|
}
|
|
17724
17751
|
]
|
|
17725
17752
|
})
|
|
@@ -18366,11 +18393,40 @@ async function connect2(options) {
|
|
|
18366
18393
|
progress.info(
|
|
18367
18394
|
`Found: ${scan.identities.length} identities, ${scan.configurationSets.length} config sets`
|
|
18368
18395
|
);
|
|
18396
|
+
if (scan.scanErrors?.identities) {
|
|
18397
|
+
throw errors.sesPermissionDenied("ListIdentities");
|
|
18398
|
+
}
|
|
18369
18399
|
if (scan.identities.length === 0) {
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18400
|
+
const otherRegions = SES_REGIONS.filter((r) => r !== region);
|
|
18401
|
+
const regionHits = await progress.execute(
|
|
18402
|
+
"Checking other regions for SES identities",
|
|
18403
|
+
async () => {
|
|
18404
|
+
const results = await Promise.all(
|
|
18405
|
+
otherRegions.map(async (r) => {
|
|
18406
|
+
try {
|
|
18407
|
+
const ids = await scanSESIdentities(r);
|
|
18408
|
+
return ids.length > 0 ? r : null;
|
|
18409
|
+
} catch {
|
|
18410
|
+
return null;
|
|
18411
|
+
}
|
|
18412
|
+
})
|
|
18413
|
+
);
|
|
18414
|
+
return results.filter((r) => r !== null);
|
|
18415
|
+
}
|
|
18373
18416
|
);
|
|
18417
|
+
if (regionHits.length > 0) {
|
|
18418
|
+
clack17.log.warn(
|
|
18419
|
+
`No SES identities found in ${pc19.cyan(region)}, but found identities in: ${regionHits.map((r) => pc19.cyan(r)).join(", ")}`
|
|
18420
|
+
);
|
|
18421
|
+
clack17.log.info(
|
|
18422
|
+
`Run ${pc19.cyan(`wraps email connect --region ${regionHits[0]}`)} to connect to your existing infrastructure.`
|
|
18423
|
+
);
|
|
18424
|
+
} else {
|
|
18425
|
+
clack17.log.warn("No SES identities found in any region.");
|
|
18426
|
+
clack17.log.info(
|
|
18427
|
+
`Use ${pc19.cyan("wraps email init")} to create new email infrastructure instead.`
|
|
18428
|
+
);
|
|
18429
|
+
}
|
|
18374
18430
|
process.exit(0);
|
|
18375
18431
|
}
|
|
18376
18432
|
const verifiedIdentities = scan.identities.filter((id) => id.verified);
|
|
@@ -18397,6 +18453,15 @@ async function connect2(options) {
|
|
|
18397
18453
|
clack17.log.warn("No identities selected. Nothing to connect.");
|
|
18398
18454
|
process.exit(0);
|
|
18399
18455
|
}
|
|
18456
|
+
const unverifiedSelected = selectedIdentities.filter((name) => {
|
|
18457
|
+
const identity2 = scan.identities.find((id) => id.name === name);
|
|
18458
|
+
return identity2 && !identity2.verified;
|
|
18459
|
+
});
|
|
18460
|
+
if (unverifiedSelected.length > 0) {
|
|
18461
|
+
clack17.log.warn(
|
|
18462
|
+
`${unverifiedSelected.map((id) => pc19.cyan(id)).join(", ")} not yet verified \u2014 SES will reject sends from unverified identities until verification is complete.`
|
|
18463
|
+
);
|
|
18464
|
+
}
|
|
18400
18465
|
const preset = await promptConfigPreset();
|
|
18401
18466
|
const emailConfig = preset === "custom" ? await Promise.resolve().then(() => (init_prompts(), prompts_exports)).then(
|
|
18402
18467
|
(m) => m.promptCustomConfig()
|
|
@@ -19749,18 +19814,17 @@ Run ${pc23.cyan("wraps email init")} first to deploy email infrastructure.
|
|
|
19749
19814
|
}
|
|
19750
19815
|
clack21.log.step(`Adding ${pc23.cyan(domain)}`);
|
|
19751
19816
|
const sesClient = new SESv2Client4({ region });
|
|
19817
|
+
let domainAlreadyExists = false;
|
|
19818
|
+
let dkimTokens = [];
|
|
19752
19819
|
try {
|
|
19753
|
-
await sesClient.send(
|
|
19820
|
+
const existing = await sesClient.send(
|
|
19754
19821
|
new GetEmailIdentityCommand2({ EmailIdentity: domain })
|
|
19755
19822
|
);
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
`
|
|
19760
|
-
Run ${pc23.cyan(`wraps email domains verify --domain ${domain}`)} to check verification status.
|
|
19761
|
-
`
|
|
19823
|
+
domainAlreadyExists = true;
|
|
19824
|
+
dkimTokens = existing.DkimAttributes?.Tokens || [];
|
|
19825
|
+
clack21.log.info(
|
|
19826
|
+
`Domain ${pc23.cyan(domain)} already exists in SES \u2014 adopting into Wraps`
|
|
19762
19827
|
);
|
|
19763
|
-
return;
|
|
19764
19828
|
} catch (error) {
|
|
19765
19829
|
if (!isAWSNotFoundError(error)) {
|
|
19766
19830
|
throw error;
|
|
@@ -19770,22 +19834,34 @@ Run ${pc23.cyan(`wraps email domains verify --domain ${domain}`)} to check verif
|
|
|
19770
19834
|
if (!options.yes) {
|
|
19771
19835
|
purpose = await promptDomainPurpose();
|
|
19772
19836
|
}
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
await
|
|
19776
|
-
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
|
|
19780
|
-
|
|
19781
|
-
|
|
19782
|
-
|
|
19837
|
+
if (domainAlreadyExists) {
|
|
19838
|
+
const { PutEmailIdentityConfigurationSetAttributesCommand } = await import("@aws-sdk/client-sesv2");
|
|
19839
|
+
await progress.execute("Associating tracking configuration", async () => {
|
|
19840
|
+
await sesClient.send(
|
|
19841
|
+
new PutEmailIdentityConfigurationSetAttributesCommand({
|
|
19842
|
+
EmailIdentity: domain,
|
|
19843
|
+
ConfigurationSetName: "wraps-email-tracking"
|
|
19844
|
+
})
|
|
19845
|
+
);
|
|
19846
|
+
});
|
|
19847
|
+
} else {
|
|
19848
|
+
const { CreateEmailIdentityCommand } = await import("@aws-sdk/client-sesv2");
|
|
19849
|
+
await progress.execute("Creating SES identity", async () => {
|
|
19850
|
+
await sesClient.send(
|
|
19851
|
+
new CreateEmailIdentityCommand({
|
|
19852
|
+
EmailIdentity: domain,
|
|
19853
|
+
ConfigurationSetName: "wraps-email-tracking",
|
|
19854
|
+
DkimSigningAttributes: {
|
|
19855
|
+
NextSigningKeyLength: "RSA_2048_BIT"
|
|
19856
|
+
}
|
|
19857
|
+
})
|
|
19858
|
+
);
|
|
19859
|
+
});
|
|
19860
|
+
const sesIdentity = await sesClient.send(
|
|
19861
|
+
new GetEmailIdentityCommand2({ EmailIdentity: domain })
|
|
19783
19862
|
);
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
new GetEmailIdentityCommand2({ EmailIdentity: domain })
|
|
19787
|
-
);
|
|
19788
|
-
const dkimTokens = sesIdentity.DkimAttributes?.Tokens || [];
|
|
19863
|
+
dkimTokens = sesIdentity.DkimAttributes?.Tokens || [];
|
|
19864
|
+
}
|
|
19789
19865
|
let mailFromDomain;
|
|
19790
19866
|
if (options.yes) {
|
|
19791
19867
|
mailFromDomain = `mail.${domain}`;
|
|
@@ -20477,12 +20553,30 @@ async function inboundInit(options) {
|
|
|
20477
20553
|
if (!metadata?.services?.email) {
|
|
20478
20554
|
throw errors.inboundRequiresOutbound();
|
|
20479
20555
|
}
|
|
20480
|
-
const
|
|
20481
|
-
|
|
20482
|
-
const domain = emailConfig.domain;
|
|
20483
|
-
if (!domain) {
|
|
20556
|
+
const trackedDomains = getAllTrackedDomains(metadata);
|
|
20557
|
+
if (trackedDomains.length === 0) {
|
|
20484
20558
|
throw errors.inboundRequiresOutbound();
|
|
20485
20559
|
}
|
|
20560
|
+
let domain;
|
|
20561
|
+
if (trackedDomains.length === 1) {
|
|
20562
|
+
domain = trackedDomains[0].domain;
|
|
20563
|
+
} else {
|
|
20564
|
+
const selected = await clack22.select({
|
|
20565
|
+
message: "Which domain do you want to receive email on?",
|
|
20566
|
+
options: trackedDomains.map((d) => ({
|
|
20567
|
+
value: d.domain,
|
|
20568
|
+
label: d.domain,
|
|
20569
|
+
hint: d.isPrimary ? "primary" : d.purpose
|
|
20570
|
+
}))
|
|
20571
|
+
});
|
|
20572
|
+
if (clack22.isCancel(selected)) {
|
|
20573
|
+
clack22.cancel("Operation cancelled.");
|
|
20574
|
+
process.exit(0);
|
|
20575
|
+
}
|
|
20576
|
+
domain = selected;
|
|
20577
|
+
}
|
|
20578
|
+
const emailService = metadata.services.email;
|
|
20579
|
+
const emailConfig = emailService.config;
|
|
20486
20580
|
const subdomain = options.root ? "" : options.subdomain ?? (options.yes ? "inbound" : await promptInboundSubdomain(domain));
|
|
20487
20581
|
const receivingDomain = subdomain ? `${subdomain}.${domain}` : domain;
|
|
20488
20582
|
clack22.log.info(`Receiving domain: ${pc24.cyan(receivingDomain)}`);
|
|
@@ -21658,6 +21752,21 @@ async function init2(options) {
|
|
|
21658
21752
|
if (domain) {
|
|
21659
21753
|
emailConfig.domain = domain;
|
|
21660
21754
|
}
|
|
21755
|
+
if (domain && !options.quick && preset !== "custom") {
|
|
21756
|
+
const wantsMailFrom = await clack26.confirm({
|
|
21757
|
+
message: `Configure MAIL FROM for ${pc28.cyan(domain)}? ${pc28.dim("(improves DMARC alignment)")}`,
|
|
21758
|
+
initialValue: true
|
|
21759
|
+
});
|
|
21760
|
+
if (clack26.isCancel(wantsMailFrom)) {
|
|
21761
|
+
clack26.cancel("Operation cancelled.");
|
|
21762
|
+
process.exit(0);
|
|
21763
|
+
}
|
|
21764
|
+
if (wantsMailFrom) {
|
|
21765
|
+
const mailFromFull = await promptMailFromSubdomain(domain);
|
|
21766
|
+
const suffix = `.${domain}`;
|
|
21767
|
+
emailConfig.mailFromSubdomain = mailFromFull.endsWith(suffix) ? mailFromFull.slice(0, -suffix.length) || "mail" : "mail";
|
|
21768
|
+
}
|
|
21769
|
+
}
|
|
21661
21770
|
let costSummary;
|
|
21662
21771
|
if (!options.quick) {
|
|
21663
21772
|
const estimatedVolume = await promptEstimatedVolume();
|