@awsless/cli 0.0.46-next.0 → 0.0.46-next.2

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/bin.js CHANGED
@@ -528,6 +528,8 @@ import {
528
528
  CreateAliasCommand as CreateAliasCommand2,
529
529
  CreateFunctionUrlConfigCommand,
530
530
  GetFunctionUrlConfigCommand,
531
+ RemovePermissionCommand,
532
+ UpdateFunctionUrlConfigCommand,
531
533
  LambdaClient as LambdaClient2,
532
534
  PutFunctionEventInvokeConfigCommand
533
535
  } from "@aws-sdk/client-lambda";
@@ -559,7 +561,8 @@ var createLambdaProvider = ({ credentials, region }) => {
559
561
  deploymentId: z2.string(),
560
562
  functionName: z2.string(),
561
563
  functionVersion: z2.string(),
562
- onFailureArn: z2.string()
564
+ onFailureArn: z2.string(),
565
+ sourceAccount: z2.string().optional()
563
566
  });
564
567
  const bundleDeploymentStateSchema = bundleDeploymentInputSchema.extend({
565
568
  deploymentAlias: z2.string(),
@@ -596,14 +599,15 @@ var createLambdaProvider = ({ credentials, region }) => {
596
599
  })
597
600
  );
598
601
  };
599
- const createPublicUrl = async (functionName, alias) => {
602
+ const createDeploymentUrl = async (functionName, alias, sourceAccount) => {
603
+ const authType = sourceAccount ? "AWS_IAM" : "NONE";
600
604
  let url;
601
605
  try {
602
606
  const result = await lambda.send(
603
607
  new CreateFunctionUrlConfigCommand({
604
608
  FunctionName: functionName,
605
609
  Qualifier: alias,
606
- AuthType: "NONE"
610
+ AuthType: authType
607
611
  })
608
612
  );
609
613
  url = result.FunctionUrl;
@@ -612,21 +616,39 @@ var createLambdaProvider = ({ credentials, region }) => {
612
616
  throw error;
613
617
  }
614
618
  const result = await lambda.send(
615
- new GetFunctionUrlConfigCommand({
619
+ new UpdateFunctionUrlConfigCommand({
616
620
  FunctionName: functionName,
617
- Qualifier: alias
621
+ Qualifier: alias,
622
+ AuthType: authType
618
623
  })
619
624
  );
620
625
  url = result.FunctionUrl;
621
626
  }
622
- const permissions = [
627
+ const permissions = sourceAccount ? [
628
+ {
629
+ StatementId: "cloudfront-url",
630
+ Principal: "cloudfront.amazonaws.com",
631
+ SourceAccount: sourceAccount,
632
+ Action: "lambda:InvokeFunctionUrl",
633
+ FunctionUrlAuthType: "AWS_IAM"
634
+ },
635
+ {
636
+ StatementId: "cloudfront-invoke",
637
+ Principal: "cloudfront.amazonaws.com",
638
+ SourceAccount: sourceAccount,
639
+ Action: "lambda:InvokeFunction",
640
+ InvokedViaFunctionUrl: true
641
+ }
642
+ ] : [
623
643
  {
624
644
  StatementId: "public-url",
645
+ Principal: "*",
625
646
  Action: "lambda:InvokeFunctionUrl",
626
647
  FunctionUrlAuthType: "NONE"
627
648
  },
628
649
  {
629
650
  StatementId: "public-invoke",
651
+ Principal: "*",
630
652
  Action: "lambda:InvokeFunction",
631
653
  InvokedViaFunctionUrl: true
632
654
  }
@@ -637,7 +659,6 @@ var createLambdaProvider = ({ credentials, region }) => {
637
659
  new AddPermissionCommand({
638
660
  FunctionName: functionName,
639
661
  Qualifier: alias,
640
- Principal: "*",
641
662
  ...permission
642
663
  })
643
664
  );
@@ -647,6 +668,23 @@ var createLambdaProvider = ({ credentials, region }) => {
647
668
  }
648
669
  }
649
670
  }
671
+ if (sourceAccount) {
672
+ for (const statementId of ["public-url", "public-invoke"]) {
673
+ try {
674
+ await lambda.send(
675
+ new RemovePermissionCommand({
676
+ FunctionName: functionName,
677
+ Qualifier: alias,
678
+ StatementId: statementId
679
+ })
680
+ );
681
+ } catch (error) {
682
+ if (!isError(error, "ResourceNotFoundException")) {
683
+ throw error;
684
+ }
685
+ }
686
+ }
687
+ }
650
688
  return url;
651
689
  };
652
690
  const createBundleDeployment = async (state2) => {
@@ -658,7 +696,7 @@ var createLambdaProvider = ({ credentials, region }) => {
658
696
  name: deploymentAlias
659
697
  });
660
698
  await configureVersion(state2);
661
- const url = await createPublicUrl(state2.functionName, deploymentAlias);
699
+ const url = await createDeploymentUrl(state2.functionName, deploymentAlias, state2.sourceAccount);
662
700
  return {
663
701
  ...state2,
664
702
  deploymentAlias,
@@ -769,7 +807,20 @@ var createLambdaProvider = ({ credentials, region }) => {
769
807
  deploymentAliases.push(deploymentAlias);
770
808
  }
771
809
  await configureVersion(proposed);
772
- const url = await createPublicUrl(proposed.functionName, deploymentAlias);
810
+ const url = await createDeploymentUrl(proposed.functionName, deploymentAlias, proposed.sourceAccount);
811
+ if (proposed.sourceAccount && prior.sourceAccount !== proposed.sourceAccount) {
812
+ await Promise.all(
813
+ deploymentAliases.filter((name) => name !== deploymentAlias).map(
814
+ (name) => createDeploymentUrl(proposed.functionName, name, proposed.sourceAccount).catch(
815
+ (error) => {
816
+ if (!isError(error, "ResourceNotFoundException")) {
817
+ throw error;
818
+ }
819
+ }
820
+ )
821
+ )
822
+ );
823
+ }
773
824
  return {
774
825
  ...proposed,
775
826
  deploymentAlias,
@@ -1276,20 +1327,16 @@ var readDeployedFunctionVersion = (state2) => {
1276
1327
  };
1277
1328
  var formatDeploymentSummary = (props) => {
1278
1329
  let previewUrl;
1279
- let lambdaUrl;
1280
1330
  for (const [urn, node] of readStateNodes(props.state)) {
1281
1331
  if (node.type === "aws_cloudfront_distribution" && urn.endsWith(":{preview}")) {
1282
1332
  previewUrl = `https://${node.output.domainName}`;
1283
1333
  }
1284
- if (node.type === "bundle-deployment") {
1285
- lambdaUrl = node.output.url;
1286
- }
1287
1334
  }
1288
1335
  return Object.keys(props.appConfig.defaults.router ?? {}).map((routerId, index) => {
1289
1336
  return [
1290
1337
  `${routerId}: deployment #${props.id}`,
1291
1338
  index === 0 ? previewUrl : void 0,
1292
- index === 0 ? lambdaUrl : void 0
1339
+ index === 0 && previewUrl ? `${previewUrl}/?awsless-deployment=${props.id}` : void 0
1293
1340
  ].filter(Boolean).join("\n");
1294
1341
  });
1295
1342
  };
@@ -1418,6 +1465,9 @@ var activateDeployment = async (props) => {
1418
1465
  });
1419
1466
  return id;
1420
1467
  };
1468
+ var promoteAppDeployment = (props) => {
1469
+ return activateDeployment({ ...props, rejectStale: true });
1470
+ };
1421
1471
  var rollbackAppDeployment = (props) => {
1422
1472
  return withAppReleaseLock(props.appConfig, () => activateDeployment(props));
1423
1473
  };
@@ -2153,6 +2203,7 @@ var RouterDefaultSchema = z20.record(
2153
2203
  z20.object({
2154
2204
  domain: ResourceIdSchema.describe("The domain id to link your Router.").optional(),
2155
2205
  subDomain: z20.string().optional(),
2206
+ redirectWww: z20.boolean().default(false).describe("Redirect all www subdomain requests to your root domain."),
2156
2207
  waf: WafSettingsSchema.optional(),
2157
2208
  geoRestrictions: z20.array(z20.string().length(2).toUpperCase()).default([]).describe("Specifies a blacklist of countries that should be blocked."),
2158
2209
  errors: z20.object({
@@ -2241,6 +2292,21 @@ var RouterDefaultSchema = z20.record(
2241
2292
  }).optional().describe(
2242
2293
  "Specifies the cookies, headers, and query values that CloudFront includes in the cache key."
2243
2294
  )
2295
+ }).superRefine((props, ctx) => {
2296
+ if (props.redirectWww && !props.domain) {
2297
+ ctx.addIssue({
2298
+ code: z20.ZodIssueCode.custom,
2299
+ path: ["redirectWww"],
2300
+ message: "The redirectWww option requires a domain to be set."
2301
+ });
2302
+ }
2303
+ if (props.redirectWww && props.subDomain) {
2304
+ ctx.addIssue({
2305
+ code: z20.ZodIssueCode.custom,
2306
+ path: ["redirectWww"],
2307
+ message: `The redirectWww option can't be combined with a subDomain, because the domain certificate only covers single level subdomains.`
2308
+ });
2309
+ }
2244
2310
  })
2245
2311
  ).optional().describe(`Define the global Router. Backed by AWS CloudFront.`);
2246
2312
 
@@ -3824,7 +3890,6 @@ var bundleTypeScriptWithRolldown = async ({
3824
3890
  format: format3 = "esm",
3825
3891
  minify = true,
3826
3892
  file,
3827
- nativeDir,
3828
3893
  external,
3829
3894
  importAsString: importAsStringList
3830
3895
  }) => {
@@ -3835,7 +3900,25 @@ var bundleTypeScriptWithRolldown = async ({
3835
3900
  return importee.startsWith("@aws-sdk") || importee.startsWith("aws-sdk") || external?.includes(importee);
3836
3901
  },
3837
3902
  treeshake: {
3838
- moduleSideEffects: (id, isExternal) => isExternal ? external?.includes(id) === true : id.startsWith(`${directories.root}/`)
3903
+ // The @awsless packages are pure clients, but their entry points
3904
+ // also export test helpers that pull in local server
3905
+ // implementations like dynamo-db-local, which crash the ESM
3906
+ // runtime with CJS globals like __dirname. Treating the scope as
3907
+ // side-effect free prunes those unused chains from production
3908
+ // bundles, while every other dependency keeps its import side
3909
+ // effects, like the fs patching of graceful-fs.
3910
+ moduleSideEffects: (id, isExternal) => {
3911
+ if (isExternal) {
3912
+ return external?.includes(id) === true;
3913
+ }
3914
+ if (id.includes("/node_modules/@awsless/")) {
3915
+ return false;
3916
+ }
3917
+ if (id.includes("/node_modules/")) {
3918
+ return true;
3919
+ }
3920
+ return id.startsWith(`${directories.root}/`);
3921
+ }
3839
3922
  },
3840
3923
  onwarn: (error) => {
3841
3924
  debugError(error.message);
@@ -3882,6 +3965,7 @@ var bundleTypeScriptWithRolldown = async ({
3882
3965
  chunkFileNames: `[name].${ext}`,
3883
3966
  minify
3884
3967
  });
3968
+ assertNoTestOnlyModules(result.output);
3885
3969
  const hash = createHash4("sha1");
3886
3970
  const files = [];
3887
3971
  for (const item of result.output) {
@@ -3903,6 +3987,22 @@ var bundleTypeScriptWithRolldown = async ({
3903
3987
  files
3904
3988
  };
3905
3989
  };
3990
+ var TEST_ONLY_MODULES = ["dynamo-db-local", "@awsless/dynamodb-server", "redis-memory-server"];
3991
+ var assertNoTestOnlyModules = (output) => {
3992
+ for (const item of output) {
3993
+ if (item.type !== "chunk") {
3994
+ continue;
3995
+ }
3996
+ for (const id of item.moduleIds ?? []) {
3997
+ const found = TEST_ONLY_MODULES.find((name) => id.includes(`/node_modules/${name}/`));
3998
+ if (found) {
3999
+ throw new Error(
4000
+ `The test-only package "${found}" was bundled into a production build through "${id}". Remove the import from the handler, or keep the package tree-shakeable.`
4001
+ );
4002
+ }
4003
+ }
4004
+ }
4005
+ };
3906
4006
 
3907
4007
  // src/feature/bundle/util.ts
3908
4008
  var ROUTE_HEADER = "x-awsless-route";
@@ -4188,7 +4288,8 @@ var bundleFeature = defineFeature({
4188
4288
  deploymentId: ctx.deploymentId ?? "local-0",
4189
4289
  functionName: lambda.functionName,
4190
4290
  functionVersion: lambda.version,
4191
- onFailureArn: onFailure
4291
+ onFailureArn: onFailure,
4292
+ sourceAccount: ctx.accountId
4192
4293
  },
4193
4294
  {
4194
4295
  // Make sure the permissions are in place before any event source is wired up.
@@ -4329,6 +4430,7 @@ var cacheFeature = defineFeature({
4329
4430
  {
4330
4431
  name,
4331
4432
  engine: "valkey",
4433
+ networkType: "dual_stack",
4332
4434
  dailySnapshotTime: "02:00",
4333
4435
  majorEngineVersion: "8",
4334
4436
  snapshotRetentionLimit: props.snapshotRetentionLimit,
@@ -4354,7 +4456,9 @@ var cacheFeature = defineFeature({
4354
4456
  },
4355
4457
  {
4356
4458
  retainOnDelete: ctx.appConfig.removal === "retain",
4357
- import: ctx.import ? name : void 0
4459
+ import: ctx.import ? name : void 0,
4460
+ // The network type can only be set at creation time.
4461
+ replaceOnChanges: ["networkType"]
4358
4462
  }
4359
4463
  );
4360
4464
  const masterHost = cache.endpoint.pipe((v) => v.at(0).address);
@@ -5893,12 +5997,15 @@ var pubsubFeature = defineFeature({
5893
5997
  {
5894
5998
  name: cacheName,
5895
5999
  engine: "valkey",
6000
+ networkType: "dual_stack",
5896
6001
  majorEngineVersion: "8",
5897
6002
  securityGroupIds: [cacheSecurityGroup.id],
5898
6003
  subnetIds: ctx.shared.get("vpc", "private-subnets")
5899
6004
  },
5900
6005
  {
5901
- import: ctx.import ? cacheName : void 0
6006
+ import: ctx.import ? cacheName : void 0,
6007
+ // The network type can only be set at creation time.
6008
+ replaceOnChanges: ["networkType"]
5902
6009
  }
5903
6010
  );
5904
6011
  const redisHost = cache.endpoint.pipe((v) => v.at(0).address);
@@ -6484,7 +6591,7 @@ var searchFeature = defineFeature({
6484
6591
  {
6485
6592
  domainName: name,
6486
6593
  engineVersion: `OpenSearch_${props.version}`,
6487
- ipAddressType: "ipv4",
6594
+ ipAddressType: "dualstack",
6488
6595
  clusterConfig: {
6489
6596
  instanceType: `${props.type}.search`,
6490
6597
  instanceCount: props.count
@@ -6539,7 +6646,7 @@ var searchFeature = defineFeature({
6539
6646
  import: ctx.import ? name : void 0
6540
6647
  }
6541
6648
  );
6542
- ctx.addEnv(`SEARCH_${constantCase8(ctx.stack.name)}_${constantCase8(id)}_DOMAIN`, openSearch.endpoint);
6649
+ ctx.addEnv(`SEARCH_${constantCase8(ctx.stack.name)}_${constantCase8(id)}_DOMAIN`, openSearch.endpointV2);
6543
6650
  ctx.addStackPermission({
6544
6651
  actions: ["es:ESHttp*"],
6545
6652
  resources: [
@@ -9083,6 +9190,7 @@ var getViewerRequestFunctionCode = (props) => {
9083
9190
  return CODE(
9084
9191
  [
9085
9192
  props.blockDirectAccess ? BLOCK_DIRECT_ACCESS_TO_CLOUDFRONT : "",
9193
+ props.redirectWww ? REDIRECT_WWW : "",
9086
9194
  props.passwordAuth ?? props.basicAuth ? AUTH_WRAPPER(
9087
9195
  [
9088
9196
  //
@@ -9091,7 +9199,7 @@ var getViewerRequestFunctionCode = (props) => {
9091
9199
  ].join("\n")
9092
9200
  ) : ""
9093
9201
  ],
9094
- ACTIVE_PREFIX(props.router)
9202
+ props.preview ? PREVIEW_PREFIX(props.router) : ACTIVE_PREFIX(props.router)
9095
9203
  );
9096
9204
  };
9097
9205
  var BLOCK_DIRECT_ACCESS_TO_CLOUDFRONT = `
@@ -9101,6 +9209,38 @@ if (headers.host && headers.host.value.includes('cloudfront.net')) {
9101
9209
  statusDescription: 'Forbidden'
9102
9210
  };
9103
9211
  }`;
9212
+ var REDIRECT_WWW = `
9213
+ if (headers.host && headers.host.value.startsWith('www.')) {
9214
+ let location = 'https://' + headers.host.value.slice(4) + request.uri;
9215
+ const query = [];
9216
+
9217
+ for(const key in request.querystring) {
9218
+ const item = request.querystring[key];
9219
+
9220
+ if(item.multiValue) {
9221
+ for(const i in item.multiValue) {
9222
+ query.push(key + '=' + item.multiValue[i].value);
9223
+ }
9224
+ } else if(item.value === '') {
9225
+ query.push(key);
9226
+ } else {
9227
+ query.push(key + '=' + item.value);
9228
+ }
9229
+ }
9230
+
9231
+ if(query.length > 0) {
9232
+ location += '?' + query.join('&');
9233
+ }
9234
+
9235
+ return {
9236
+ statusCode: 301,
9237
+ statusDescription: 'Moved Permanently',
9238
+ headers: {
9239
+ 'location': { value: location },
9240
+ 'strict-transport-security': { value: 'max-age=31536000; includeSubdomains; preload' }
9241
+ }
9242
+ };
9243
+ }`;
9104
9244
  var BASIC_AUTH_CHECK = (username, password) => `
9105
9245
  authMethods.push('Basic realm="Protected"');
9106
9246
 
@@ -9119,6 +9259,58 @@ if(!isAuthorized) {
9119
9259
  }
9120
9260
  }
9121
9261
  `;
9262
+ var PREVIEW_PREFIX = (router) => `
9263
+ const router = ${JSON.stringify(router)};
9264
+ let deployment;
9265
+
9266
+ if (request.querystring['awsless-deployment'] && request.querystring['awsless-deployment'].value) {
9267
+ deployment = request.querystring['awsless-deployment'].value;
9268
+ } else if (request.cookies && request.cookies['awsless-deployment'] && request.cookies['awsless-deployment'].value) {
9269
+ deployment = request.cookies['awsless-deployment'].value;
9270
+ }
9271
+
9272
+ let prefix;
9273
+
9274
+ try {
9275
+ const pointer = deployment ? '$deploy:' + deployment : '$active';
9276
+ prefix = (await cf.kvs().get(pointer)).split(':')[0] + ':' + router + ':';
9277
+ } catch (e) {
9278
+ return deployment
9279
+ ? { statusCode: 404, statusDescription: 'Unknown Deployment' }
9280
+ : { statusCode: 503, statusDescription: 'Service Unavailable' };
9281
+ }
9282
+
9283
+ if (request.querystring['awsless-deployment']) {
9284
+ delete request.querystring['awsless-deployment'];
9285
+
9286
+ const query = [];
9287
+
9288
+ for (const key in request.querystring) {
9289
+ const entry = request.querystring[key];
9290
+
9291
+ if (entry.multiValue) {
9292
+ for (const item of entry.multiValue) {
9293
+ query.push(key + '=' + item.value);
9294
+ }
9295
+ } else {
9296
+ query.push(key + '=' + entry.value);
9297
+ }
9298
+ }
9299
+
9300
+ return {
9301
+ statusCode: 302,
9302
+ statusDescription: 'Found',
9303
+ headers: {
9304
+ location: { value: request.uri + (query.length ? '?' + query.join('&') : '') }
9305
+ },
9306
+ cookies: {
9307
+ 'awsless-deployment': {
9308
+ value: deployment,
9309
+ attributes: 'Path=/; Secure; SameSite=Lax'
9310
+ }
9311
+ }
9312
+ };
9313
+ }`;
9122
9314
  var ACTIVE_PREFIX = (router) => `
9123
9315
  const router = ${JSON.stringify(router)};
9124
9316
  let prefix;
@@ -9416,6 +9608,7 @@ var routerFeature = defineFeature({
9416
9608
  code: getViewerRequestFunctionCode({
9417
9609
  router: id,
9418
9610
  blockDirectAccess: !!props.domain,
9611
+ redirectWww: !!props.domain && props.redirectWww,
9419
9612
  basicAuth: props.basicAuth,
9420
9613
  passwordAuth: props.passwordAuth
9421
9614
  }),
@@ -9734,6 +9927,7 @@ var routerFeature = defineFeature({
9734
9927
  runtime: "cloudfront-js-2.0",
9735
9928
  code: getViewerRequestFunctionCode({
9736
9929
  router: id,
9930
+ preview: true,
9737
9931
  basicAuth: props.basicAuth,
9738
9932
  passwordAuth: props.passwordAuth
9739
9933
  }),
@@ -9746,6 +9940,7 @@ var routerFeature = defineFeature({
9746
9940
  },
9747
9941
  comment: `${name} preview`,
9748
9942
  enabled: true,
9943
+ isIpv6Enabled: true,
9749
9944
  waitForDeployment: true,
9750
9945
  origin: [
9751
9946
  {
@@ -9841,22 +10036,6 @@ var routerFeature = defineFeature({
9841
10036
  dependsOn: Array.from(routeDependencies)
9842
10037
  }
9843
10038
  );
9844
- if (!(props.basicAuth ?? props.passwordAuth)) {
9845
- bundle.addEnv(
9846
- "AWSLESS_PREVIEW",
9847
- $resolve(
9848
- [routes],
9849
- (routes2) => JSON.stringify({
9850
- router: id,
9851
- routes: Object.fromEntries(
9852
- Object.entries(routes2).filter(
9853
- ([key, route]) => key.startsWith(`${id}:`) && route.type !== "url"
9854
- )
9855
- )
9856
- })
9857
- )
9858
- );
9859
- }
9860
10039
  });
9861
10040
  }
9862
10041
  ctx.shared.add("router", "id", id, distribution.id);
@@ -9864,29 +10043,50 @@ var routerFeature = defineFeature({
9864
10043
  distributionIds.push(distribution.id);
9865
10044
  if (props.domain) {
9866
10045
  const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
10046
+ const wwwDomainName = props.redirectWww ? `www.${domainName}` : void 0;
9867
10047
  const certificateArn = ctx.shared.entry("domain", `global-certificate-arn`, props.domain);
9868
10048
  const zoneId = ctx.shared.entry("domain", "zone-id", props.domain);
9869
10049
  const connectionGroup = new aws30.cloudfront.ConnectionGroup(group, "connection-group", {
9870
- name
10050
+ name,
10051
+ ipv6Enabled: true
9871
10052
  });
9872
10053
  new aws30.cloudfront.DistributionTenant(group, `tenant`, {
9873
10054
  name,
9874
10055
  enabled: true,
9875
10056
  distributionId: distribution.id,
9876
10057
  connectionGroupId: connectionGroup.id,
9877
- domain: [{ domain: domainName }],
10058
+ domain: [
10059
+ //
10060
+ { domain: domainName },
10061
+ ...wwwDomainName ? [{ domain: wwwDomainName }] : []
10062
+ ],
9878
10063
  customizations: [{ certificate: [{ arn: certificateArn }] }]
9879
10064
  });
9880
- new aws30.route53.Record(group, `record`, {
9881
- zoneId,
9882
- type: "A",
9883
- name: domainName,
9884
- alias: {
9885
- name: connectionGroup.routingEndpoint,
9886
- zoneId: "Z2FDTNDATAQYW2",
9887
- evaluateTargetHealth: false
9888
- }
9889
- });
10065
+ for (const [recordId, recordName] of [
10066
+ ["record", domainName],
10067
+ ...wwwDomainName ? [["www-record", wwwDomainName]] : []
10068
+ ]) {
10069
+ new aws30.route53.Record(group, recordId, {
10070
+ zoneId,
10071
+ type: "A",
10072
+ name: recordName,
10073
+ alias: {
10074
+ name: connectionGroup.routingEndpoint,
10075
+ zoneId: "Z2FDTNDATAQYW2",
10076
+ evaluateTargetHealth: false
10077
+ }
10078
+ });
10079
+ new aws30.route53.Record(group, `${recordId}-ipv6`, {
10080
+ zoneId,
10081
+ type: "AAAA",
10082
+ name: recordName,
10083
+ alias: {
10084
+ name: connectionGroup.routingEndpoint,
10085
+ zoneId: "Z2FDTNDATAQYW2",
10086
+ evaluateTargetHealth: false
10087
+ }
10088
+ });
10089
+ }
9890
10090
  ctx.bind(`ROUTER_${constantCase15(id)}_ENDPOINT`, domainName);
9891
10091
  }
9892
10092
  }
@@ -11391,6 +11591,10 @@ var deploy = (program2) => {
11391
11591
  appConfig,
11392
11592
  id: deployment.id
11393
11593
  });
11594
+ await promoteAppDeployment({
11595
+ appConfig,
11596
+ id: deployment.id
11597
+ });
11394
11598
  return deployments3;
11395
11599
  } finally {
11396
11600
  await release();
@@ -560,6 +560,7 @@ var RouterDefaultSchema = z16.record(
560
560
  z16.object({
561
561
  domain: ResourceIdSchema.describe("The domain id to link your Router.").optional(),
562
562
  subDomain: z16.string().optional(),
563
+ redirectWww: z16.boolean().default(false).describe("Redirect all www subdomain requests to your root domain."),
563
564
  waf: WafSettingsSchema.optional(),
564
565
  geoRestrictions: z16.array(z16.string().length(2).toUpperCase()).default([]).describe("Specifies a blacklist of countries that should be blocked."),
565
566
  errors: z16.object({
@@ -648,6 +649,21 @@ var RouterDefaultSchema = z16.record(
648
649
  }).optional().describe(
649
650
  "Specifies the cookies, headers, and query values that CloudFront includes in the cache key."
650
651
  )
652
+ }).superRefine((props, ctx) => {
653
+ if (props.redirectWww && !props.domain) {
654
+ ctx.addIssue({
655
+ code: z16.ZodIssueCode.custom,
656
+ path: ["redirectWww"],
657
+ message: "The redirectWww option requires a domain to be set."
658
+ });
659
+ }
660
+ if (props.redirectWww && props.subDomain) {
661
+ ctx.addIssue({
662
+ code: z16.ZodIssueCode.custom,
663
+ path: ["redirectWww"],
664
+ message: `The redirectWww option can't be combined with a subDomain, because the domain certificate only covers single level subdomains.`
665
+ });
666
+ }
651
667
  })
652
668
  ).optional().describe(`Define the global Router. Backed by AWS CloudFront.`);
653
669