@lsts_tech/infra 1.0.0 → 1.0.2
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/README.md +58 -70
- package/dist/bin/init.d.ts +4 -3
- package/dist/bin/init.d.ts.map +1 -1
- package/dist/bin/init.js +619 -117
- package/dist/bin/init.js.map +1 -1
- package/dist/src/auth/index.d.ts +17 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +18 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/stacks/Dns.d.ts +24 -14
- package/dist/stacks/Dns.d.ts.map +1 -1
- package/dist/stacks/Dns.js +69 -18
- package/dist/stacks/Dns.js.map +1 -1
- package/dist/stacks/Pipeline.d.ts +7 -0
- package/dist/stacks/Pipeline.d.ts.map +1 -1
- package/dist/stacks/Pipeline.js +60 -7
- package/dist/stacks/Pipeline.js.map +1 -1
- package/docs/CLI.md +58 -15
- package/docs/CONFIGURATION.md +73 -30
- package/docs/EXAMPLES.md +5 -1
- package/examples/delegated-subdomain/infra.config.ts +102 -0
- package/examples/next-and-expo/infra.config.ts +33 -28
- package/examples/next-only/infra.config.ts +35 -22
- package/package.json +10 -4
- package/scripts/ensure-pipelines.sh +151 -43
- package/scripts/postdeploy-update-dns.sh +42 -11
- package/scripts/predeploy-checks.sh +38 -5
- package/templates/buildspec.yml +23 -0
- package/templates/ensure-pipelines.sh +157 -22
- package/templates/env.example +15 -0
- package/templates/infra.config.expo-web.ts +153 -0
- package/templates/infra.config.next-only.ts +159 -0
- package/templates/infra.config.ts +21 -4
- package/templates/pipelines.example.json +19 -0
- package/templates/private.example.json +13 -0
- package/templates/scaffold.gitignore +29 -0
- package/templates/scaffold.package.json +25 -0
- package/templates/scaffold.tsconfig.json +22 -0
- package/templates/secrets.schema.expo-web.json +8 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template: infra.config.ts (profile: expo-web)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/// <reference path="./sst-env.d.ts" />
|
|
6
|
+
|
|
7
|
+
import { resolveDomain, createExpoSite, createPipeline } from "@lsts_tech/infra";
|
|
8
|
+
|
|
9
|
+
type PipelineStage = "production" | "dev" | "mobile";
|
|
10
|
+
|
|
11
|
+
const profile = process.env.INFRA_PROFILE ?? "__PROFILE__";
|
|
12
|
+
const rootDomain = process.env.INFRA_ROOT_DOMAIN ?? "__ROOT_DOMAIN__";
|
|
13
|
+
const pipelineRepo = process.env.INFRA_PIPELINE_REPO ?? "__PIPELINE_REPO__";
|
|
14
|
+
const pipelinePrefix = process.env.INFRA_PIPELINE_PREFIX ?? "__PROJECT_PREFIX__";
|
|
15
|
+
const pipelineProjectTag = process.env.INFRA_PROJECT_TAG ?? "__PROJECT_PREFIX__";
|
|
16
|
+
const appName = process.env.INFRA_APP_NAME ?? "__APP_NAME__";
|
|
17
|
+
const selectedPipelinesRaw = process.env.INFRA_PIPELINES ?? "__PIPELINES_DEFAULT__";
|
|
18
|
+
const createPipelines = (process.env.INFRA_CREATE_PIPELINES ?? "__CREATE_PIPELINES_DEFAULT__") === "true";
|
|
19
|
+
const hostedZoneDomain = process.env.INFRA_HOSTED_ZONE_DOMAIN || undefined;
|
|
20
|
+
|
|
21
|
+
const pipelinePermissionsMode =
|
|
22
|
+
(process.env.INFRA_PIPELINE_PERMISSIONS_MODE ?? "__PIPELINE_PERMISSIONS_MODE__") === "least-privilege"
|
|
23
|
+
? "least-privilege"
|
|
24
|
+
: "admin";
|
|
25
|
+
|
|
26
|
+
const expoStageMap: Record<string, string> = {
|
|
27
|
+
production: process.env.INFRA_EXPO_DOMAIN_PRODUCTION ?? `mobile.${rootDomain}`,
|
|
28
|
+
dev: process.env.INFRA_EXPO_DOMAIN_DEV ?? `dev.mobile.${rootDomain}`,
|
|
29
|
+
mobile: process.env.INFRA_EXPO_DOMAIN_MOBILE ?? `preview.mobile.${rootDomain}`,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const expoCerts: Record<string, string | undefined> = {
|
|
33
|
+
production: process.env.INFRA_EXPO_CERT_ARN_PRODUCTION,
|
|
34
|
+
dev: process.env.INFRA_EXPO_CERT_ARN_DEV,
|
|
35
|
+
mobile: process.env.INFRA_EXPO_CERT_ARN_MOBILE,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const commonBuildEnv = {
|
|
39
|
+
INFRA_PROFILE: profile,
|
|
40
|
+
INFRA_APP_NAME: appName,
|
|
41
|
+
INFRA_ROOT_DOMAIN: rootDomain,
|
|
42
|
+
INFRA_HOSTED_ZONE_DOMAIN: hostedZoneDomain ?? "",
|
|
43
|
+
INFRA_PIPELINE_REPO: pipelineRepo,
|
|
44
|
+
INFRA_PIPELINE_PREFIX: pipelinePrefix,
|
|
45
|
+
INFRA_PROJECT_TAG: pipelineProjectTag,
|
|
46
|
+
INFRA_PIPELINE_BRANCH_PROD: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "__BRANCH_PROD__",
|
|
47
|
+
INFRA_PIPELINE_BRANCH_DEV: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "__BRANCH_DEV__",
|
|
48
|
+
INFRA_PIPELINE_BRANCH_MOBILE: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "__BRANCH_MOBILE__",
|
|
49
|
+
INFRA_PIPELINES_CONFIG_PATH: process.env.INFRA_PIPELINES_CONFIG_PATH ?? "config/pipelines.json",
|
|
50
|
+
INFRA_PIPELINE_PERMISSIONS_MODE: pipelinePermissionsMode,
|
|
51
|
+
INFRA_CREATE_PIPELINES: "false",
|
|
52
|
+
INFRA_ENABLE_EXPO_SITE: "true",
|
|
53
|
+
INFRA_EXPO_DOMAIN_PRODUCTION: expoStageMap.production,
|
|
54
|
+
INFRA_EXPO_DOMAIN_DEV: expoStageMap.dev,
|
|
55
|
+
INFRA_EXPO_DOMAIN_MOBILE: expoStageMap.mobile,
|
|
56
|
+
INFRA_EXPO_CERT_ARN_PRODUCTION: expoCerts.production ?? "",
|
|
57
|
+
INFRA_EXPO_CERT_ARN_DEV: expoCerts.dev ?? "",
|
|
58
|
+
INFRA_EXPO_CERT_ARN_MOBILE: expoCerts.mobile ?? "",
|
|
59
|
+
DOMAIN_ROOT: rootDomain,
|
|
60
|
+
PROJECT_PREFIX: pipelinePrefix,
|
|
61
|
+
PREFIX: pipelinePrefix,
|
|
62
|
+
DOMAIN_PRODUCTION: expoStageMap.production,
|
|
63
|
+
DOMAIN_DEV: expoStageMap.dev,
|
|
64
|
+
DOMAIN_MOBILE: expoStageMap.mobile,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function parsePipelineStage(value: string): PipelineStage | undefined {
|
|
68
|
+
const normalized = value.trim().toLowerCase();
|
|
69
|
+
if (normalized === "production" || normalized === "prod") return "production";
|
|
70
|
+
if (normalized === "dev") return "dev";
|
|
71
|
+
if (normalized === "mobile") return "mobile";
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const selectedPipelines = new Set<PipelineStage>(
|
|
76
|
+
selectedPipelinesRaw
|
|
77
|
+
.split(",")
|
|
78
|
+
.map((value) => parsePipelineStage(value))
|
|
79
|
+
.filter((value): value is PipelineStage => value !== undefined)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const pipelineSpecs: Record<PipelineStage, { suffix: string; branch: string; stage: PipelineStage }> = {
|
|
83
|
+
production: {
|
|
84
|
+
suffix: "prod",
|
|
85
|
+
branch: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "__BRANCH_PROD__",
|
|
86
|
+
stage: "production",
|
|
87
|
+
},
|
|
88
|
+
dev: {
|
|
89
|
+
suffix: "dev",
|
|
90
|
+
branch: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "__BRANCH_DEV__",
|
|
91
|
+
stage: "dev",
|
|
92
|
+
},
|
|
93
|
+
mobile: {
|
|
94
|
+
suffix: "mobile",
|
|
95
|
+
branch: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "__BRANCH_MOBILE__",
|
|
96
|
+
stage: "mobile",
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export function createInfrastructure() {
|
|
101
|
+
const stage = $app.stage;
|
|
102
|
+
|
|
103
|
+
const { domain, domainName } = resolveDomain({
|
|
104
|
+
rootDomain,
|
|
105
|
+
stage,
|
|
106
|
+
stageMap: expoStageMap,
|
|
107
|
+
hostedZoneDomain,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const { url } = createExpoSite({
|
|
111
|
+
appPath: "../../apps/mobile",
|
|
112
|
+
id: `mobile-${stage}`,
|
|
113
|
+
domain,
|
|
114
|
+
certificateArn: expoCerts[stage],
|
|
115
|
+
environment: {
|
|
116
|
+
EXPO_PUBLIC_STAGE: stage,
|
|
117
|
+
EXPO_PUBLIC_SITE_URL: `https://${domainName}`,
|
|
118
|
+
},
|
|
119
|
+
invalidation: {
|
|
120
|
+
paths: ["/*"],
|
|
121
|
+
wait: stage === "production" || stage === "mobile",
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const outputs: Record<string, unknown> = {
|
|
126
|
+
profile,
|
|
127
|
+
mobileUrl: url,
|
|
128
|
+
mobileDomain: domainName,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (stage === "production" && createPipelines && selectedPipelines.size > 0) {
|
|
132
|
+
const pipelineOutputs: Record<string, string> = {};
|
|
133
|
+
|
|
134
|
+
for (const pipelineStage of selectedPipelines) {
|
|
135
|
+
const spec = pipelineSpecs[pipelineStage];
|
|
136
|
+
const pipeline = createPipeline({
|
|
137
|
+
name: `${pipelinePrefix}-${spec.suffix}`,
|
|
138
|
+
repo: pipelineRepo,
|
|
139
|
+
branch: spec.branch,
|
|
140
|
+
stage: spec.stage,
|
|
141
|
+
projectTag: pipelineProjectTag,
|
|
142
|
+
buildEnv: commonBuildEnv,
|
|
143
|
+
permissionsMode: pipelinePermissionsMode,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
pipelineOutputs[`${pipelineStage}PipelineName`] = pipeline.pipelineName;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
outputs.pipelines = pipelineOutputs;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return outputs;
|
|
153
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template: infra.config.ts (profile: next-only)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/// <reference path="./sst-env.d.ts" />
|
|
6
|
+
|
|
7
|
+
import { resolveDomain, createNextSite, createPipeline } from "@lsts_tech/infra";
|
|
8
|
+
|
|
9
|
+
type PipelineStage = "production" | "dev" | "mobile";
|
|
10
|
+
|
|
11
|
+
const secrets = {
|
|
12
|
+
DatabaseUrl: new sst.Secret("DatabaseUrl"),
|
|
13
|
+
AuthSecret: new sst.Secret("AuthSecret"),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const profile = process.env.INFRA_PROFILE ?? "__PROFILE__";
|
|
17
|
+
const rootDomain = process.env.INFRA_ROOT_DOMAIN ?? "__ROOT_DOMAIN__";
|
|
18
|
+
const pipelineRepo = process.env.INFRA_PIPELINE_REPO ?? "__PIPELINE_REPO__";
|
|
19
|
+
const pipelinePrefix = process.env.INFRA_PIPELINE_PREFIX ?? "__PROJECT_PREFIX__";
|
|
20
|
+
const pipelineProjectTag = process.env.INFRA_PROJECT_TAG ?? "__PROJECT_PREFIX__";
|
|
21
|
+
const appName = process.env.INFRA_APP_NAME ?? "__APP_NAME__";
|
|
22
|
+
const selectedPipelinesRaw = process.env.INFRA_PIPELINES ?? "__PIPELINES_DEFAULT__";
|
|
23
|
+
const createPipelines = (process.env.INFRA_CREATE_PIPELINES ?? "__CREATE_PIPELINES_DEFAULT__") === "true";
|
|
24
|
+
const hostedZoneDomain = process.env.INFRA_HOSTED_ZONE_DOMAIN || undefined;
|
|
25
|
+
|
|
26
|
+
const pipelinePermissionsMode =
|
|
27
|
+
(process.env.INFRA_PIPELINE_PERMISSIONS_MODE ?? "__PIPELINE_PERMISSIONS_MODE__") === "least-privilege"
|
|
28
|
+
? "least-privilege"
|
|
29
|
+
: "admin";
|
|
30
|
+
|
|
31
|
+
const webStageMap: Record<string, string> = {
|
|
32
|
+
production: process.env.INFRA_WEB_DOMAIN_PRODUCTION ?? rootDomain,
|
|
33
|
+
dev: process.env.INFRA_WEB_DOMAIN_DEV ?? `dev.${rootDomain}`,
|
|
34
|
+
mobile: process.env.INFRA_WEB_DOMAIN_MOBILE ?? `api.${rootDomain}`,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const webCerts: Record<string, string | undefined> = {
|
|
38
|
+
production: process.env.INFRA_WEB_CERT_ARN_PRODUCTION,
|
|
39
|
+
dev: process.env.INFRA_WEB_CERT_ARN_DEV,
|
|
40
|
+
mobile: process.env.INFRA_WEB_CERT_ARN_MOBILE,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const commonBuildEnv = {
|
|
44
|
+
INFRA_PROFILE: profile,
|
|
45
|
+
INFRA_APP_NAME: appName,
|
|
46
|
+
INFRA_ROOT_DOMAIN: rootDomain,
|
|
47
|
+
INFRA_HOSTED_ZONE_DOMAIN: hostedZoneDomain ?? "",
|
|
48
|
+
INFRA_PIPELINE_REPO: pipelineRepo,
|
|
49
|
+
INFRA_PIPELINE_PREFIX: pipelinePrefix,
|
|
50
|
+
INFRA_PROJECT_TAG: pipelineProjectTag,
|
|
51
|
+
INFRA_PIPELINE_BRANCH_PROD: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "__BRANCH_PROD__",
|
|
52
|
+
INFRA_PIPELINE_BRANCH_DEV: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "__BRANCH_DEV__",
|
|
53
|
+
INFRA_PIPELINE_BRANCH_MOBILE: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "__BRANCH_MOBILE__",
|
|
54
|
+
INFRA_PIPELINES_CONFIG_PATH: process.env.INFRA_PIPELINES_CONFIG_PATH ?? "config/pipelines.json",
|
|
55
|
+
INFRA_PIPELINE_PERMISSIONS_MODE: pipelinePermissionsMode,
|
|
56
|
+
INFRA_CREATE_PIPELINES: "false",
|
|
57
|
+
INFRA_WEB_DOMAIN_PRODUCTION: webStageMap.production,
|
|
58
|
+
INFRA_WEB_DOMAIN_DEV: webStageMap.dev,
|
|
59
|
+
INFRA_WEB_DOMAIN_MOBILE: webStageMap.mobile,
|
|
60
|
+
INFRA_WEB_CERT_ARN_PRODUCTION: webCerts.production ?? "",
|
|
61
|
+
INFRA_WEB_CERT_ARN_DEV: webCerts.dev ?? "",
|
|
62
|
+
INFRA_WEB_CERT_ARN_MOBILE: webCerts.mobile ?? "",
|
|
63
|
+
DOMAIN_ROOT: rootDomain,
|
|
64
|
+
PROJECT_PREFIX: pipelinePrefix,
|
|
65
|
+
PREFIX: pipelinePrefix,
|
|
66
|
+
DOMAIN_PRODUCTION: webStageMap.production,
|
|
67
|
+
DOMAIN_DEV: webStageMap.dev,
|
|
68
|
+
DOMAIN_MOBILE: webStageMap.mobile,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function parsePipelineStage(value: string): PipelineStage | undefined {
|
|
72
|
+
const normalized = value.trim().toLowerCase();
|
|
73
|
+
if (normalized === "production" || normalized === "prod") return "production";
|
|
74
|
+
if (normalized === "dev") return "dev";
|
|
75
|
+
if (normalized === "mobile") return "mobile";
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const selectedPipelines = new Set<PipelineStage>(
|
|
80
|
+
selectedPipelinesRaw
|
|
81
|
+
.split(",")
|
|
82
|
+
.map((value) => parsePipelineStage(value))
|
|
83
|
+
.filter((value): value is PipelineStage => value !== undefined)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const pipelineSpecs: Record<PipelineStage, { suffix: string; branch: string; stage: PipelineStage }> = {
|
|
87
|
+
production: {
|
|
88
|
+
suffix: "prod",
|
|
89
|
+
branch: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "__BRANCH_PROD__",
|
|
90
|
+
stage: "production",
|
|
91
|
+
},
|
|
92
|
+
dev: {
|
|
93
|
+
suffix: "dev",
|
|
94
|
+
branch: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "__BRANCH_DEV__",
|
|
95
|
+
stage: "dev",
|
|
96
|
+
},
|
|
97
|
+
mobile: {
|
|
98
|
+
suffix: "mobile",
|
|
99
|
+
branch: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "__BRANCH_MOBILE__",
|
|
100
|
+
stage: "mobile",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export function createInfrastructure() {
|
|
105
|
+
const stage = $app.stage;
|
|
106
|
+
|
|
107
|
+
const { domain, domainName } = resolveDomain({
|
|
108
|
+
rootDomain,
|
|
109
|
+
stage,
|
|
110
|
+
stageMap: webStageMap,
|
|
111
|
+
hostedZoneDomain,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const { url } = createNextSite({
|
|
115
|
+
appPath: "../../apps/web",
|
|
116
|
+
id: `web-${stage}`,
|
|
117
|
+
domain,
|
|
118
|
+
certificateArn: webCerts[stage],
|
|
119
|
+
environment: {
|
|
120
|
+
NEXT_PUBLIC_APP_URL: `https://${domainName}`,
|
|
121
|
+
DATABASE_URL: secrets.DatabaseUrl.value,
|
|
122
|
+
AUTH_SECRET: secrets.AuthSecret.value,
|
|
123
|
+
},
|
|
124
|
+
warm: stage === "production" ? 1 : 0,
|
|
125
|
+
invalidation: {
|
|
126
|
+
paths: ["/*"],
|
|
127
|
+
wait: stage === "production",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const outputs: Record<string, unknown> = {
|
|
132
|
+
profile,
|
|
133
|
+
siteUrl: url,
|
|
134
|
+
domain: domainName,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (stage === "production" && createPipelines && selectedPipelines.size > 0) {
|
|
138
|
+
const pipelineOutputs: Record<string, string> = {};
|
|
139
|
+
|
|
140
|
+
for (const pipelineStage of selectedPipelines) {
|
|
141
|
+
const spec = pipelineSpecs[pipelineStage];
|
|
142
|
+
const pipeline = createPipeline({
|
|
143
|
+
name: `${pipelinePrefix}-${spec.suffix}`,
|
|
144
|
+
repo: pipelineRepo,
|
|
145
|
+
branch: spec.branch,
|
|
146
|
+
stage: spec.stage,
|
|
147
|
+
projectTag: pipelineProjectTag,
|
|
148
|
+
buildEnv: commonBuildEnv,
|
|
149
|
+
permissionsMode: pipelinePermissionsMode,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
pipelineOutputs[`${pipelineStage}PipelineName`] = pipeline.pipelineName;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
outputs.pipelines = pipelineOutputs;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return outputs;
|
|
159
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Template: infra.config.ts
|
|
2
|
+
* Template: infra.config.ts (profile: next-expo)
|
|
3
3
|
*
|
|
4
4
|
* Generated by `npx @lsts_tech/infra init`.
|
|
5
|
-
*
|
|
5
|
+
* White-label and environment-driven for public repositories.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/// <reference path="./sst-env.d.ts" />
|
|
@@ -16,13 +16,21 @@ const secrets = {
|
|
|
16
16
|
AuthSecret: new sst.Secret("AuthSecret"),
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
const profile = process.env.INFRA_PROFILE ?? "__PROFILE__";
|
|
19
20
|
const rootDomain = process.env.INFRA_ROOT_DOMAIN ?? "__ROOT_DOMAIN__";
|
|
20
21
|
const pipelineRepo = process.env.INFRA_PIPELINE_REPO ?? "__PIPELINE_REPO__";
|
|
21
22
|
const pipelinePrefix = process.env.INFRA_PIPELINE_PREFIX ?? "__PROJECT_PREFIX__";
|
|
22
23
|
const pipelineProjectTag = process.env.INFRA_PROJECT_TAG ?? "__PROJECT_PREFIX__";
|
|
23
|
-
const appName = process.env.INFRA_APP_NAME ?? "
|
|
24
|
+
const appName = process.env.INFRA_APP_NAME ?? "__APP_NAME__";
|
|
24
25
|
const selectedPipelinesRaw = process.env.INFRA_PIPELINES ?? "__PIPELINES_DEFAULT__";
|
|
26
|
+
const createPipelines = (process.env.INFRA_CREATE_PIPELINES ?? "__CREATE_PIPELINES_DEFAULT__") === "true";
|
|
25
27
|
const enableExpoSite = (process.env.INFRA_ENABLE_EXPO_SITE ?? "__ENABLE_EXPO_SITE__") === "true";
|
|
28
|
+
const hostedZoneDomain = process.env.INFRA_HOSTED_ZONE_DOMAIN || undefined;
|
|
29
|
+
|
|
30
|
+
const pipelinePermissionsMode =
|
|
31
|
+
(process.env.INFRA_PIPELINE_PERMISSIONS_MODE ?? "__PIPELINE_PERMISSIONS_MODE__") === "least-privilege"
|
|
32
|
+
? "least-privilege"
|
|
33
|
+
: "admin";
|
|
26
34
|
|
|
27
35
|
const webStageMap: Record<string, string> = {
|
|
28
36
|
production: process.env.INFRA_WEB_DOMAIN_PRODUCTION ?? rootDomain,
|
|
@@ -49,14 +57,19 @@ const expoCerts: Record<string, string | undefined> = {
|
|
|
49
57
|
};
|
|
50
58
|
|
|
51
59
|
const commonBuildEnv = {
|
|
60
|
+
INFRA_PROFILE: profile,
|
|
52
61
|
INFRA_APP_NAME: appName,
|
|
53
62
|
INFRA_ROOT_DOMAIN: rootDomain,
|
|
63
|
+
INFRA_HOSTED_ZONE_DOMAIN: hostedZoneDomain ?? "",
|
|
54
64
|
INFRA_PIPELINE_REPO: pipelineRepo,
|
|
55
65
|
INFRA_PIPELINE_PREFIX: pipelinePrefix,
|
|
56
66
|
INFRA_PROJECT_TAG: pipelineProjectTag,
|
|
57
67
|
INFRA_PIPELINE_BRANCH_PROD: process.env.INFRA_PIPELINE_BRANCH_PROD ?? "__BRANCH_PROD__",
|
|
58
68
|
INFRA_PIPELINE_BRANCH_DEV: process.env.INFRA_PIPELINE_BRANCH_DEV ?? "__BRANCH_DEV__",
|
|
59
69
|
INFRA_PIPELINE_BRANCH_MOBILE: process.env.INFRA_PIPELINE_BRANCH_MOBILE ?? "__BRANCH_MOBILE__",
|
|
70
|
+
INFRA_PIPELINES_CONFIG_PATH: process.env.INFRA_PIPELINES_CONFIG_PATH ?? "config/pipelines.json",
|
|
71
|
+
INFRA_PIPELINE_PERMISSIONS_MODE: pipelinePermissionsMode,
|
|
72
|
+
INFRA_CREATE_PIPELINES: "false",
|
|
60
73
|
INFRA_WEB_DOMAIN_PRODUCTION: webStageMap.production,
|
|
61
74
|
INFRA_WEB_DOMAIN_DEV: webStageMap.dev,
|
|
62
75
|
INFRA_WEB_DOMAIN_MOBILE: webStageMap.mobile,
|
|
@@ -117,6 +130,7 @@ export function createInfrastructure() {
|
|
|
117
130
|
rootDomain,
|
|
118
131
|
stage,
|
|
119
132
|
stageMap: webStageMap,
|
|
133
|
+
hostedZoneDomain,
|
|
120
134
|
});
|
|
121
135
|
|
|
122
136
|
const { url } = createNextSite({
|
|
@@ -144,6 +158,7 @@ export function createInfrastructure() {
|
|
|
144
158
|
rootDomain,
|
|
145
159
|
stage,
|
|
146
160
|
stageMap: expoStageMap,
|
|
161
|
+
hostedZoneDomain,
|
|
147
162
|
});
|
|
148
163
|
|
|
149
164
|
mobileDomainName = expoDomainResult.domainName;
|
|
@@ -166,6 +181,7 @@ export function createInfrastructure() {
|
|
|
166
181
|
}
|
|
167
182
|
|
|
168
183
|
const outputs: Record<string, unknown> = {
|
|
184
|
+
profile,
|
|
169
185
|
siteUrl: url,
|
|
170
186
|
domain: domainName,
|
|
171
187
|
};
|
|
@@ -175,7 +191,7 @@ export function createInfrastructure() {
|
|
|
175
191
|
outputs.mobileDomain = mobileDomainName;
|
|
176
192
|
}
|
|
177
193
|
|
|
178
|
-
if (stage === "production" && selectedPipelines.size > 0) {
|
|
194
|
+
if (stage === "production" && createPipelines && selectedPipelines.size > 0) {
|
|
179
195
|
const pipelineOutputs: Record<string, string> = {};
|
|
180
196
|
|
|
181
197
|
for (const pipelineStage of selectedPipelines) {
|
|
@@ -187,6 +203,7 @@ export function createInfrastructure() {
|
|
|
187
203
|
stage: spec.stage,
|
|
188
204
|
projectTag: pipelineProjectTag,
|
|
189
205
|
buildEnv: commonBuildEnv,
|
|
206
|
+
permissionsMode: pipelinePermissionsMode,
|
|
190
207
|
});
|
|
191
208
|
|
|
192
209
|
pipelineOutputs[`${pipelineStage}PipelineName`] = pipeline.pipelineName;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pipelines": {
|
|
3
|
+
"production": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"branch": "__BRANCH_PROD__",
|
|
6
|
+
"repo": "__PIPELINE_REPO__"
|
|
7
|
+
},
|
|
8
|
+
"dev": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"branch": "__BRANCH_DEV__",
|
|
11
|
+
"repo": "__PIPELINE_REPO__"
|
|
12
|
+
},
|
|
13
|
+
"mobile": {
|
|
14
|
+
"enabled": false,
|
|
15
|
+
"branch": "__BRANCH_MOBILE__",
|
|
16
|
+
"repo": "__PIPELINE_REPO__"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"notes": "Copy this file to config/private.json and keep it untracked.",
|
|
3
|
+
"secrets": {
|
|
4
|
+
"DatabaseUrl": "postgresql://username:password@host:5432/database",
|
|
5
|
+
"AuthSecret": "replace-with-secure-random-value"
|
|
6
|
+
},
|
|
7
|
+
"deploy": {
|
|
8
|
+
"region": "us-east-1",
|
|
9
|
+
"tags": {
|
|
10
|
+
"Project": "__PROJECT_PREFIX__"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Build output
|
|
2
|
+
dist/
|
|
3
|
+
|
|
4
|
+
# SST state
|
|
5
|
+
.sst/
|
|
6
|
+
state.json
|
|
7
|
+
Pulumi.*.yaml
|
|
8
|
+
|
|
9
|
+
# Node
|
|
10
|
+
node_modules/
|
|
11
|
+
|
|
12
|
+
# Environment
|
|
13
|
+
.env
|
|
14
|
+
.env.*
|
|
15
|
+
!.env.example
|
|
16
|
+
|
|
17
|
+
# Local private config
|
|
18
|
+
config/*.json
|
|
19
|
+
!config/*.example.json
|
|
20
|
+
|
|
21
|
+
# Editor
|
|
22
|
+
.vscode/
|
|
23
|
+
*.swp
|
|
24
|
+
|
|
25
|
+
# OS
|
|
26
|
+
.DS_Store
|
|
27
|
+
|
|
28
|
+
# Tarball
|
|
29
|
+
*.tgz
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_PREFIX__-infra",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "sst dev",
|
|
7
|
+
"deploy": "sst deploy",
|
|
8
|
+
"deploy:dev": "sst deploy --stage dev",
|
|
9
|
+
"deploy:prod": "sst deploy --stage production",
|
|
10
|
+
"doctor": "npx @lsts_tech/infra doctor --target .",
|
|
11
|
+
"ensure:pipelines": "APPROVE=true bash scripts/ensure-pipelines.sh",
|
|
12
|
+
"ensure:secrets": "bash scripts/ensure-secrets.sh dev"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@lsts_tech/infra": "__INFRA_VERSION__",
|
|
16
|
+
"sst": "^3.7.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.13.9",
|
|
20
|
+
"typescript": "^5"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"infra.config.ts",
|
|
14
|
+
"sst.config.ts",
|
|
15
|
+
"sst-env.d.ts"
|
|
16
|
+
],
|
|
17
|
+
"exclude": [
|
|
18
|
+
"node_modules",
|
|
19
|
+
"dist",
|
|
20
|
+
".sst"
|
|
21
|
+
]
|
|
22
|
+
}
|