@ts-cloud/core 0.5.1 → 0.5.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/index.js CHANGED
@@ -45522,11 +45522,16 @@ function composeServerlessAppTemplate(opts) {
45522
45522
  ...hasQueue ? { TSCLOUD_QUEUE: queueNames[0] } : {},
45523
45523
  ...app.env ?? {}
45524
45524
  });
45525
+ const efsEnabled = Boolean(app.efs);
45526
+ const efsOpts = typeof app.efs === "object" ? app.efs : {};
45527
+ const efsMountPath = efsOpts.mountPath ?? "/mnt/local";
45528
+ const efsProvision = efsEnabled && !efsOpts.accessPointArn;
45529
+ const efsAccessPoint = efsOpts.accessPointArn ?? (efsProvision ? Fn2.getAtt("EfsAccessPoint", "Arn") : undefined);
45525
45530
  const subnets = app.vpc?.subnets ?? [];
45526
45531
  const hasVpc = subnets.length > 0;
45527
- const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy);
45532
+ const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy) || efsEnabled;
45528
45533
  if (needsDataVpc && !hasVpc) {
45529
- throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy require app.vpc.subnets (private subnets) to be set.");
45534
+ throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy / efs require app.vpc.subnets (private subnets) to be set.");
45530
45535
  }
45531
45536
  const vpcConfig = hasVpc ? {
45532
45537
  VpcConfig: {
@@ -45537,6 +45542,15 @@ function composeServerlessAppTemplate(opts) {
45537
45542
  ]
45538
45543
  }
45539
45544
  } : {};
45545
+ const efsDependsOn = efsProvision ? subnets.map((_, i) => `EfsMountTarget${i}`) : [];
45546
+ const efsConfig = efsEnabled ? { FileSystemConfigs: [{ Arn: efsAccessPoint, LocalMountPath: efsMountPath }] } : {};
45547
+ if (efsEnabled) {
45548
+ inlinePolicies[0].PolicyDocument.Statement.push({
45549
+ Effect: "Allow",
45550
+ Action: ["elasticfilesystem:ClientMount", "elasticfilesystem:ClientWrite", "elasticfilesystem:ClientRootAccess", "elasticfilesystem:DescribeMountTargets"],
45551
+ Resource: "*"
45552
+ });
45553
+ }
45540
45554
  function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency, tmp = tmpStorage) {
45541
45555
  resources[`${logicalId}LogGroup`] = {
45542
45556
  Type: "AWS::Logs::LogGroup",
@@ -45554,7 +45568,7 @@ function composeServerlessAppTemplate(opts) {
45554
45568
  };
45555
45569
  resources[logicalId] = {
45556
45570
  Type: "AWS::Lambda::Function",
45557
- DependsOn: [`${logicalId}LogGroup`],
45571
+ DependsOn: [`${logicalId}LogGroup`, ...efsDependsOn],
45558
45572
  Properties: {
45559
45573
  FunctionName: name,
45560
45574
  Architectures: [architecture],
@@ -45565,7 +45579,8 @@ function composeServerlessAppTemplate(opts) {
45565
45579
  EphemeralStorage: { Size: tmp },
45566
45580
  ...codeProps,
45567
45581
  ...reservedConcurrency !== undefined ? { ReservedConcurrentExecutions: reservedConcurrency } : {},
45568
- ...vpcConfig
45582
+ ...vpcConfig,
45583
+ ...efsConfig
45569
45584
  }
45570
45585
  };
45571
45586
  }
@@ -45814,10 +45829,37 @@ function composeServerlessAppTemplate(opts) {
45814
45829
  DomainName: Fn2.getAtt("AssetsBucket", "RegionalDomainName"),
45815
45830
  OriginAccessControlId: Fn2.ref("AssetsOAC"),
45816
45831
  S3OriginConfig: { OriginAccessIdentity: "" }
45817
- }]
45832
+ }],
45833
+ ...app.assetDomain ? {
45834
+ Aliases: [app.assetDomain],
45835
+ ViewerCertificate: {
45836
+ AcmCertificateArn: app.assetCertificateArn,
45837
+ SslSupportMethod: "sni-only",
45838
+ MinimumProtocolVersion: "TLSv1.2_2021"
45839
+ }
45840
+ } : {}
45818
45841
  }
45819
45842
  }
45820
45843
  };
45844
+ if (app.assetDomain) {
45845
+ if (!app.assetCertificateArn)
45846
+ throw new Error("serverless app: `assetDomain` requires `assetCertificateArn` (a us-east-1 ACM cert — CloudFront only accepts certs from us-east-1).");
45847
+ if (app.hostedZoneId) {
45848
+ resources.AssetsDomainRecord = {
45849
+ Type: "AWS::Route53::RecordSet",
45850
+ Properties: {
45851
+ HostedZoneId: app.hostedZoneId,
45852
+ Name: app.assetDomain,
45853
+ Type: "A",
45854
+ AliasTarget: {
45855
+ DNSName: Fn2.getAtt("AssetsDistribution", "DomainName"),
45856
+ HostedZoneId: "Z2FDTNDATAQYW2"
45857
+ }
45858
+ }
45859
+ };
45860
+ }
45861
+ outputs.AssetDomain = { Description: "Custom asset CDN host", Value: app.assetDomain };
45862
+ }
45821
45863
  resources.AssetsBucketPolicy = {
45822
45864
  Type: "AWS::S3::BucketPolicy",
45823
45865
  Properties: {
@@ -45896,6 +45938,37 @@ function composeServerlessAppTemplate(opts) {
45896
45938
  }
45897
45939
  };
45898
45940
  }
