@lsts_tech/infra 1.0.0

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -0
  3. package/dist/bin/init.d.ts +9 -0
  4. package/dist/bin/init.d.ts.map +1 -0
  5. package/dist/bin/init.js +315 -0
  6. package/dist/bin/init.js.map +1 -0
  7. package/dist/stacks/Dns.d.ts +69 -0
  8. package/dist/stacks/Dns.d.ts.map +1 -0
  9. package/dist/stacks/Dns.js +57 -0
  10. package/dist/stacks/Dns.js.map +1 -0
  11. package/dist/stacks/ExpoSite.d.ts +72 -0
  12. package/dist/stacks/ExpoSite.d.ts.map +1 -0
  13. package/dist/stacks/ExpoSite.js +49 -0
  14. package/dist/stacks/ExpoSite.js.map +1 -0
  15. package/dist/stacks/NextSite.d.ts +86 -0
  16. package/dist/stacks/NextSite.d.ts.map +1 -0
  17. package/dist/stacks/NextSite.js +60 -0
  18. package/dist/stacks/NextSite.js.map +1 -0
  19. package/dist/stacks/Pipeline.d.ts +128 -0
  20. package/dist/stacks/Pipeline.d.ts.map +1 -0
  21. package/dist/stacks/Pipeline.js +311 -0
  22. package/dist/stacks/Pipeline.js.map +1 -0
  23. package/dist/stacks/index.d.ts +41 -0
  24. package/dist/stacks/index.d.ts.map +1 -0
  25. package/dist/stacks/index.js +38 -0
  26. package/dist/stacks/index.js.map +1 -0
  27. package/docs/CLI.md +59 -0
  28. package/docs/CONFIGURATION.md +78 -0
  29. package/docs/EXAMPLES.md +9 -0
  30. package/examples/next-and-expo/infra.config.ts +104 -0
  31. package/examples/next-only/infra.config.ts +60 -0
  32. package/package.json +102 -0
  33. package/schemas/pipeline.schema.json +25 -0
  34. package/scripts/cleanup-orphan-lambdas.sh +102 -0
  35. package/scripts/delete-amplify-app.sh +50 -0
  36. package/scripts/ensure-pipelines.sh +144 -0
  37. package/scripts/ensure-secrets.sh +58 -0
  38. package/scripts/postdeploy-update-dns.sh +158 -0
  39. package/scripts/predeploy-checks.sh +192 -0
  40. package/scripts/pulumi-deploy.sh +29 -0
  41. package/scripts/sst-deploy.sh +79 -0
  42. package/templates/buildspec.yml +77 -0
  43. package/templates/ensure-pipelines.sh +117 -0
  44. package/templates/env.example +38 -0
  45. package/templates/infra.config.ts +199 -0
  46. package/templates/secrets.schema.json +20 -0
  47. package/templates/sst-env.d.ts +50 -0
  48. package/templates/sst.config.ts +28 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Pipeline — AWS CodePipeline + CodeBuild CI/CD Construct
