@stacksjs/ts-cloud 0.2.11 → 0.2.12

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.
@@ -25,6 +25,9 @@ export declare class InfrastructureGenerator {
25
25
  */
26
26
  private shouldDeploy;
27
27
  private resolveApiOriginPort;
28
+ private normalizeMountPath;
29
+ private storageBucketLogicalId;
30
+ private pathMountRewriteFunctionCode;
28
31
  /**
29
32
  * Generate complete infrastructure
30
33
  * Auto-detects what to generate based on configuration
package/dist/index.js CHANGED
@@ -62098,6 +62098,19 @@ class InfrastructureGenerator {
62098
62098
  const port = Number(configuredPort || 3008);
62099
62099
  return Number.isFinite(port) && port > 0 ? port : 3008;
62100
62100
  }
62101
+ normalizeMountPath(config6) {
62102
+ const rawPath = config6?.mountPath || config6?.path;
62103
+ if (!rawPath || rawPath === "/")
62104
+ return;
62105
+ const normalized = `/${rawPath}`.replace(/\/+/g, "/").replace(/\/$/, "");
62106
+ return normalized === "/" ? undefined : normalized;
62107
+ }
62108
+ storageBucketLogicalId(slug, env, name) {
62109
+ return `${slug}-${env}-s3-${name}`.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
62110
+ }
62111
+ pathMountRewriteFunctionCode(mountPath) {
62112
+ return `function handler(event) { var request = event.request; var prefix = ${JSON.stringify(mountPath)}; var uri = request.uri; if (uri === prefix) { uri = '/'; } else if (uri.indexOf(prefix + '/') === 0) { uri = uri.substring(prefix.length); } if (uri === '' || uri === '/') { request.uri = '/index.html'; return request; } if (uri.endsWith('/')) { request.uri = uri + 'index.html'; return request; } var lastSegment = uri.substring(uri.lastIndexOf('/') + 1); if (lastSegment.indexOf('.') === -1) { request.uri = uri + '/index.html'; return request; } request.uri = uri; return request; }`;
62113
+ }
62101
62114
  generate() {
62102
62115
  const slug = this.mergedConfig.project.slug;
62103
62116
  const env = this.environment;
@@ -62952,9 +62965,6 @@ class InfrastructureGenerator {
62952
62965
  }
62953
62966
  return d;
62954
62967
  });
62955
- if (this.mergedConfig.infrastructure?.storage?.docs && !subdomains.includes("docs")) {
62956
- subdomains.push("docs");
62957
- }
62958
62968
  const { certificate, logicalId: certLogicalId } = Security.createCertificate({
62959
62969
  domain: primaryDomain,
62960
62970
  subdomains,
@@ -62984,6 +62994,12 @@ class InfrastructureGenerator {
62984
62994
  }
62985
62995
  });
62986
62996
  }
62997
+ const pathMountedWebsiteBuckets = Object.entries(this.mergedConfig.infrastructure.storage).map(([bucketName, bucketConfig]) => ({
62998
+ name: bucketName,
62999
+ config: bucketConfig,
63000
+ mountPath: this.normalizeMountPath(bucketConfig),
63001
+ logicalId: this.storageBucketLogicalId(slug, env, bucketName)
63002
+ })).filter((bucket) => bucket.name !== "public" && bucket.config.website && bucket.mountPath);
62987
63003
  for (const [name, storageConfig] of Object.entries(this.mergedConfig.infrastructure.storage)) {
62988
63004
  const serveViaCloudFront = !!(storageConfig.website && sharedOacLogicalId);
62989
63005
  const { bucket, logicalId } = Storage.createBucket({
@@ -63009,7 +63025,8 @@ class InfrastructureGenerator {
63009
63025
  const enhanced = Storage.enableWebsiteHosting(bucket, websiteConfig.indexDocument || "index.html", websiteConfig.errorDocument);
63010
63026
  this.builder.addResource(logicalId, enhanced);
63011
63027
  }
63012
- if (serveViaCloudFront && sharedOacLogicalId && domain) {
63028
+ const mountPath = this.normalizeMountPath(storageConfig);
63029
+ if (serveViaCloudFront && sharedOacLogicalId && domain && !(mountPath && name !== "public")) {
63013
63030
  const distLogicalId = `${slug}${env}${name}CDN`.replace(/[^a-zA-Z0-9]/g, "");
63014
63031
  const aliases = [];
63015
63032
  if (storageConfig.aliases && storageConfig.aliases.length > 0) {
@@ -63111,6 +63128,47 @@ else if (!uri.includes('.')) { request.uri += '.html'; } return request; }`
63111
63128
  OriginRequestPolicyId: "b689b0a8-53d0-40ab-baf2-68738e2966ac"
63112
63129
  });
63113
63130
  }
63131
+ for (const mountedBucket of pathMountedWebsiteBuckets) {
63132
+ const mountedOriginId = `S3-${slug}-${env}-${mountedBucket.name}`;
63133
+ const mountedFunctionLogicalId = `${slug}${env}${mountedBucket.name}PathMountRewrite`.replace(/[^a-zA-Z0-9]/g, "");
63134
+ this.builder.addResource(mountedFunctionLogicalId, {
63135
+ Type: "AWS::CloudFront::Function",
63136
+ Properties: {
63137
+ Name: `${slug}-${env}-${mountedBucket.name}-path-mount-rewrite`,
63138
+ AutoPublish: true,
63139
+ FunctionConfig: {
63140
+ Comment: `Path mount rewrite for ${slug} ${env} ${mountedBucket.name} at ${mountedBucket.mountPath}`,
63141
+ Runtime: "cloudfront-js-2.0"
63142
+ },
63143
+ FunctionCode: this.pathMountRewriteFunctionCode(mountedBucket.mountPath)
63144
+ }
63145
+ });
63146
+ origins.push({
63147
+ Id: mountedOriginId,
63148
+ DomainName: { "Fn::Sub": `\${${mountedBucket.logicalId}}.s3.${region}.amazonaws.com` },
63149
+ OriginPath: "",
63150
+ S3OriginConfig: {
63151
+ OriginAccessIdentity: ""
63152
+ },
63153
+ OriginAccessControlId: { Ref: sharedOacLogicalId }
63154
+ });
63155
+ extraDependsOn.push(mountedBucket.logicalId, mountedFunctionLogicalId);
63156
+ for (const pathPattern of [mountedBucket.mountPath, `${mountedBucket.mountPath}/*`]) {
63157
+ cacheBehaviors.push({
63158
+ PathPattern: pathPattern,
63159
+ TargetOriginId: mountedOriginId,
63160
+ ViewerProtocolPolicy: "redirect-to-https",
63161
+ AllowedMethods: ["GET", "HEAD", "OPTIONS"],
63162
+ CachedMethods: ["GET", "HEAD", "OPTIONS"],
63163
+ Compress: true,
63164
+ CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6",
63165
+ FunctionAssociations: [{
63166
+ EventType: "viewer-request",
63167
+ FunctionARN: { "Fn::GetAtt": [mountedFunctionLogicalId, "FunctionARN"] }
63168
+ }]
63169
+ });
63170
+ }
63171
+ }
63114
63172
  }
63115
63173
  const distribution = {
63116
63174
  Type: "AWS::CloudFront::Distribution",
@@ -63174,6 +63232,35 @@ else if (!uri.includes('.')) { request.uri += '.html'; } return request; }`
63174
63232
  }
63175
63233
  }
63176
63234
  });
63235
+ if (name === "public") {
63236
+ for (const mountedBucket of pathMountedWebsiteBuckets) {
63237
+ const mountedPolicyLogicalId = `${mountedBucket.logicalId}CloudFrontPolicy`;
63238
+ this.builder.addResource(mountedPolicyLogicalId, {
63239
+ Type: "AWS::S3::BucketPolicy",
63240
+ DependsOn: [mountedBucket.logicalId, distLogicalId],
63241
+ Properties: {
63242
+ Bucket: { Ref: mountedBucket.logicalId },
63243
+ PolicyDocument: {
63244
+ Version: "2012-10-17",
63245
+ Statement: [{
63246
+ Sid: "AllowCloudFrontServicePrincipal",
63247
+ Effect: "Allow",
63248
+ Principal: {
63249
+ Service: "cloudfront.amazonaws.com"
63250
+ },
63251
+ Action: "s3:GetObject",
63252
+ Resource: { "Fn::Sub": `arn:aws:s3:::\${${mountedBucket.logicalId}}/*` },
63253
+ Condition: {
63254
+ StringEquals: {
63255
+ "AWS:SourceArn": { "Fn::Sub": `arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${distLogicalId}}` }
63256
+ }
63257
+ }
63258
+ }]
63259
+ }
63260
+ }
63261
+ });
63262
+ }
63263
+ }
63177
63264
  if (aliases.length > 0) {
63178
63265
  websiteBucketDistributions.push({
63179
63266
  name,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/ts-cloud",
3
3
  "type": "module",
4
- "version": "0.2.11",
4
+ "version": "0.2.12",
5
5
  "description": "A lightweight, performant infrastructure-as-code library and CLI for deploying both server-based (EC2) and serverless applications.",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
7
7
  "license": "MIT",
@@ -85,8 +85,8 @@
85
85
  "test": "bun test"
86
86
  },
87
87
  "dependencies": {
88
- "@ts-cloud/aws-types": "0.2.11",
89
- "@ts-cloud/core": "0.2.11",
88
+ "@ts-cloud/aws-types": "0.2.12",
89
+ "@ts-cloud/core": "0.2.12",
90
90
  "@stacksjs/ts-xml": "^0.1.0"
91
91
  },
92
92
  "devDependencies": {