45941
+ if (efsProvision) {
45942
+ resources.EfsFileSystem = {
45943
+ Type: "AWS::EFS::FileSystem",
45944
+ Properties: {
45945
+ Encrypted: true,
45946
+ FileSystemTags: [{ Key: "Name", Value: `${slug}-${environment}-efs` }]
45947
+ }
45948
+ };
45949
+ subnets.forEach((subnetId, i) => {
45950
+ resources[`EfsMountTarget${i}`] = {
45951
+ Type: "AWS::EFS::MountTarget",
45952
+ Properties: {
45953
+ FileSystemId: Fn2.ref("EfsFileSystem"),
45954
+ SubnetId: subnetId,
45955
+ SecurityGroups: [Fn2.getAtt("DataSecurityGroup", "GroupId")]
45956
+ }
45957
+ };
45958
+ });
45959
+ resources.EfsAccessPoint = {
45960
+ Type: "AWS::EFS::AccessPoint",
45961
+ Properties: {
45962
+ FileSystemId: Fn2.ref("EfsFileSystem"),
45963
+ PosixUser: { Uid: 1001, Gid: 1001 },
45964
+ RootDirectory: {
45965
+ Path: "/lambda",
45966
+ CreationInfo: { OwnerUid: 1001, OwnerGid: 1001, Permissions: "0755" }
45967
+ }
45968
+ }
45969
+ };
45970
+ outputs.EfsFileSystemId = { Description: "EFS file system id", Value: Fn2.ref("EfsFileSystem") };
45971
+ }
45899
45972
  if (app.cache?.driver === "elasticache") {
45900
45973
  resources.CacheSubnetGroup = {
45901
45974
  Type: "AWS::ElastiCache::SubnetGroup",
package/dist/types.d.ts CHANGED
@@ -918,9 +918,10 @@ export interface SiteConfig {
918
918
  queues?: QueueWorkerConfig[];
919
919
  /**
920
920
  * Run the Laravel scheduler for this site
921
- * (`* * * * * php artisan schedule:run`).
921
+ * (`* * * * * php artisan schedule:run`). `true` enables it with defaults;
922
+ * pass a {@link SchedulerConfig} to attach heartbeat monitoring.
922
923
  */
923
- scheduler?: boolean;
924
+ scheduler?: boolean | SchedulerConfig;
924
925
  /** Arbitrary long-running processes to keep alive (systemd-managed). */
925
926
  daemons?: DaemonConfig[];
926
927
  /** TLS configuration for this site's nginx vhost. */
@@ -955,6 +956,29 @@ export interface SiteConfig {
955
956
  * Composer/npm credentials feature).
956
957
  */
957
958
  credentials?: SiteCredentialsConfig;
959
+ /**
960
+ * Custom nginx for this site's vhost (Forge's "Edit Nginx Configuration").
961
+ * Directives are injected into the generated `server { … }` block, so the
962
+ * managed root/locations/SSL still apply.
963
+ */
964
+ nginx?: SiteNginxConfig;
965
+ }
966
+ /** Per-site nginx customization injected into the generated server block. */
967
+ export interface SiteNginxConfig {
968
+ /**
969
+ * Name of a reusable template defined in
970
+ * {@link ComputeConfig.nginxTemplates}. Its directive lines are injected into
971
+ * this site's server block.
972
+ */
973
+ template?: string;
974
+ /**
975
+ * Raw nginx directive lines added to this site's server block (after the
976
+ * managed directives), e.g. `['gzip on;', 'location /metrics { deny all; }']`.
977
+ * Applied on top of {@link template} when both are set.
978
+ */
979
+ serverSnippet?: string[];
980
+ /** `client_max_body_size` for this vhost (e.g. `'256M'`) for large uploads. */
981
+ clientMaxBodySize?: string;
958
982
  }
959
983
  /** Private package-registry credentials for a site's build. */
960
984
  export interface SiteCredentialsConfig {
@@ -1050,6 +1074,22 @@ export interface SslDnsConfig {
1050
1074
  /** Seconds to wait for DNS propagation before certbot asks the CA to verify. */
1051
1075
  propagationSeconds?: number;
1052
1076
  }
1077
+ /**
1078
+ * Laravel scheduler options for a site (Forge's scheduler + heartbeat
1079
+ * monitoring). The scheduler runs `php artisan schedule:run` every minute.
1080
+ */
1081
+ export interface SchedulerConfig {
1082
+ /**
1083
+ * Heartbeat monitor URL pinged after each successful `schedule:run`
1084
+ * (healthchecks.io, Oh Dear, Better Uptime, …). If the scheduler stops, the
1085
+ * monitor stops receiving pings and alerts you.
1086
+ */
1087
+ heartbeatUrl?: string;
1088
+ /**
1089
+ * HTTP method for the heartbeat ping. @default 'GET'
1090
+ */
1091
+ heartbeatMethod?: 'GET' | 'POST' | 'HEAD';
1092
+ }
1053
1093
  /**
1054
1094
  * A Laravel queue worker (or Horizon supervisor) run as a systemd service.
1055
1095
  * Mirrors Forge's queue configuration. See {@link SiteConfig.queues}.
@@ -1621,6 +1661,18 @@ export interface ServerlessAppConfig {
1621
1661
  storage?: {
1622
1662
  bucket?: string;
1623
1663
  };
1664
+ /**
1665
+ * Mount a shared Elastic File System on the functions (Vapor's `/mnt/local`).
1666
+ * Requires a VPC. `true` provisions an EFS file system + access point;
1667
+ * otherwise attach an existing access point by ARN. The mount path defaults
1668
+ * to `/mnt/local`.
1669
+ */
1670
+ efs?: boolean | {
1671
+ /** Existing EFS Access Point ARN to attach (skips provisioning). */
1672
+ accessPointArn?: string;
1673
+ /** Mount path inside the functions. @default '/mnt/local' */
1674
+ mountPath?: string;
1675
+ };
1624
1676
  /** Managed WAF in front of the HTTP API / CloudFront. */
1625
1677
  firewall?: WafConfig;
1626
1678
  /** Custom domain(s) for the app's HTTP API (overrides {@link EnvironmentConfig.domain}). */
@@ -1636,6 +1688,19 @@ export interface ServerlessAppConfig {
1636
1688
  hostedZoneId?: string;
1637
1689
  /** Local directory whose contents are uploaded to S3/CloudFront as versioned assets. */
1638
1690
  assets?: string;
1691
+ /**
1692
+ * Serve assets from a custom CDN host instead of the default CloudFront domain
1693
+ * (Vapor `asset-domain`). Requires a us-east-1 ACM cert via assetCertificateArn.
1694
+ */
1695
+ assetDomain?: string;
1696
+ /** us-east-1 ACM certificate ARN for {@link assetDomain} (CloudFront requirement). */
1697
+ assetCertificateArn?: string;
1698
+ /** Include dotfiles when uploading assets (Vapor `dot-files-as-assets`). @default false */
1699
+ dotFilesAsAssets?: boolean;
1700
+ /** Serve assets from the app/root domain too (Vapor `serve_assets`). Injected as env. */
1701
+ serveAssets?: boolean;
1702
+ /** Redirect robots.txt to the asset CDN (Vapor `redirect_robots_txt`). Injected as env. @default true */
1703
+ redirectRobotsTxt?: boolean;
1639
1704
  /** PHP version for the runtime layer. @default '8.3' */
1640
1705
  phpVersion?: PhpVersion;
1641
1706
  /** CPU architecture. @default 'x86_64' */
@@ -2141,6 +2206,13 @@ export interface ComputeConfig {
2141
2206
  * @default 'nginx'
2142
2207
  */
2143
2208
  webServer?: 'nginx' | 'rpx';
2209
+ /**
2210
+ * Reusable nginx config templates, keyed by name. A site references one via
2211
+ * `site.nginx.template`, and its directive lines are injected into that
2212
+ * site's server block — define a hardening/caching/proxy snippet once and
2213
+ * share it across sites (Forge's nginx templates).
2214
+ */
2215
+ nginxTemplates?: Record<string, string[]>;
2144
2216
  /**
2145
2217
  * On-box managed services to install (Forge's single-server model): the
2146
2218
  * database engine, cache, and search. Each may be `true` for defaults or an
@@ -2228,6 +2300,19 @@ export interface ComputePhpConfig {
2228
2300
  * e.g. `['imagick', 'swoole']`.
2229
2301
  */
2230
2302
  extensions?: string[];
2303
+ /**
2304
+ * Apply the production OPcache + php.ini tuning (Forge's "Optimize for
2305
+ * Production"): OPcache on with timestamp validation off, larger file/string
2306
+ * buffers, and a bigger realpath cache. Deploys restart php-fpm, so disabled
2307
+ * timestamp validation is safe. @default true
2308
+ */
2309
+ optimizeForProduction?: boolean;
2310
+ /**
2311
+ * Extra `php.ini` directives merged on top of the production tuning (and
2312
+ * applied even when {@link optimizeForProduction} is false), e.g.
2313
+ * `{ memory_limit: '512M', upload_max_filesize: '128M' }`.
2314
+ */
2315
+ ini?: Record<string, string>;
2231
2316
  }
2232
2317
  /**
2233
2318
  * On-box managed services (database / cache / search) for a compute box.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-cloud/core",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "description": "Core CloudFormation generation library for ts-cloud",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
@@ -31,7 +31,7 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@ts-cloud/aws-types": "0.5.1"
34
+ "@ts-cloud/aws-types": "0.5.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^5.9.3"