@ts-cloud/core 0.2.15 → 0.2.17

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.
@@ -0,0 +1 @@
1
+ export * from './types';
@@ -0,0 +1,97 @@
1
+ import type { CloudConfig, EnvironmentType, SiteConfig } from '../types';
2
+ export type CloudProviderName = 'aws' | 'hetzner';
3
+ export interface ComputeTarget {
4
+ id: string;
5
+ name?: string;
6
+ publicIp?: string;
7
+ privateIp?: string;
8
+ status?: string;
9
+ }
10
+ export interface ComputeStackOutputs {
11
+ deployBucketName?: string;
12
+ deployStoragePath?: string;
13
+ appInstanceId?: string;
14
+ appPublicIp?: string;
15
+ sshUser?: string;
16
+ }
17
+ export interface RemoteDeployInstanceResult {
18
+ instanceId: string;
19
+ status: string;
20
+ output?: string;
21
+ error?: string;
22
+ }
23
+ export interface RemoteDeployResult {
24
+ success: boolean;
25
+ instanceCount: number;
26
+ perInstance: RemoteDeployInstanceResult[];
27
+ error?: string;
28
+ }
29
+ export interface ProvisionComputeOptions {
30
+ config: CloudConfig;
31
+ environment: EnvironmentType;
32
+ }
33
+ export interface FindComputeTargetsOptions {
34
+ slug: string;
35
+ environment: EnvironmentType;
36
+ role?: string;
37
+ }
38
+ export interface UploadReleaseOptions {
39
+ config: CloudConfig;
40
+ environment: EnvironmentType;
41
+ localPath: string;
42
+ remoteKey: string;
43
+ targets?: ComputeTarget[];
44
+ }
45
+ export interface UploadReleaseResult {
46
+ /** Server-local path or remote URI the deploy script reads from */
47
+ artifactRef: string;
48
+ }
49
+ export interface RunRemoteDeployOptions {
50
+ targets: ComputeTarget[];
51
+ commands: string[];
52
+ comment?: string;
53
+ timeoutSeconds?: number;
54
+ tags?: Record<string, string>;
55
+ }
56
+ /**
57
+ * Cloud infrastructure driver — abstracts compute provisioning and Forge-style
58
+ * app deploys across providers (AWS EC2+SSM+S3, Hetzner Cloud+SSH, etc.).
59
+ *
60
+ * DNS remains provider-agnostic via the separate `DnsProvider` abstraction.
61
+ */
62
+ export interface CloudDriver {
63
+ readonly name: CloudProviderName;
64
+ /** Whether this driver uses CloudFormation for infrastructure */
65
+ readonly usesCloudFormation: boolean;
66
+ /** Provision compute infrastructure (Hetzner). AWS uses InfrastructureGenerator + CFN. */
67
+ provisionComputeInfrastructure?(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
68
+ /** Read outputs needed for deploy (stack outputs, state file, or live API) */
69
+ getComputeOutputs(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
70
+ /** Upload a release tarball to provider-specific staging storage */
71
+ uploadRelease(options: UploadReleaseOptions): Promise<UploadReleaseResult>;
72
+ /** Find compute targets matching project tags/labels */
73
+ findComputeTargets(options: FindComputeTargetsOptions): Promise<ComputeTarget[]>;
74
+ /** Run a shell script on every target (SSM, SSH, etc.) */
75
+ runRemoteDeploy(options: RunRemoteDeployOptions): Promise<RemoteDeployResult>;
76
+ }
77
+ export interface DeploySiteReleaseOptions {
78
+ config: CloudConfig;
79
+ environment: EnvironmentType;
80
+ siteName: string;
81
+ site: SiteConfig;
82
+ slug: string;
83
+ sha: string;
84
+ runtime: 'bun' | 'node' | 'deno';
85
+ localTarballPath: string;
86
+ }
87
+ export interface DeploySiteReleaseResult {
88
+ success: boolean;
89
+ error?: string;
90
+ instanceCount?: number;
91
+ perInstance?: RemoteDeployInstanceResult[];
92
+ }
93
+ /**
94
+ * Resolve the configured cloud provider. Defaults to AWS for backward compatibility.
95
+ * Environment-based auto-detection is handled when constructing the Hetzner driver.
96
+ */
97
+ export declare function resolveCloudProvider(config: CloudConfig): CloudProviderName;
package/dist/index.d.ts CHANGED
@@ -2,10 +2,12 @@
2
2
  * ts-cloud Core - CloudFormation Generator Engine
3
3
  */
4
4
  export * from './types';
5
+ export * from './drivers';
5
6
  export * from './template-builder';
6
7
  export { validateTemplate, validateTemplateSize, validateResourceLimits, type ValidationResult, } from './template-validator';
7
8
  export { Pseudo } from './intrinsic-functions';
8
9
  export * from './resource-naming';
10
+ export * from './stack-naming';
9
11
  export * from './dependency-graph';
10
12
  export * from './stack-diff';
11
13
  export * from './modules';
package/dist/index.js CHANGED
@@ -1075,6 +1075,14 @@ var RealtimePresets = {
1075
1075
  }
1076
1076
  }
1077
1077
  };
