sst 2.2.8 → 2.3.1
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/cli/commands/bind.js +191 -19
- package/cli/commands/dev.js +2 -17
- package/cli/commands/secrets/set.js +22 -5
- package/cli/sst.js +0 -2
- package/cli/ui/deploy.d.ts +1 -1
- package/cli/ui/deploy.js +3 -6
- package/cli/ui/functions.d.ts +1 -1
- package/config.d.ts +14 -1
- package/config.js +68 -31
- package/constructs/App.d.ts +2 -2
- package/constructs/App.js +2 -4
- package/constructs/AstroSite.d.ts +1 -0
- package/constructs/AstroSite.js +1 -0
- package/constructs/Function.d.ts +1 -1
- package/constructs/Metadata.d.ts +5 -1
- package/constructs/NextjsSite.d.ts +1 -0
- package/constructs/NextjsSite.js +1 -0
- package/constructs/RemixSite.d.ts +1 -0
- package/constructs/RemixSite.js +1 -0
- package/constructs/SolidStartSite.d.ts +1 -0
- package/constructs/SolidStartSite.js +1 -0
- package/constructs/SsrSite.d.ts +15 -7
- package/constructs/SsrSite.js +52 -21
- package/constructs/StaticSite.d.ts +2 -1
- package/constructs/StaticSite.js +3 -15
- package/constructs/deprecated/NextjsSite.d.ts +5 -2
- package/constructs/deprecated/NextjsSite.js +4 -16
- package/constructs/deprecated/cross-region-helper.js +3 -3
- package/package.json +1 -1
- package/sst.mjs +350 -268
- package/stacks/metadata.d.ts +0 -1
- package/stacks/metadata.js +1 -26
- package/stacks/monitor.js +1 -0
- package/support/bootstrap-metadata-function/index.mjs +2 -2
- package/cli/commands/env.d.ts +0 -15
- package/cli/commands/env.js +0 -48
- package/site-env.d.ts +0 -13
- package/site-env.js +0 -48
package/cli/commands/bind.js
CHANGED
|
@@ -1,31 +1,203 @@
|
|
|
1
|
+
import path from "path";
|
|
1
2
|
import { VisibleError } from "../../error.js";
|
|
3
|
+
const SITE_CONFIG = {
|
|
4
|
+
NextjsSite: "next.config",
|
|
5
|
+
AstroSite: "astro.config",
|
|
6
|
+
RemixSite: "remix.config",
|
|
7
|
+
SolidStartSite: "vite.config",
|
|
8
|
+
StaticSite: "vite.config",
|
|
9
|
+
SlsNextjsSite: "next.config",
|
|
10
|
+
};
|
|
2
11
|
export const bind = (program) => program
|
|
3
|
-
.command("bind <command..>", "Bind your app's resources to a command", (yargs) => yargs
|
|
12
|
+
.command(["bind <command..>", "env <command..>"], "Bind your app's resources to a command", (yargs) => yargs
|
|
4
13
|
.array("command")
|
|
5
14
|
.example(`sst bind "vitest run"`, "Bind your resources to your tests")
|
|
6
15
|
.example(`sst bind "tsx scripts/myscript.ts"`, "Bind your resources to a script"), async (args) => {
|
|
7
|
-
const {
|
|
8
|
-
const { spawnSync } = await import("child_process");
|
|
9
|
-
const { useAWSCredentials } = await import("../../credentials.js");
|
|
16
|
+
const { spawn } = await import("child_process");
|
|
10
17
|
const { useProject } = await import("../../project.js");
|
|
11
|
-
const
|
|
18
|
+
const { useBus } = await import("../../bus.js");
|
|
19
|
+
const { useIOT } = await import("../../iot.js");
|
|
20
|
+
const { Colors } = await import("../colors.js");
|
|
21
|
+
if (args._[0] === "env") {
|
|
22
|
+
Colors.line(Colors.warning(`Warning: ${Colors.bold(`sst env`)} has been renamed to ${Colors.bold(`sst bind`)}`));
|
|
23
|
+
}
|
|
24
|
+
await useIOT();
|
|
25
|
+
const bus = useBus();
|
|
12
26
|
const project = useProject();
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
const command = args.command?.join(" ");
|
|
28
|
+
const isFrontend = await isRunningInFrontend();
|
|
29
|
+
let p;
|
|
30
|
+
let timer;
|
|
31
|
+
let metadataCache;
|
|
32
|
+
// Handle missing command
|
|
33
|
+
if (!command) {
|
|
34
|
+
throw new VisibleError(`Command is required, e.g. sst bind ${isFrontend ? "next dev" : "env"}`);
|
|
35
|
+
}
|
|
36
|
+
// Bind script
|
|
37
|
+
const initialMetadata = await getSiteMetadata();
|
|
38
|
+
if (!initialMetadata) {
|
|
39
|
+
return await bindScript();
|
|
40
|
+
}
|
|
41
|
+
// Bind site
|
|
42
|
+
await bindSite("init");
|
|
43
|
+
bus.subscribe("stacks.metadata.updated", () => bindSite("metadata_updated"));
|
|
44
|
+
bus.subscribe("stacks.metadata.deleted", () => bindSite("metadata_updated"));
|
|
45
|
+
bus.subscribe("config.secret.updated", (payload) => {
|
|
46
|
+
const secretName = payload.properties.name;
|
|
47
|
+
if (metadataCache?.secrets === undefined)
|
|
48
|
+
return;
|
|
49
|
+
if (!metadataCache.secrets.includes(secretName))
|
|
50
|
+
return;
|
|
51
|
+
Colors.line(`\n`, `SST secrets have been updated. Restarting \`${command}\`...`);
|
|
52
|
+
bindSite("secrets_updated");
|
|
53
|
+
});
|
|
54
|
+
async function isRunningInFrontend() {
|
|
55
|
+
const { existsAsync } = await import("../../util/fs.js");
|
|
56
|
+
const results = await Promise.all(Object.values(SITE_CONFIG)
|
|
57
|
+
.map((config) => [".js", ".cjs", ".mjs", ".ts"].map((ext) => existsAsync(`${config}${ext}`)))
|
|
58
|
+
.flat());
|
|
59
|
+
return results.some(Boolean);
|
|
60
|
+
}
|
|
61
|
+
async function bindSite(reason) {
|
|
62
|
+
// Get metadata
|
|
63
|
+
const metadata = (reason === "init" ? initialMetadata : await getSiteMetadata());
|
|
64
|
+
// Handle rebind due to metadata updated
|
|
65
|
+
if (reason === "metadata_updated") {
|
|
66
|
+
if (areEnvsSame(metadata.envs, metadataCache?.envs || {}))
|
|
67
|
+
return;
|
|
68
|
+
Colors.line(`\n`, `SST resources have been updated. Restarting \`${command}\`...`);
|
|
69
|
+
}
|
|
70
|
+
metadataCache = metadata;
|
|
71
|
+
// Assume function's role credentials
|
|
72
|
+
if (metadata.role) {
|
|
73
|
+
const credentials = await assumeSsrRole(metadata.role);
|
|
74
|
+
if (credentials) {
|
|
75
|
+
// refresh crecentials 1 minute before expiration
|
|
76
|
+
const expireAt = credentials.Expiration.getTime() - 60000;
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
timer = setTimeout(() => {
|
|
79
|
+
Colors.line(`\n`, `Your AWS session is about to expire. Creating a new session and restarting \`${command}\`...`);
|
|
80
|
+
bindSite("iam_expired");
|
|
81
|
+
}, expireAt - Date.now());
|
|
82
|
+
runCommand({
|
|
83
|
+
...metadata.envs,
|
|
84
|
+
AWS_ACCESS_KEY_ID: credentials.AccessKeyId,
|
|
85
|
+
AWS_SECRET_ACCESS_KEY: credentials.SecretAccessKey,
|
|
86
|
+
AWS_SESSION_TOKEN: credentials.SessionToken,
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Fallback to use local IAM credentials
|
|
92
|
+
runCommand({
|
|
93
|
+
...metadata.envs,
|
|
94
|
+
...(await localIamCredentials()),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function bindScript() {
|
|
98
|
+
const { Config } = await import("../../config.js");
|
|
99
|
+
runCommand({
|
|
100
|
+
...(await Config.env()),
|
|
101
|
+
...(await localIamCredentials()),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async function getSiteMetadata() {
|
|
105
|
+
const { metadata } = await import("../../stacks/metadata.js");
|
|
106
|
+
const { createSpinner } = await import("../spinner.js");
|
|
107
|
+
const { LambdaClient, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
108
|
+
const { useAWSClient } = await import("../../credentials.js");
|
|
109
|
+
const spinner = createSpinner({});
|
|
110
|
+
while (true) {
|
|
111
|
+
const metadataData = await metadata();
|
|
112
|
+
const data = Object.values(metadataData)
|
|
113
|
+
.flat()
|
|
114
|
+
.filter((c) => Boolean(c))
|
|
115
|
+
.filter((c) => Boolean(SITE_CONFIG[c.type]))
|
|
116
|
+
.find((c) => path.resolve(project.paths.root, c.data.path) ===
|
|
117
|
+
process.cwd());
|
|
118
|
+
// Do not retry if not running in frontend
|
|
119
|
+
if (!data && !isFrontend) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Handle site metadata not found
|
|
123
|
+
if (!data) {
|
|
124
|
+
spinner.start(
|
|
125
|
+
//"Waiting for SST to start for the first time. Run `sst dev`..."
|
|
126
|
+
//"Run `sst dev` for the first time. Waiting..."
|
|
127
|
+
"Make sure `sst dev` is running...");
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// Handle site metadata is old
|
|
132
|
+
const isBindSupported = data.type !== "StaticSite" && data.type !== "SlsNextjsSite";
|
|
133
|
+
if ((isBindSupported && !data.data.server) ||
|
|
134
|
+
(!isBindSupported && !data.data.environment)) {
|
|
135
|
+
spinner.start("This was deployed with an old version of SST. Make sure to restart `sst dev`...");
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
spinner.isSpinning && spinner.stop().clear();
|
|
140
|
+
// Handle StaticSite
|
|
141
|
+
if (!isBindSupported) {
|
|
142
|
+
return { envs: data.data.environment };
|
|
143
|
+
}
|
|
144
|
+
// Get function details
|
|
145
|
+
const lambda = useAWSClient(LambdaClient);
|
|
146
|
+
const { Configuration: functionConfig } = await lambda.send(new GetFunctionCommand({
|
|
147
|
+
FunctionName: data.data.server,
|
|
148
|
+
}));
|
|
149
|
+
return {
|
|
150
|
+
role: functionConfig?.Role,
|
|
151
|
+
envs: functionConfig?.Environment?.Variables || {},
|
|
152
|
+
secrets: data.data.secrets,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function assumeSsrRole(roleArn) {
|
|
157
|
+
const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
|
|
158
|
+
const { Logger } = await import("../../logger.js");
|
|
159
|
+
const { useAWSClient } = await import("../../credentials.js");
|
|
160
|
+
const sts = useAWSClient(STSClient);
|
|
161
|
+
try {
|
|
162
|
+
const { Credentials: credentials } = await sts.send(new AssumeRoleCommand({
|
|
163
|
+
RoleArn: roleArn,
|
|
164
|
+
RoleSessionName: "dev-session",
|
|
165
|
+
DurationSeconds: 43200,
|
|
166
|
+
}));
|
|
167
|
+
return credentials;
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
Colors.line(Colors.warning(`Failed to assume SSR role ${roleArn}. Falling back to using local IAM credentials.`));
|
|
171
|
+
Logger.debug(`Failed to assume ${roleArn}.`, e);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function localIamCredentials() {
|
|
175
|
+
const { useAWSCredentials } = await import("../../credentials.js");
|
|
176
|
+
const credentials = await useAWSCredentials();
|
|
177
|
+
return {
|
|
21
178
|
AWS_ACCESS_KEY_ID: credentials.accessKeyId,
|
|
22
179
|
AWS_SECRET_ACCESS_KEY: credentials.secretAccessKey,
|
|
23
180
|
AWS_SESSION_TOKEN: credentials.sessionToken,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function runCommand(envs) {
|
|
184
|
+
Colors.gap();
|
|
185
|
+
if (p) {
|
|
186
|
+
p.kill();
|
|
187
|
+
}
|
|
188
|
+
p = spawn(command, {
|
|
189
|
+
env: {
|
|
190
|
+
...process.env,
|
|
191
|
+
...envs,
|
|
192
|
+
AWS_REGION: project.config.region,
|
|
193
|
+
},
|
|
194
|
+
stdio: "inherit",
|
|
195
|
+
shell: true,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function areEnvsSame(envs1, envs2) {
|
|
199
|
+
return (Object.keys(envs1).length === Object.keys(envs2).length &&
|
|
200
|
+
Object.keys(envs1).every((key) => envs1[key] === envs2[key]));
|
|
201
|
+
}
|
|
30
202
|
})
|
|
31
203
|
.strict(false);
|
package/cli/commands/dev.js
CHANGED
|
@@ -4,7 +4,7 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
4
4
|
}), async (args) => {
|
|
5
5
|
const { Colors } = await import("../colors.js");
|
|
6
6
|
const { printHeader } = await import("../ui/header.js");
|
|
7
|
-
const { mapValues
|
|
7
|
+
const { mapValues } = await import("remeda");
|
|
8
8
|
const path = await import("path");
|
|
9
9
|
const { useRuntimeWorkers } = await import("../../runtime/workers.js");
|
|
10
10
|
const { useIOTBridge } = await import("../../runtime/iot.js");
|
|
@@ -23,7 +23,6 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
23
23
|
const fs = await import("fs/promises");
|
|
24
24
|
const crypto = await import("crypto");
|
|
25
25
|
const { useFunctions } = await import("../../constructs/Function.js");
|
|
26
|
-
const { SiteEnv } = await import("../../site-env.js");
|
|
27
26
|
const { usePothosBuilder } = await import("./plugins/pothos.js");
|
|
28
27
|
const { useKyselyTypeGenerator } = await import("./plugins/kysely.js");
|
|
29
28
|
const { useRDSWarmer } = await import("./plugins/warmer.js");
|
|
@@ -178,22 +177,8 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
178
177
|
await saveAppMetadata({ mode: "dev" });
|
|
179
178
|
}
|
|
180
179
|
lastDeployed = nextChecksum;
|
|
181
|
-
// Update site env
|
|
182
|
-
const keys = await SiteEnv.keys();
|
|
183
|
-
const result = {};
|
|
184
|
-
for (const key of keys) {
|
|
185
|
-
const stack = results[key.stack];
|
|
186
|
-
const value = stack.outputs[key.output];
|
|
187
|
-
let existing = result[key.path];
|
|
188
|
-
if (!existing) {
|
|
189
|
-
result[key.path] = existing;
|
|
190
|
-
existing = result[key.path] = {};
|
|
191
|
-
}
|
|
192
|
-
existing[key.environment] = value;
|
|
193
|
-
}
|
|
194
|
-
await SiteEnv.writeValues(result);
|
|
195
180
|
// Write outputs.json
|
|
196
|
-
fs.writeFile(path.join(project.paths.out, "outputs.json"), JSON.stringify(
|
|
181
|
+
fs.writeFile(path.join(project.paths.out, "outputs.json"), JSON.stringify(mapValues(results, (val) => val.outputs), null, 2));
|
|
197
182
|
isWorking = false;
|
|
198
183
|
if (isDirty)
|
|
199
184
|
build();
|
|
@@ -14,8 +14,10 @@ export const set = (program) => program.command("set <name> <value>", "Set the v
|
|
|
14
14
|
describe: "Set the fallback value",
|
|
15
15
|
}), async (args) => {
|
|
16
16
|
const { Config } = await import("../../../config.js");
|
|
17
|
+
const { Colors } = await import("../../colors.js");
|
|
17
18
|
const { blue } = await import("colorette");
|
|
18
19
|
const { createSpinner } = await import("../../spinner.js");
|
|
20
|
+
// Set secret value
|
|
19
21
|
const setting = createSpinner(` Setting "${args.name}"`).start();
|
|
20
22
|
await Config.setSecret({
|
|
21
23
|
key: args.name,
|
|
@@ -23,9 +25,24 @@ export const set = (program) => program.command("set <name> <value>", "Set the v
|
|
|
23
25
|
fallback: args.fallback === true,
|
|
24
26
|
});
|
|
25
27
|
setting.succeed();
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Restart functions & sites
|
|
29
|
+
const restarting = createSpinner(` Reloading all resources using ${blue(args.name)}...`).start();
|
|
30
|
+
const { edgeSites, sites, placeholderSites, functions } = await Config.restart(args.name);
|
|
31
|
+
restarting.stop().clear();
|
|
32
|
+
const siteCount = sites.length + placeholderSites.length;
|
|
33
|
+
if (siteCount > 0) {
|
|
34
|
+
Colors.line(Colors.success(`✔ `), siteCount === 1
|
|
35
|
+
? `Reloaded ${siteCount} site`
|
|
36
|
+
: `Reloaded ${siteCount} sites`);
|
|
37
|
+
}
|
|
38
|
+
const functionCount = functions.length;
|
|
39
|
+
if (functionCount > 0) {
|
|
40
|
+
Colors.line(Colors.success(`✔ `), functionCount === 1
|
|
41
|
+
? `Reloaded ${functionCount} function`
|
|
42
|
+
: `Reloaded ${functionCount} functions`);
|
|
43
|
+
}
|
|
44
|
+
edgeSites.forEach(({ id, type }) => {
|
|
45
|
+
Colors.line(Colors.danger(`➜ `), `Redeploy the "${id}" ${type} to use the new secret`);
|
|
46
|
+
});
|
|
47
|
+
process.exit(0);
|
|
31
48
|
});
|
package/cli/sst.js
CHANGED
|
@@ -13,7 +13,6 @@ dotenv.config({
|
|
|
13
13
|
override: true,
|
|
14
14
|
});
|
|
15
15
|
import { bootstrap } from "./commands/bootstrap.js";
|
|
16
|
-
import { env } from "./commands/env.js";
|
|
17
16
|
import { dev } from "./commands/dev.js";
|
|
18
17
|
import { bind } from "./commands/bind.js";
|
|
19
18
|
import { build } from "./commands/build.js";
|
|
@@ -31,7 +30,6 @@ dev(program);
|
|
|
31
30
|
deploy(program);
|
|
32
31
|
build(program);
|
|
33
32
|
bind(program);
|
|
34
|
-
env(program);
|
|
35
33
|
secrets(program);
|
|
36
34
|
remove(program);
|
|
37
35
|
update(program);
|
package/cli/ui/deploy.d.ts
CHANGED
package/cli/ui/deploy.js
CHANGED
|
@@ -71,13 +71,10 @@ export function printDeploymentResults(assembly, results, remove) {
|
|
|
71
71
|
Colors.gap();
|
|
72
72
|
Colors.line(Colors.success(`✔`), Colors.bold(remove ? ` Removed:` : ` Deployed:`));
|
|
73
73
|
for (const [stack, result] of success) {
|
|
74
|
-
const outputs = Object.entries(result.outputs).filter(([key, _]) => !key.includes("SstSiteEnv"));
|
|
75
74
|
Colors.line(` ${Colors.dim(stackNameToId(stack))}`);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
Colors.line(` ${Colors.bold.dim(key + ":")} ${value}`);
|
|
80
|
-
}
|
|
75
|
+
for (const key of Object.keys(result.outputs).sort()) {
|
|
76
|
+
const value = result.outputs[key];
|
|
77
|
+
Colors.line(` ${Colors.bold.dim(key + ":")} ${value}`);
|
|
81
78
|
}
|
|
82
79
|
}
|
|
83
80
|
Colors.gap();
|
package/cli/ui/functions.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
1
|
+
/// <reference types="react" resolution-mode="require"/>
|
|
2
2
|
export declare function Functions(): JSX.Element;
|
package/config.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { FunctionMetadata, SsrSiteMetadata } from "./constructs/Metadata.js";
|
|
2
|
+
declare module "./bus.js" {
|
|
3
|
+
interface Events {
|
|
4
|
+
"config.secret.updated": {
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
}
|
|
1
9
|
interface Secret {
|
|
2
10
|
value?: string;
|
|
3
11
|
fallback?: string;
|
|
@@ -38,6 +46,11 @@ export declare namespace Config {
|
|
|
38
46
|
key: string;
|
|
39
47
|
fallback?: boolean;
|
|
40
48
|
}): Promise<void>;
|
|
41
|
-
function restart(key: string): Promise<
|
|
49
|
+
function restart(key: string): Promise<{
|
|
50
|
+
edgeSites: SsrSiteMetadata[];
|
|
51
|
+
sites: SsrSiteMetadata[];
|
|
52
|
+
placeholderSites: SsrSiteMetadata[];
|
|
53
|
+
functions: FunctionMetadata[];
|
|
54
|
+
}>;
|
|
42
55
|
}
|
|
43
56
|
export {};
|
package/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { GetFunctionConfigurationCommand, LambdaClient, UpdateFunctionConfigurat
|
|
|
3
3
|
import { pipe, map } from "remeda";
|
|
4
4
|
import { useProject } from "./project.js";
|
|
5
5
|
import { useAWSClient } from "./credentials.js";
|
|
6
|
+
import { useIOT } from "./iot.js";
|
|
6
7
|
import { Stacks } from "./stacks/index.js";
|
|
7
8
|
export var Config;
|
|
8
9
|
(function (Config) {
|
|
@@ -71,7 +72,7 @@ export var Config;
|
|
|
71
72
|
Config.env = env;
|
|
72
73
|
async function setSecret(input) {
|
|
73
74
|
const ssm = useAWSClient(SSMClient);
|
|
74
|
-
|
|
75
|
+
await ssm.send(new PutParameterCommand({
|
|
75
76
|
Name: pathFor({
|
|
76
77
|
id: input.key,
|
|
77
78
|
type: "Secret",
|
|
@@ -82,6 +83,10 @@ export var Config;
|
|
|
82
83
|
Type: "SecureString",
|
|
83
84
|
Overwrite: true,
|
|
84
85
|
}));
|
|
86
|
+
// Publish event
|
|
87
|
+
const iot = await useIOT();
|
|
88
|
+
const topic = `${iot.prefix}/events`;
|
|
89
|
+
await iot.publish(topic, "config.secret.updated", { name: input.key });
|
|
85
90
|
}
|
|
86
91
|
Config.setSecret = setSecret;
|
|
87
92
|
async function getSecret(input) {
|
|
@@ -111,38 +116,44 @@ export var Config;
|
|
|
111
116
|
}
|
|
112
117
|
Config.removeSecret = removeSecret;
|
|
113
118
|
async function restart(key) {
|
|
114
|
-
const lambda = useAWSClient(LambdaClient);
|
|
115
119
|
const metadata = await Stacks.metadata();
|
|
116
|
-
const
|
|
120
|
+
const siteData = Object.values(metadata)
|
|
117
121
|
.flat()
|
|
118
|
-
.filter((
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
122
|
+
.filter((c) => c.type === "AstroSite" ||
|
|
123
|
+
c.type === "NextjsSite" ||
|
|
124
|
+
c.type === "RemixSite" ||
|
|
125
|
+
c.type === "SolidStartSite")
|
|
126
|
+
.filter((c) => c.data.secrets.includes(key));
|
|
127
|
+
const siteDataPlaceholder = siteData.filter((c) => c.data.mode === "placeholder");
|
|
128
|
+
const siteDataEdge = siteData
|
|
129
|
+
.filter((c) => c.data.mode === "deployed")
|
|
130
|
+
.filter((c) => c.data.edge);
|
|
131
|
+
const siteDataRegional = siteData
|
|
132
|
+
.filter((c) => c.data.mode === "deployed")
|
|
133
|
+
.filter((c) => !c.data.edge);
|
|
134
|
+
const regionalSiteArns = siteData.map((s) => s.data.server);
|
|
135
|
+
const functionData = Object.values(metadata)
|
|
136
|
+
.flat()
|
|
137
|
+
.filter((c) => c.type === "Function")
|
|
138
|
+
// filter out SSR functions for sites
|
|
139
|
+
.filter((c) => !regionalSiteArns.includes(c.data.arn))
|
|
140
|
+
.filter((c) => c.data.secrets.includes(key));
|
|
141
|
+
// Restart sites
|
|
142
|
+
const restartedSites = (await Promise.all(siteDataRegional.map(async (s) => {
|
|
143
|
+
const restarted = await restartFunction(s.data.server);
|
|
144
|
+
return restarted ? s : restarted;
|
|
145
|
+
}))).filter((c) => Boolean(c));
|
|
146
|
+
// Restart functions
|
|
147
|
+
const restartedFunctions = (await Promise.all(functionData.map(async (f) => {
|
|
148
|
+
const restarted = await restartFunction(f.data.arn);
|
|
149
|
+
return restarted ? f : restarted;
|
|
150
|
+
}))).filter((c) => Boolean(c));
|
|
151
|
+
return {
|
|
152
|
+
edgeSites: siteDataEdge,
|
|
153
|
+
sites: restartedSites,
|
|
154
|
+
placeholderSites: siteDataPlaceholder,
|
|
155
|
+
functions: restartedFunctions,
|
|
156
|
+
};
|
|
146
157
|
}
|
|
147
158
|
Config.restart = restart;
|
|
148
159
|
})(Config || (Config = {}));
|
|
@@ -182,3 +193,29 @@ function parse(ssmName) {
|
|
|
182
193
|
prop: parts.slice(6).join("/"),
|
|
183
194
|
};
|
|
184
195
|
}
|
|
196
|
+
async function restartFunction(arn) {
|
|
197
|
+
const lambda = useAWSClient(LambdaClient);
|
|
198
|
+
// Note: in the case where the function is removed, but the metadata
|
|
199
|
+
// is not updated, we ignore the Function not found error.
|
|
200
|
+
try {
|
|
201
|
+
const config = await lambda.send(new GetFunctionConfigurationCommand({
|
|
202
|
+
FunctionName: arn,
|
|
203
|
+
}));
|
|
204
|
+
await lambda.send(new UpdateFunctionConfigurationCommand({
|
|
205
|
+
FunctionName: arn,
|
|
206
|
+
Environment: {
|
|
207
|
+
Variables: {
|
|
208
|
+
...(config.Environment?.Variables || {}),
|
|
209
|
+
[SECRET_UPDATED_AT_ENV]: Date.now().toString(),
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
}));
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
if (e.name === "ResourceNotFoundException" &&
|
|
217
|
+
e.message.startsWith("Function not found")) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
package/constructs/App.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare class App extends CDKApp {
|
|
|
50
50
|
*/
|
|
51
51
|
readonly mode: AppDeployProps["mode"];
|
|
52
52
|
/**
|
|
53
|
-
* The name of your app, comes from the `name` in your `sst.
|
|
53
|
+
* The name of your app, comes from the `name` in your `sst.config.ts`
|
|
54
54
|
*/
|
|
55
55
|
readonly name: string;
|
|
56
56
|
/**
|
|
@@ -58,7 +58,7 @@ export declare class App extends CDKApp {
|
|
|
58
58
|
*/
|
|
59
59
|
readonly stage: string;
|
|
60
60
|
/**
|
|
61
|
-
* The region the app is being deployed to. If this is not specified as the --region option in the SST CLI, it'll default to the region in your sst.
|
|
61
|
+
* The region the app is being deployed to. If this is not specified as the --region option in the SST CLI, it'll default to the region in your sst.config.ts.
|
|
62
62
|
*/
|
|
63
63
|
readonly region: string;
|
|
64
64
|
/**
|
package/constructs/App.js
CHANGED
|
@@ -11,7 +11,6 @@ import { useDeferredTasks } from "./deferred_task.js";
|
|
|
11
11
|
import { AppContext } from "./context.js";
|
|
12
12
|
import { useProject } from "../project.js";
|
|
13
13
|
import { Logger } from "../logger.js";
|
|
14
|
-
import { SiteEnv } from "../site-env.js";
|
|
15
14
|
import { App as CDKApp, Tags, CfnResource, RemovalPolicy, CustomResourceProvider, CustomResourceProviderRuntime, CustomResource, Aspects, } from "aws-cdk-lib";
|
|
16
15
|
import { CfnFunction } from "aws-cdk-lib/aws-lambda";
|
|
17
16
|
import { Bucket } from "aws-cdk-lib/aws-s3";
|
|
@@ -35,7 +34,7 @@ export class App extends CDKApp {
|
|
|
35
34
|
*/
|
|
36
35
|
mode;
|
|
37
36
|
/**
|
|
38
|
-
* The name of your app, comes from the `name` in your `sst.
|
|
37
|
+
* The name of your app, comes from the `name` in your `sst.config.ts`
|
|
39
38
|
*/
|
|
40
39
|
name;
|
|
41
40
|
/**
|
|
@@ -43,7 +42,7 @@ export class App extends CDKApp {
|
|
|
43
42
|
*/
|
|
44
43
|
stage;
|
|
45
44
|
/**
|
|
46
|
-
* The region the app is being deployed to. If this is not specified as the --region option in the SST CLI, it'll default to the region in your sst.
|
|
45
|
+
* The region the app is being deployed to. If this is not specified as the --region option in the SST CLI, it'll default to the region in your sst.config.ts.
|
|
47
46
|
*/
|
|
48
47
|
region;
|
|
49
48
|
/**
|
|
@@ -71,7 +70,6 @@ export class App extends CDKApp {
|
|
|
71
70
|
constructor(deployProps, props = {}) {
|
|
72
71
|
super(props);
|
|
73
72
|
AppContext.provide(this);
|
|
74
|
-
SiteEnv.reset();
|
|
75
73
|
this.appPath = process.cwd();
|
|
76
74
|
this.mode = deployProps.mode;
|
|
77
75
|
this.local = this.mode === "dev";
|
|
@@ -14,6 +14,7 @@ import { EdgeFunction } from "./EdgeFunction.js";
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class AstroSite extends SsrSite {
|
|
16
16
|
protected initBuildConfig(): {
|
|
17
|
+
typesPath: string;
|
|
17
18
|
serverBuildOutputFile: string;
|
|
18
19
|
clientBuildOutputDir: string;
|
|
19
20
|
clientBuildVersionedSubDir: string;
|
package/constructs/AstroSite.js
CHANGED
|
@@ -18,6 +18,7 @@ import { EdgeFunction } from "./EdgeFunction.js";
|
|
|
18
18
|
export class AstroSite extends SsrSite {
|
|
19
19
|
initBuildConfig() {
|
|
20
20
|
return {
|
|
21
|
+
typesPath: "src",
|
|
21
22
|
serverBuildOutputFile: "dist/server/entry.mjs",
|
|
22
23
|
clientBuildOutputDir: "dist/client",
|
|
23
24
|
clientBuildVersionedSubDir: "assets",
|
package/constructs/Function.d.ts
CHANGED
package/constructs/Metadata.d.ts
CHANGED
|
@@ -24,10 +24,14 @@ import type { KinesisStream } from "./KinesisStream.js";
|
|
|
24
24
|
export type KinesisStreamMetadata = ExtractMetadata<KinesisStream>;
|
|
25
25
|
import type { NextjsSite } from "./NextjsSite.js";
|
|
26
26
|
export type NextjsMetadata = ExtractMetadata<NextjsSite>;
|
|
27
|
+
import type { NextjsSite as SlsNextjsSite } from "./deprecated/NextjsSite.js";
|
|
28
|
+
export type SlsNextjsMetadata = ExtractMetadata<SlsNextjsSite>;
|
|
27
29
|
import type { Queue } from "./Queue.js";
|
|
28
30
|
export type QueueMetadata = ExtractMetadata<Queue>;
|
|
29
31
|
import type { StaticSite } from "./StaticSite.js";
|
|
30
32
|
export type StaticSiteMetadata = ExtractMetadata<StaticSite>;
|
|
33
|
+
import type { SsrSite } from "./SsrSite.js";
|
|
34
|
+
export type SsrSiteMetadata = ExtractMetadata<SsrSite>;
|
|
31
35
|
import type { Table } from "./Table.js";
|
|
32
36
|
export type TableMetadata = ExtractMetadata<Table>;
|
|
33
37
|
import type { Topic } from "./Topic.js";
|
|
@@ -36,5 +40,5 @@ import type { WebSocketApi } from "./WebSocketApi.js";
|
|
|
36
40
|
export type WebSocketApiMetadata = ExtractMetadata<WebSocketApi>;
|
|
37
41
|
import type { RDS } from "./RDS.js";
|
|
38
42
|
export type RDSMetadata = ExtractMetadata<RDS>;
|
|
39
|
-
export type Metadata = ApiMetadata | ApiGatewayV1ApiMetadata | AuthMetadata | AppSyncApiMetadata | BucketMetadata | CronMetadata | EventBusMetadata | FunctionMetadata | KinesisStreamMetadata | NextjsMetadata | QueueMetadata | StaticSiteMetadata | TableMetadata | TopicMetadata | WebSocketApiMetadata | RDSMetadata;
|
|
43
|
+
export type Metadata = ApiMetadata | ApiGatewayV1ApiMetadata | AuthMetadata | AppSyncApiMetadata | BucketMetadata | CronMetadata | EventBusMetadata | FunctionMetadata | KinesisStreamMetadata | NextjsMetadata | SlsNextjsMetadata | QueueMetadata | StaticSiteMetadata | SsrSiteMetadata | TableMetadata | TopicMetadata | WebSocketApiMetadata | RDSMetadata;
|
|
40
44
|
export {};
|
|
@@ -37,6 +37,7 @@ export declare class NextjsSite extends SsrSite {
|
|
|
37
37
|
};
|
|
38
38
|
constructor(scope: Construct, id: string, props?: NextjsSiteProps);
|
|
39
39
|
protected initBuildConfig(): {
|
|
40
|
+
typesPath: string;
|
|
40
41
|
serverBuildOutputFile: string;
|
|
41
42
|
clientBuildOutputDir: string;
|
|
42
43
|
clientBuildVersionedSubDir: string;
|
package/constructs/NextjsSite.js
CHANGED
|
@@ -16,6 +16,7 @@ import { EdgeFunction } from "./EdgeFunction.js";
|
|
|
16
16
|
*/
|
|
17
17
|
export declare class RemixSite extends SsrSite {
|
|
18
18
|
protected initBuildConfig(): {
|
|
19
|
+
typesPath: string;
|
|
19
20
|
serverBuildOutputFile: string;
|
|
20
21
|
clientBuildOutputDir: string;
|
|
21
22
|
clientBuildVersionedSubDir: string;
|