@wraps.dev/cli 2.15.0 → 2.16.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
@@ -65,6 +65,7 @@ var init_ci_detection = __esm({
65
65
  // src/utils/shared/s3-state.ts
66
66
  var s3_state_exports = {};
67
67
  __export(s3_state_exports, {
68
+ clearS3StackLocks: () => clearS3StackLocks,
68
69
  deleteMetadata: () => deleteMetadata,
69
70
  downloadMetadata: () => downloadMetadata,
70
71
  ensureStateBucket: () => ensureStateBucket,
@@ -196,6 +197,27 @@ async function deleteMetadata(bucketName, accountId, region) {
196
197
  })
197
198
  );
198
199
  }
200
+ async function clearS3StackLocks(accountId, region) {
201
+ const { S3Client: S3Client2, ListObjectsV2Command: ListObjectsV2Command2, DeleteObjectCommand } = await import("@aws-sdk/client-s3");
202
+ const client = new S3Client2({ region });
203
+ const bucketName = getStateBucketName(accountId, region);
204
+ const prefix = ".pulumi/locks/";
205
+ const response = await client.send(
206
+ new ListObjectsV2Command2({ Bucket: bucketName, Prefix: prefix })
207
+ );
208
+ const lockObjects = response.Contents ?? [];
209
+ if (lockObjects.length === 0) {
210
+ return 0;
211
+ }
212
+ for (const obj of lockObjects) {
213
+ if (obj.Key) {
214
+ await client.send(
215
+ new DeleteObjectCommand({ Bucket: bucketName, Key: obj.Key })
216
+ );
217
+ }
218
+ }
219
+ return lockObjects.length;
220
+ }
199
221
  async function downloadMetadata(bucketName, accountId, region) {
200
222
  const { S3Client: S3Client2, GetObjectCommand: GetObjectCommand2 } = await import("@aws-sdk/client-s3");
201
223
  const client = new S3Client2({ region });
@@ -302,8 +324,16 @@ var init_s3_state = __esm({
302
324
  });
303
325
 
304
326
  // src/utils/shared/fs.ts
327
+ var fs_exports = {};
328
+ __export(fs_exports, {
329
+ clearLocalStackLocks: () => clearLocalStackLocks,
330
+ ensurePulumiWorkDir: () => ensurePulumiWorkDir,
331
+ ensureWrapsDir: () => ensureWrapsDir,
332
+ getPulumiWorkDir: () => getPulumiWorkDir,
333
+ getWrapsDir: () => getWrapsDir
334
+ });
305
335
  import { existsSync as existsSync2 } from "fs";
306
- import { mkdir } from "fs/promises";
336
+ import { mkdir, readdir as readdir2, rm } from "fs/promises";
307
337
  import { homedir } from "os";
308
338
  import { join as join2 } from "path";
309
339
  function getWrapsDir() {
@@ -318,6 +348,27 @@ async function ensureWrapsDir() {
318
348
  await mkdir(wrapsDir, { recursive: true });
319
349
  }
320
350
  }
351
+ async function clearLocalStackLocks() {
352
+ const locksDir = join2(getPulumiWorkDir(), ".pulumi", "locks");
353
+ if (!existsSync2(locksDir)) {
354
+ return 0;
355
+ }
356
+ let count = 0;
357
+ async function walkAndDelete(dir) {
358
+ const entries = await readdir2(dir, { withFileTypes: true });
359
+ for (const entry of entries) {
360
+ const fullPath = join2(dir, entry.name);
361
+ if (entry.isDirectory()) {
362
+ await walkAndDelete(fullPath);
363
+ } else if (entry.name.endsWith(".json")) {
364
+ await rm(fullPath);
365
+ count++;
366
+ }
367
+ }
368
+ }
369
+ await walkAndDelete(locksDir);
370
+ return count;
371
+ }
321
372
  async function ensurePulumiWorkDir(options) {
322
373
  await ensureWrapsDir();
323
374
  const pulumiDir = getPulumiWorkDir();
@@ -4296,8 +4347,8 @@ async function listConnections() {
4296
4347
  return [];
4297
4348
  }
4298
4349
  try {
4299
- const { readdir: readdir4 } = await import("fs/promises");
4300
- const files = await readdir4(connectionsDir);
4350
+ const { readdir: readdir5 } = await import("fs/promises");
4351
+ const files = await readdir5(connectionsDir);
4301
4352
  const connections = [];
4302
4353
  for (const file of files) {
4303
4354
  if (file.endsWith(".json")) {
@@ -6728,7 +6779,7 @@ var init_cloudflare = __esm({
6728
6779
  };
6729
6780
  }
6730
6781
  async createEmailRecords(data) {
6731
- const { domain, dkimTokens, mailFromDomain, region } = data;
6782
+ const { domain, dkimTokens, mailFromDomain, customTrackingDomain, region } = data;
6732
6783
  const errors2 = [];
6733
6784
  let recordsCreated = 0;
6734
6785
  try {
@@ -6765,6 +6816,20 @@ var init_cloudflare = __esm({
6765
6816
  } else {
6766
6817
  errors2.push(`Failed to create DMARC record for ${domain}`);
6767
6818
  }
6819
+ if (customTrackingDomain) {
6820
+ const trackingSuccess = await this.createRecord(
6821
+ customTrackingDomain,
6822
+ "CNAME",
6823
+ `r.${region}.awstrack.me`
6824
+ );
6825
+ if (trackingSuccess) {
6826
+ recordsCreated++;
6827
+ } else {
6828
+ errors2.push(
6829
+ `Failed to create tracking CNAME for ${customTrackingDomain}`
6830
+ );
6831
+ }
6832
+ }
6768
6833
  if (mailFromDomain) {
6769
6834
  const mxSuccess = await this.createRecord(
6770
6835
  mailFromDomain,
@@ -7096,7 +7161,7 @@ var init_vercel = __esm({
7096
7161
  };
7097
7162
  }
7098
7163
  async createEmailRecords(data) {
7099
- const { domain, dkimTokens, mailFromDomain, region } = data;
7164
+ const { domain, dkimTokens, mailFromDomain, customTrackingDomain, region } = data;
7100
7165
  const errors2 = [];
7101
7166
  let recordsCreated = 0;
7102
7167
  try {
@@ -7133,6 +7198,20 @@ var init_vercel = __esm({
7133
7198
  } else {
7134
7199
  errors2.push(`Failed to create DMARC record for ${domain}`);
7135
7200
  }
7201
+ if (customTrackingDomain) {
7202
+ const trackingSuccess = await this.createRecord(
7203
+ customTrackingDomain,
7204
+ "CNAME",
7205
+ `r.${region}.awstrack.me`
7206
+ );
7207
+ if (trackingSuccess) {
7208
+ recordsCreated++;
7209
+ } else {
7210
+ errors2.push(
7211
+ `Failed to create tracking CNAME for ${customTrackingDomain}`
7212
+ );
7213
+ }
7214
+ }
7136
7215
  if (mailFromDomain) {
7137
7216
  const mxSuccess = await this.createRecord(
7138
7217
  mailFromDomain,
@@ -7348,7 +7427,7 @@ import {
7348
7427
  Route53Client as Route53Client2
7349
7428
  } from "@aws-sdk/client-route-53";
7350
7429
  function buildEmailDNSRecords(data) {
7351
- const { domain, dkimTokens, mailFromDomain, region } = data;
7430
+ const { domain, dkimTokens, mailFromDomain, customTrackingDomain, region } = data;
7352
7431
  const records = [];
7353
7432
  for (const token of dkimTokens) {
7354
7433
  records.push({
@@ -7371,6 +7450,14 @@ function buildEmailDNSRecords(data) {
7371
7450
  value: `v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}`,
7372
7451
  category: "dmarc"
7373
7452
  });
7453
+ if (customTrackingDomain) {
7454
+ records.push({
7455
+ name: customTrackingDomain,
7456
+ type: "CNAME",
7457
+ value: `r.${region}.awstrack.me`,
7458
+ category: "tracking"
7459
+ });
7460
+ }
7374
7461
  if (mailFromDomain) {
7375
7462
  records.push({
7376
7463
  name: mailFromDomain,
@@ -7431,8 +7518,7 @@ async function createDNSRecordsForProvider(credentials, data, selectedCategories
7431
7518
  data.dkimTokens,
7432
7519
  data.region,
7433
7520
  categories,
7434
- void 0,
7435
- // customTrackingDomain - not used here
7521
+ data.customTrackingDomain,
7436
7522
  data.mailFromDomain
7437
7523
  );
7438
7524
  let recordsCreated = 0;
@@ -10078,6 +10164,48 @@ async function previewWithResourceChanges(stack, options) {
10078
10164
  resourceChanges
10079
10165
  };
10080
10166
  }
10167
+ async function clearStackLocks(accountId, region) {
10168
+ const backendUrl = process.env.PULUMI_BACKEND_URL || "";
10169
+ if (backendUrl.startsWith("s3://")) {
10170
+ const { clearS3StackLocks: clearS3StackLocks2 } = await Promise.resolve().then(() => (init_s3_state(), s3_state_exports));
10171
+ return clearS3StackLocks2(accountId, region);
10172
+ }
10173
+ const { clearLocalStackLocks: clearLocalStackLocks2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
10174
+ return clearLocalStackLocks2();
10175
+ }
10176
+ async function withLockRetry(fn, options) {
10177
+ try {
10178
+ return await fn();
10179
+ } catch (error) {
10180
+ if (!(error instanceof Error)) {
10181
+ throw error;
10182
+ }
10183
+ const parsed = parsePulumiError(error);
10184
+ if (parsed.code !== "STACK_LOCKED") {
10185
+ throw error;
10186
+ }
10187
+ const clack51 = await import("@clack/prompts");
10188
+ const pc54 = (await import("picocolors")).default;
10189
+ if (options.autoConfirm) {
10190
+ clack51.log.warn(
10191
+ "Stack is locked from a previous interrupted run. Auto-clearing..."
10192
+ );
10193
+ } else {
10194
+ const shouldClear = await clack51.confirm({
10195
+ message: `Stack is locked from a previous interrupted run. ${pc54.yellow("Clear the stale lock and retry?")}`,
10196
+ initialValue: true
10197
+ });
10198
+ if (clack51.isCancel(shouldClear) || !shouldClear) {
10199
+ throw errors.stackLocked();
10200
+ }
10201
+ }
10202
+ const cleared = await clearStackLocks(options.accountId, options.region);
10203
+ clack51.log.info(
10204
+ `Cleared ${cleared} lock file${cleared === 1 ? "" : "s"}. Retrying...`
10205
+ );
10206
+ return fn();
10207
+ }
10208
+ }
10081
10209
 
10082
10210
  // src/commands/cdn/destroy.ts
10083
10211
  async function cdnDestroy(options) {
@@ -10268,8 +10396,12 @@ async function cdnDestroy(options) {
10268
10396
  } catch (_error) {
10269
10397
  throw new Error("No CDN infrastructure found to destroy");
10270
10398
  }
10271
- await stack.destroy({ onOutput: () => {
10272
- } });
10399
+ await withLockRetry(() => stack.destroy({ onOutput: () => {
10400
+ } }), {
10401
+ accountId: identity.accountId,
10402
+ region,
10403
+ autoConfirm: options.force
10404
+ });
10273
10405
  await stack.workspace.removeStack(stackName);
10274
10406
  }
10275
10407
  );
@@ -10284,10 +10416,6 @@ async function cdnDestroy(options) {
10284
10416
  }
10285
10417
  process.exit(0);
10286
10418
  }
10287
- if (msg.includes("stack is currently locked")) {
10288
- trackError("STACK_LOCKED", "storage destroy", { step: "destroy" });
10289
- throw errors.stackLocked();
10290
- }
10291
10419
  trackError("DESTROY_FAILED", "storage destroy", { step: "destroy" });
10292
10420
  clack9.log.error("CDN infrastructure destruction failed");
10293
10421
  throw error;
@@ -11677,8 +11805,11 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
11677
11805
  `wraps-cdn-${identity.accountId}-${region}`
11678
11806
  );
11679
11807
  await stack.setConfig("aws:region", { value: region });
11680
- const upResult = await stack.up({ onOutput: () => {
11681
- } });
11808
+ const upResult = await withLockRetry(
11809
+ () => stack.up({ onOutput: () => {
11810
+ } }),
11811
+ { accountId: identity.accountId, region, autoConfirm: options.yes }
11812
+ );
11682
11813
  const pulumiOutputs = upResult.outputs;
11683
11814
  return {
11684
11815
  roleArn: pulumiOutputs.roleArn?.value,
@@ -11704,10 +11835,6 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
11704
11835
  region,
11705
11836
  duration_ms: Date.now() - startTime
11706
11837
  });
11707
- if (msg.includes("stack is currently locked")) {
11708
- trackError("STACK_LOCKED", "storage:init", { step: "deploy" });
11709
- throw errors.stackLocked();
11710
- }
11711
11838
  trackError("DEPLOYMENT_FAILED", "storage:init", { step: "deploy" });
11712
11839
  throw new Error(`Pulumi deployment failed: ${msg}`);
11713
11840
  }
@@ -18155,11 +18282,14 @@ async function emailDestroy(options) {
18155
18282
  }
18156
18283
  await stack.refresh({ onOutput: () => {
18157
18284
  } });
18158
- await withTimeout(
18159
- stack.destroy({ onOutput: () => {
18160
- }, continueOnError: true }),
18161
- DEFAULT_PULUMI_TIMEOUT_MS,
18162
- "Pulumi destroy"
18285
+ await withLockRetry(
18286
+ () => withTimeout(
18287
+ stack.destroy({ onOutput: () => {
18288
+ }, continueOnError: true }),
18289
+ DEFAULT_PULUMI_TIMEOUT_MS,
18290
+ "Pulumi destroy"
18291
+ ),
18292
+ { accountId: identity.accountId, region, autoConfirm: options.force }
18163
18293
  );
18164
18294
  await stack.workspace.removeStack(stackName);
18165
18295
  }
@@ -18172,10 +18302,6 @@ async function emailDestroy(options) {
18172
18302
  await deleteConnectionMetadata(identity.accountId, region);
18173
18303
  process.exit(0);
18174
18304
  }
18175
- if (msg.includes("stack is currently locked")) {
18176
- trackError("STACK_LOCKED", "email destroy", { step: "destroy" });
18177
- throw errors.stackLocked();
18178
- }
18179
18305
  trackError("DESTROY_FAILED", "email destroy", { step: "destroy" });
18180
18306
  clack18.log.error("Email infrastructure destruction failed");
18181
18307
  destroyFailed = true;
@@ -19425,8 +19551,8 @@ async function inboundInit(options) {
19425
19551
  if (!domain) {
19426
19552
  throw errors.inboundRequiresOutbound();
19427
19553
  }
19428
- const subdomain = options.subdomain || (options.yes ? "inbound" : await promptInboundSubdomain(domain));
19429
- const receivingDomain = `${subdomain}.${domain}`;
19554
+ const subdomain = options.root ? "" : options.subdomain ?? (options.yes ? "inbound" : await promptInboundSubdomain(domain));
19555
+ const receivingDomain = subdomain ? `${subdomain}.${domain}` : domain;
19430
19556
  clack20.log.info(`Receiving domain: ${pc21.cyan(receivingDomain)}`);
19431
19557
  const webhookUrl = options.webhookUrl || (options.yes ? void 0 : await promptWebhookUrl());
19432
19558
  const webhookSecret = randomBytes3(32).toString("hex");
@@ -19490,22 +19616,25 @@ async function inboundInit(options) {
19490
19616
  );
19491
19617
  await stack.setConfig("aws:region", { value: region });
19492
19618
  const pulumiOutput = [];
19493
- await withTimeout(
19494
- stack.up({
19495
- onOutput: (msg) => {
19496
- pulumiOutput.push(msg);
19619
+ await withLockRetry(
19620
+ () => withTimeout(
19621
+ stack.up({
19622
+ onOutput: (msg) => {
19623
+ pulumiOutput.push(msg);
19624
+ }
19625
+ }),
19626
+ DEFAULT_PULUMI_TIMEOUT_MS,
19627
+ "Pulumi deployment"
19628
+ ).catch((error) => {
19629
+ if (pulumiOutput.length > 0) {
19630
+ const fullOutput = pulumiOutput.join("");
19631
+ clack20.log.error("Pulumi deployment output:");
19632
+ console.error(fullOutput);
19497
19633
  }
19634
+ throw error;
19498
19635
  }),
19499
- DEFAULT_PULUMI_TIMEOUT_MS,
19500
- "Pulumi deployment"
19501
- ).catch((error) => {
19502
- if (pulumiOutput.length > 0) {
19503
- const fullOutput = pulumiOutput.join("");
19504
- clack20.log.error("Pulumi deployment output:");
19505
- console.error(fullOutput);
19506
- }
19507
- throw error;
19508
- });
19636
+ { accountId: identity.accountId, region, autoConfirm: options.yes }
19637
+ );
19509
19638
  });
19510
19639
  await progress.execute("Creating SES receipt rules", async () => {
19511
19640
  await createReceiptRuleSet(region);
@@ -19731,11 +19860,14 @@ Deploy first: ${pc21.cyan("wraps email inbound init")}
19731
19860
  }
19732
19861
  );
19733
19862
  await stack.setConfig("aws:region", { value: region });
19734
- await withTimeout(
19735
- stack.up({ onOutput: () => {
19736
- } }),
19737
- DEFAULT_PULUMI_TIMEOUT_MS,
19738
- "Pulumi deployment"
19863
+ await withLockRetry(
19864
+ () => withTimeout(
19865
+ stack.up({ onOutput: () => {
19866
+ } }),
19867
+ DEFAULT_PULUMI_TIMEOUT_MS,
19868
+ "Pulumi deployment"
19869
+ ),
19870
+ { accountId: identity.accountId, region, autoConfirm: options.force }
19739
19871
  );
19740
19872
  });
19741
19873
  await progress.execute("Saving configuration", async () => {
@@ -19785,7 +19917,7 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
19785
19917
  const inboundDomains = emailConfig.inboundDomains ?? [];
19786
19918
  const activeRuleSet = await getActiveReceiptRuleSet(region);
19787
19919
  const domainList = inboundDomains.length > 0 ? inboundDomains.map((d) => d.receivingDomain) : [
19788
- inbound.receivingDomain || `${inbound.subdomain}.${emailConfig.domain}`
19920
+ inbound.receivingDomain || (inbound.subdomain ? `${inbound.subdomain}.${emailConfig.domain}` : emailConfig.domain || "")
19789
19921
  ];
19790
19922
  if (isJsonMode()) {
19791
19923
  jsonSuccess("email.inbound.status", {
@@ -19804,9 +19936,7 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
19804
19936
  console.log(pc21.bold(" Inbound Email Configuration"));
19805
19937
  console.log();
19806
19938
  if (domainList.length === 1) {
19807
- console.log(
19808
- ` ${pc21.dim("Receiving domain:")} ${pc21.cyan(domainList[0])}`
19809
- );
19939
+ console.log(` ${pc21.dim("Receiving domain:")} ${pc21.cyan(domainList[0])}`);
19810
19940
  } else {
19811
19941
  console.log(` ${pc21.dim("Receiving domains:")}`);
19812
19942
  for (const d of domainList) {
@@ -19850,7 +19980,7 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
19850
19980
  const inbound = emailConfig.inbound;
19851
19981
  const inboundDomains = emailConfig.inboundDomains ?? [];
19852
19982
  const domainList = inboundDomains.length > 0 ? inboundDomains.map((d) => d.receivingDomain) : [
19853
- inbound.receivingDomain || `${inbound.subdomain}.${emailConfig.domain}`
19983
+ inbound.receivingDomain || (inbound.subdomain ? `${inbound.subdomain}.${emailConfig.domain}` : emailConfig.domain || "")
19854
19984
  ];
19855
19985
  let allPassed = true;
19856
19986
  const domainChecks = {};
@@ -19965,7 +20095,7 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
19965
20095
  }
19966
20096
  const emailConfig = metadata.services.email.config;
19967
20097
  const inbound = emailConfig.inbound;
19968
- const receivingDomain = inbound.receivingDomain || `${inbound.subdomain}.${emailConfig.domain}`;
20098
+ const receivingDomain = inbound.receivingDomain || (inbound.subdomain ? `${inbound.subdomain}.${emailConfig.domain}` : emailConfig.domain || "");
19969
20099
  const bucketName = inbound.bucketName || `wraps-inbound-${identity.accountId}-${region}`;
19970
20100
  const testRecipient = `test@${receivingDomain}`;
19971
20101
  const testSubject = `Wraps Inbound Test - ${(/* @__PURE__ */ new Date()).toISOString()}`;
@@ -20108,11 +20238,9 @@ async function inboundAdd(options) {
20108
20238
  const metadata = await loadConnectionMetadata(identity.accountId, region);
20109
20239
  if (!metadata?.services?.email?.config?.inbound?.enabled) {
20110
20240
  clack20.log.error("Inbound email infrastructure is not deployed.");
20111
- console.log(
20112
- `
20241
+ console.log(`
20113
20242
  Deploy first: ${pc21.cyan("wraps email inbound init")}
20114
- `
20115
- );
20243
+ `);
20116
20244
  process.exit(1);
20117
20245
  }
20118
20246
  const emailConfig = metadata.services.email.config;
@@ -20145,8 +20273,8 @@ Deploy first: ${pc21.cyan("wraps email inbound init")}
20145
20273
  parentDomain = selected;
20146
20274
  }
20147
20275
  }
20148
- const subdomain = options.subdomain || (options.yes ? "inbound" : await promptInboundSubdomain(parentDomain));
20149
- const receivingDomain = `${subdomain}.${parentDomain}`;
20276
+ const subdomain = options.root ? "" : options.subdomain ?? (options.yes ? "inbound" : await promptInboundSubdomain(parentDomain));
20277
+ const receivingDomain = subdomain ? `${subdomain}.${parentDomain}` : parentDomain;
20150
20278
  const existingDomains = emailConfig.inboundDomains ?? [];
20151
20279
  if (existingDomains.some((d) => d.receivingDomain === receivingDomain)) {
20152
20280
  clack20.log.warn(
@@ -20279,16 +20407,12 @@ Deploy first: ${pc21.cyan("wraps email inbound init")}
20279
20407
  `${pc21.bold("Added inbound domain:")} ${pc21.cyan(receivingDomain)}`
20280
20408
  );
20281
20409
  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
- );
20410
+ if (dnsAutoCreated) {
20411
+ console.log(` Verify: ${pc21.cyan("wraps email inbound verify")}`);
20289
20412
  } else {
20413
+ console.log(` ${pc21.dim("1.")} Add DNS records above to your DNS provider`);
20290
20414
  console.log(
20291
- ` Verify: ${pc21.cyan("wraps email inbound verify")}`
20415
+ ` ${pc21.dim("2.")} Verify: ${pc21.cyan("wraps email inbound verify")}`
20292
20416
  );
20293
20417
  }
20294
20418
  console.log();
@@ -20306,11 +20430,9 @@ async function inboundRemove(options) {
20306
20430
  const metadata = await loadConnectionMetadata(identity.accountId, region);
20307
20431
  if (!metadata?.services?.email?.config?.inbound?.enabled) {
20308
20432
  clack20.log.error("Inbound email infrastructure is not deployed.");
20309
- console.log(
20310
- `
20433
+ console.log(`
20311
20434
  Deploy first: ${pc21.cyan("wraps email inbound init")}
20312
- `
20313
- );
20435
+ `);
20314
20436
  process.exit(1);
20315
20437
  }
20316
20438
  const emailConfig = metadata.services.email.config;
@@ -20803,12 +20925,19 @@ ${pc24.yellow(pc24.bold("Configuration Warnings:"))}`);
20803
20925
  `wraps-${identity.accountId}-${region}`
20804
20926
  );
20805
20927
  await stack.setConfig("aws:region", { value: region });
20806
- const upResult = await withTimeout(
20807
- stack.up({ onOutput: () => {
20808
- } }),
20809
- // Suppress Pulumi output
20810
- DEFAULT_PULUMI_TIMEOUT_MS,
20811
- "Pulumi deployment"
20928
+ const upResult = await withLockRetry(
20929
+ () => withTimeout(
20930
+ stack.up({ onOutput: () => {
20931
+ } }),
20932
+ // Suppress Pulumi output
20933
+ DEFAULT_PULUMI_TIMEOUT_MS,
20934
+ "Pulumi deployment"
20935
+ ),
20936
+ {
20937
+ accountId: identity.accountId,
20938
+ region,
20939
+ autoConfirm: options.yes || options.quick
20940
+ }
20812
20941
  );
20813
20942
  const pulumiOutputs = upResult.outputs;
20814
20943
  return {
@@ -20835,10 +20964,6 @@ ${pc24.yellow(pc24.bold("Configuration Warnings:"))}`);
20835
20964
  region,
20836
20965
  duration_ms: Date.now() - startTime
20837
20966
  });
20838
- if (msg.includes("stack is currently locked")) {
20839
- trackError("STACK_LOCKED", "email:init", { step: "deploy" });
20840
- throw errors.stackLocked();
20841
- }
20842
20967
  if (isPulumiError(error)) {
20843
20968
  const { code, iamAction, service } = parsePulumiError(error);
20844
20969
  trackError(`PULUMI_${code}`, "email:init", {
@@ -20956,6 +21081,7 @@ ${pc24.yellow(pc24.bold("Configuration Warnings:"))}`);
20956
21081
  domain: outputs.domain,
20957
21082
  dkimTokens: outputs.dkimTokens,
20958
21083
  mailFromDomain: outputs.mailFromDomain,
21084
+ customTrackingDomain: outputs.customTrackingDomain,
20959
21085
  region
20960
21086
  },
20961
21087
  selectedCategories
@@ -20988,6 +21114,7 @@ ${pc24.yellow(pc24.bold("Configuration Warnings:"))}`);
20988
21114
  domain: outputs.domain,
20989
21115
  dkimTokens: outputs.dkimTokens,
20990
21116
  mailFromDomain: outputs.mailFromDomain,
21117
+ customTrackingDomain: outputs.customTrackingDomain,
20991
21118
  region
20992
21119
  };
20993
21120
  const records = buildEmailDNSRecords2(recordData);
@@ -22001,8 +22128,8 @@ async function templatesInit(options) {
22001
22128
  const { homedir: homedir3 } = await import("os");
22002
22129
  const connectionsDir = join9(homedir3(), ".wraps", "connections");
22003
22130
  if (existsSync8(connectionsDir)) {
22004
- const { readdir: readdir4 } = await import("fs/promises");
22005
- const files = await readdir4(connectionsDir);
22131
+ const { readdir: readdir5 } = await import("fs/promises");
22132
+ const files = await readdir5(connectionsDir);
22006
22133
  if (files.length > 0) {
22007
22134
  const firstFile = files[0];
22008
22135
  const match = firstFile.match(/\d+-(.+)\.json$/);
@@ -22321,7 +22448,7 @@ import pc28 from "picocolors";
22321
22448
  // src/utils/email/template-compiler.ts
22322
22449
  init_esm_shims();
22323
22450
  import { existsSync as existsSync9 } from "fs";
22324
- import { mkdir as mkdir4, readdir as readdir2, writeFile as writeFile6 } from "fs/promises";
22451
+ import { mkdir as mkdir4, readdir as readdir3, writeFile as writeFile6 } from "fs/promises";
22325
22452
  import { join as join10 } from "path";
22326
22453
  async function loadWrapsConfig(wrapsDir) {
22327
22454
  const configPath = join10(wrapsDir, "wraps.config.ts");
@@ -22356,7 +22483,7 @@ async function loadWrapsConfig(wrapsDir) {
22356
22483
  return config2;
22357
22484
  }
22358
22485
  async function discoverTemplates(dir, filter) {
22359
- const entries = await readdir2(dir);
22486
+ const entries = await readdir3(dir);
22360
22487
  const templates = entries.filter(
22361
22488
  (f) => (f.endsWith(".tsx") || f.endsWith(".ts")) && !f.startsWith("_") && !f.endsWith(".d.ts")
22362
22489
  );
@@ -24864,6 +24991,7 @@ ${pc30.bold("Cost Impact:")}`);
24864
24991
  domain: outputs.domain,
24865
24992
  dkimTokens: outputs.dkimTokens,
24866
24993
  mailFromDomain,
24994
+ customTrackingDomain: outputs.customTrackingDomain,
24867
24995
  region
24868
24996
  };
24869
24997
  try {
@@ -24914,6 +25042,7 @@ ${pc30.bold("Cost Impact:")}`);
24914
25042
  domain: outputs.domain,
24915
25043
  dkimTokens: outputs.dkimTokens,
24916
25044
  mailFromDomain,
25045
+ customTrackingDomain: outputs.customTrackingDomain,
24917
25046
  region
24918
25047
  };
24919
25048
  const dnsRecords = buildEmailDNSRecords(dnsData);
@@ -25630,8 +25759,8 @@ async function workflowsInit(options) {
25630
25759
  }
25631
25760
  const progress = new DeploymentProgress();
25632
25761
  if (existsSync13(workflowsDir) && !options.force) {
25633
- const { readdir: readdir4 } = await import("fs/promises");
25634
- const files = await readdir4(workflowsDir);
25762
+ const { readdir: readdir5 } = await import("fs/promises");
25763
+ const files = await readdir5(workflowsDir);
25635
25764
  const tsFiles = files.filter(
25636
25765
  (f) => f.endsWith(".ts") && !f.startsWith("_")
25637
25766
  );
@@ -25965,13 +26094,13 @@ function assignPositions(steps, transitions) {
25965
26094
  init_esm_shims();
25966
26095
  import { createHash as createHash2 } from "crypto";
25967
26096
  import { existsSync as existsSync14 } from "fs";
25968
- import { mkdir as mkdir8, readdir as readdir3, readFile as readFile8, writeFile as writeFile10 } from "fs/promises";
26097
+ import { mkdir as mkdir8, readdir as readdir4, readFile as readFile8, writeFile as writeFile10 } from "fs/promises";
25969
26098
  import { basename, join as join15 } from "path";
25970
26099
  async function discoverWorkflows(dir, filter) {
25971
26100
  if (!existsSync14(dir)) {
25972
26101
  return [];
25973
26102
  }
25974
- const entries = await readdir3(dir);
26103
+ const entries = await readdir4(dir);
25975
26104
  const workflows = entries.filter(
25976
26105
  (f) => (
25977
26106
  // Include .ts files only (not .tsx for workflows)
@@ -32946,7 +33075,6 @@ import * as pulumi26 from "@pulumi/pulumi";
32946
33075
  import pc43 from "picocolors";
32947
33076
  init_events();
32948
33077
  init_aws();
32949
- init_errors();
32950
33078
  init_fs();
32951
33079
  init_json_output();
32952
33080
  init_metadata();
@@ -33624,7 +33752,10 @@ ${pc43.yellow(pc43.bold("Important Notes:"))}`);
33624
33752
  }
33625
33753
  );
33626
33754
  await stack.setConfig("aws:region", { value: region });
33627
- const upResult = await stack.up({ onOutput: console.log });
33755
+ const upResult = await withLockRetry(
33756
+ () => stack.up({ onOutput: console.log }),
33757
+ { accountId: identity.accountId, region, autoConfirm: options.yes }
33758
+ );
33628
33759
  const pulumiOutputs = upResult.outputs;
33629
33760
  return {
33630
33761
  roleArn: pulumiOutputs.roleArn?.value,
@@ -33707,10 +33838,6 @@ ${pc43.yellow(pc43.bold("Important Notes:"))}`);
33707
33838
  duration_ms: Date.now() - startTime
33708
33839
  });
33709
33840
  const errorMessage = error instanceof Error ? error.message : String(error);
33710
- if (errorMessage.includes("stack is currently locked")) {
33711
- trackError("STACK_LOCKED", "sms:init", { step: "deploy" });
33712
- throw errors.stackLocked();
33713
- }
33714
33841
  trackError("DEPLOYMENT_FAILED", "sms:init", { step: "deploy" });
33715
33842
  throw new Error(`SMS deployment failed: ${errorMessage}`);
33716
33843
  }