@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.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/bin/init.d.ts +9 -0
- package/dist/bin/init.d.ts.map +1 -0
- package/dist/bin/init.js +315 -0
- package/dist/bin/init.js.map +1 -0
- package/dist/stacks/Dns.d.ts +69 -0
- package/dist/stacks/Dns.d.ts.map +1 -0
- package/dist/stacks/Dns.js +57 -0
- package/dist/stacks/Dns.js.map +1 -0
- package/dist/stacks/ExpoSite.d.ts +72 -0
- package/dist/stacks/ExpoSite.d.ts.map +1 -0
- package/dist/stacks/ExpoSite.js +49 -0
- package/dist/stacks/ExpoSite.js.map +1 -0
- package/dist/stacks/NextSite.d.ts +86 -0
- package/dist/stacks/NextSite.d.ts.map +1 -0
- package/dist/stacks/NextSite.js +60 -0
- package/dist/stacks/NextSite.js.map +1 -0
- package/dist/stacks/Pipeline.d.ts +128 -0
- package/dist/stacks/Pipeline.d.ts.map +1 -0
- package/dist/stacks/Pipeline.js +311 -0
- package/dist/stacks/Pipeline.js.map +1 -0
- package/dist/stacks/index.d.ts +41 -0
- package/dist/stacks/index.d.ts.map +1 -0
- package/dist/stacks/index.js +38 -0
- package/dist/stacks/index.js.map +1 -0
- package/docs/CLI.md +59 -0
- package/docs/CONFIGURATION.md +78 -0
- package/docs/EXAMPLES.md +9 -0
- package/examples/next-and-expo/infra.config.ts +104 -0
- package/examples/next-only/infra.config.ts +60 -0
- package/package.json +102 -0
- package/schemas/pipeline.schema.json +25 -0
- package/scripts/cleanup-orphan-lambdas.sh +102 -0
- package/scripts/delete-amplify-app.sh +50 -0
- package/scripts/ensure-pipelines.sh +144 -0
- package/scripts/ensure-secrets.sh +58 -0
- package/scripts/postdeploy-update-dns.sh +158 -0
- package/scripts/predeploy-checks.sh +192 -0
- package/scripts/pulumi-deploy.sh +29 -0
- package/scripts/sst-deploy.sh +79 -0
- package/templates/buildspec.yml +77 -0
- package/templates/ensure-pipelines.sh +117 -0
- package/templates/env.example +38 -0
- package/templates/infra.config.ts +199 -0
- package/templates/secrets.schema.json +20 -0
- package/templates/sst-env.d.ts +50 -0
- 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
|
+
});
|