@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,38 @@
1
+ # Provider support in v1.0.0: AWS only
2
+ INFRA_PROVIDER=__PROVIDER__
3
+
4
+ # SST app identity
5
+ INFRA_APP_NAME=__APP_NAME__
6
+
7
+ # Core domain and repository
8
+ INFRA_ROOT_DOMAIN=__ROOT_DOMAIN__
9
+ INFRA_PIPELINE_REPO=__PIPELINE_REPO__
10
+ INFRA_PIPELINE_PREFIX=__PROJECT_PREFIX__
11
+ INFRA_PROJECT_TAG=__PROJECT_PREFIX__
12
+
13
+ # Optional pipeline stages: production,dev,mobile
14
+ INFRA_PIPELINES=__PIPELINES_DEFAULT__
15
+ INFRA_PIPELINE_BRANCH_PROD=__BRANCH_PROD__
16
+ INFRA_PIPELINE_BRANCH_DEV=__BRANCH_DEV__
17
+ INFRA_PIPELINE_BRANCH_MOBILE=__BRANCH_MOBILE__
18
+
19
+ # Optional Expo static site deployment
20
+ INFRA_ENABLE_EXPO_SITE=__ENABLE_EXPO_SITE__
21
+
22
+ # Optional explicit domain overrides per stage
23
+ INFRA_WEB_DOMAIN_PRODUCTION=__ROOT_DOMAIN__
24
+ INFRA_WEB_DOMAIN_DEV=dev.__ROOT_DOMAIN__
25
+ INFRA_WEB_DOMAIN_MOBILE=api.__ROOT_DOMAIN__
26
+
27
+ INFRA_EXPO_DOMAIN_PRODUCTION=mobile.__ROOT_DOMAIN__
28
+ INFRA_EXPO_DOMAIN_DEV=dev.mobile.__ROOT_DOMAIN__
29
+ INFRA_EXPO_DOMAIN_MOBILE=preview.mobile.__ROOT_DOMAIN__
30
+
31
+ # Optional existing ACM cert ARNs (leave empty to let SST manage certificates)
32
+ INFRA_WEB_CERT_ARN_PRODUCTION=
33
+ INFRA_WEB_CERT_ARN_DEV=
34
+ INFRA_WEB_CERT_ARN_MOBILE=
35
+
36
+ INFRA_EXPO_CERT_ARN_PRODUCTION=
37
+ INFRA_EXPO_CERT_ARN_DEV=
38
+ INFRA_EXPO_CERT_ARN_MOBILE=
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Template: infra.config.ts
3
+ *
4
+ * Generated by `npx @lsts_tech/infra init`.
5
+ * This file is white-label and environment-driven for public repositories.
6
+ */
7
+
8
+ /// <reference path="./sst-env.d.ts" />
9
+
10
+ import { resolveDomain, createNextSite, createExpoSite, createPipeline } from "@lsts_tech/infra";
11
+
12
+ type PipelineStage = "production" | "dev" | "mobile";
13
+
14
+ const secrets = {
15
+ DatabaseUrl: new sst.Secret("DatabaseUrl"),
16
+ AuthSecret: new sst.Secret("AuthSecret"),
17
+ };
18
+
19
+ const rootDomain = process.env.INFRA_ROOT_DOMAIN ?? "__ROOT_DOMAIN__";
20
+ const pipelineRepo = process.env.INFRA_PIPELINE_REPO ?? "__PIPELINE_REPO__";
21
+ const pipelinePrefix = process.env.INFRA_PIPELINE_PREFIX ?? "__PROJECT_PREFIX__";
22
+ const pipelineProjectTag = process.env.INFRA_PROJECT_TAG ?? "__PROJECT_PREFIX__";
23
+ const appName = process.env.INFRA_APP_NAME ?? "__PROJECT_PREFIX__";
24
+ const selectedPipelinesRaw = process.env.INFRA_PIPELINES ?? "__PIPELINES_DEFAULT__";
25
+ const enableExpoSite = (process.env.INFRA_ENABLE_EXPO_SITE ?? "__ENABLE_EXPO_SITE__") === "true";
26
+
27
+ const webStageMap: Record<string, string> = {
28
+ production: process.env.INFRA_WEB_DOMAIN_PRODUCTION ?? rootDomain,
29
+ dev: process.env.INFRA_WEB_DOMAIN_DEV ?? `dev.${rootDomain}`,
30
+ mobile: process.env.INFRA_WEB_DOMAIN_MOBILE ?? `api.${rootDomain}`,
31
+ };
32
+
33
+ const expoStageMap: Record<string, string> = {
34
+ production: process.env.INFRA_EXPO_DOMAIN_PRODUCTION ?? `mobile.${rootDomain}`,
35
+ dev: process.env.INFRA_EXPO_DOMAIN_DEV ?? `dev.mobile.${rootDomain}`,
36
+ mobile: process.env.INFRA_EXPO_DOMAIN_MOBILE ?? `preview.mobile.${rootDomain}`,
37
+ };
38
+
39
+ const webCerts: Record<string, string | undefined> = {
40
+ production: process.env.INFRA_WEB_CERT_ARN_PRODUCTION,
41
+ dev: process.env.INFRA_WEB_CERT_ARN_DEV,
42
+ mobile: process.env.INFRA_WEB_CERT_ARN_MOBILE,
43
+ };
44
+
45
+ const expoCerts: Record<string, string | undefined> = {
46
+ production: process.env.INFRA_EXPO_CERT_ARN_PRODUCTION,
47
+ dev: process.env.INFRA_EXPO_CERT_ARN_DEV,
48
+ mobile: process.env.INFRA_EXPO_CERT_ARN_MOBILE,
49
+ };
50
+
51
+ const commonBuildEnv = {
52
+ INFRA_APP_NAME: appName,
53
+ INFRA_ROOT_DOMAIN: rootDomain,
54
+ INFRA_PIPELINE_REPO: pipelineRepo,
55
+ INFRA_PIPELINE_PREFIX: pipelinePrefix,
56
+ INFRA_PROJECT_TAG: pipelineProjectTag,
57
+ INFRA_PIPELINE_BRANCH_PROD: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "__BRANCH_PROD__",
58
+ INFRA_PIPELINE_BRANCH_DEV: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "__BRANCH_DEV__",
59
+ INFRA_PIPELINE_BRANCH_MOBILE: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "__BRANCH_MOBILE__",
60
+ INFRA_WEB_DOMAIN_PRODUCTION: webStageMap.production,
61
+ INFRA_WEB_DOMAIN_DEV: webStageMap.dev,
62
+ INFRA_WEB_DOMAIN_MOBILE: webStageMap.mobile,
63
+ INFRA_EXPO_DOMAIN_PRODUCTION: expoStageMap.production,
64
+ INFRA_EXPO_DOMAIN_DEV: expoStageMap.dev,
65
+ INFRA_EXPO_DOMAIN_MOBILE: expoStageMap.mobile,
66
+ INFRA_WEB_CERT_ARN_PRODUCTION: webCerts.production ?? "",
67
+ INFRA_WEB_CERT_ARN_DEV: webCerts.dev ?? "",
68
+ INFRA_WEB_CERT_ARN_MOBILE: webCerts.mobile ?? "",
69
+ INFRA_EXPO_CERT_ARN_PRODUCTION: expoCerts.production ?? "",
70
+ INFRA_EXPO_CERT_ARN_DEV: expoCerts.dev ?? "",
71
+ INFRA_EXPO_CERT_ARN_MOBILE: expoCerts.mobile ?? "",
72
+ DOMAIN_ROOT: rootDomain,
73
+ PROJECT_PREFIX: pipelinePrefix,
74
+ PREFIX: pipelinePrefix,
75
+ DOMAIN_PRODUCTION: webStageMap.production,
76
+ DOMAIN_DEV: webStageMap.dev,
77
+ DOMAIN_MOBILE: webStageMap.mobile,
78
+ };
79
+
80
+ function parsePipelineStage(value: string): PipelineStage | undefined {
81
+ const normalized = value.trim().toLowerCase();
82
+ if (normalized === "production" || normalized === "prod") return "production";
83
+ if (normalized === "dev") return "dev";
84
+ if (normalized === "mobile") return "mobile";
85
+ return undefined;
86
+ }
87
+
88
+ const selectedPipelines = new Set<PipelineStage>(
89
+ selectedPipelinesRaw
90
+ .split(",")
91
+ .map((value) => parsePipelineStage(value))
92
+ .filter((value): value is PipelineStage => value !== undefined)
93
+ );
94
+
95
+ const pipelineSpecs: Record<PipelineStage, { suffix: string; branch: string; stage: PipelineStage }> = {
96
+ production: {
97
+ suffix: "prod",
98
+ branch: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "__BRANCH_PROD__",
99
+ stage: "production",
100
+ },
101
+ dev: {
102
+ suffix: "dev",
103
+ branch: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "__BRANCH_DEV__",
104
+ stage: "dev",
105
+ },
106
+ mobile: {
107
+ suffix: "mobile",
108
+ branch: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "__BRANCH_MOBILE__",
109
+ stage: "mobile",
110
+ },
111
+ };
112
+
113
+ export function createInfrastructure() {
114
+ const stage = $app.stage;
115
+
116
+ const { domain, domainName } = resolveDomain({
117
+ rootDomain,
118
+ stage,
119
+ stageMap: webStageMap,
120
+ });
121
+
122
+ const { url } = createNextSite({
123
+ appPath: "../../apps/web",
124
+ id: `web-${stage}`,
125
+ domain,
126
+ certificateArn: webCerts[stage],
127
+ environment: {
128
+ NEXT_PUBLIC_APP_URL: `https://${domainName}`,
129
+ DATABASE_URL: secrets.DatabaseUrl.value,
130
+ AUTH_SECRET: secrets.AuthSecret.value,
131
+ },
132
+ warm: stage === "production" ? 1 : 0,
133
+ invalidation: {
134
+ paths: ["/*"],
135
+ wait: stage === "production",
136
+ },
137
+ });
138
+
139
+ let mobileUrl: string | undefined;
140
+ let mobileDomainName: string | undefined;
141
+
142
+ if (enableExpoSite) {
143
+ const expoDomainResult = resolveDomain({
144
+ rootDomain,
145
+ stage,
146
+ stageMap: expoStageMap,
147
+ });
148
+
149
+ mobileDomainName = expoDomainResult.domainName;
150
+
151
+ const expoSiteResult = createExpoSite({
152
+ appPath: "../../apps/mobile",
153
+ id: `mobile-${stage}`,
154
+ domain: expoDomainResult.domain,
155
+ certificateArn: expoCerts[stage],
156
+ environment: {
157
+ EXPO_PUBLIC_API_URL: `https://${domainName}/api`,
158
+ },
159
+ invalidation: {
160
+ paths: ["/*"],
161
+ wait: stage === "production" || stage === "mobile",
162
+ },
163
+ });
164
+
165
+ mobileUrl = expoSiteResult.url;
166
+ }
167
+
168
+ const outputs: Record<string, unknown> = {
169
+ siteUrl: url,
170
+ domain: domainName,
171
+ };
172
+
173
+ if (mobileUrl && mobileDomainName) {
174
+ outputs.mobileUrl = mobileUrl;
175
+ outputs.mobileDomain = mobileDomainName;
176
+ }
177
+
178
+ if (stage === "production" && selectedPipelines.size > 0) {
179
+ const pipelineOutputs: Record<string, string> = {};
180
+
181
+ for (const pipelineStage of selectedPipelines) {
182
+ const spec = pipelineSpecs[pipelineStage];
183
+ const pipeline = createPipeline({
184
+ name: `${pipelinePrefix}-${spec.suffix}`,
185
+ repo: pipelineRepo,
186
+ branch: spec.branch,
187
+ stage: spec.stage,
188
+ projectTag: pipelineProjectTag,
189
+ buildEnv: commonBuildEnv,
190
+ });
191
+
192
+ pipelineOutputs[`${pipelineStage}PipelineName`] = pipeline.pipelineName;
193
+ }
194
+
195
+ outputs.pipelines = pipelineOutputs;
196
+ }
197
+
198
+ return outputs;
199
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Secrets Schema",
4
+ "description": "Define the required secrets for your web app deployment. Use with the ensure-secrets.sh script to provision placeholders or real values via `sst secret set`.",
5
+ "type": "object",
6
+ "properties": {
7
+ "DatabaseUrl": {
8
+ "type": "string",
9
+ "description": "Database connection string"
10
+ },
11
+ "AuthSecret": {
12
+ "type": "string",
13
+ "description": "Application auth secret (for session signing, JWT, etc.)"
14
+ }
15
+ },
16
+ "required": [
17
+ "DatabaseUrl",
18
+ "AuthSecret"
19
+ ]
20
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * SST v3 (Ion) Global Type Declarations
3
+ *
4
+ * Lightweight stubs for standalone TypeScript compilation.
5
+ * Replace or regenerate this file with SST-generated types in your project if needed.
6
+ */
7
+
8
+ /* eslint-disable @typescript-eslint/no-explicit-any */
9
+
10
+ declare const sst: {
11
+ aws: {
12
+ Nextjs: new (id: string, props: any) => any;
13
+ StaticSite: new (id: string, props: any) => any;
14
+ [key: string]: any;
15
+ };
16
+ Secret: new (name: string) => { value: any };
17
+ [key: string]: any;
18
+ };
19
+
20
+ declare const aws: {
21
+ codestarconnections: {
22
+ Connection: new (name: string, props: any) => { arn: any };
23
+ };
24
+ s3: {
25
+ BucketV2: new (name: string, props: any) => { id: any; arn: any; bucket: any };
26
+ BucketLifecycleConfigurationV2: new (name: string, props: any) => any;
27
+ };
28
+ iam: {
29
+ Role: new (name: string, props: any) => { id: any; arn: any; name: any };
30
+ RolePolicy: new (name: string, props: any) => any;
31
+ RolePolicyAttachment: new (name: string, props: any) => any;
32
+ };
33
+ codebuild: {
34
+ Project: new (name: string, props: any) => { arn: any; name: any };
35
+ };
36
+ codepipeline: {
37
+ Pipeline: new (name: string, props: any) => { name: any; arn: any };
38
+ };
39
+ [key: string]: any;
40
+ };
41
+
42
+ declare const $app: {
43
+ stage: string;
44
+ name: string;
45
+ [key: string]: any;
46
+ };
47
+
48
+ declare function $interpolate(strings: TemplateStringsArray, ...values: any[]): any;
49
+ declare function $jsonStringify(obj: any): any;
50
+ declare function $config(config: any): any;
@@ -0,0 +1,28 @@
1
+ /// <reference path="./sst-env.d.ts" />
2
+
3
+ /**
4
+ * Template: sst.config.ts
5
+ *
6
+ * Generated by `npx @lsts_tech/infra init`.
7
+ * Provider support in v1.0.0: AWS only.
8
+ */
9
+
10
+ export default $config({
11
+ app(input: any) {
12
+ return {
13
+ name: process.env.INFRA_APP_NAME ?? "__APP_NAME__",
14
+ removal: input?.stage === "production" ? "retain" : "remove",
15
+ protect: ["production"].includes(input?.stage),
16
+ home: "aws",
17
+ providers: {
18
+ aws: {
19
+ region: process.env.AWS_REGION ?? "us-east-1",
20
+ },
21
+ },
22
+ };
23
+ },
24
+ async run() {
25
+ const { createInfrastructure } = await import("./infra.config.js");
26
+ return createInfrastructure();
27
+ },
28
+ });