1078
+ // src/drivers/types.ts
1079
+ function resolveCloudProvider(config) {
1080
+ if (config.cloud?.provider)
1081
+ return config.cloud.provider;
1082
+ if (config.hetzner?.apiToken)
1083
+ return "hetzner";
1084
+ return "aws";
1085
+ }
1078
1086
  // src/template-builder.ts
1079
1087
  class TemplateBuilder {
1080
1088
  template;
@@ -1639,6 +1647,29 @@ function getTimestamp() {
1639
1647
  function sanitizeName(name) {
1640
1648
  return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
1641
1649
  }
1650
+ // src/stack-naming.ts
1651
+ function resolveProjectStackName(config, environment) {
1652
+ return config.project.stackName ?? `${config.project.slug}-${environment}`;
1653
+ }
1654
+ function resolveSiteStackName(config, siteKey, site, environment) {
1655
+ return site.stackName ?? `${config.project.slug}-${environment}-${siteKey}-site`;
1656
+ }
1657
+ function resolveSiteResourceName(config, siteKey) {
1658
+ return `${config.project.slug}-${siteKey}`;
1659
+ }
1660
+ function resolveSiteBucketName(slug, environment, siteKey, explicitBucket) {
1661
+ if (explicitBucket)
1662
+ return explicitBucket;
1663
+ if (siteKey === "main")
1664
+ return `${slug}-${environment}-site`;
1665
+ return `${slug}-${environment}-${siteKey}`;
1666
+ }
1667
+ function resolveStorageBucketName(slug, environment, bucketKey, explicitBucket) {
1668
+ return explicitBucket ?? `${slug}-${environment}-${bucketKey}`;
1669
+ }
1670
+ function resolveDeployBucketName(slug, environment) {
1671
+ return `${slug}-${environment}-deploy`;
1672
+ }
1642
1673
  // src/dependency-graph.ts
1643
1674
  class DependencyGraph {
1644
1675
  nodes = new Map;
@@ -1990,6 +2021,7 @@ class Storage {
1990
2021
  static createBucket(options) {
1991
2022
  const {
1992
2023
  name,
2024
+ bucketName: explicitBucketName,
1993
2025
  slug,
1994
2026
  environment,
1995
2027
  public: isPublic = false,
@@ -2000,7 +2032,7 @@ class Storage {
2000
2032
  cors,
2001
2033
  lifecycleRules
2002
2034
  } = options;
2003
- const resourceName = generateResourceName({
2035
+ const resourceName = explicitBucketName ?? generateResourceName({
2004
2036
  slug,
2005
2037
  environment,
2006
2038
  resourceType: "s3",
@@ -6138,7 +6170,8 @@ echo "Server setup complete!"
6138
6170
  runtime = "bun",
6139
6171
  runtimeVersion = "latest",
6140
6172
  systemPackages = [],
6141
- database
6173
+ database,
6174
+ caddyfile
6142
6175
  } = options;
6143
6176
  const packages = new Set(systemPackages);
6144
6177
  if (database === "sqlite")
@@ -6196,7 +6229,57 @@ ln -sf /root/.deno/bin/deno /usr/local/bin/deno
6196
6229
  script += `
6197
6230
  # Reserved root for site deploys (each site lands at /var/www/<site>/)
6198
6231
  mkdir -p /var/www
6232
+ `;
6233
+ if (caddyfile) {
6234
+ const escaped = caddyfile.replace(/\$/g, "\\$");
6235
+ script += `
6236
+ # Caddy (reverse proxy + automatic TLS via Let's Encrypt)
6237
+ ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
6238
+ curl -fsSL "https://caddyserver.com/api/download?os=linux&arch=\${ARCH}" -o /usr/local/bin/caddy
6239
+ chmod +x /usr/local/bin/caddy
6240
+
6241
+ # Dedicated service account
6242
+ getent group caddy >/dev/null || groupadd --system caddy
6243
+ getent passwd caddy >/dev/null || useradd --system --gid caddy \\
6244
+ --create-home --home-dir /var/lib/caddy \\
6245
+ --shell /usr/sbin/nologin --comment "Caddy web server" caddy
6246
+
6247
+ mkdir -p /etc/caddy /var/lib/caddy /var/log/caddy
6248
+ chown -R caddy:caddy /var/lib/caddy /var/log/caddy
6249
+
6250
+ cat > /etc/systemd/system/caddy.service <<'CADDY_UNIT_EOF'
6251
+ [Unit]
6252
+ Description=Caddy
6253
+ Documentation=https://caddyserver.com/docs/
6254
+ After=network.target network-online.target
6255
+ Requires=network-online.target
6256
+
6257
+ [Service]
6258
+ Type=notify
6259
+ User=caddy
6260
+ Group=caddy
6261
+ ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
6262
+ ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force
6263
+ TimeoutStopSec=5s
6264
+ LimitNOFILE=1048576
6265
+ PrivateTmp=true
6266
+ ProtectSystem=full
6267
+ AmbientCapabilities=CAP_NET_BIND_SERVICE
6268
+
6269
+ [Install]
6270
+ WantedBy=multi-user.target
6271
+ CADDY_UNIT_EOF
6272
+
6273
+ cat > /etc/caddy/Caddyfile <<'CADDY_CONFIG_EOF'
6274
+ ${escaped}
6275
+ CADDY_CONFIG_EOF
6199
6276
 
6277
+ systemctl daemon-reload
6278
+ systemctl enable caddy
6279
+ systemctl start caddy
6280
+ `;
6281
+ }
6282
+ script += `
6200
6283
  echo "ts-cloud bootstrap complete — instance is ready for site deploys"
6201
6284
  `;
6202
6285
  return script;
@@ -12678,8 +12761,8 @@ class Messaging {
12678
12761
  return topic;
12679
12762
  }
12680
12763
  static FilterPolicies = {
12681
- eventType: (types) => ({
12682
- eventType: types
12764
+ eventType: (types2) => ({
12765
+ eventType: types2
12683
12766
  }),
12684
12767
  status: (statuses) => ({
12685
12768
  status: statuses
@@ -45350,8 +45433,15 @@ export {
45350
45433
  route53RoutingManager,
45351
45434
  route53ResolverManager,
45352
45435
  resourceManagementManager,
45436
+ resolveStorageBucketName,
45437
+ resolveSiteStackName,
45438
+ resolveSiteResourceName,
45439
+ resolveSiteBucketName,
45353
45440
  resolveRegion,
45441
+ resolveProjectStackName,
45442
+ resolveDeployBucketName,
45354
45443
  resolveCredentials,
45444
+ resolveCloudProvider,
45355
45445
  requiresReplacement,
45356
45446
  replicaManager,
45357
45447
  regionPairManager,
@@ -1149,6 +1149,13 @@ export declare class Compute {
1149
1149
  runtimeVersion?: string;
1150
1150
  systemPackages?: string[];
1151
1151
  database?: "sqlite" | "mysql" | "postgres";
1152
+ /**
1153
+ * Caddyfile content. When set, installs Caddy and configures it as
1154
+ * a reverse proxy + TLS terminator using the supplied Caddyfile.
1155
+ * Caddy auto-fetches Let's Encrypt certs for any site block whose
1156
+ * address is a public hostname pointed at this instance.
1157
+ */
1158
+ caddyfile?: string;
1152
1159
  }) => string;
1153
1160
  /**
1154
1161
  * Individual installation scripts
@@ -0,0 +1,31 @@
1
+ import type { CloudConfig, EnvironmentType, SiteConfig } from './types';
2
+ /**
3
+ * CloudFormation stack for project-wide infrastructure (VPC, compute, shared storage).
4
+ * Convention: `{slug}-{environment}` (e.g. `pantry-production`).
5
+ */
6
+ export declare function resolveProjectStackName(config: Pick<CloudConfig, 'project'>, environment: EnvironmentType): string;
7
+ /**
8
+ * CloudFormation stack for a static site (S3 + CloudFront + ACM).
9
+ * Convention: `{slug}-{environment}-{siteKey}-site` (e.g. `pantry-production-main-site`).
10
+ */
11
+ export declare function resolveSiteStackName(config: Pick<CloudConfig, 'project'>, siteKey: string, site: Pick<SiteConfig, 'stackName'>, environment: EnvironmentType): string;
12
+ /**
13
+ * Prefix for site-scoped AWS resource names (deploy tarball paths, systemd units, etc.).
14
+ * Convention: `{slug}-{siteKey}` (e.g. `pantry-main`).
15
+ */
16
+ export declare function resolveSiteResourceName(config: Pick<CloudConfig, 'project'>, siteKey: string): string;
17
+ /**
18
+ * S3 bucket for a site (install script / static assets).
19
+ * Convention: `{slug}-{environment}-site` (e.g. `pantry-production-site`).
20
+ */
21
+ export declare function resolveSiteBucketName(slug: string, environment: EnvironmentType, siteKey: string, explicitBucket?: string): string;
22
+ /**
23
+ * S3 bucket name for a storage block in infrastructure.storage.
24
+ * Convention: `{slug}-{environment}-{bucketKey}` unless `bucket` is set on the item.
25
+ */
26
+ export declare function resolveStorageBucketName(slug: string, environment: EnvironmentType, bucketKey: string, explicitBucket?: string): string;
27
+ /**
28
+ * Deploy staging bucket for compute app releases.
29
+ * Convention: `{slug}-{environment}-deploy`.
30
+ */
31
+ export declare function resolveDeployBucketName(slug: string, environment: EnvironmentType): string;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,59 @@
1
+ /**
2
+ * Top-level cloud provider selection
3
+ */
4
+ export interface CloudProviderConfig {
5
+ /**
6
+ * Infrastructure provider for compute and related resources.
7
+ * @default 'aws'
8
+ */
9
+ provider?: 'aws' | 'hetzner';
10
+ }
11
+ /**
12
+ * Object storage provider selection.
13
+ *
14
+ * AWS S3, Backblaze B2 and Hetzner Object Storage are all S3-compatible, so the
15
+ * same client drives any of them — only the endpoint, addressing style and
16
+ * credentials differ. Choose a provider to move object storage (static assets,
17
+ * release artifacts, registry tarballs/binaries) off AWS S3 for cost without
18
+ * touching the rest of the deployment.
19
+ */
20
+ export interface ObjectStorageConfig {
21
+ /**
22
+ * Object storage provider.
23
+ * @default 'aws'
24
+ */
25
+ provider?: 'aws' | 'backblaze' | 'hetzner';
26
+ /**
27
+ * Region / location slug. Provider-specific default when omitted
28
+ * (aws: us-east-1, backblaze: us-west-004, hetzner: fsn1).
29
+ */
30
+ region?: string;
31
+ /**
32
+ * Endpoint host override (no scheme), e.g. `s3.us-west-004.backblazeb2.com`.
33
+ * Defaults to the provider's standard endpoint for the region.
34
+ */
35
+ endpoint?: string;
36
+ /**
37
+ * Force path-style addressing (bucket in the path) instead of virtual-hosted.
38
+ * @default false
39
+ */
40
+ forcePathStyle?: boolean;
41
+ }
42
+ /**
43
+ * Hetzner Cloud configuration
44
+ */
45
+ export interface HetznerConfig {
46
+ /** Hetzner Cloud API token (falls back to HCLOUD_TOKEN / HETZNER_API_TOKEN) */
47
+ apiToken?: string;
48
+ /** Location slug, e.g. fsn1, nbg1, hel1 @default 'fsn1' */
49
+ location?: string;
50
+ /** Server image slug @default 'ubuntu-24.04' */
51
+ image?: string;
52
+ /** Path to SSH private key used for deploy commands @default ~/.ssh/id_ed25519 */
53
+ sshPrivateKeyPath?: string;
54
+ /** SSH user for deploy commands @default 'root' */
55
+ sshUser?: string;
56
+ }
1
57
  /**
2
58
  * AWS-specific configuration
3
59
  */
@@ -25,6 +81,20 @@ export interface CloudConfig {
25
81
  * AWS-specific configuration
26
82
  */
27
83
  aws?: AwsConfig;
84
+ /**
85
+ * Cloud provider selection (AWS, Hetzner, …)
86
+ */
87
+ cloud?: CloudProviderConfig;
88
+ /**
89
+ * Hetzner Cloud configuration (when cloud.provider is 'hetzner')
90
+ */
91
+ hetzner?: HetznerConfig;
92
+ /**
93
+ * Object storage provider selection (AWS S3, Backblaze B2, Hetzner Object Storage).
94
+ * Independent of `cloud.provider` — you can run compute on AWS while keeping
95
+ * object storage on Backblaze, for example.
96
+ */
97
+ objectStorage?: ObjectStorageConfig;
28
98
  /**
29
99
  * Feature flags to enable/disable resources conditionally
30
100
  * Example: { enableCache: true, enableMonitoring: false }
@@ -54,6 +124,11 @@ export interface ProjectConfig {
54
124
  name: string;
55
125
  slug: string;
56
126
  region: string;
127
+ /**
128
+ * Override the main CloudFormation stack name.
129
+ * Default: `{slug}-{environment}` (e.g. `pantry-production`).
130
+ */
131
+ stackName?: string;
57
132
  }
58
133
  /**
59
134
  * Deployment mode (optional)
@@ -151,6 +226,12 @@ export interface MessagingConfig {
151
226
  }>;
152
227
  }
153
228
  export interface InfrastructureConfig {
229
+ /**
230
+ * When false, `cloud deploy` skips the main CloudFormation stack and only runs
231
+ * site deploys, compute sync, and hooks. Use when infrastructure is managed
232
+ * elsewhere (e.g. registry EC2 via SSH) but site CDN/S3 still uses ts-cloud.
233
+ */
234
+ deployStack?: boolean;
154
235
  vpc?: VpcConfig;
155
236
  /**
156
237
  * Network/VPC configuration
@@ -619,8 +700,16 @@ export interface SiteConfig {
619
700
  path?: string;
620
701
  /** Custom domain for the site (e.g., 'stage.easyotc.com') */
621
702
  domain?: string;
622
- /** S3 bucket name (auto-generated from domain if not provided) */
703
+ /**
704
+ * S3 bucket name. Default: `{slug}-{environment}-site` for `main`,
705
+ * else `{slug}-{environment}-{siteKey}`.
706
+ */
623
707
  bucket?: string;
708
+ /**
709
+ * CloudFormation stack for this site's S3 + CloudFront infrastructure.
710
+ * Default: `{slug}-{environment}-{siteKey}-site` (e.g. `pantry-production-main-site`).
711
+ */
712
+ stackName?: string;
624
713
  /** SSL certificate ARN (auto-created if not provided) */
625
714
  certificateArn?: string;
626
715
  /** Build command to run before deployment (e.g., 'bun run generate', 'npm run build') */
@@ -811,6 +900,10 @@ export interface AlarmItemConfig {
811
900
  service?: string;
812
901
  }
813
902
  export interface StorageItemConfig {
903
+ /**
904
+ * Physical S3 bucket name. When omitted, defaults to `{slug}-{environment}-{key}`.
905
+ */
906
+ bucket?: string;
814
907
  /**
815
908
  * Make bucket publicly accessible
816
909
  */
@@ -851,6 +944,16 @@ export interface StorageItemConfig {
851
944
  * and 403/404 errors return a proper 404 page. This is correct for multi-page SSG sites.
852
945
  */
853
946
  spa?: boolean;
947
+ /**
948
+ * Route dynamic app paths to the compute server (EC2) via CloudFront cache behaviors.
949
+ * The `public` bucket enables this automatically; set explicitly for other bucket names.
950
+ */
951
+ routeCompute?: boolean;
952
+ /**
953
+ * CloudFront path patterns forwarded to compute (POST/PUT/PATCH/DELETE allowed).
954
+ * Used when `routeCompute` is true. Defaults to a registry-style path set when omitted.
955
+ */
956
+ computeRoutes?: string[];
854
957
  /**
855
958
  * Root directory containing the built static files to upload (e.g., 'dist', '.output/public').
856
959
  * When set, `cloud deploy` will auto-upload files from this directory to the S3 bucket
@@ -1145,6 +1248,15 @@ export interface ComputeConfig {
1145
1248
  * If not specified, uses the provider's default Linux image
1146
1249
  */
1147
1250
  image?: string;
1251
+ /**
1252
+ * CloudFront custom origin for the registry/app server (when the site stack
1253
+ * fronts EC2 instead of S3-only). Use the EC2 public DNS name, not a raw IP.
1254
+ */
1255
+ cloudFrontOriginDomain?: string;
1256
+ /** HTTP port on the compute origin. @default 3008 */
1257
+ cloudFrontOriginPort?: number;
1258
+ /** Stable CloudFront origin Id (preserve across stack updates). @default `{slug}-compute` */
1259
+ cloudFrontOriginId?: string;
1148
1260
  /**
1149
1261
  * Server mode (EC2) configuration
1150
1262
  */
@@ -1457,6 +1569,14 @@ export interface CdnItemConfig {
1457
1569
  functionArn?: string;
1458
1570
  name?: string;
1459
1571
  }>;
1572
+ /**
1573
+ * Forward dynamic paths to the compute app (EC2) in addition to the S3 origin.
1574
+ */
1575
+ routeCompute?: boolean;
1576
+ /**
1577
+ * CloudFront path patterns for compute routing. See `StorageConfig.computeRoutes`.
1578
+ */
1579
+ computeRoutes?: string[];
1460
1580
  }
1461
1581
  /**
1462
1582
  * Lambda trigger configuration for SQS queues
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-cloud/core",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
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.2.15"
34
+ "@ts-cloud/aws-types": "0.2.17"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^5.9.3"