3
+ *
4
+ * Creates a fully managed CI/CD pipeline using AWS-native services:
5
+ * - CodeStar Connection → GitHub (source)
6
+ * - CodeBuild → builds & deploys via SST
7
+ * - CodePipeline → orchestrates source → build
8
+ *
9
+ * Each pipeline is branch-aware: push to a branch triggers deployment
10
+ * to the corresponding SST stage.
11
+ *
12
+ * Security:
13
+ * - No AWS credentials stored in GitHub
14
+ * - Secrets fetched from SSM Parameter Store at build time
15
+ * - IAM roles scoped per pipeline
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * createPipeline({
20
+ * name: "myapp-prod",
21
+ * repo: "myorg/myapp",
22
+ * branch: "main",
23
+ * stage: "production",
24
+ * region: "us-east-1",
25
+ * });
26
+ * ```
27
+ */
28
+ /**
29
+ * Creates an AWS CodePipeline with CodeBuild for SST deployments.
30
+ *
31
+ * Architecture:
32
+ * GitHub (push) → CodeStar Connection → CodePipeline → CodeBuild → SST Deploy
33
+ *
34
+ * The pipeline:
35
+ * 1. Detects push to the configured branch via webhook
36
+ * 2. Pulls source code from GitHub
37
+ * 3. CodeBuild installs dependencies, builds the monorepo, and runs `sst deploy`
38
+ * 4. SST deploys the Next.js app to Lambda + CloudFront + S3
39
+ */
40
+ export function createPipeline(config) {
41
+ const { name, repo, branch, stage, region = "us-east-1", nodeVersion = "22", pnpmVersion = "9.15.0", infraPath = "packages/infra", buildEnv = {}, computeType = "BUILD_GENERAL1_MEDIUM", timeoutMinutes = 30, codestarConnectionArn, projectTag, } = config;
42
+ // ── 1. CodeStar Connection (GitHub) ──────────────────────────────────
43
+ // If no existing connection ARN is provided, create a new one.
44
+ // NOTE: New connections require manual confirmation in the AWS Console:
45
+ // Developer Tools → Settings → Connections → Pending → Update pending connection
46
+ // Allow providing the connection ARN via config or environment for deterministic
47
+ // deployments. Prefer explicit `codestarConnectionArn`, then an env var, then
48
+ // create a new connection if neither is provided.
49
+ const envKey = `${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_CODESTAR_CONNECTION_ARN`;
50
+ const envConnectionArn = process.env[envKey] || process.env.CODESTAR_CONNECTION_ARN;
51
+ const connection = codestarConnectionArn || envConnectionArn
52
+ ? { arn: (codestarConnectionArn || envConnectionArn) }
53
+ : (() => {
54
+ const conn = new aws.codestarconnections.Connection(`${name}-github-connection`, {
55
+ name: `${name}-github`,
56
+ providerType: "GitHub",
57
+ });
58
+ return { arn: conn.arn };
59
+ })();
60
+ // ── 2. S3 Artifact Bucket ───────────────────────────────────────────
61
+ const artifactBucket = new aws.s3.BucketV2(`${name}-artifacts`, {
62
+ bucketPrefix: `${name}-pipeline-artifacts`,
63
+ forceDestroy: true,
64
+ });
65
+ // tags to mark infra-managed critical resources so we can protect them
66
+ const commonTags = {
67
+ Project: projectTag ?? name.split("-")[0],
68
+ ManagedBy: "infra",
69
+ Stage: stage,
70
+ Protected: "true",
71
+ };
72
+ new aws.s3.BucketLifecycleConfigurationV2(`${name}-artifacts-lifecycle`, {
73
+ bucket: artifactBucket.id,
74
+ rules: [
75
+ {
76
+ id: "expire-artifacts",
77
+ status: "Enabled",
78
+ expiration: { days: 7 },
79
+ },
80
+ ],
81
+ });
82
+ // ── 3. IAM Role for CodeBuild ───────────────────────────────────────
83
+ const codebuildRole = new aws.iam.Role(`${name}-codebuild-role`, {
84
+ name: `${name}-codebuild-role`,
85
+ assumeRolePolicy: JSON.stringify({
86
+ Version: "2012-10-17",
87
+ Statement: [
88
+ {
89
+ Effect: "Allow",
90
+ Principal: { Service: "codebuild.amazonaws.com" },
91
+ Action: "sts:AssumeRole",
92
+ },
93
+ ],
94
+ }),
95
+ });
96
+ // CodeBuild needs broad permissions for SST deployments
97
+ // (CloudFormation, Lambda, S3, CloudFront, Route53, ACM, IAM, etc.)
98
+ new aws.iam.RolePolicyAttachment(`${name}-codebuild-admin`, {
99
+ role: codebuildRole.name,
100
+ policyArn: "arn:aws:iam::aws:policy/AdministratorAccess",
101
+ });
102
+ // ── 4. IAM Role for CodePipeline ────────────────────────────────────
103
+ const pipelineRole = new aws.iam.Role(`${name}-pipeline-role`, {
104
+ name: `${name}-pipeline-role`,
105
+ assumeRolePolicy: JSON.stringify({
106
+ Version: "2012-10-17",
107
+ Statement: [
108
+ {
109
+ Effect: "Allow",
110
+ Principal: { Service: "codepipeline.amazonaws.com" },
111
+ Action: "sts:AssumeRole",
112
+ },
113
+ ],
114
+ }),
115
+ });
116
+ const pipelinePolicy = new aws.iam.RolePolicy(`${name}-pipeline-policy`, {
117
+ role: pipelineRole.id,
118
+ policy: $jsonStringify({
119
+ Version: "2012-10-17",
120
+ Statement: [
121
+ {
122
+ Effect: "Allow",
123
+ Action: [
124
+ "s3:GetObject",
125
+ "s3:GetObjectVersion",
126
+ "s3:GetBucketVersioning",
127
+ "s3:PutObjectAcl",
128
+ "s3:PutObject",
129
+ ],
130
+ Resource: [$interpolate `${artifactBucket.arn}`, $interpolate `${artifactBucket.arn}/*`],
131
+ },
132
+ {
133
+ Effect: "Allow",
134
+ Action: ["codestar-connections:UseConnection"],
135
+ Resource: [connection.arn],
136
+ },
137
+ {
138
+ Effect: "Allow",
139
+ Action: [
140
+ "codebuild:BatchGetBuilds",
141
+ "codebuild:StartBuild",
142
+ ],
143
+ Resource: ["*"],
144
+ },
145
+ ],
146
+ }),
147
+ });
148
+ // ── 5. CodeBuild Project ────────────────────────────────────────────
149
+ const buildEnvVars = [
150
+ { name: "SST_STAGE", value: stage },
151
+ { name: "NODE_VERSION", value: nodeVersion },
152
+ { name: "PNPM_VERSION", value: pnpmVersion },
153
+ { name: "INFRA_PATH", value: infraPath },
154
+ ...Object.entries(buildEnv).map(([key, value]) => ({
155
+ name: key,
156
+ value,
157
+ type: value.startsWith("arn:aws:ssm:") ? "PARAMETER_STORE" : "PLAINTEXT",
158
+ })),
159
+ ];
160
+ const codebuildProject = new aws.codebuild.Project(`${name}-build`, {
161
+ name: `${name}-build`,
162
+ description: `Build & deploy ${name} (stage: ${stage}) via SST`,
163
+ serviceRole: codebuildRole.arn,
164
+ buildTimeout: timeoutMinutes,
165
+ environment: {
166
+ computeType,
167
+ image: "aws/codebuild/amazonlinux2-x86_64-standard:5.0",
168
+ type: "LINUX_CONTAINER",
169
+ environmentVariables: buildEnvVars,
170
+ },
171
+ source: {
172
+ type: "CODEPIPELINE",
173
+ buildspec: `${infraPath}/buildspec.yml`,
174
+ },
175
+ artifacts: {
176
+ type: "CODEPIPELINE",
177
+ },
178
+ logsConfig: {
179
+ cloudwatchLogs: {
180
+ groupName: `/codebuild/${name}`,
181
+ streamName: "build-log",
182
+ },
183
+ },
184
+ // tag build resources for ownership and automated checks
185
+ tags: commonTags,
186
+ });
187
+ // Ensure the pipeline role can start the CodeBuild project used by the pipeline.
188
+ // CodePipeline assumes `pipelineRole` and must be allowed to call `codebuild:StartBuild`.
189
+ new aws.iam.RolePolicy(`${name}-pipeline-codebuild-start`, {
190
+ role: pipelineRole.id,
191
+ policy: $jsonStringify({
192
+ Version: "2012-10-17",
193
+ Statement: [
194
+ {
195
+ Effect: "Allow",
196
+ Action: ["codebuild:StartBuild", "codebuild:BatchGetBuilds"],
197
+ Resource: [$interpolate `${codebuildProject.arn}`],
198
+ },
199
+ ],
200
+ }),
201
+ });
202
+ // Ensure pipeline role can operate on the artifact S3 bucket (upload/download artifacts)
203
+ new aws.iam.RolePolicy(`${name}-pipeline-s3`, {
204
+ role: pipelineRole.id,
205
+ policy: $jsonStringify({
206
+ Version: "2012-10-17",
207
+ Statement: [
208
+ {
209
+ Effect: "Allow",
210
+ Action: ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:GetObjectVersion"],
211
+ Resource: [$interpolate `${artifactBucket.arn}/*`],
212
+ },
213
+ {
214
+ Effect: "Allow",
215
+ Action: ["s3:ListBucket", "s3:GetBucketLocation"],
216
+ Resource: [$interpolate `${artifactBucket.arn}`],
217
+ },
218
+ ],
219
+ }),
220
+ });
221
+ // Ensure pipeline role can use the CodeStar Connection (GitHub) for source actions
222
+ new aws.iam.RolePolicy(`${name}-pipeline-codestar`, {
223
+ role: pipelineRole.id,
224
+ policy: $jsonStringify({
225
+ Version: "2012-10-17",
226
+ Statement: [
227
+ {
228
+ Effect: "Allow",
229
+ Action: ["codestar-connections:UseConnection", "codestar-connections:GetConnection"],
230
+ Resource: [connection.arn],
231
+ },
232
+ ],
233
+ }),
234
+ });
235
+ // ── 6. CodePipeline ─────────────────────────────────────────────────
236
+ const pipeline = new aws.codepipeline.Pipeline(`${name}-pipeline`, {
237
+ name: `${name}-pipeline`,
238
+ roleArn: pipelineRole.arn,
239
+ pipelineType: "V2",
240
+ artifactStores: [
241
+ {
242
+ location: artifactBucket.bucket,
243
+ type: "S3",
244
+ },
245
+ ],
246
+ stages: [
247
+ {
248
+ name: "Source",
249
+ actions: [
250
+ {
251
+ name: "GitHub-Source",
252
+ category: "Source",
253
+ owner: "AWS",
254
+ provider: "CodeStarSourceConnection",
255
+ version: "1",
256
+ outputArtifacts: ["source_output"],
257
+ configuration: {
258
+ ConnectionArn: connection.arn,
259
+ FullRepositoryId: repo,
260
+ BranchName: branch,
261
+ OutputArtifactFormat: "CODE_ZIP",
262
+ DetectChanges: "true",
263
+ },
264
+ },
265
+ ],
266
+ },
267
+ {
268
+ name: "Build-Deploy",
269
+ actions: [
270
+ {
271
+ name: "SST-Deploy",
272
+ category: "Build",
273
+ owner: "AWS",
274
+ provider: "CodeBuild",
275
+ inputArtifacts: ["source_output"],
276
+ version: "1",
277
+ configuration: {
278
+ ProjectName: codebuildProject.name,
279
+ },
280
+ },
281
+ ],
282
+ },
283
+ ],
284
+ triggers: [
285
+ {
286
+ providerType: "CodeStarSourceConnection",
287
+ gitConfiguration: {
288
+ sourceActionName: "GitHub-Source",
289
+ pushes: [
290
+ {
291
+ branches: {
292
+ includes: [branch],
293
+ },
294
+ },
295
+ ],
296
+ },
297
+ },
298
+ ],
299
+ // ensure the pipeline is tagged so it can be identified
300
+ tags: commonTags,
301
+ });
302
+ return {
303
+ pipeline,
304
+ codebuildProject,
305
+ connection,
306
+ artifactBucket,
307
+ pipelineName: pipeline.name,
308
+ pipelineArn: pipeline.arn,
309
+ };
310
+ }
311
+ //# sourceMappingURL=Pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pipeline.js","sourceRoot":"","sources":["../../stacks/Pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @lsts_tech/infra — Opinionated SST v3 Infrastructure Constructs
3
+ *
4
+ * This package provides reusable, cloud-agnostic infrastructure primitives
5
+ * for deploying Next.js and Expo web apps from a pnpm/Turborepo monorepo
6
+ * using SST v3 and AWS CodePipeline.
7
+ *
8
+ * Configure your project-specific settings in an `infra.config.ts` file
9
+ * at your infra package root. Run `npx @lsts_tech/infra init` to scaffold one.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // infra.config.ts (your project)
14
+ * import { resolveDomain, createNextSite, createPipeline } from "@lsts_tech/infra";
15
+ *
16
+ * export function createInfrastructure() {
17
+ * const stage = $app.stage;
18
+ * const { domain, domainName } = resolveDomain({
19
+ * rootDomain: "example.com",
20
+ * stage,
21
+ * });
22
+ *
23
+ * const { site, url } = createNextSite({
24
+ * appPath: "../../apps/web",
25
+ * id: `web-${stage}`,
26
+ * domain,
27
+ * });
28
+ *
29
+ * return { siteUrl: url, domain: domainName };
30
+ * }
31
+ * ```
32
+ */
33
+ export { resolveDomain } from "./Dns.js";
34
+ export type { DnsConfig, DomainResult } from "./Dns.js";
35
+ export { createNextSite } from "./NextSite.js";
36
+ export type { NextSiteConfig } from "./NextSite.js";
37
+ export { createExpoSite } from "./ExpoSite.js";
38
+ export type { ExpoSiteConfig } from "./ExpoSite.js";
39
+ export { createPipeline } from "./Pipeline.js";
40
+ export type { PipelineConfig } from "./Pipeline.js";
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../stacks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @lsts_tech/infra — Opinionated SST v3 Infrastructure Constructs
3
+ *
4
+ * This package provides reusable, cloud-agnostic infrastructure primitives
5
+ * for deploying Next.js and Expo web apps from a pnpm/Turborepo monorepo
6
+ * using SST v3 and AWS CodePipeline.
7
+ *
8
+ * Configure your project-specific settings in an `infra.config.ts` file
9
+ * at your infra package root. Run `npx @lsts_tech/infra init` to scaffold one.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // infra.config.ts (your project)
14
+ * import { resolveDomain, createNextSite, createPipeline } from "@lsts_tech/infra";
15
+ *
16
+ * export function createInfrastructure() {
17
+ * const stage = $app.stage;
18
+ * const { domain, domainName } = resolveDomain({
19
+ * rootDomain: "example.com",
20
+ * stage,
21
+ * });
22
+ *
23
+ * const { site, url } = createNextSite({
24
+ * appPath: "../../apps/web",
25
+ * id: `web-${stage}`,
26
+ * domain,
27
+ * });
28
+ *
29
+ * return { siteUrl: url, domain: domainName };
30
+ * }
31
+ * ```
32
+ */
33
+ // ── Re-exports ─────────────────────────────────────────────────────────
34
+ export { resolveDomain } from "./Dns.js";
35
+ export { createNextSite } from "./NextSite.js";
36
+ export { createExpoSite } from "./ExpoSite.js";
37
+ export { createPipeline } from "./Pipeline.js";
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../stacks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,0EAA0E;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
package/docs/CLI.md ADDED
@@ -0,0 +1,59 @@
1
+ # CLI Reference
2
+
3
+ The package ships with a bootstrap CLI:
4
+
5
+ ```bash
6
+ npx @lsts_tech/infra init [options]
7
+ ```
8
+
9
+ Provider support in v1.0.0: AWS only.
10
+
11
+ ## Options
12
+
13
+ | Option | Description | Default |
14
+ |---|---|---|
15
+ | `--provider <name>` | Cloud provider (`aws`) | `aws` |
16
+ | `--project <slug>` | Project prefix for app/pipelines/tags | `myapp` |
17
+ | `--app-name <name>` | SST app name | `--project` |
18
+ | `--domain <domain>` | Root domain | `example.com` |
19
+ | `--repo <owner/repo>` | GitHub repo for CodePipeline source | `myorg/myrepo` |
20
+ | `--pipelines <list>` | Comma list: `production,dev,mobile` or `none` | `production,dev` |
21
+ | `--branch-prod <branch>` | Production pipeline branch | `main` |
22
+ | `--branch-dev <branch>` | Dev pipeline branch | `develop` |
23
+ | `--branch-mobile <branch>` | Mobile pipeline branch | `mobile` |
24
+ | `--with-expo` | Enable Expo web deployment defaults | `false` |
25
+ | `--infra-path <path>` | Infra path from monorepo root | `packages/infra` |
26
+ | `--target <path>` | Scaffold destination directory | `.` |
27
+ | `--force` | Overwrite existing files | `false` |
28
+ | `--help` | Print help | - |
29
+
30
+ ## Generated Files
31
+
32
+ - `sst.config.ts`
33
+ - `sst-env.d.ts`
34
+ - `infra.config.ts`
35
+ - `.env.example`
36
+ - `buildspec.yml`
37
+ - `schemas/secrets.schema.json`
38
+ - `scripts/ensure-pipelines.sh`
39
+
40
+ ## Example Commands
41
+
42
+ ```bash
43
+ # Next.js only, production+dev pipelines
44
+ npx @lsts_tech/infra init \
45
+ --project acme \
46
+ --domain acme.com \
47
+ --repo acme/web
48
+
49
+ # Next.js + Expo Web + mobile pipeline
50
+ npx @lsts_tech/infra init \
51
+ --project acme \
52
+ --domain acme.com \
53
+ --repo acme/web \
54
+ --with-expo \
55
+ --pipelines production,dev,mobile
56
+
57
+ # Re-generate into packages/infra and overwrite existing files
58
+ npx @lsts_tech/infra init --target packages/infra --force
59
+ ```
@@ -0,0 +1,78 @@
1
+ # Configuration Guide
2
+
3
+ ## Environment Variables
4
+
5
+ The scaffolded `infra.config.ts` is environment-driven.
6
+
7
+ | Variable | Required | Description |
8
+ |---|---|---|
9
+ | `INFRA_APP_NAME` | No | SST app name |
10
+ | `INFRA_ROOT_DOMAIN` | Yes | Root domain for stage resolution |
11
+ | `INFRA_PIPELINE_REPO` | No | GitHub repo in `owner/repo` format |
12
+ | `INFRA_PIPELINE_PREFIX` | No | Prefix for pipeline names |
13
+ | `INFRA_PROJECT_TAG` | No | Resource tag value |
14
+ | `INFRA_PIPELINES` | No | Pipeline stages CSV: `production,dev,mobile` |
15
+ | `INFRA_PIPELINE_BRANCH_PROD` | No | Branch for production pipeline |
16
+ | `INFRA_PIPELINE_BRANCH_DEV` | No | Branch for dev pipeline |
17
+ | `INFRA_PIPELINE_BRANCH_MOBILE` | No | Branch for mobile pipeline |
18
+ | `INFRA_ENABLE_EXPO_SITE` | No | `true` enables Expo `StaticSite` deploy |
19
+
20
+ ### Domain Overrides (Optional)
21
+
22
+ | Variable | Description |
23
+ |---|---|
24
+ | `INFRA_WEB_DOMAIN_PRODUCTION` | Web production domain |
25
+ | `INFRA_WEB_DOMAIN_DEV` | Web dev domain |
26
+ | `INFRA_WEB_DOMAIN_MOBILE` | Web mobile-stage domain |
27
+ | `INFRA_EXPO_DOMAIN_PRODUCTION` | Expo production domain |
28
+ | `INFRA_EXPO_DOMAIN_DEV` | Expo dev domain |
29
+ | `INFRA_EXPO_DOMAIN_MOBILE` | Expo mobile-stage domain |
30
+
31
+ ## DNS Helper Script Overrides
32
+
33
+ These are consumed by `predeploy-checks.sh` and `postdeploy-update-dns.sh` (typically via `buildspec.yml`):
34
+
35
+ | Variable | Description |
36
+ |---|---|
37
+ | `DOMAIN_ROOT` | Base/root domain |
38
+ | `DOMAIN_PRODUCTION` | Explicit production domain override |
39
+ | `DOMAIN_DEV` | Explicit dev domain override |
40
+ | `DOMAIN_MOBILE` | Explicit mobile domain override |
41
+ | `DOMAIN_CUSTOM` | Explicit override for non-standard stage names |
42
+
43
+ ### Certificate Reuse (Optional)
44
+
45
+ | Variable | Description |
46
+ |---|---|
47
+ | `INFRA_WEB_CERT_ARN_PRODUCTION` | Existing ACM cert ARN for web prod domain |
48
+ | `INFRA_WEB_CERT_ARN_DEV` | Existing ACM cert ARN for web dev domain |
49
+ | `INFRA_WEB_CERT_ARN_MOBILE` | Existing ACM cert ARN for web mobile-stage domain |
50
+ | `INFRA_EXPO_CERT_ARN_PRODUCTION` | Existing ACM cert ARN for expo prod domain |
51
+ | `INFRA_EXPO_CERT_ARN_DEV` | Existing ACM cert ARN for expo dev domain |
52
+ | `INFRA_EXPO_CERT_ARN_MOBILE` | Existing ACM cert ARN for expo mobile-stage domain |
53
+
54
+ ## SST Secrets
55
+
56
+ Minimum secrets from the default template:
57
+
58
+ - `DatabaseUrl`
59
+ - `AuthSecret`
60
+
61
+ Set per stage:
62
+
63
+ ```bash
64
+ npx sst secrets set DatabaseUrl "postgresql://..." --stage dev
65
+ npx sst secrets set AuthSecret "your-secret" --stage dev
66
+ ```
67
+
68
+ ## Pipeline Provisioning
69
+
70
+ 1. Ensure your `INFRA_PIPELINES` and branch variables are correct.
71
+ 2. Deploy once (`sst deploy`) from the infra package.
72
+ 3. Run:
73
+
74
+ ```bash
75
+ APPROVE=true bash scripts/ensure-pipelines.sh
76
+ ```
77
+
78
+ This script checks for missing pipelines and creates them by running stage-specific SST deploys.
@@ -0,0 +1,9 @@
1
+ # Examples
2
+
3
+ - [`next-only/infra.config.ts`](../examples/next-only/infra.config.ts)
4
+ Next.js site + optional production/dev pipelines.
5
+
6
+ - [`next-and-expo/infra.config.ts`](../examples/next-and-expo/infra.config.ts)
7
+ Next.js + Expo web static site + production/dev/mobile pipelines.
8
+
9
+ Both examples are provider-scoped to AWS for v1.0.0.
@@ -0,0 +1,104 @@
1
+ import { resolveDomain, createNextSite, createExpoSite, createPipeline } from "@lsts_tech/infra";
2
+
3
+ const secrets = {
4
+ DatabaseUrl: new sst.Secret("DatabaseUrl"),
5
+ AuthSecret: new sst.Secret("AuthSecret"),
6
+ };
7
+
8
+ export function createInfrastructure() {
9
+ const stage = $app.stage;
10
+ const rootDomain = process.env.INFRA_ROOT_DOMAIN ?? "example.com";
11
+ const repo = process.env.INFRA_PIPELINE_REPO ?? "myorg/myrepo";
12
+ const prefix = process.env.INFRA_PIPELINE_PREFIX ?? "myapp";
13
+
14
+ const webDomain = resolveDomain({
15
+ rootDomain,
16
+ stage,
17
+ stageMap: {
18
+ production: process.env.INFRA_WEB_DOMAIN_PRODUCTION ?? rootDomain,
19
+ dev: process.env.INFRA_WEB_DOMAIN_DEV ?? `dev.${rootDomain}`,
20
+ mobile: process.env.INFRA_WEB_DOMAIN_MOBILE ?? `api.${rootDomain}`,
21
+ },
22
+ });
23
+
24
+ const web = createNextSite({
25
+ appPath: "../../apps/web",
26
+ id: `web-${stage}`,
27
+ domain: webDomain.domain,
28
+ certificateArn: {
29
+ production: process.env.INFRA_WEB_CERT_ARN_PRODUCTION,
30
+ dev: process.env.INFRA_WEB_CERT_ARN_DEV,
31
+ mobile: process.env.INFRA_WEB_CERT_ARN_MOBILE,
32
+ }[stage],
33
+ environment: {
34
+ NEXT_PUBLIC_APP_URL: `https://${webDomain.domainName}`,
35
+ DATABASE_URL: secrets.DatabaseUrl.value,
36
+ AUTH_SECRET: secrets.AuthSecret.value,
37
+ },
38
+ });
39
+
40
+ const expoDomain = resolveDomain({
41
+ rootDomain,
42
+ stage,
43
+ stageMap: {
44
+ production: process.env.INFRA_EXPO_DOMAIN_PRODUCTION ?? `mobile.${rootDomain}`,
45
+ dev: process.env.INFRA_EXPO_DOMAIN_DEV ?? `dev.mobile.${rootDomain}`,
46
+ mobile: process.env.INFRA_EXPO_DOMAIN_MOBILE ?? `preview.mobile.${rootDomain}`,
47
+ },
48
+ });
49
+
50
+ const expo = createExpoSite({
51
+ appPath: "../../apps/mobile",
52
+ id: `mobile-${stage}`,
53
+ domain: expoDomain.domain,
54
+ certificateArn: {
55
+ production: process.env.INFRA_EXPO_CERT_ARN_PRODUCTION,
56
+ dev: process.env.INFRA_EXPO_CERT_ARN_DEV,
57
+ mobile: process.env.INFRA_EXPO_CERT_ARN_MOBILE,
58
+ }[stage],
59
+ environment: {
60
+ EXPO_PUBLIC_API_URL: `https://${webDomain.domainName}/api`,
61
+ },
62
+ });
63
+
64
+ const outputs: Record<string, unknown> = {
65
+ siteUrl: web.url,
66
+ domain: webDomain.domainName,
67
+ mobileUrl: expo.url,
68
+ mobileDomain: expoDomain.domainName,
69
+ };
70
+
71
+ if (stage === "production") {
72
+ const production = createPipeline({
73
+ name: `${prefix}-prod`,
74
+ repo,
75
+ branch: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "main",
76
+ stage: "production",
77
+ projectTag: process.env.INFRA_PROJECT_TAG ?? prefix,
78
+ });
79
+
80
+ const dev = createPipeline({
81
+ name: `${prefix}-dev`,
82
+ repo,
83
+ branch: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "develop",
84
+ stage: "dev",
85
+ projectTag: process.env.INFRA_PROJECT_TAG ?? prefix,
86
+ });
87
+
88
+ const mobile = createPipeline({
89
+ name: `${prefix}-mobile`,
90
+ repo,
91
+ branch: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "mobile",
92
+ stage: "mobile",
93
+ projectTag: process.env.INFRA_PROJECT_TAG ?? prefix,
94
+ });
95
+
96
+ outputs.pipelines = {
97
+ production: production.pipelineName,
98
+ dev: dev.pipelineName,
99
+ mobile: mobile.pipelineName,
100
+ };
101
+ }
102
+
103
+ return outputs;
104
+ }