@awsless/awsless 0.0.489 → 0.0.490

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
@@ -861,7 +861,7 @@ var LogRetentionSchema = DurationSchema.refine(
861
861
  (duration) => {
862
862
  return validLogRetentionDays.includes(toDays(duration));
863
863
  },
864
- `Invalid log retention. Valid days are: ${validLogRetentionDays.map((days6) => `${days6}`).join(", ")}`
864
+ `Invalid log retention. Valid days are: ${validLogRetentionDays.map((days7) => `${days7}`).join(", ")}`
865
865
  ).describe("The log retention duration.");
866
866
  var LogSchema = z14.union([
867
867
  z14.boolean().transform((enabled) => ({ retention: enabled ? days(7) : days(0) })),
@@ -1241,7 +1241,7 @@ var AppSchema = z24.object({
1241
1241
  });
1242
1242
 
1243
1243
  // src/config/stack.ts
1244
- import { z as z38 } from "zod";
1244
+ import { z as z39 } from "zod";
1245
1245
 
1246
1246
  // src/feature/cache/schema.ts
1247
1247
  import { gibibytes as gibibytes2 } from "@awsless/size";
@@ -1676,24 +1676,96 @@ var StoresSchema = z33.union([
1676
1676
  )
1677
1677
  ]).optional().describe("Define the stores in your stack.");
1678
1678
 
1679
- // src/feature/table/schema.ts
1680
- import { minutes as minutes4, seconds as seconds4 } from "@awsless/duration";
1679
+ // src/feature/images/schema.ts
1681
1680
  import { z as z34 } from "zod";
1682
- var KeySchema = z34.string().min(1).max(255);
1683
- var TablesSchema = z34.record(
1681
+ var transformationOptionsSchema = z34.object({
1682
+ // Resize options
1683
+ width: z34.number().int().positive().optional(),
1684
+ height: z34.number().int().positive().optional(),
1685
+ fit: z34.enum(["cover", "contain", "fill", "inside", "outside"]).optional(),
1686
+ position: z34.enum(["top", "right top", "right", "right bottom", "bottom", "left bottom", "left", "left top", "center"]).optional(),
1687
+ // Format options
1688
+ quality: z34.number().int().min(1).max(100).optional(),
1689
+ progressive: z34.boolean().optional(),
1690
+ // Processing options
1691
+ rotate: z34.number().optional(),
1692
+ flip: z34.boolean().optional(),
1693
+ flop: z34.boolean().optional(),
1694
+ blur: z34.number().min(0.3).max(1e3).optional(),
1695
+ sharpen: z34.boolean().optional(),
1696
+ grayscale: z34.boolean().optional(),
1697
+ normalize: z34.boolean().optional()
1698
+ });
1699
+ var extensionOptionsSchema = z34.object({
1700
+ // WebP specific
1701
+ effort: z34.number().int().min(0).max(6).optional(),
1702
+ lossless: z34.boolean().optional(),
1703
+ nearLossless: z34.boolean().optional(),
1704
+ // smartSubsample: z.boolean().optional(),
1705
+ // JPEG specific
1706
+ mozjpeg: z34.boolean().optional(),
1707
+ // trellisQuantisation: z.boolean().optional(),
1708
+ // overshootDeringing: z.boolean().optional(),
1709
+ // optimiseScans: z.boolean().optional(),
1710
+ // PNG specific
1711
+ compressionLevel: z34.number().int().min(0).max(9).optional(),
1712
+ adaptiveFiltering: z34.boolean().optional(),
1713
+ // palette: z.boolean().optional(),
1714
+ // AVIF specific
1715
+ speed: z34.number().int().min(0).max(9).optional()
1716
+ // chromaSubsampling: z.string().optional(),
1717
+ });
1718
+ var staticOriginSchema = LocalDirectorySchema.describe(
1719
+ "Specifies the path to a image directory that will be uploaded in S3."
1720
+ );
1721
+ var functionOriginSchema = FunctionSchema.describe(
1722
+ "Specifies the file that will be called when an image isn't found in the S3 bucket."
1723
+ );
1724
+ var ImagesSchema = z34.record(
1684
1725
  ResourceIdSchema,
1685
1726
  z34.object({
1727
+ domain: ResourceIdSchema.describe("The domain id to link your site with.").optional(),
1728
+ subDomain: z34.string().optional(),
1729
+ presets: z34.record(z34.string(), transformationOptionsSchema).describe("Named presets for image transformations"),
1730
+ extensions: z34.record(z34.enum(["jpeg", "jpg", "png", "webp"]), extensionOptionsSchema).describe("Format-specific optimization options"),
1731
+ origin: z34.union([
1732
+ z34.object({
1733
+ static: staticOriginSchema,
1734
+ function: functionOriginSchema.optional()
1735
+ }),
1736
+ z34.object({
1737
+ static: staticOriginSchema.optional(),
1738
+ function: functionOriginSchema
1739
+ }),
1740
+ z34.object({
1741
+ static: staticOriginSchema,
1742
+ function: functionOriginSchema
1743
+ })
1744
+ ]).describe(
1745
+ "Image transformation will be applied from a base image. Base images orginates from a local directory that will be uploaded to S3 or from a lambda function."
1746
+ )
1747
+ // postprocess: FunctionSchema.optional()
1748
+ })
1749
+ ).optional().describe("Define image CDN & transformations in your stack.");
1750
+
1751
+ // src/feature/table/schema.ts
1752
+ import { minutes as minutes4, seconds as seconds4 } from "@awsless/duration";
1753
+ import { z as z35 } from "zod";
1754
+ var KeySchema = z35.string().min(1).max(255);
1755
+ var TablesSchema = z35.record(
1756
+ ResourceIdSchema,
1757
+ z35.object({
1686
1758
  hash: KeySchema.describe(
1687
1759
  "Specifies the name of the partition / hash key that makes up the primary key for the table."
1688
1760
  ),
1689
1761
  sort: KeySchema.optional().describe(
1690
1762
  "Specifies the name of the range / sort key that makes up the primary key for the table."
1691
1763
  ),
1692
- fields: z34.record(z34.string(), z34.enum(["string", "number", "binary"])).optional().describe(
1764
+ fields: z35.record(z35.string(), z35.enum(["string", "number", "binary"])).optional().describe(
1693
1765
  'A list of attributes that describe the key schema for the table and indexes. If no attribute field is defined we default to "string".'
1694
1766
  ),
1695
- class: z34.enum(["standard", "standard-infrequent-access"]).default("standard").describe("The table class of the table."),
1696
- pointInTimeRecovery: z34.boolean().default(false).describe("Indicates whether point in time recovery is enabled on the table."),
1767
+ class: z35.enum(["standard", "standard-infrequent-access"]).default("standard").describe("The table class of the table."),
1768
+ pointInTimeRecovery: z35.boolean().default(false).describe("Indicates whether point in time recovery is enabled on the table."),
1697
1769
  ttl: KeySchema.optional().describe(
1698
1770
  [
1699
1771
  "The name of the TTL attribute used to store the expiration time for items in the table.",
@@ -1701,8 +1773,8 @@ var TablesSchema = z34.record(
1701
1773
  ].join("\n")
1702
1774
  ),
1703
1775
  // deletionProtection: DeletionProtectionSchema.optional(),
1704
- stream: z34.object({
1705
- type: z34.enum(["keys-only", "new-image", "old-image", "new-and-old-images"]).describe(
1776
+ stream: z35.object({
1777
+ type: z35.enum(["keys-only", "new-image", "old-image", "new-and-old-images"]).describe(
1706
1778
  [
1707
1779
  "When an item in the table is modified, you can determines what information is written to the stream for this table.",
1708
1780
  "Valid values are:",
@@ -1712,7 +1784,7 @@ var TablesSchema = z34.record(
1712
1784
  "- new-and-old-images - Both the new and the old item images of the item are written to the stream."
1713
1785
  ].join("\n")
1714
1786
  ),
1715
- batchSize: z34.number().min(1).max(1e4).default(1).describe(
1787
+ batchSize: z35.number().min(1).max(1e4).default(1).describe(
1716
1788
  [
1717
1789
  "The maximum number of records in each batch that Lambda pulls from your stream and sends to your function.",
1718
1790
  "Lambda passes all of the records in the batch to the function in a single call, up to the payload limit for synchronous invocation (6 MB).",
@@ -1742,7 +1814,7 @@ var TablesSchema = z34.record(
1742
1814
  // 'You can specify a number from -1 to 10000.',
1743
1815
  // ].join('\n')
1744
1816
  // ),
1745
- retryAttempts: z34.number().min(-1).max(1e4).default(-1).describe(
1817
+ retryAttempts: z35.number().min(-1).max(1e4).default(-1).describe(
1746
1818
  [
1747
1819
  "Discard records after the specified number of retries.",
1748
1820
  "The default value is -1, which sets the maximum number of retries to infinite.",
@@ -1750,7 +1822,7 @@ var TablesSchema = z34.record(
1750
1822
  "You can specify a number from -1 to 10000."
1751
1823
  ].join("\n")
1752
1824
  ),
1753
- concurrencyPerShard: z34.number().min(1).max(10).default(1).describe(
1825
+ concurrencyPerShard: z35.number().min(1).max(10).default(1).describe(
1754
1826
  [
1755
1827
  "The number of batches to process concurrently from each shard.",
1756
1828
  "You can specify a number from 1 to 10."
@@ -1760,16 +1832,16 @@ var TablesSchema = z34.record(
1760
1832
  }).optional().describe(
1761
1833
  "The settings for the DynamoDB table stream, which capture changes to items stored in the table."
1762
1834
  ),
1763
- indexes: z34.record(
1764
- z34.string(),
1765
- z34.object({
1835
+ indexes: z35.record(
1836
+ z35.string(),
1837
+ z35.object({
1766
1838
  hash: KeySchema.describe(
1767
1839
  "Specifies the name of the partition / hash key that makes up the primary key for the global secondary index."
1768
1840
  ),
1769
1841
  sort: KeySchema.optional().describe(
1770
1842
  "Specifies the name of the range / sort key that makes up the primary key for the global secondary index."
1771
1843
  ),
1772
- projection: z34.enum(["all", "keys-only"]).default("all").describe(
1844
+ projection: z35.enum(["all", "keys-only"]).default("all").describe(
1773
1845
  [
1774
1846
  "The set of attributes that are projected into the index:",
1775
1847
  "- all - All of the table attributes are projected into the index.",
@@ -1783,11 +1855,11 @@ var TablesSchema = z34.record(
1783
1855
  ).optional().describe("Define the tables in your stack.");
1784
1856
 
1785
1857
  // src/feature/task/schema.ts
1786
- import { z as z35 } from "zod";
1787
- var RetryAttemptsSchema2 = z35.number().int().min(0).max(2).describe(
1858
+ import { z as z36 } from "zod";
1859
+ var RetryAttemptsSchema2 = z36.number().int().min(0).max(2).describe(
1788
1860
  "The maximum number of times to retry when the function returns an error. You can specify a number from 0 to 2."
1789
1861
  );
1790
- var TaskSchema = z35.union([
1862
+ var TaskSchema = z36.union([
1791
1863
  LocalFileSchema.transform((file) => ({
1792
1864
  consumer: {
1793
1865
  code: {
@@ -1798,33 +1870,33 @@ var TaskSchema = z35.union([
1798
1870
  },
1799
1871
  retryAttempts: void 0
1800
1872
  })),
1801
- z35.object({
1873
+ z36.object({
1802
1874
  consumer: FunctionSchema,
1803
1875
  retryAttempts: RetryAttemptsSchema2.optional()
1804
1876
  })
1805
1877
  ]);
1806
- var TasksSchema = z35.record(ResourceIdSchema, TaskSchema).optional().describe("Define the tasks in your stack.");
1878
+ var TasksSchema = z36.record(ResourceIdSchema, TaskSchema).optional().describe("Define the tasks in your stack.");
1807
1879
 
1808
1880
  // src/feature/test/schema.ts
1809
- import { z as z36 } from "zod";
1810
- var TestsSchema = z36.union([LocalDirectorySchema.transform((v) => [v]), LocalDirectorySchema.array()]).describe("Define the location of your tests for your stack.").optional();
1881
+ import { z as z37 } from "zod";
1882
+ var TestsSchema = z37.union([LocalDirectorySchema.transform((v) => [v]), LocalDirectorySchema.array()]).describe("Define the location of your tests for your stack.").optional();
1811
1883
 
1812
1884
  // src/feature/topic/schema.ts
1813
1885
  import { kebabCase as kebabCase3 } from "change-case";
1814
- import { z as z37 } from "zod";
1815
- var TopicNameSchema = z37.string().min(3).max(256).regex(/^[a-z0-9\-]+$/i, "Invalid topic name").transform((value) => kebabCase3(value)).describe("Define event topic name.");
1816
- var TopicsSchema = z37.array(TopicNameSchema).refine((topics) => {
1886
+ import { z as z38 } from "zod";
1887
+ var TopicNameSchema = z38.string().min(3).max(256).regex(/^[a-z0-9\-]+$/i, "Invalid topic name").transform((value) => kebabCase3(value)).describe("Define event topic name.");
1888
+ var TopicsSchema = z38.array(TopicNameSchema).refine((topics) => {
1817
1889
  return topics.length === new Set(topics).size;
1818
1890
  }, "Must be a list of unique topic names").optional().describe("Define the event topics to publish too in your stack.");
1819
- var SubscribersSchema = z37.record(TopicNameSchema, FunctionSchema).optional().describe("Define the event topics to subscribe too in your stack.");
1891
+ var SubscribersSchema = z38.record(TopicNameSchema, FunctionSchema).optional().describe("Define the event topics to subscribe too in your stack.");
1820
1892
 
1821
1893
  // src/config/stack.ts
1822
1894
  var DependsSchema = ResourceIdSchema.array().optional().describe("Define the stacks that this stack is depended on.");
1823
1895
  var NameSchema = ResourceIdSchema.refine((name) => !["base", "hostedzones"].includes(name), {
1824
1896
  message: `Stack name can't be a reserved name.`
1825
1897
  }).describe("Stack name.");
1826
- var StackSchema = z38.object({
1827
- $schema: z38.string().optional(),
1898
+ var StackSchema = z39.object({
1899
+ $schema: z39.string().optional(),
1828
1900
  name: NameSchema,
1829
1901
  depends: DependsSchema,
1830
1902
  commands: CommandsSchema,
@@ -1849,7 +1921,8 @@ var StackSchema = z38.object({
1849
1921
  pubsub: PubSubSchema,
1850
1922
  searchs: SearchsSchema,
1851
1923
  sites: SitesSchema,
1852
- tests: TestsSchema
1924
+ tests: TestsSchema,
1925
+ images: ImagesSchema
1853
1926
  });
1854
1927
 
1855
1928
  // src/config/load/read.ts
@@ -1891,13 +1964,13 @@ var readConfigWithStage = async (file, stage) => {
1891
1964
  };
1892
1965
 
1893
1966
  // src/config/load/validate.ts
1894
- import { z as z39 } from "zod";
1967
+ import { z as z40 } from "zod";
1895
1968
  var validateConfig = async (schema, file, data) => {
1896
1969
  try {
1897
1970
  const result = await schema.parseAsync(data);
1898
1971
  return result;
1899
1972
  } catch (error) {
1900
- if (error instanceof z39.ZodError) {
1973
+ if (error instanceof z40.ZodError) {
1901
1974
  throw new ConfigError(file, error, data);
1902
1975
  }
1903
1976
  throw error;
@@ -4076,19 +4149,23 @@ var createPrebuildLambdaFunction = (group, ctx, ns, id, props) => {
4076
4149
  }
4077
4150
  if (props.log?.retention && props.log?.retention?.value > 0n) {
4078
4151
  const logGroup = new $12.aws.cloudwatch.LogGroup(group, "log", {
4079
- name: lambda.functionName.pipe((name2) => `/aws/lambda/${name2}`),
4152
+ name: `/aws/lambda/${name}`,
4080
4153
  retentionInDays: toDays4(props.log.retention ?? days3(7))
4081
4154
  });
4082
- addPermission(
4083
- {
4084
- actions: ["logs:CreateLogStream"],
4085
- resources: [logGroup.arn]
4086
- },
4087
- {
4088
- actions: ["logs:PutLogEvents"],
4089
- resources: [logGroup.arn.pipe((arn) => `${arn}:*`)]
4090
- }
4091
- );
4155
+ addPermission({
4156
+ actions: ["logs:PutLogEvents", "logs:CreateLogStream"],
4157
+ resources: [logGroup.arn.pipe((arn) => `${arn}:*`)]
4158
+ });
4159
+ const onLogArn = getGlobalOnLog(ctx);
4160
+ if (onLogArn && ctx.appConfig.defaults.onLog) {
4161
+ const logFilter = ctx.appConfig.defaults.onLog.filter;
4162
+ new $12.aws.cloudwatch.LogSubscriptionFilter(group, `on-log`, {
4163
+ name: "log-subscription",
4164
+ destinationArn: onLogArn,
4165
+ logGroupName: logGroup.name,
4166
+ filterPattern: formatFilterPattern(logFilter)
4167
+ });
4168
+ }
4092
4169
  }
4093
4170
  if (props.warm) {
4094
4171
  const rule = new $12.aws.cloudwatch.EventRule(group, "warm", {
@@ -5696,10 +5773,6 @@ import { $ as $21, Group as Group22 } from "@awsless/formation";
5696
5773
  var layerFeature = defineFeature({
5697
5774
  name: "layer",
5698
5775
  onBefore(ctx) {
5699
- const layers = Object.entries(ctx.appConfig.defaults.layers ?? {});
5700
- if (layers.length === 0) {
5701
- return;
5702
- }
5703
5776
  const group = new Group22(ctx.base, "layer", "asset");
5704
5777
  const bucket = new $21.aws.s3.Bucket(group, "bucket", {
5705
5778
  bucket: formatGlobalResourceName({
@@ -5731,7 +5804,7 @@ var layerFeature = defineFeature({
5731
5804
  for (const [id, _props] of layers) {
5732
5805
  const props = _props;
5733
5806
  const group = new Group22(ctx.base, "layer", id);
5734
- const code = new $21.aws.s3.BucketObject(group, "code", {
5807
+ const zip = new $21.aws.s3.BucketObject(group, "zip", {
5735
5808
  bucket: ctx.shared.get("layer", "bucket-name"),
5736
5809
  key: `/layer/${id}.zip`,
5737
5810
  contentType: "application/zip",
@@ -5750,9 +5823,9 @@ var layerFeature = defineFeature({
5750
5823
  description: id,
5751
5824
  compatibleArchitectures: props.architecture ? [props.architecture] : void 0,
5752
5825
  compatibleRuntimes: props.runtimes,
5753
- s3Bucket: code.bucket,
5754
- s3ObjectVersion: code.versionId,
5755
- s3Key: code.key.pipe((name) => {
5826
+ s3Bucket: zip.bucket,
5827
+ s3ObjectVersion: zip.versionId,
5828
+ s3Key: zip.key.pipe((name) => {
5756
5829
  if (name.startsWith("/")) {
5757
5830
  return name.substring(1);
5758
5831
  }
@@ -5761,7 +5834,7 @@ var layerFeature = defineFeature({
5761
5834
  sourceCodeHash: $hash(props.file)
5762
5835
  },
5763
5836
  {
5764
- dependsOn: [code]
5837
+ dependsOn: [zip]
5765
5838
  }
5766
5839
  );
5767
5840
  ctx.shared.add("layer", "arn", id, layer.arn);
@@ -5770,6 +5843,248 @@ var layerFeature = defineFeature({
5770
5843
  }
5771
5844
  });
5772
5845
 
5846
+ // src/feature/images/index.ts
5847
+ import { $ as $22, Group as Group23 } from "@awsless/formation";
5848
+ import { glob as glob3 } from "glob";
5849
+ import { join as join12, extname as extname3, dirname as dirname8 } from "path";
5850
+ import { contentType as contentType2 } from "mime-types";
5851
+ import { Future as Future4 } from "@awsless/formation";
5852
+ import { createHash as createHash5 } from "crypto";
5853
+ import { mebibytes as mebibytes4 } from "@awsless/size";
5854
+ import { days as days6, seconds as seconds9, toSeconds as toSeconds8, weeks } from "@awsless/duration";
5855
+ import { constantCase as constantCase11 } from "change-case";
5856
+ import { fileURLToPath as fileURLToPath2 } from "url";
5857
+ var __dirname2 = dirname8(fileURLToPath2(import.meta.url));
5858
+ var imagesFeature = defineFeature({
5859
+ name: "images",
5860
+ onStack(ctx) {
5861
+ for (const [id, props] of Object.entries(ctx.stackConfig.images ?? {})) {
5862
+ const group = new Group23(ctx.stack, "images", id);
5863
+ const name = formatLocalResourceName({
5864
+ appName: ctx.app.name,
5865
+ stackName: ctx.stack.name,
5866
+ resourceType: "images",
5867
+ resourceName: id
5868
+ });
5869
+ const originGroup = new Group23(ctx.stack, "images-origins", id);
5870
+ let lambdaOrigin = void 0;
5871
+ if (props.origin.function) {
5872
+ lambdaOrigin = createLambdaFunction(originGroup, ctx, `images-lambda-origin`, id, props.origin.function);
5873
+ }
5874
+ let s3Origin;
5875
+ if (props.origin.static) {
5876
+ s3Origin = new $22.aws.s3.Bucket(originGroup, "bucket", {
5877
+ bucket: formatLocalResourceName({
5878
+ appName: ctx.app.name,
5879
+ stackName: ctx.stack.name,
5880
+ resourceType: "images",
5881
+ resourceName: id,
5882
+ postfix: ctx.appId
5883
+ }),
5884
+ forceDestroy: true
5885
+ });
5886
+ }
5887
+ const path = join12(__dirname2, "/layers/sharp-arm.zip");
5888
+ const layerId = formatLocalResourceName({
5889
+ appName: ctx.appConfig.name,
5890
+ stackName: ctx.stack.name,
5891
+ resourceType: "layer",
5892
+ resourceName: shortId(id)
5893
+ });
5894
+ const zipFile = new $22.aws.s3.BucketObject(group, "layer-zip", {
5895
+ bucket: ctx.shared.get("layer", "bucket-name"),
5896
+ key: `/layer/${layerId}.zip`,
5897
+ contentType: "application/zip",
5898
+ source: path,
5899
+ sourceHash: $hash(path)
5900
+ });
5901
+ const layer = new $22.aws.lambda.LayerVersion(
5902
+ group,
5903
+ "layer",
5904
+ {
5905
+ layerName: layerId,
5906
+ description: "sharp-arm.zip for the awsless images feature.",
5907
+ compatibleArchitectures: ["arm64"],
5908
+ compatibleRuntimes: ["nodejs22.x"],
5909
+ s3Bucket: zipFile.bucket,
5910
+ s3ObjectVersion: zipFile.versionId,
5911
+ s3Key: zipFile.key.pipe((name2) => {
5912
+ if (name2.startsWith("/")) {
5913
+ return name2.substring(1);
5914
+ }
5915
+ return name2;
5916
+ }),
5917
+ sourceCodeHash: $hash(path)
5918
+ },
5919
+ {
5920
+ dependsOn: [zipFile]
5921
+ }
5922
+ );
5923
+ ctx.shared.add("layer", "arn", layerId, layer.arn);
5924
+ const transformFn = createPrebuildLambdaFunction(group, ctx, "images", id, {
5925
+ bundleFile: join12(__dirname2, "/prebuild/images/bundle.zip"),
5926
+ bundleHash: join12(__dirname2, "/prebuild/images/HASH"),
5927
+ memorySize: mebibytes4(512),
5928
+ timeout: seconds9(10),
5929
+ handler: "index.default",
5930
+ runtime: "nodejs22.x",
5931
+ log: { retention: weeks(2) },
5932
+ layers: [layerId]
5933
+ });
5934
+ new UpdateFunctionCode(group, "update", {
5935
+ version: transformFn.code.sourceHash,
5936
+ functionName: transformFn.lambda.functionName,
5937
+ architectures: transformFn.lambda.architectures,
5938
+ s3Bucket: transformFn.lambda.s3Bucket,
5939
+ s3Key: transformFn.lambda.s3Key,
5940
+ s3ObjectVersion: transformFn.lambda.s3ObjectVersion,
5941
+ imageUri: transformFn.lambda.imageUri
5942
+ });
5943
+ const permission = new $22.aws.lambda.Permission(group, "permission", {
5944
+ principal: "cloudfront.amazonaws.com",
5945
+ action: "lambda:InvokeFunctionUrl",
5946
+ functionName: transformFn.lambda.functionName,
5947
+ functionUrlAuthType: "AWS_IAM",
5948
+ sourceArn: `arn:aws:cloudfront::${ctx.accountId}:distribution/*`
5949
+ });
5950
+ const transformFnUrl = new $22.aws.lambda.FunctionUrl(
5951
+ group,
5952
+ "url",
5953
+ {
5954
+ functionName: transformFn.lambda.functionName,
5955
+ authorizationType: "AWS_IAM"
5956
+ },
5957
+ { dependsOn: [permission] }
5958
+ );
5959
+ transformFn.setEnvironment(
5960
+ "IMAGES_CONFIG",
5961
+ JSON.stringify({
5962
+ presets: props.presets,
5963
+ extensions: props.extensions
5964
+ })
5965
+ );
5966
+ if (lambdaOrigin) {
5967
+ transformFn.setEnvironment("IMAGES_ORIGIN_LAMBDA", lambdaOrigin.name);
5968
+ }
5969
+ if (s3Origin) {
5970
+ transformFn.setEnvironment("IMAGES_ORIGIN_S3", s3Origin.bucket);
5971
+ transformFn.addPermission({
5972
+ actions: [
5973
+ "s3:ListBucket",
5974
+ "s3:ListBucketV2",
5975
+ "s3:HeadObject",
5976
+ "s3:GetObject",
5977
+ "s3:PutObject",
5978
+ "s3:DeleteObject",
5979
+ // 's3:CopyObject',
5980
+ "s3:GetObjectAttributes"
5981
+ ],
5982
+ resources: [
5983
+ //
5984
+ s3Origin.arn,
5985
+ s3Origin.arn.pipe((arn) => `${arn}/*`)
5986
+ ]
5987
+ });
5988
+ }
5989
+ const versions = [];
5990
+ ctx.onReady(() => {
5991
+ if (props.origin.static && s3Origin) {
5992
+ const files = glob3.sync("**", {
5993
+ cwd: props.origin.static,
5994
+ nodir: true
5995
+ });
5996
+ for (const file of files) {
5997
+ const object = new $22.aws.s3.BucketObject(group, `static-${file}`, {
5998
+ bucket: s3Origin.bucket,
5999
+ key: file,
6000
+ cacheControl: "public, max-age=31536000, immutable",
6001
+ contentType: contentType2(extname3(file)) || `image/${extname3(file).slice(1)}`,
6002
+ source: join12(props.origin.static, file),
6003
+ sourceHash: $hash(join12(props.origin.static, file))
6004
+ });
6005
+ versions.push(object.key);
6006
+ versions.push(object.sourceHash);
6007
+ }
6008
+ }
6009
+ });
6010
+ const domainName = props.domain ? formatFullDomainName(ctx.appConfig, props.domain, props.subDomain) : void 0;
6011
+ const certificateArn = props.domain ? ctx.shared.entry("domain", `global-certificate-arn`, props.domain) : void 0;
6012
+ const accessControl = new $22.aws.cloudfront.OriginAccessControl(group, "access", {
6013
+ name,
6014
+ description: "Policy for Images Lambda Transformation Function URL",
6015
+ originAccessControlOriginType: "lambda",
6016
+ signingBehavior: "always",
6017
+ signingProtocol: "sigv4"
6018
+ });
6019
+ const cache = new $22.aws.cloudfront.CachePolicy(group, "cache", {
6020
+ name,
6021
+ defaultTtl: toSeconds8(days6(365))
6022
+ });
6023
+ const distribution = new $22.aws.cloudfront.Distribution(group, "distribution", {
6024
+ comment: name,
6025
+ enabled: true,
6026
+ aliases: domainName ? [domainName] : void 0,
6027
+ priceClass: "PriceClass_All",
6028
+ httpVersion: "http2and3",
6029
+ viewerCertificate: certificateArn ? {
6030
+ sslSupportMethod: "sni-only",
6031
+ minimumProtocolVersion: "TLSv1.2_2021",
6032
+ acmCertificateArn: certificateArn
6033
+ } : {
6034
+ cloudfrontDefaultCertificate: true
6035
+ },
6036
+ origin: [
6037
+ {
6038
+ originId: "default",
6039
+ domainName: transformFnUrl.functionUrl.pipe((url) => url.split("/")[2]),
6040
+ originAccessControlId: accessControl.id,
6041
+ customOriginConfig: {
6042
+ originProtocolPolicy: "https-only",
6043
+ httpPort: 80,
6044
+ httpsPort: 443,
6045
+ originSslProtocols: ["TLSv1.2"]
6046
+ }
6047
+ }
6048
+ ],
6049
+ defaultCacheBehavior: {
6050
+ compress: true,
6051
+ targetOriginId: "default",
6052
+ cachePolicyId: cache.id,
6053
+ viewerProtocolPolicy: "redirect-to-https",
6054
+ allowedMethods: ["GET", "HEAD"],
6055
+ cachedMethods: ["GET", "HEAD"]
6056
+ }
6057
+ });
6058
+ new Invalidation(group, "invalidate", {
6059
+ distributionId: distribution.id,
6060
+ paths: ["/*"],
6061
+ version: new Future4((resolve) => {
6062
+ $combine(...versions).then((versions2) => {
6063
+ const combined = versions2.filter((v) => !!v).sort().join(",");
6064
+ const version = createHash5("sha1").update(combined).digest("hex");
6065
+ resolve(version);
6066
+ });
6067
+ })
6068
+ });
6069
+ if (domainName) {
6070
+ new $22.aws.route53.Record(group, `record`, {
6071
+ zoneId: ctx.shared.entry("domain", "zone-id", props.domain),
6072
+ type: "A",
6073
+ name: domainName,
6074
+ alias: {
6075
+ name: distribution.domainName,
6076
+ zoneId: distribution.hostedZoneId,
6077
+ evaluateTargetHealth: false
6078
+ }
6079
+ });
6080
+ ctx.bind(`IMAGES_${ctx.stack.name}_${constantCase11(id)}_ENDPOINT`, domainName);
6081
+ } else {
6082
+ ctx.bind(`IMAGES_${ctx.stack.name}_${constantCase11(id)}_ENDPOINT`, distribution.domainName);
6083
+ }
6084
+ }
6085
+ }
6086
+ });
6087
+
5773
6088
  // src/feature/index.ts
5774
6089
  var features = [
5775
6090
  // 1
@@ -5802,6 +6117,7 @@ var features = [
5802
6117
  // httpFeature,
5803
6118
  restFeature,
5804
6119
  siteFeature,
6120
+ imagesFeature,
5805
6121
  // 4
5806
6122
  rpcFeature
5807
6123
  ];
@@ -6663,13 +6979,13 @@ import wildstring4 from "wildstring";
6663
6979
  import { log as log9 } from "@clack/prompts";
6664
6980
  import chalk5 from "chalk";
6665
6981
  import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
6666
- import { join as join14 } from "path";
6982
+ import { join as join15 } from "path";
6667
6983
  import wildstring3 from "wildstring";
6668
6984
 
6669
6985
  // src/build/__fingerprint.ts
6670
- import { createHash as createHash5 } from "crypto";
6986
+ import { createHash as createHash6 } from "crypto";
6671
6987
  import { readdir as readdir4, readFile as readFile4, stat as stat4 } from "fs/promises";
6672
- import { basename as basename4, dirname as dirname8, extname as extname3, join as join12 } from "path";
6988
+ import { basename as basename4, dirname as dirname9, extname as extname4, join as join13 } from "path";
6673
6989
  import parseStaticImports from "parse-static-imports";
6674
6990
  var extensions = ["js", "mjs", "jsx", "ts", "mts", "tsx"];
6675
6991
  var generateFileHashes = async (file, hashes) => {
@@ -6678,7 +6994,7 @@ var generateFileHashes = async (file, hashes) => {
6678
6994
  }
6679
6995
  const code = await readModuleFile(file);
6680
6996
  const deps = await findDependencies(file, code);
6681
- const hash = createHash5("sha1").update(code).digest();
6997
+ const hash = createHash6("sha1").update(code).digest();
6682
6998
  hashes.set(file, hash);
6683
6999
  for (const dep of deps) {
6684
7000
  if (dep.startsWith("/")) {
@@ -6690,12 +7006,12 @@ var fingerprintFromDirectory = async (dir) => {
6690
7006
  const hashes = /* @__PURE__ */ new Map();
6691
7007
  const files = await readdir4(dir, { recursive: true });
6692
7008
  for (const file of files) {
6693
- if (extensions.includes(extname3(file).substring(1)) && file.at(0) !== "_") {
6694
- await generateFileHashes(join12(dir, file), hashes);
7009
+ if (extensions.includes(extname4(file).substring(1)) && file.at(0) !== "_") {
7010
+ await generateFileHashes(join13(dir, file), hashes);
6695
7011
  }
6696
7012
  }
6697
7013
  const merge2 = Buffer.concat(Array.from(hashes.values()).sort());
6698
- return createHash5("sha1").update(merge2).digest("hex");
7014
+ return createHash6("sha1").update(merge2).digest("hex");
6699
7015
  };
6700
7016
  var readModuleFile = (file) => {
6701
7017
  if (file.endsWith(".js")) {
@@ -6705,7 +7021,7 @@ var readModuleFile = (file) => {
6705
7021
  return readFiles([
6706
7022
  file,
6707
7023
  ...extensions.map((exp) => `${file}.${exp}`),
6708
- ...extensions.map((exp) => join12(file, `/index.${exp}`))
7024
+ ...extensions.map((exp) => join13(file, `/index.${exp}`))
6709
7025
  ]);
6710
7026
  }
6711
7027
  return readFile4(file, "utf8");
@@ -6725,7 +7041,7 @@ var readFiles = async (files) => {
6725
7041
  };
6726
7042
  var findDependencies = async (file, code) => {
6727
7043
  const imports = await parseStaticImports(code);
6728
- return imports.map((entry) => entry.moduleName).filter(Boolean).map((value) => value?.startsWith(".") ? join12(dirname8(file), value) : value);
7044
+ return imports.map((entry) => entry.moduleName).filter(Boolean).map((value) => value?.startsWith(".") ? join13(dirname9(file), value) : value);
6729
7045
  };
6730
7046
 
6731
7047
  // src/test/reporter.ts
@@ -6805,13 +7121,13 @@ var CustomReporter = class {
6805
7121
  import commonjs2 from "@rollup/plugin-commonjs";
6806
7122
  import json2 from "@rollup/plugin-json";
6807
7123
  import nodeResolve2 from "@rollup/plugin-node-resolve";
6808
- import { dirname as dirname9, join as join13 } from "path";
7124
+ import { dirname as dirname10, join as join14 } from "path";
6809
7125
  import { swc as swc2 } from "rollup-plugin-swc3";
6810
- import { fileURLToPath as fileURLToPath2 } from "url";
7126
+ import { fileURLToPath as fileURLToPath3 } from "url";
6811
7127
  import { configDefaults } from "vitest/config";
6812
7128
  import { startVitest } from "vitest/node";
6813
7129
  var startTest = async (props) => {
6814
- const __dirname2 = dirname9(fileURLToPath2(import.meta.url));
7130
+ const __dirname3 = dirname10(fileURLToPath3(import.meta.url));
6815
7131
  const result = await startVitest(
6816
7132
  "test",
6817
7133
  props.filters,
@@ -6827,7 +7143,7 @@ var startTest = async (props) => {
6827
7143
  reporters: props.reporter,
6828
7144
  setupFiles: [
6829
7145
  //
6830
- join13(__dirname2, "test-global-setup.js")
7146
+ join14(__dirname3, "test-global-setup.js")
6831
7147
  ]
6832
7148
  // globalSetup: [
6833
7149
  // //
@@ -6926,7 +7242,7 @@ var logTestErrors = (event) => {
6926
7242
  var runTest = async (stack, dir, filters) => {
6927
7243
  await mkdir4(directories.test, { recursive: true });
6928
7244
  const fingerprint = await fingerprintFromDirectory(dir);
6929
- const file = join14(directories.test, `${stack}.json`);
7245
+ const file = join15(directories.test, `${stack}.json`);
6930
7246
  const exists = await fileExist(file);
6931
7247
  if (exists && !process.env.NO_CACHE) {
6932
7248
  const raw = await readFile5(file, { encoding: "utf8" });
@@ -7165,7 +7481,7 @@ var auth = (program2) => {
7165
7481
 
7166
7482
  // src/cli/command/bind.ts
7167
7483
  import { log as log10, note as note3 } from "@clack/prompts";
7168
- import { constantCase as constantCase11 } from "change-case";
7484
+ import { constantCase as constantCase12 } from "change-case";
7169
7485
  import { spawn } from "child_process";
7170
7486
  var bind = (program2) => {
7171
7487
  program2.command("bind").argument("[command...]", "The command to execute").option("--config <string...>", "List of config values that will be accessable", (v) => v.split(",")).description(`Bind your site environment variables to a command`).action(async (commands7 = [], opts) => {
@@ -7194,10 +7510,10 @@ var bind = (program2) => {
7194
7510
  const configList = opts.config ?? [];
7195
7511
  const configs = {};
7196
7512
  for (const name of configList) {
7197
- configs[`CONFIG_${constantCase11(name)}`] = name;
7513
+ configs[`CONFIG_${constantCase12(name)}`] = name;
7198
7514
  }
7199
7515
  if (configList.length ?? 0 > 0) {
7200
- note3(wrap(configList.map((v) => color.label(constantCase11(v)))), "Bind Config");
7516
+ note3(wrap(configList.map((v) => color.label(constantCase12(v)))), "Bind Config");
7201
7517
  }
7202
7518
  if (commands7.length === 0) {
7203
7519
  return "No command to execute.";
@@ -7260,7 +7576,7 @@ import { log as log11 } from "@clack/prompts";
7260
7576
 
7261
7577
  // src/type-gen/generate.ts
7262
7578
  import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
7263
- import { dirname as dirname10, join as join15, relative as relative7 } from "path";
7579
+ import { dirname as dirname11, join as join16, relative as relative7 } from "path";
7264
7580
  var generateTypes = async (props) => {
7265
7581
  const files = [];
7266
7582
  await Promise.all(
@@ -7269,12 +7585,12 @@ var generateTypes = async (props) => {
7269
7585
  ...props,
7270
7586
  async write(file, data, include = false) {
7271
7587
  const code = data?.toString("utf8");
7272
- const path = join15(directories.types, file);
7588
+ const path = join16(directories.types, file);
7273
7589
  if (code) {
7274
7590
  if (include) {
7275
7591
  files.push(relative7(directories.root, path));
7276
7592
  }
7277
- await mkdir5(dirname10(path), { recursive: true });
7593
+ await mkdir5(dirname11(path), { recursive: true });
7278
7594
  await writeFile4(path, code);
7279
7595
  }
7280
7596
  }
@@ -7283,7 +7599,7 @@ var generateTypes = async (props) => {
7283
7599
  );
7284
7600
  if (files.length) {
7285
7601
  const code = files.map((file) => `/// <reference path='${file}' />`).join("\n");
7286
- await writeFile4(join15(directories.root, `awsless.d.ts`), code);
7602
+ await writeFile4(join16(directories.root, `awsless.d.ts`), code);
7287
7603
  }
7288
7604
  };
7289
7605