@wraps.dev/cli 2.14.8 → 2.15.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 +595 -69
- package/dist/cli.js.map +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/dist/lambda/inbound-processor/.bundled +1 -1
- package/dist/lambda/inbound-processor/index.js +31 -31
- package/dist/lambda/inbound-processor/index.ts +5 -0
- package/dist/lambda/sms-event-processor/.bundled +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4102,6 +4102,7 @@ var init_route53 = __esm({
|
|
|
4102
4102
|
var metadata_exports = {};
|
|
4103
4103
|
__export(metadata_exports, {
|
|
4104
4104
|
addDomainToMetadata: () => addDomainToMetadata,
|
|
4105
|
+
addInboundDomainToMetadata: () => addInboundDomainToMetadata,
|
|
4105
4106
|
addServiceToConnection: () => addServiceToConnection,
|
|
4106
4107
|
applyConfigUpdates: () => applyConfigUpdates,
|
|
4107
4108
|
buildEmailStackConfig: () => buildEmailStackConfig,
|
|
@@ -4117,7 +4118,9 @@ __export(metadata_exports, {
|
|
|
4117
4118
|
hasService: () => hasService,
|
|
4118
4119
|
listConnections: () => listConnections,
|
|
4119
4120
|
loadConnectionMetadata: () => loadConnectionMetadata,
|
|
4121
|
+
migrateInboundToMultiDomain: () => migrateInboundToMultiDomain,
|
|
4120
4122
|
removeDomainFromMetadata: () => removeDomainFromMetadata,
|
|
4123
|
+
removeInboundDomainFromMetadata: () => removeInboundDomainFromMetadata,
|
|
4121
4124
|
removeServiceFromConnection: () => removeServiceFromConnection,
|
|
4122
4125
|
saveConnectionMetadata: () => saveConnectionMetadata,
|
|
4123
4126
|
updateEmailConfig: () => updateEmailConfig,
|
|
@@ -4180,6 +4183,9 @@ async function loadConnectionMetadata(accountId, region) {
|
|
|
4180
4183
|
}
|
|
4181
4184
|
localData = data;
|
|
4182
4185
|
}
|
|
4186
|
+
if (localData && migrateInboundToMultiDomain(localData)) {
|
|
4187
|
+
await saveConnectionMetadataLocal(localData);
|
|
4188
|
+
}
|
|
4183
4189
|
} catch (error) {
|
|
4184
4190
|
console.error(
|
|
4185
4191
|
"Error loading connection metadata:",
|
|
@@ -4599,6 +4605,61 @@ function getAllTrackedDomains(metadata) {
|
|
|
4599
4605
|
}
|
|
4600
4606
|
return result;
|
|
4601
4607
|
}
|
|
4608
|
+
function migrateInboundToMultiDomain(metadata) {
|
|
4609
|
+
const emailConfig = metadata.services.email?.config;
|
|
4610
|
+
if (!emailConfig?.inbound?.enabled) {
|
|
4611
|
+
return false;
|
|
4612
|
+
}
|
|
4613
|
+
if (emailConfig.inboundDomains && emailConfig.inboundDomains.length > 0) {
|
|
4614
|
+
return false;
|
|
4615
|
+
}
|
|
4616
|
+
const inbound = emailConfig.inbound;
|
|
4617
|
+
const receivingDomain = inbound.receivingDomain || (inbound.subdomain && emailConfig.domain ? `${inbound.subdomain}.${emailConfig.domain}` : null);
|
|
4618
|
+
if (!receivingDomain) {
|
|
4619
|
+
return false;
|
|
4620
|
+
}
|
|
4621
|
+
const parentDomain = emailConfig.domain || "";
|
|
4622
|
+
const subdomain = inbound.subdomain || "inbound";
|
|
4623
|
+
emailConfig.inboundDomains = [
|
|
4624
|
+
{
|
|
4625
|
+
subdomain,
|
|
4626
|
+
receivingDomain,
|
|
4627
|
+
parentDomain,
|
|
4628
|
+
addedAt: metadata.services.email?.deployedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
4629
|
+
}
|
|
4630
|
+
];
|
|
4631
|
+
return true;
|
|
4632
|
+
}
|
|
4633
|
+
function addInboundDomainToMetadata(metadata, entry) {
|
|
4634
|
+
if (!metadata.services.email) {
|
|
4635
|
+
throw new Error("Email service not configured in metadata");
|
|
4636
|
+
}
|
|
4637
|
+
const config2 = metadata.services.email.config;
|
|
4638
|
+
const existing = config2.inboundDomains ?? [];
|
|
4639
|
+
const idx = existing.findIndex(
|
|
4640
|
+
(d) => d.receivingDomain === entry.receivingDomain
|
|
4641
|
+
);
|
|
4642
|
+
if (idx >= 0) {
|
|
4643
|
+
existing[idx] = entry;
|
|
4644
|
+
} else {
|
|
4645
|
+
existing.push(entry);
|
|
4646
|
+
}
|
|
4647
|
+
config2.inboundDomains = existing;
|
|
4648
|
+
metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4649
|
+
}
|
|
4650
|
+
function removeInboundDomainFromMetadata(metadata, receivingDomain) {
|
|
4651
|
+
if (!metadata.services.email) {
|
|
4652
|
+
return;
|
|
4653
|
+
}
|
|
4654
|
+
const config2 = metadata.services.email.config;
|
|
4655
|
+
if (!config2.inboundDomains) {
|
|
4656
|
+
return;
|
|
4657
|
+
}
|
|
4658
|
+
config2.inboundDomains = config2.inboundDomains.filter(
|
|
4659
|
+
(d) => d.receivingDomain !== receivingDomain
|
|
4660
|
+
);
|
|
4661
|
+
metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4662
|
+
}
|
|
4602
4663
|
var init_metadata = __esm({
|
|
4603
4664
|
"src/utils/shared/metadata.ts"() {
|
|
4604
4665
|
"use strict";
|
|
@@ -5781,14 +5842,18 @@ var init_lambda = __esm({
|
|
|
5781
5842
|
var acm_exports = {};
|
|
5782
5843
|
__export(acm_exports, {
|
|
5783
5844
|
checkCertificateValidation: () => checkCertificateValidation,
|
|
5784
|
-
createACMCertificate: () => createACMCertificate
|
|
5845
|
+
createACMCertificate: () => createACMCertificate,
|
|
5846
|
+
getCertificateValidationRecords: () => getCertificateValidationRecords
|
|
5785
5847
|
});
|
|
5786
|
-
import {
|
|
5848
|
+
import {
|
|
5849
|
+
ACMClient as ACMClient2,
|
|
5850
|
+
DescribeCertificateCommand as DescribeCertificateCommand2,
|
|
5851
|
+
ListCertificatesCommand
|
|
5852
|
+
} from "@aws-sdk/client-acm";
|
|
5787
5853
|
import * as aws12 from "@pulumi/aws";
|
|
5788
5854
|
async function checkCertificateValidation(domain) {
|
|
5789
5855
|
try {
|
|
5790
5856
|
const acm3 = new ACMClient2({ region: "us-east-1" });
|
|
5791
|
-
const { ListCertificatesCommand } = await import("@aws-sdk/client-acm");
|
|
5792
5857
|
const listResponse = await acm3.send(
|
|
5793
5858
|
new ListCertificatesCommand({
|
|
5794
5859
|
CertificateStatuses: ["ISSUED"]
|
|
@@ -5864,6 +5929,31 @@ async function createACMCertificate(config2) {
|
|
|
5864
5929
|
validationRecords
|
|
5865
5930
|
};
|
|
5866
5931
|
}
|
|
5932
|
+
async function getCertificateValidationRecords(domain) {
|
|
5933
|
+
const acm3 = new ACMClient2({ region: "us-east-1" });
|
|
5934
|
+
const listResponse = await acm3.send(
|
|
5935
|
+
new ListCertificatesCommand({
|
|
5936
|
+
CertificateStatuses: ["PENDING_VALIDATION", "ISSUED"]
|
|
5937
|
+
})
|
|
5938
|
+
);
|
|
5939
|
+
const cert = listResponse.CertificateSummaryList?.find(
|
|
5940
|
+
(c) => c.DomainName === domain
|
|
5941
|
+
);
|
|
5942
|
+
if (!cert?.CertificateArn) {
|
|
5943
|
+
return [];
|
|
5944
|
+
}
|
|
5945
|
+
const describeResponse = await acm3.send(
|
|
5946
|
+
new DescribeCertificateCommand2({
|
|
5947
|
+
CertificateArn: cert.CertificateArn
|
|
5948
|
+
})
|
|
5949
|
+
);
|
|
5950
|
+
const options = describeResponse.Certificate?.DomainValidationOptions ?? [];
|
|
5951
|
+
return options.filter((opt) => opt.ResourceRecord?.Name && opt.ResourceRecord?.Value).map((opt) => ({
|
|
5952
|
+
name: opt.ResourceRecord.Name,
|
|
5953
|
+
type: opt.ResourceRecord.Type ?? "CNAME",
|
|
5954
|
+
value: opt.ResourceRecord.Value
|
|
5955
|
+
}));
|
|
5956
|
+
}
|
|
5867
5957
|
var init_acm = __esm({
|
|
5868
5958
|
"src/infrastructure/resources/acm.ts"() {
|
|
5869
5959
|
"use strict";
|
|
@@ -19098,7 +19188,8 @@ import {
|
|
|
19098
19188
|
DescribeActiveReceiptRuleSetCommand,
|
|
19099
19189
|
DescribeReceiptRuleCommand,
|
|
19100
19190
|
SESClient as SESClient3,
|
|
19101
|
-
SetActiveReceiptRuleSetCommand
|
|
19191
|
+
SetActiveReceiptRuleSetCommand,
|
|
19192
|
+
UpdateReceiptRuleCommand
|
|
19102
19193
|
} from "@aws-sdk/client-ses";
|
|
19103
19194
|
var RULE_SET_NAME = "wraps-inbound-rules";
|
|
19104
19195
|
var RULE_NAME = "wraps-inbound-catch-all";
|
|
@@ -19221,6 +19312,77 @@ async function deleteReceiptRuleSet(region) {
|
|
|
19221
19312
|
throw error;
|
|
19222
19313
|
}
|
|
19223
19314
|
}
|
|
19315
|
+
async function addDomainToReceiptRule(region, domain, s3BucketName) {
|
|
19316
|
+
const ses = createSESClient(region);
|
|
19317
|
+
try {
|
|
19318
|
+
const response = await ses.send(
|
|
19319
|
+
new DescribeReceiptRuleCommand({
|
|
19320
|
+
RuleSetName: RULE_SET_NAME,
|
|
19321
|
+
RuleName: RULE_NAME
|
|
19322
|
+
})
|
|
19323
|
+
);
|
|
19324
|
+
const existingRecipients = response.Rule?.Recipients ?? [];
|
|
19325
|
+
if (existingRecipients.includes(domain)) {
|
|
19326
|
+
return;
|
|
19327
|
+
}
|
|
19328
|
+
await ses.send(
|
|
19329
|
+
new UpdateReceiptRuleCommand({
|
|
19330
|
+
RuleSetName: RULE_SET_NAME,
|
|
19331
|
+
Rule: {
|
|
19332
|
+
Name: RULE_NAME,
|
|
19333
|
+
Enabled: response.Rule?.Enabled,
|
|
19334
|
+
ScanEnabled: response.Rule?.ScanEnabled,
|
|
19335
|
+
TlsPolicy: response.Rule?.TlsPolicy,
|
|
19336
|
+
Actions: response.Rule?.Actions,
|
|
19337
|
+
Recipients: [...existingRecipients, domain]
|
|
19338
|
+
}
|
|
19339
|
+
})
|
|
19340
|
+
);
|
|
19341
|
+
} catch (error) {
|
|
19342
|
+
if (error instanceof Error && (error.name === "RuleDoesNotExistException" || error.name === "RuleSetDoesNotExistException")) {
|
|
19343
|
+
await createReceiptRuleSet(region);
|
|
19344
|
+
await createReceiptRule(region, domain, s3BucketName);
|
|
19345
|
+
await setActiveReceiptRuleSet(region, RULE_SET_NAME);
|
|
19346
|
+
return;
|
|
19347
|
+
}
|
|
19348
|
+
throw error;
|
|
19349
|
+
}
|
|
19350
|
+
}
|
|
19351
|
+
async function removeDomainFromReceiptRule(region, domain) {
|
|
19352
|
+
const ses = createSESClient(region);
|
|
19353
|
+
try {
|
|
19354
|
+
const response = await ses.send(
|
|
19355
|
+
new DescribeReceiptRuleCommand({
|
|
19356
|
+
RuleSetName: RULE_SET_NAME,
|
|
19357
|
+
RuleName: RULE_NAME
|
|
19358
|
+
})
|
|
19359
|
+
);
|
|
19360
|
+
const existingRecipients = response.Rule?.Recipients ?? [];
|
|
19361
|
+
const updated = existingRecipients.filter((r) => r !== domain);
|
|
19362
|
+
if (updated.length === 0) {
|
|
19363
|
+
await deleteReceiptRule(region);
|
|
19364
|
+
return;
|
|
19365
|
+
}
|
|
19366
|
+
await ses.send(
|
|
19367
|
+
new UpdateReceiptRuleCommand({
|
|
19368
|
+
RuleSetName: RULE_SET_NAME,
|
|
19369
|
+
Rule: {
|
|
19370
|
+
Name: RULE_NAME,
|
|
19371
|
+
Enabled: response.Rule?.Enabled,
|
|
19372
|
+
ScanEnabled: response.Rule?.ScanEnabled,
|
|
19373
|
+
TlsPolicy: response.Rule?.TlsPolicy,
|
|
19374
|
+
Actions: response.Rule?.Actions,
|
|
19375
|
+
Recipients: updated
|
|
19376
|
+
}
|
|
19377
|
+
})
|
|
19378
|
+
);
|
|
19379
|
+
} catch (error) {
|
|
19380
|
+
if (error instanceof Error && (error.name === "RuleDoesNotExistException" || error.name === "RuleSetDoesNotExistException")) {
|
|
19381
|
+
return;
|
|
19382
|
+
}
|
|
19383
|
+
throw error;
|
|
19384
|
+
}
|
|
19385
|
+
}
|
|
19224
19386
|
|
|
19225
19387
|
// src/commands/email/inbound.ts
|
|
19226
19388
|
init_aws();
|
|
@@ -19299,7 +19461,15 @@ async function inboundInit(options) {
|
|
|
19299
19461
|
bucketName: `wraps-inbound-${identity.accountId}-${region}`,
|
|
19300
19462
|
webhookUrl,
|
|
19301
19463
|
webhookSecret
|
|
19302
|
-
}
|
|
19464
|
+
},
|
|
19465
|
+
inboundDomains: [
|
|
19466
|
+
{
|
|
19467
|
+
subdomain,
|
|
19468
|
+
receivingDomain,
|
|
19469
|
+
parentDomain: domain,
|
|
19470
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19471
|
+
}
|
|
19472
|
+
]
|
|
19303
19473
|
};
|
|
19304
19474
|
const stackConfig = buildEmailStackConfig(metadata, region, {
|
|
19305
19475
|
emailConfig: updatedEmailConfig
|
|
@@ -19540,7 +19710,8 @@ Deploy first: ${pc21.cyan("wraps email inbound init")}
|
|
|
19540
19710
|
const stackName = emailService.pulumiStackName || `wraps-${identity.accountId}-${region}`;
|
|
19541
19711
|
const updatedEmailConfig = {
|
|
19542
19712
|
...emailService.config,
|
|
19543
|
-
inbound: void 0
|
|
19713
|
+
inbound: void 0,
|
|
19714
|
+
inboundDomains: void 0
|
|
19544
19715
|
};
|
|
19545
19716
|
const stackConfig = buildEmailStackConfig(metadata, region, {
|
|
19546
19717
|
emailConfig: updatedEmailConfig
|
|
@@ -19609,13 +19780,18 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
|
|
|
19609
19780
|
`);
|
|
19610
19781
|
return;
|
|
19611
19782
|
}
|
|
19612
|
-
const
|
|
19783
|
+
const emailConfig = metadata.services.email.config;
|
|
19784
|
+
const inbound = emailConfig.inbound;
|
|
19785
|
+
const inboundDomains = emailConfig.inboundDomains ?? [];
|
|
19613
19786
|
const activeRuleSet = await getActiveReceiptRuleSet(region);
|
|
19614
|
-
const
|
|
19787
|
+
const domainList = inboundDomains.length > 0 ? inboundDomains.map((d) => d.receivingDomain) : [
|
|
19788
|
+
inbound.receivingDomain || `${inbound.subdomain}.${emailConfig.domain}`
|
|
19789
|
+
];
|
|
19615
19790
|
if (isJsonMode()) {
|
|
19616
19791
|
jsonSuccess("email.inbound.status", {
|
|
19617
19792
|
enabled: true,
|
|
19618
|
-
|
|
19793
|
+
receivingDomains: domainList,
|
|
19794
|
+
receivingDomain: domainList[0],
|
|
19619
19795
|
bucketName: inbound.bucketName || "",
|
|
19620
19796
|
region,
|
|
19621
19797
|
webhookUrl: inbound.webhookUrl || null,
|
|
@@ -19627,7 +19803,16 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
|
|
|
19627
19803
|
console.log();
|
|
19628
19804
|
console.log(pc21.bold(" Inbound Email Configuration"));
|
|
19629
19805
|
console.log();
|
|
19630
|
-
|
|
19806
|
+
if (domainList.length === 1) {
|
|
19807
|
+
console.log(
|
|
19808
|
+
` ${pc21.dim("Receiving domain:")} ${pc21.cyan(domainList[0])}`
|
|
19809
|
+
);
|
|
19810
|
+
} else {
|
|
19811
|
+
console.log(` ${pc21.dim("Receiving domains:")}`);
|
|
19812
|
+
for (const d of domainList) {
|
|
19813
|
+
console.log(` ${pc21.cyan(d)}`);
|
|
19814
|
+
}
|
|
19815
|
+
}
|
|
19631
19816
|
console.log(
|
|
19632
19817
|
` ${pc21.dim("S3 bucket:")} ${pc21.cyan(inbound.bucketName || "")}`
|
|
19633
19818
|
);
|
|
@@ -19661,61 +19846,77 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
|
|
|
19661
19846
|
`);
|
|
19662
19847
|
process.exit(1);
|
|
19663
19848
|
}
|
|
19664
|
-
const
|
|
19665
|
-
const
|
|
19849
|
+
const emailConfig = metadata.services.email.config;
|
|
19850
|
+
const inbound = emailConfig.inbound;
|
|
19851
|
+
const inboundDomains = emailConfig.inboundDomains ?? [];
|
|
19852
|
+
const domainList = inboundDomains.length > 0 ? inboundDomains.map((d) => d.receivingDomain) : [
|
|
19853
|
+
inbound.receivingDomain || `${inbound.subdomain}.${emailConfig.domain}`
|
|
19854
|
+
];
|
|
19666
19855
|
let allPassed = true;
|
|
19856
|
+
const domainChecks = {};
|
|
19667
19857
|
console.log();
|
|
19668
|
-
const
|
|
19669
|
-
|
|
19670
|
-
|
|
19671
|
-
try {
|
|
19672
|
-
const records = await dns2.resolveMx(receivingDomain);
|
|
19673
|
-
const hasSES = records.some((r) => r.exchange.includes("inbound-smtp"));
|
|
19674
|
-
return { found: true, hasSES, records };
|
|
19675
|
-
} catch {
|
|
19676
|
-
return { found: false, hasSES: false, records: [] };
|
|
19677
|
-
}
|
|
19858
|
+
for (const receivingDomain of domainList) {
|
|
19859
|
+
if (domainList.length > 1) {
|
|
19860
|
+
clack20.log.info(pc21.bold(`Checking ${pc21.cyan(receivingDomain)}`));
|
|
19678
19861
|
}
|
|
19679
|
-
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
|
|
19684
|
-
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19688
|
-
|
|
19689
|
-
|
|
19690
|
-
|
|
19691
|
-
`MX record: ${pc21.red("not found")}. Add: ${pc21.cyan(`${receivingDomain} MX 10 inbound-smtp.${region}.amazonaws.com`)}`
|
|
19692
|
-
);
|
|
19693
|
-
allPassed = false;
|
|
19694
|
-
}
|
|
19695
|
-
const spfResult = await progress.execute(
|
|
19696
|
-
`Checking SPF record for ${receivingDomain}`,
|
|
19697
|
-
async () => {
|
|
19698
|
-
try {
|
|
19699
|
-
const records = await dns2.resolveTxt(receivingDomain);
|
|
19700
|
-
const flat = records.map((r) => r.join(""));
|
|
19701
|
-
const spf = flat.find((r) => r.startsWith("v=spf1"));
|
|
19702
|
-
const hasSES = spf?.includes("amazonses.com") ?? false;
|
|
19703
|
-
return { found: !!spf, hasSES, value: spf };
|
|
19704
|
-
} catch {
|
|
19705
|
-
return { found: false, hasSES: false, value: null };
|
|
19862
|
+
const mxResult = await progress.execute(
|
|
19863
|
+
`Checking MX record for ${receivingDomain}`,
|
|
19864
|
+
async () => {
|
|
19865
|
+
try {
|
|
19866
|
+
const records = await dns2.resolveMx(receivingDomain);
|
|
19867
|
+
const hasSES = records.some(
|
|
19868
|
+
(r) => r.exchange.includes("inbound-smtp")
|
|
19869
|
+
);
|
|
19870
|
+
return { found: true, hasSES, records };
|
|
19871
|
+
} catch {
|
|
19872
|
+
return { found: false, hasSES: false, records: [] };
|
|
19873
|
+
}
|
|
19706
19874
|
}
|
|
19875
|
+
);
|
|
19876
|
+
if (mxResult.hasSES) {
|
|
19877
|
+
clack20.log.success(
|
|
19878
|
+
`MX record: ${pc21.green("verified")} \u2192 inbound-smtp.${region}.amazonaws.com`
|
|
19879
|
+
);
|
|
19880
|
+
} else if (mxResult.found) {
|
|
19881
|
+
clack20.log.warn(
|
|
19882
|
+
`MX record found but not pointing to SES. Expected: ${pc21.cyan(`10 inbound-smtp.${region}.amazonaws.com`)}`
|
|
19883
|
+
);
|
|
19884
|
+
allPassed = false;
|
|
19885
|
+
} else {
|
|
19886
|
+
clack20.log.error(
|
|
19887
|
+
`MX record: ${pc21.red("not found")}. Add: ${pc21.cyan(`${receivingDomain} MX 10 inbound-smtp.${region}.amazonaws.com`)}`
|
|
19888
|
+
);
|
|
19889
|
+
allPassed = false;
|
|
19707
19890
|
}
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
|
|
19716
|
-
|
|
19891
|
+
const spfResult = await progress.execute(
|
|
19892
|
+
`Checking SPF record for ${receivingDomain}`,
|
|
19893
|
+
async () => {
|
|
19894
|
+
try {
|
|
19895
|
+
const records = await dns2.resolveTxt(receivingDomain);
|
|
19896
|
+
const flat = records.map((r) => r.join(""));
|
|
19897
|
+
const spf = flat.find((r) => r.startsWith("v=spf1"));
|
|
19898
|
+
const hasSES = spf?.includes("amazonses.com") ?? false;
|
|
19899
|
+
return { found: !!spf, hasSES, value: spf };
|
|
19900
|
+
} catch {
|
|
19901
|
+
return { found: false, hasSES: false, value: null };
|
|
19902
|
+
}
|
|
19903
|
+
}
|
|
19717
19904
|
);
|
|
19718
|
-
|
|
19905
|
+
if (spfResult.hasSES) {
|
|
19906
|
+
clack20.log.success(`SPF record: ${pc21.green("verified")}`);
|
|
19907
|
+
} else if (spfResult.found) {
|
|
19908
|
+
clack20.log.warn("SPF record exists but missing amazonses.com include");
|
|
19909
|
+
allPassed = false;
|
|
19910
|
+
} else {
|
|
19911
|
+
clack20.log.error(
|
|
19912
|
+
`SPF record: ${pc21.red("not found")}. Add TXT: ${pc21.cyan("v=spf1 include:amazonses.com ~all")}`
|
|
19913
|
+
);
|
|
19914
|
+
allPassed = false;
|
|
19915
|
+
}
|
|
19916
|
+
domainChecks[receivingDomain] = {
|
|
19917
|
+
mx: { found: mxResult.found, verified: mxResult.hasSES },
|
|
19918
|
+
spf: { found: spfResult.found, verified: spfResult.hasSES }
|
|
19919
|
+
};
|
|
19719
19920
|
}
|
|
19720
19921
|
const activeRuleSet = await getActiveReceiptRuleSet(region);
|
|
19721
19922
|
if (activeRuleSet === RULE_SET_NAME) {
|
|
@@ -19728,13 +19929,11 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
|
|
|
19728
19929
|
}
|
|
19729
19930
|
if (isJsonMode()) {
|
|
19730
19931
|
jsonSuccess("email.inbound.verify", {
|
|
19731
|
-
|
|
19932
|
+
receivingDomains: domainList,
|
|
19933
|
+
receivingDomain: domainList[0],
|
|
19732
19934
|
allPassed,
|
|
19733
|
-
|
|
19734
|
-
|
|
19735
|
-
spf: { found: spfResult.found, verified: spfResult.hasSES },
|
|
19736
|
-
receiptRuleSet: { active: activeRuleSet === RULE_SET_NAME }
|
|
19737
|
-
}
|
|
19935
|
+
domainChecks,
|
|
19936
|
+
receiptRuleSet: { active: activeRuleSet === RULE_SET_NAME }
|
|
19738
19937
|
});
|
|
19739
19938
|
return;
|
|
19740
19939
|
}
|
|
@@ -19891,6 +20090,304 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
|
|
|
19891
20090
|
clack20.log.success(pc21.bold("Inbound email is working correctly!"));
|
|
19892
20091
|
console.log();
|
|
19893
20092
|
}
|
|
20093
|
+
async function inboundAdd(options) {
|
|
20094
|
+
if (!isJsonMode()) {
|
|
20095
|
+
clack20.intro(pc21.bold("Add Inbound Receiving Domain"));
|
|
20096
|
+
}
|
|
20097
|
+
const progress = new DeploymentProgress();
|
|
20098
|
+
const identity = await progress.execute(
|
|
20099
|
+
"Validating AWS credentials",
|
|
20100
|
+
async () => validateAWSCredentials()
|
|
20101
|
+
);
|
|
20102
|
+
const region = options.region || await getAWSRegion();
|
|
20103
|
+
if (!SES_RECEIVING_REGIONS.includes(
|
|
20104
|
+
region
|
|
20105
|
+
)) {
|
|
20106
|
+
throw errors.inboundRegionNotSupported(region);
|
|
20107
|
+
}
|
|
20108
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
20109
|
+
if (!metadata?.services?.email?.config?.inbound?.enabled) {
|
|
20110
|
+
clack20.log.error("Inbound email infrastructure is not deployed.");
|
|
20111
|
+
console.log(
|
|
20112
|
+
`
|
|
20113
|
+
Deploy first: ${pc21.cyan("wraps email inbound init")}
|
|
20114
|
+
`
|
|
20115
|
+
);
|
|
20116
|
+
process.exit(1);
|
|
20117
|
+
}
|
|
20118
|
+
const emailConfig = metadata.services.email.config;
|
|
20119
|
+
const primaryDomain = emailConfig.domain || "";
|
|
20120
|
+
const allDomains = [primaryDomain];
|
|
20121
|
+
for (const d of emailConfig.additionalDomains ?? []) {
|
|
20122
|
+
if (!allDomains.includes(d.domain)) {
|
|
20123
|
+
allDomains.push(d.domain);
|
|
20124
|
+
}
|
|
20125
|
+
}
|
|
20126
|
+
let parentDomain = options.domain;
|
|
20127
|
+
if (!parentDomain) {
|
|
20128
|
+
if (options.yes) {
|
|
20129
|
+
parentDomain = primaryDomain;
|
|
20130
|
+
} else if (allDomains.length === 1) {
|
|
20131
|
+
parentDomain = allDomains[0];
|
|
20132
|
+
} else {
|
|
20133
|
+
const selected = await clack20.select({
|
|
20134
|
+
message: "Which domain should the inbound subdomain be under?",
|
|
20135
|
+
options: allDomains.map((d) => ({
|
|
20136
|
+
value: d,
|
|
20137
|
+
label: d,
|
|
20138
|
+
hint: d === primaryDomain ? "primary" : void 0
|
|
20139
|
+
}))
|
|
20140
|
+
});
|
|
20141
|
+
if (clack20.isCancel(selected)) {
|
|
20142
|
+
clack20.cancel("Operation cancelled.");
|
|
20143
|
+
process.exit(0);
|
|
20144
|
+
}
|
|
20145
|
+
parentDomain = selected;
|
|
20146
|
+
}
|
|
20147
|
+
}
|
|
20148
|
+
const subdomain = options.subdomain || (options.yes ? "inbound" : await promptInboundSubdomain(parentDomain));
|
|
20149
|
+
const receivingDomain = `${subdomain}.${parentDomain}`;
|
|
20150
|
+
const existingDomains = emailConfig.inboundDomains ?? [];
|
|
20151
|
+
if (existingDomains.some((d) => d.receivingDomain === receivingDomain)) {
|
|
20152
|
+
clack20.log.warn(
|
|
20153
|
+
`${pc21.cyan(receivingDomain)} is already configured as an inbound domain.`
|
|
20154
|
+
);
|
|
20155
|
+
return;
|
|
20156
|
+
}
|
|
20157
|
+
clack20.log.info(`Adding receiving domain: ${pc21.cyan(receivingDomain)}`);
|
|
20158
|
+
const bucketName = emailConfig.inbound?.bucketName || `wraps-inbound-${identity.accountId}-${region}`;
|
|
20159
|
+
await progress.execute("Updating SES receipt rule", async () => {
|
|
20160
|
+
await addDomainToReceiptRule(region, receivingDomain, bucketName);
|
|
20161
|
+
});
|
|
20162
|
+
let dnsAutoCreated = false;
|
|
20163
|
+
const {
|
|
20164
|
+
detectAvailableDNSProviders: detectAvailableDNSProviders2,
|
|
20165
|
+
getDNSCredentials: getDNSCredentials2,
|
|
20166
|
+
createInboundDNSRecordsForProvider: createInboundDNSRecordsForProvider2,
|
|
20167
|
+
getDNSProviderDisplayName: getDNSProviderDisplayName2,
|
|
20168
|
+
getDNSProviderTokenUrl: getDNSProviderTokenUrl2,
|
|
20169
|
+
buildInboundDNSRecords: buildRecords,
|
|
20170
|
+
formatDNSRecordsForDisplay: formatRecords
|
|
20171
|
+
} = await Promise.resolve().then(() => (init_dns(), dns_exports));
|
|
20172
|
+
const { promptDNSProvider: promptDNSProvider2, promptContinueManualDNS: promptContinueManualDNS2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
|
|
20173
|
+
const existingDnsProvider = metadata.services.email.dnsProvider;
|
|
20174
|
+
let dnsProvider = existingDnsProvider;
|
|
20175
|
+
if (!dnsProvider || dnsProvider === "manual") {
|
|
20176
|
+
progress.start("Detecting DNS providers");
|
|
20177
|
+
const availableProviders = await detectAvailableDNSProviders2(
|
|
20178
|
+
parentDomain,
|
|
20179
|
+
region
|
|
20180
|
+
);
|
|
20181
|
+
progress.stop();
|
|
20182
|
+
dnsProvider = options.yes ? "manual" : await promptDNSProvider2(parentDomain, availableProviders);
|
|
20183
|
+
}
|
|
20184
|
+
if (dnsProvider !== "manual") {
|
|
20185
|
+
progress.start(
|
|
20186
|
+
`Validating ${getDNSProviderDisplayName2(dnsProvider)} credentials`
|
|
20187
|
+
);
|
|
20188
|
+
const credentialResult = await getDNSCredentials2(
|
|
20189
|
+
dnsProvider,
|
|
20190
|
+
parentDomain,
|
|
20191
|
+
region
|
|
20192
|
+
);
|
|
20193
|
+
progress.stop();
|
|
20194
|
+
if (credentialResult.valid && credentialResult.credentials) {
|
|
20195
|
+
const records = buildRecords(receivingDomain, region);
|
|
20196
|
+
clack20.log.info(pc21.bold("DNS records to create:"));
|
|
20197
|
+
for (const record of records) {
|
|
20198
|
+
const value = record.priority ? `${record.priority} ${record.value}` : record.value;
|
|
20199
|
+
clack20.log.info(pc21.dim(` ${record.type} ${record.name} \u2192 ${value}`));
|
|
20200
|
+
}
|
|
20201
|
+
progress.start(
|
|
20202
|
+
`Creating DNS records in ${getDNSProviderDisplayName2(dnsProvider)}`
|
|
20203
|
+
);
|
|
20204
|
+
const result = await createInboundDNSRecordsForProvider2(
|
|
20205
|
+
credentialResult.credentials,
|
|
20206
|
+
receivingDomain,
|
|
20207
|
+
region,
|
|
20208
|
+
parentDomain
|
|
20209
|
+
);
|
|
20210
|
+
if (result.success && result.recordsCreated > 0) {
|
|
20211
|
+
progress.succeed(
|
|
20212
|
+
`Created ${result.recordsCreated} DNS records in ${getDNSProviderDisplayName2(dnsProvider)}`
|
|
20213
|
+
);
|
|
20214
|
+
dnsAutoCreated = true;
|
|
20215
|
+
} else {
|
|
20216
|
+
progress.fail("Failed to create some DNS records");
|
|
20217
|
+
if (result.errors) {
|
|
20218
|
+
for (const err of result.errors) {
|
|
20219
|
+
clack20.log.warn(err);
|
|
20220
|
+
}
|
|
20221
|
+
}
|
|
20222
|
+
}
|
|
20223
|
+
} else {
|
|
20224
|
+
clack20.log.warn(
|
|
20225
|
+
credentialResult.error || "Could not validate credentials"
|
|
20226
|
+
);
|
|
20227
|
+
if (dnsProvider === "vercel" || dnsProvider === "cloudflare") {
|
|
20228
|
+
clack20.log.info(
|
|
20229
|
+
`Set the ${dnsProvider === "vercel" ? "VERCEL_TOKEN" : "CLOUDFLARE_API_TOKEN"} environment variable to enable automatic DNS management.`
|
|
20230
|
+
);
|
|
20231
|
+
clack20.log.info(
|
|
20232
|
+
`You can create a token at: ${pc21.cyan(getDNSProviderTokenUrl2(dnsProvider))}`
|
|
20233
|
+
);
|
|
20234
|
+
}
|
|
20235
|
+
if (!options.yes) {
|
|
20236
|
+
const continueManual = await promptContinueManualDNS2();
|
|
20237
|
+
if (continueManual) {
|
|
20238
|
+
dnsProvider = "manual";
|
|
20239
|
+
}
|
|
20240
|
+
}
|
|
20241
|
+
}
|
|
20242
|
+
}
|
|
20243
|
+
if (!dnsAutoCreated) {
|
|
20244
|
+
const dnsRecords = buildRecords(receivingDomain, region);
|
|
20245
|
+
const formattedRecords = formatRecords(dnsRecords);
|
|
20246
|
+
console.log();
|
|
20247
|
+
clack20.log.info(pc21.bold("DNS Records Required"));
|
|
20248
|
+
console.log(
|
|
20249
|
+
` Add these records to your DNS provider for ${pc21.cyan(receivingDomain)}:
|
|
20250
|
+
`
|
|
20251
|
+
);
|
|
20252
|
+
for (const record of formattedRecords) {
|
|
20253
|
+
console.log(` ${pc21.dim(record.type.padEnd(6))} ${record.name}`);
|
|
20254
|
+
console.log(` ${pc21.dim("\u2192")} ${pc21.green(record.value)}
|
|
20255
|
+
`);
|
|
20256
|
+
}
|
|
20257
|
+
}
|
|
20258
|
+
await progress.execute("Saving configuration", async () => {
|
|
20259
|
+
addInboundDomainToMetadata(metadata, {
|
|
20260
|
+
subdomain,
|
|
20261
|
+
receivingDomain,
|
|
20262
|
+
parentDomain,
|
|
20263
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20264
|
+
});
|
|
20265
|
+
await saveConnectionMetadata(metadata);
|
|
20266
|
+
});
|
|
20267
|
+
if (isJsonMode()) {
|
|
20268
|
+
jsonSuccess("email.inbound.add", {
|
|
20269
|
+
receivingDomain,
|
|
20270
|
+
subdomain,
|
|
20271
|
+
parentDomain,
|
|
20272
|
+
dnsAutoCreated,
|
|
20273
|
+
region
|
|
20274
|
+
});
|
|
20275
|
+
return;
|
|
20276
|
+
}
|
|
20277
|
+
console.log();
|
|
20278
|
+
clack20.log.success(
|
|
20279
|
+
`${pc21.bold("Added inbound domain:")} ${pc21.cyan(receivingDomain)}`
|
|
20280
|
+
);
|
|
20281
|
+
console.log();
|
|
20282
|
+
if (!dnsAutoCreated) {
|
|
20283
|
+
console.log(
|
|
20284
|
+
` ${pc21.dim("1.")} Add DNS records above to your DNS provider`
|
|
20285
|
+
);
|
|
20286
|
+
console.log(
|
|
20287
|
+
` ${pc21.dim("2.")} Verify: ${pc21.cyan("wraps email inbound verify")}`
|
|
20288
|
+
);
|
|
20289
|
+
} else {
|
|
20290
|
+
console.log(
|
|
20291
|
+
` Verify: ${pc21.cyan("wraps email inbound verify")}`
|
|
20292
|
+
);
|
|
20293
|
+
}
|
|
20294
|
+
console.log();
|
|
20295
|
+
}
|
|
20296
|
+
async function inboundRemove(options) {
|
|
20297
|
+
if (!isJsonMode()) {
|
|
20298
|
+
clack20.intro(pc21.bold("Remove Inbound Receiving Domain"));
|
|
20299
|
+
}
|
|
20300
|
+
const progress = new DeploymentProgress();
|
|
20301
|
+
const identity = await progress.execute(
|
|
20302
|
+
"Validating AWS credentials",
|
|
20303
|
+
async () => validateAWSCredentials()
|
|
20304
|
+
);
|
|
20305
|
+
const region = options.region || await getAWSRegion();
|
|
20306
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
20307
|
+
if (!metadata?.services?.email?.config?.inbound?.enabled) {
|
|
20308
|
+
clack20.log.error("Inbound email infrastructure is not deployed.");
|
|
20309
|
+
console.log(
|
|
20310
|
+
`
|
|
20311
|
+
Deploy first: ${pc21.cyan("wraps email inbound init")}
|
|
20312
|
+
`
|
|
20313
|
+
);
|
|
20314
|
+
process.exit(1);
|
|
20315
|
+
}
|
|
20316
|
+
const emailConfig = metadata.services.email.config;
|
|
20317
|
+
const inboundDomains = emailConfig.inboundDomains ?? [];
|
|
20318
|
+
if (inboundDomains.length === 0) {
|
|
20319
|
+
clack20.log.warn("No inbound domains configured.");
|
|
20320
|
+
return;
|
|
20321
|
+
}
|
|
20322
|
+
let domainToRemove = options.domain;
|
|
20323
|
+
if (!domainToRemove) {
|
|
20324
|
+
if (inboundDomains.length === 1) {
|
|
20325
|
+
domainToRemove = inboundDomains[0].receivingDomain;
|
|
20326
|
+
} else {
|
|
20327
|
+
const selected = await clack20.select({
|
|
20328
|
+
message: "Which inbound domain do you want to remove?",
|
|
20329
|
+
options: inboundDomains.map((d) => ({
|
|
20330
|
+
value: d.receivingDomain,
|
|
20331
|
+
label: d.receivingDomain,
|
|
20332
|
+
hint: `added ${d.addedAt.split("T")[0]}`
|
|
20333
|
+
}))
|
|
20334
|
+
});
|
|
20335
|
+
if (clack20.isCancel(selected)) {
|
|
20336
|
+
clack20.cancel("Operation cancelled.");
|
|
20337
|
+
process.exit(0);
|
|
20338
|
+
}
|
|
20339
|
+
domainToRemove = selected;
|
|
20340
|
+
}
|
|
20341
|
+
}
|
|
20342
|
+
if (!inboundDomains.some((d) => d.receivingDomain === domainToRemove)) {
|
|
20343
|
+
clack20.log.error(
|
|
20344
|
+
`${pc21.cyan(domainToRemove)} is not in the inbound domains list.`
|
|
20345
|
+
);
|
|
20346
|
+
return;
|
|
20347
|
+
}
|
|
20348
|
+
if (inboundDomains.length === 1) {
|
|
20349
|
+
clack20.log.error(
|
|
20350
|
+
"Cannot remove the last inbound domain. Use " + pc21.cyan("wraps email inbound destroy") + " to remove all inbound infrastructure."
|
|
20351
|
+
);
|
|
20352
|
+
return;
|
|
20353
|
+
}
|
|
20354
|
+
if (!options.yes) {
|
|
20355
|
+
const confirmed = await clack20.confirm({
|
|
20356
|
+
message: `Remove inbound domain ${pc21.cyan(domainToRemove)}?`,
|
|
20357
|
+
initialValue: false
|
|
20358
|
+
});
|
|
20359
|
+
if (clack20.isCancel(confirmed) || !confirmed) {
|
|
20360
|
+
clack20.cancel("Operation cancelled.");
|
|
20361
|
+
process.exit(0);
|
|
20362
|
+
}
|
|
20363
|
+
}
|
|
20364
|
+
await progress.execute("Updating SES receipt rule", async () => {
|
|
20365
|
+
await removeDomainFromReceiptRule(region, domainToRemove);
|
|
20366
|
+
});
|
|
20367
|
+
await progress.execute("Saving configuration", async () => {
|
|
20368
|
+
removeInboundDomainFromMetadata(metadata, domainToRemove);
|
|
20369
|
+
await saveConnectionMetadata(metadata);
|
|
20370
|
+
});
|
|
20371
|
+
if (isJsonMode()) {
|
|
20372
|
+
jsonSuccess("email.inbound.remove", {
|
|
20373
|
+
removedDomain: domainToRemove,
|
|
20374
|
+
remainingDomains: (emailConfig.inboundDomains ?? []).map(
|
|
20375
|
+
(d) => d.receivingDomain
|
|
20376
|
+
),
|
|
20377
|
+
region
|
|
20378
|
+
});
|
|
20379
|
+
return;
|
|
20380
|
+
}
|
|
20381
|
+
console.log();
|
|
20382
|
+
clack20.log.success(
|
|
20383
|
+
`${pc21.bold("Removed inbound domain:")} ${pc21.cyan(domainToRemove)}`
|
|
20384
|
+
);
|
|
20385
|
+
console.log();
|
|
20386
|
+
console.log(
|
|
20387
|
+
` ${pc21.dim("Remember to remove the MX and SPF DNS records for")} ${pc21.cyan(domainToRemove)}`
|
|
20388
|
+
);
|
|
20389
|
+
console.log();
|
|
20390
|
+
}
|
|
19894
20391
|
|
|
19895
20392
|
// src/commands/email/init.ts
|
|
19896
20393
|
init_esm_shims();
|
|
@@ -24461,6 +24958,13 @@ ${pc30.bold("Add these DNS records to your DNS provider:")}
|
|
|
24461
24958
|
if (outputs.httpsTrackingEnabled && outputs.acmCertificateValidationRecords) {
|
|
24462
24959
|
acmValidationRecords.push(...outputs.acmCertificateValidationRecords);
|
|
24463
24960
|
}
|
|
24961
|
+
if (acmValidationRecords.length === 0 && outputs.httpsTrackingPending && outputs.customTrackingDomain) {
|
|
24962
|
+
const { getCertificateValidationRecords: getCertificateValidationRecords2 } = await Promise.resolve().then(() => (init_acm(), acm_exports));
|
|
24963
|
+
const directRecords = await getCertificateValidationRecords2(
|
|
24964
|
+
outputs.customTrackingDomain
|
|
24965
|
+
);
|
|
24966
|
+
acmValidationRecords.push(...directRecords);
|
|
24967
|
+
}
|
|
24464
24968
|
let acmDnsAutoCreated = false;
|
|
24465
24969
|
if (outputs.httpsTrackingPending && acmValidationRecords.length > 0 && outputs.customTrackingDomain) {
|
|
24466
24970
|
const trackingDnsProvider = metadata.services.email?.dnsProvider;
|
|
@@ -27140,7 +27644,7 @@ import {
|
|
|
27140
27644
|
IAMClient as IAMClient2,
|
|
27141
27645
|
PutRolePolicyCommand
|
|
27142
27646
|
} from "@aws-sdk/client-iam";
|
|
27143
|
-
import { confirm as confirm14, intro as intro32, isCancel as isCancel20, log as log32, outro as outro19, select as
|
|
27647
|
+
import { confirm as confirm14, intro as intro32, isCancel as isCancel20, log as log32, outro as outro19, select as select15 } from "@clack/prompts";
|
|
27144
27648
|
import * as pulumi21 from "@pulumi/pulumi";
|
|
27145
27649
|
import pc36 from "picocolors";
|
|
27146
27650
|
init_events();
|
|
@@ -27483,7 +27987,7 @@ async function resolveOrganization() {
|
|
|
27483
27987
|
if (orgs.length === 1) {
|
|
27484
27988
|
return orgs[0];
|
|
27485
27989
|
}
|
|
27486
|
-
const selected = await
|
|
27990
|
+
const selected = await select15({
|
|
27487
27991
|
message: "Which organization should this AWS account connect to?",
|
|
27488
27992
|
options: orgs.map((org) => ({
|
|
27489
27993
|
value: org.id,
|
|
@@ -27779,7 +28283,7 @@ Run ${pc36.cyan("wraps email init")} or ${pc36.cyan("wraps sms init")} first.
|
|
|
27779
28283
|
log32.info(
|
|
27780
28284
|
`Already connected to Wraps Platform (AWS Account: ${pc36.cyan(metadata.accountId)})`
|
|
27781
28285
|
);
|
|
27782
|
-
const action = await
|
|
28286
|
+
const action = await select15({
|
|
27783
28287
|
message: "What would you like to do?",
|
|
27784
28288
|
options: [
|
|
27785
28289
|
{
|
|
@@ -36139,6 +36643,11 @@ args.options([
|
|
|
36139
36643
|
name: "org",
|
|
36140
36644
|
description: "Organization slug",
|
|
36141
36645
|
defaultValue: void 0
|
|
36646
|
+
},
|
|
36647
|
+
{
|
|
36648
|
+
name: "subdomain",
|
|
36649
|
+
description: "Subdomain for inbound email (e.g., inbound, support)",
|
|
36650
|
+
defaultValue: void 0
|
|
36142
36651
|
}
|
|
36143
36652
|
]);
|
|
36144
36653
|
var flags = args.parse(process.argv);
|
|
@@ -36453,13 +36962,30 @@ Usage: ${pc53.cyan("wraps email verify --domain yourapp.com")}
|
|
|
36453
36962
|
json: flags.json
|
|
36454
36963
|
});
|
|
36455
36964
|
break;
|
|
36965
|
+
case "add":
|
|
36966
|
+
await inboundAdd({
|
|
36967
|
+
region: flags.region,
|
|
36968
|
+
subdomain: flags.subdomain,
|
|
36969
|
+
domain: flags.domain,
|
|
36970
|
+
yes: flags.yes,
|
|
36971
|
+
json: flags.json
|
|
36972
|
+
});
|
|
36973
|
+
break;
|
|
36974
|
+
case "remove":
|
|
36975
|
+
await inboundRemove({
|
|
36976
|
+
region: flags.region,
|
|
36977
|
+
domain: flags.domain,
|
|
36978
|
+
yes: flags.yes,
|
|
36979
|
+
json: flags.json
|
|
36980
|
+
});
|
|
36981
|
+
break;
|
|
36456
36982
|
default:
|
|
36457
36983
|
clack50.log.error(
|
|
36458
36984
|
`Unknown inbound command: ${inboundSubCommand || "(none)"}`
|
|
36459
36985
|
);
|
|
36460
36986
|
console.log(
|
|
36461
36987
|
`
|
|
36462
|
-
Available commands: ${pc53.cyan("init")}, ${pc53.cyan("destroy")}, ${pc53.cyan("status")}, ${pc53.cyan("verify")}, ${pc53.cyan("test")}
|
|
36988
|
+
Available commands: ${pc53.cyan("init")}, ${pc53.cyan("destroy")}, ${pc53.cyan("status")}, ${pc53.cyan("verify")}, ${pc53.cyan("test")}, ${pc53.cyan("add")}, ${pc53.cyan("remove")}
|
|
36463
36989
|
`
|
|
36464
36990
|
);
|
|
36465
36991
|
throw new Error(
|