@ts-cloud/core 0.7.47 → 0.7.49

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
@@ -10836,6 +10836,136 @@ class SMS {
10836
10836
  return { resources, outputs };
10837
10837
  }
10838
10838
  }
10839
+ // src/modules/sftp.ts
10840
+ function logicalPart(value) {
10841
+ const clean = value.replace(/[^a-zA-Z0-9]/g, " ").trim();
10842
+ const part = clean.split(/\s+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
10843
+ return part || "User";
10844
+ }
10845
+ function normalizeHomeDirectory(value, username) {
10846
+ const path = (value || username).replace(/^\/+|\/+$/g, "");
10847
+ if (!path || path.split("/").includes(".."))
10848
+ throw new Error(`sftp: invalid homeDirectory for user ${username}`);
10849
+ return path;
10850
+ }
10851
+
10852
+ class Sftp {
10853
+ static create(options) {
10854
+ if (!options.bucket.trim())
10855
+ throw new Error("sftp: bucket is required");
10856
+ const endpointType = options.endpointType ?? "PUBLIC";
10857
+ if (endpointType === "VPC" && (!options.endpointDetails?.vpcId || !options.endpointDetails.subnetIds.length))
10858
+ throw new Error("sftp: VPC endpoints require endpointDetails.vpcId and at least one subnet");
10859
+ const prefix = `${logicalPart(options.slug)}${logicalPart(options.environment)}Sftp`;
10860
+ const serverLogicalId = `${prefix}Server`;
10861
+ const resources = {};
10862
+ let loggingRoleArn;
10863
+ if (options.logging !== false) {
10864
+ const loggingRoleLogicalId = `${prefix}LoggingRole`;
10865
+ resources[loggingRoleLogicalId] = {
10866
+ Type: "AWS::IAM::Role",
10867
+ Properties: {
10868
+ AssumeRolePolicyDocument: {
10869
+ Version: "2012-10-17",
10870
+ Statement: [{ Effect: "Allow", Principal: { Service: "transfer.amazonaws.com" }, Action: "sts:AssumeRole" }]
10871
+ },
10872
+ Policies: [{
10873
+ PolicyName: "TransferLogging",
10874
+ PolicyDocument: {
10875
+ Version: "2012-10-17",
10876
+ Statement: [{
10877
+ Effect: "Allow",
10878
+ Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:DescribeLogStreams", "logs:PutLogEvents"],
10879
+ Resource: "arn:aws:logs:*:*:log-group:/aws/transfer/*"
10880
+ }]
10881
+ }
10882
+ }]
10883
+ }
10884
+ };
10885
+ loggingRoleArn = { "Fn::GetAtt": [loggingRoleLogicalId, "Arn"] };
10886
+ }
10887
+ resources[serverLogicalId] = {
10888
+ Type: "AWS::Transfer::Server",
10889
+ Properties: {
10890
+ Domain: "S3",
10891
+ EndpointType: endpointType,
10892
+ IdentityProviderType: "SERVICE_MANAGED",
10893
+ Protocols: ["SFTP"],
10894
+ ...endpointType === "VPC" ? {
10895
+ EndpointDetails: {
10896
+ VpcId: options.endpointDetails.vpcId,
10897
+ SubnetIds: options.endpointDetails.subnetIds,
10898
+ ...options.endpointDetails.securityGroupIds?.length ? { SecurityGroupIds: options.endpointDetails.securityGroupIds } : {},
10899
+ ...options.endpointDetails.addressAllocationIds?.length ? { AddressAllocationIds: options.endpointDetails.addressAllocationIds } : {}
10900
+ }
10901
+ } : {},
10902
+ ...options.securityPolicyName ? { SecurityPolicyName: options.securityPolicyName } : {},
10903
+ ...loggingRoleArn ? { LoggingRole: loggingRoleArn } : {},
10904
+ Tags: [
10905
+ { Key: "Project", Value: options.slug },
10906
+ { Key: "Environment", Value: options.environment },
10907
+ { Key: "ManagedBy", Value: "ts-cloud" }
10908
+ ]
10909
+ }
10910
+ };
10911
+ for (const [username, user] of Object.entries(options.users)) {
10912
+ if (!/^[a-zA-Z0-9_.@-]{3,100}$/.test(username))
10913
+ throw new Error(`sftp: invalid username ${username}`);
10914
+ if (!user.sshPublicKeys.length)
10915
+ throw new Error(`sftp: user ${username} requires at least one SSH public key`);
10916
+ const userPart = logicalPart(username);
10917
+ const home = normalizeHomeDirectory(user.homeDirectory, username);
10918
+ const userLogicalId = `${prefix}${userPart}User`;
10919
+ let roleArn = user.roleArn;
10920
+ if (!roleArn) {
10921
+ const roleLogicalId = `${prefix}${userPart}Role`;
10922
+ const bucketArn = `arn:aws:s3:::${options.bucket}`;
10923
+ resources[roleLogicalId] = {
10924
+ Type: "AWS::IAM::Role",
10925
+ Properties: {
10926
+ AssumeRolePolicyDocument: {
10927
+ Version: "2012-10-17",
10928
+ Statement: [{ Effect: "Allow", Principal: { Service: "transfer.amazonaws.com" }, Action: "sts:AssumeRole" }]
10929
+ },
10930
+ Policies: [{
10931
+ PolicyName: "SftpHomeDirectory",
10932
+ PolicyDocument: {
10933
+ Version: "2012-10-17",
10934
+ Statement: [
10935
+ {
10936
+ Effect: "Allow",
10937
+ Action: ["s3:ListBucket", "s3:GetBucketLocation"],
10938
+ Resource: bucketArn,
10939
+ Condition: { StringLike: { "s3:prefix": [home, `${home}/*`] } }
10940
+ },
10941
+ {
10942
+ Effect: "Allow",
10943
+ Action: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:GetObjectVersion"],
10944
+ Resource: `${bucketArn}/${home}/*`
10945
+ }
10946
+ ]
10947
+ }
10948
+ }]
10949
+ }
10950
+ };
10951
+ roleArn = { "Fn::GetAtt": [roleLogicalId, "Arn"] };
10952
+ }
10953
+ resources[userLogicalId] = {
10954
+ Type: "AWS::Transfer::User",
10955
+ DependsOn: [serverLogicalId],
10956
+ Properties: {
10957
+ ServerId: { Ref: serverLogicalId },
10958
+ UserName: username,
10959
+ Role: roleArn,
10960
+ HomeDirectoryType: "PATH",
10961
+ HomeDirectory: `/${options.bucket}/${home}`,
10962
+ SshPublicKeys: user.sshPublicKeys
10963
+ }
10964
+ };
10965
+ }
10966
+ return { resources, serverLogicalId };
10967
+ }
10968
+ }
10839
10969
  // src/modules/ai.ts
10840
10970
  class AI {
10841
10971
  static createBedrockRole(servicePrincipal, options) {
@@ -25298,6 +25428,9 @@ var cloud_config_schema_default = {
25298
25428
  cdn: {
25299
25429
  $ref: "#/definitions/cdn"
25300
25430
  },
25431
+ sftp: {
25432
+ $ref: "#/definitions/sftp"
25433
+ },
25301
25434
  functions: {
25302
25435
  type: "object",
25303
25436
  description: "Lambda functions configuration",
@@ -25348,6 +25481,14 @@ var cloud_config_schema_default = {
25348
25481
  timeout: { type: "number", description: "HTTP request timeout in seconds" },
25349
25482
  gatewayVersion: { type: "number", enum: [1, 2], description: "API Gateway version (2 = HTTP API, 1 = REST)" },
25350
25483
  warm: { type: "number", description: "Keep-warm / provisioned concurrency count" },
25484
+ lambdaInsights: {
25485
+ type: "object",
25486
+ description: "CloudWatch Lambda Insights extension configuration for zip deployments",
25487
+ required: ["layerArn"],
25488
+ properties: {
25489
+ layerArn: { type: "string", description: "Region- and architecture-specific Lambda Insights extension layer ARN" }
25490
+ }
25491
+ },
25351
25492
  queues: { description: "Queue names (true = single default queue, false = disabled)" },
25352
25493
  scheduler: { type: "string", enum: ["off", "on", "sub-minute"], description: "Task scheduler mode" },
25353
25494
  build: { type: "array", items: { type: "string" }, description: "Commands run locally before packaging" },
@@ -25621,6 +25762,15 @@ var cloud_config_schema_default = {
25621
25762
  type: "boolean",
25622
25763
  default: true
25623
25764
  },
25765
+ originShield: {
25766
+ type: "boolean",
25767
+ description: "Enable CloudFront Origin Shield",
25768
+ default: false
25769
+ },
25770
+ originShieldRegion: {
25771
+ type: "string",
25772
+ description: "AWS region used by CloudFront Origin Shield"
25773
+ },
25624
25774
  compress: {
25625
25775
  type: "boolean",
25626
25776
  description: "Enable automatic compression",
@@ -25661,6 +25811,38 @@ var cloud_config_schema_default = {
25661
25811
  }
25662
25812
  }
25663
25813
  },
25814
+ sftp: {
25815
+ type: "object",
25816
+ required: ["bucket", "users"],
25817
+ properties: {
25818
+ bucket: { type: "string", description: "S3 bucket backing the SFTP server" },
25819
+ endpointType: { type: "string", enum: ["PUBLIC", "VPC"], default: "PUBLIC" },
25820
+ endpointDetails: {
25821
+ type: "object",
25822
+ required: ["vpcId", "subnetIds"],
25823
+ properties: {
25824
+ vpcId: { type: "string" },
25825
+ subnetIds: { type: "array", items: { type: "string" }, minItems: 1 },
25826
+ securityGroupIds: { type: "array", items: { type: "string" } },
25827
+ addressAllocationIds: { type: "array", items: { type: "string" } }
25828
+ }
25829
+ },
25830
+ securityPolicyName: { type: "string" },
25831
+ logging: { type: "boolean", default: true },
25832
+ users: {
25833
+ type: "object",
25834
+ additionalProperties: {
25835
+ type: "object",
25836
+ required: ["sshPublicKeys"],
25837
+ properties: {
25838
+ sshPublicKeys: { type: "array", items: { type: "string" }, minItems: 1 },
25839
+ homeDirectory: { type: "string" },
25840
+ roleArn: { type: "string" }
25841
+ }
25842
+ }
25843
+ }
25844
+ }
25845
+ },
25664
25846
  function: {
25665
25847
  type: "object",
25666
25848
  required: ["name", "runtime"],
@@ -45568,6 +45750,9 @@ function composeServerlessAppTemplate(opts) {
45568
45750
  const hasQueue = queueNames.length > 0;
45569
45751
  const logRetention = app.logRetention ?? 14;
45570
45752
  const imageMode = app.packaging === "image";
45753
+ if (imageMode && app.lambdaInsights) {
45754
+ throw new Error("serverless app: `lambdaInsights` layer attachment is only supported for zip packaging. Container images must install the Lambda Insights extension in the image.");
45755
+ }
45571
45756
  const schedulerEnabled = (app.scheduler ?? "on") !== "off";
45572
45757
  const cacheEnabled = (app.cache?.driver ?? "dynamodb") === "dynamodb";
45573
45758
  const assetsEnabled = Boolean(app.assets);
@@ -45629,6 +45814,8 @@ function composeServerlessAppTemplate(opts) {
45629
45814
  const managedPolicies = ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"];
45630
45815
  if (app.vpc?.subnets?.length)
45631
45816
  managedPolicies.push("arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole");
45817
+ if (app.lambdaInsights)
45818
+ managedPolicies.push("arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy");
45632
45819
  resources.AppRole = {
45633
45820
  Type: "AWS::IAM::Role",
45634
45821
  Properties: {
@@ -45684,6 +45871,7 @@ function composeServerlessAppTemplate(opts) {
45684
45871
  Type: "AWS::Logs::LogGroup",
45685
45872
  Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: logRetention }
45686
45873
  };
45874
+ const layers = [...opts.runtimeLayers ?? [], ...app.lambdaInsights ? [app.lambdaInsights.layerArn] : []];
45687
45875
  const codeProps = imageMode ? {
45688
45876
  PackageType: "Image",
45689
45877
  Code: { ImageUri: Fn2.ref("ImageUri") },
@@ -45692,7 +45880,7 @@ function composeServerlessAppTemplate(opts) {
45692
45880
  Runtime: runtime,
45693
45881
  Handler: handler8,
45694
45882
  Code: { S3Bucket: Fn2.ref("ArtifactBucket"), S3Key: Fn2.ref("ArtifactKey") },
45695
- ...opts.runtimeLayers?.length ? { Layers: opts.runtimeLayers } : {}
45883
+ ...layers.length ? { Layers: [...new Set(layers)] } : {}
45696
45884
  };
45697
45885
  resources[logicalId] = {
45698
45886
  Type: "AWS::Lambda::Function",
@@ -47494,6 +47682,7 @@ export {
47494
47682
  Spinner,
47495
47683
  exports_send as SmsHandlers,
47496
47684
  exports_advanced3 as SmsAdvanced,
47685
+ Sftp,
47497
47686
  ServiceMeshManager,
47498
47687
  SenderReputationManager,
47499
47688
  SecurityScanningManager,
@@ -14,6 +14,7 @@ export * from './email';
14
14
  export * from './phone';
15
15
  export * from './queue';
16
16
  export * from './sms';
17
+ export * from './sftp';
17
18
  export * from './ai';
18
19
  export * from './database';
19
20
  export * from './cache';
@@ -0,0 +1,12 @@
1
+ import type { EnvironmentType, SftpConfig } from '../types';
2
+ export interface SftpResources {
3
+ resources: Record<string, any>;
4
+ serverLogicalId: string;
5
+ }
6
+ /** Build an AWS Transfer Family SFTP server with service-managed users. */
7
+ export declare class Sftp {
8
+ static create(options: SftpConfig & {
9
+ slug: string;
10
+ environment: EnvironmentType;
11
+ }): SftpResources;
12
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/types.d.ts CHANGED
@@ -354,6 +354,8 @@ export interface InfrastructureConfig {
354
354
  appDatabase?: DatabaseConfig;
355
355
  cache?: CacheConfig;
356
356
  cdn?: Record<string, CdnItemConfig & ResourceConditions> | CdnItemConfig;
357
+ /** AWS Transfer Family SFTP server backed by S3. */
358
+ sftp?: SftpConfig;
357
359
  /**
358
360
  * Elastic File System (EFS) configuration
359
361
  * For shared file storage across multiple instances
@@ -1378,6 +1380,23 @@ export interface CdnConfig {
1378
1380
  customDomain?: string;
1379
1381
  certificateArn?: string;
1380
1382
  }
1383
+ export interface SftpConfig {
1384
+ bucket: string;
1385
+ users: Record<string, {
1386
+ sshPublicKeys: string[];
1387
+ homeDirectory?: string;
1388
+ roleArn?: string;
1389
+ }>;
1390
+ endpointType?: 'PUBLIC' | 'VPC';
1391
+ endpointDetails?: {
1392
+ vpcId: string;
1393
+ subnetIds: string[];
1394
+ securityGroupIds?: string[];
1395
+ addressAllocationIds?: string[];
1396
+ };
1397
+ securityPolicyName?: string;
1398
+ logging?: boolean;
1399
+ }
1381
1400
  export interface DnsConfig {
1382
1401
  domain?: string;
1383
1402
  hostedZoneId?: string;
@@ -1717,6 +1736,17 @@ export interface ServerlessAppConfig {
1717
1736
  provisionedConcurrency?: number;
1718
1737
  /** CloudWatch log retention (days) for all function log groups. @default 14 */
1719
1738
  logRetention?: number;
1739
+ /**
1740
+ * Enable CloudWatch Lambda Insights for every function in the app.
1741
+ *
1742
+ * AWS publishes architecture- and region-specific extension layers, so the
1743
+ * layer ARN stays explicit instead of ts-cloud pinning a version that can go
1744
+ * stale. Zip deployments attach the layer and the required AWS managed role
1745
+ * policy. Container-image deployments must bake the extension into the image.
1746
+ */
1747
+ lambdaInsights?: {
1748
+ layerArn: string;
1749
+ };
1720
1750
  /** CLI function memory in MB. @default 1024 */
1721
1751
  cliMemory?: number;
1722
1752
  /** CLI command timeout in seconds (allow room for migrations). @default 900 */
@@ -2684,6 +2714,10 @@ export interface CdnFrontConfig {
2684
2714
  secret?: string;
2685
2715
  /** Header name carrying {@link secret}. @default 'X-Origin-Verify' */
2686
2716
  secretHeader?: string;
2717
+ /** Enable CloudFront Origin Shield for the gateway origin. @default false */
2718
+ originShield?: boolean;
2719
+ /** AWS region used by Origin Shield. Required when {@link originShield} is enabled. */
2720
+ originShieldRegion?: string;
2687
2721
  }
2688
2722
  export interface DatabaseItemConfig {
2689
2723
  engine?: 'dynamodb' | 'postgres' | 'mysql';
@@ -2757,6 +2791,10 @@ export interface CdnItemConfig {
2757
2791
  * Enable CDN
2758
2792
  */
2759
2793
  enabled?: boolean;
2794
+ /** Enable CloudFront Origin Shield for this origin. @default false */
2795
+ originShield?: boolean;
2796
+ /** AWS region used by Origin Shield. Defaults to the deployment region. */
2797
+ originShieldRegion?: string;
2760
2798
  /**
2761
2799
  * Cache policy configuration
2762
2800
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-cloud/core",
3
- "version": "0.7.47",
3
+ "version": "0.7.49",
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.7.47"
34
+ "@ts-cloud/aws-types": "0.7.49"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^7.0.2"