sst 2.4.0 → 2.4.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.
@@ -1,11 +1,10 @@
1
1
  import path from "path";
2
2
  import { VisibleError } from "../../error.js";
3
- const SITE_CONFIG = {
3
+ const SSR_SITE_CONFIG = {
4
4
  NextjsSite: "next.config",
5
5
  AstroSite: "astro.config",
6
6
  RemixSite: "remix.config",
7
7
  SolidStartSite: "vite.config",
8
- StaticSite: "vite.config",
9
8
  SlsNextjsSite: "next.config",
10
9
  };
11
10
  export const bind = (program) => program
@@ -18,6 +17,7 @@ export const bind = (program) => program
18
17
  const { useBus } = await import("../../bus.js");
19
18
  const { useIOT } = await import("../../iot.js");
20
19
  const { Colors } = await import("../colors.js");
20
+ const { Logger } = await import("../../logger.js");
21
21
  if (args._[0] === "env") {
22
22
  Colors.line(Colors.warning(`Warning: ${Colors.bold(`sst env`)} has been renamed to ${Colors.bold(`sst bind`)}`));
23
23
  }
@@ -25,17 +25,18 @@ export const bind = (program) => program
25
25
  const bus = useBus();
26
26
  const project = useProject();
27
27
  const command = args.command?.join(" ");
28
- const isFrontend = await isRunningInFrontend();
28
+ const isSsrSite = await isRunningInSsrSite();
29
29
  let p;
30
30
  let timer;
31
- let metadataCache;
31
+ let siteConfigCache;
32
32
  // Handle missing command
33
33
  if (!command) {
34
- throw new VisibleError(`Command is required, e.g. sst bind ${isFrontend ? "next dev" : "env"}`);
34
+ throw new VisibleError(`Command is required, e.g. sst bind ${isSsrSite ? "next dev" : "env"}`);
35
35
  }
36
36
  // Bind script
37
37
  const initialMetadata = await getSiteMetadata();
38
- if (!initialMetadata) {
38
+ if (!initialMetadata && !isSsrSite) {
39
+ Logger.debug("Running in script mode.");
39
40
  return await bindScript();
40
41
  }
41
42
  // Bind site
@@ -44,33 +45,44 @@ export const bind = (program) => program
44
45
  bus.subscribe("stacks.metadata.deleted", () => bindSite("metadata_updated"));
45
46
  bus.subscribe("config.secret.updated", (payload) => {
46
47
  const secretName = payload.properties.name;
47
- if (metadataCache?.secrets === undefined)
48
+ if (siteConfigCache?.secrets === undefined)
48
49
  return;
49
- if (!metadataCache.secrets.includes(secretName))
50
+ if (!siteConfigCache.secrets.includes(secretName))
50
51
  return;
51
52
  Colors.line(`\n`, `SST secrets have been updated. Restarting \`${command}\`...`);
52
53
  bindSite("secrets_updated");
53
54
  });
54
- async function isRunningInFrontend() {
55
+ async function isRunningInSsrSite() {
55
56
  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}`)))
57
+ const { readFile } = await import("fs/promises");
58
+ const results = await Promise.all(Object.values(SSR_SITE_CONFIG)
59
+ .map((config) => [".js", ".cjs", ".mjs", ".ts"].map(async (ext) => {
60
+ const exists = await existsAsync(`${config}${ext}`);
61
+ if (exists && config === "vite.config") {
62
+ const content = await readFile(`${config}${ext}`);
63
+ return content.includes("solid-start");
64
+ }
65
+ return exists;
66
+ }))
58
67
  .flat());
59
68
  return results.some(Boolean);
60
69
  }
61
70
  async function bindSite(reason) {
62
71
  // Get metadata
63
- const metadata = (reason === "init" ? initialMetadata : await getSiteMetadata());
72
+ const siteMetadata = (reason === "init"
73
+ ? initialMetadata
74
+ : await getSiteMetadataUntilAvailable());
75
+ const siteConfig = await parseSiteConfig(siteMetadata);
64
76
  // Handle rebind due to metadata updated
65
77
  if (reason === "metadata_updated") {
66
- if (areEnvsSame(metadata.envs, metadataCache?.envs || {}))
78
+ if (areEnvsSame(siteConfig.envs, siteConfigCache?.envs || {}))
67
79
  return;
68
80
  Colors.line(`\n`, `SST resources have been updated. Restarting \`${command}\`...`);
69
81
  }
70
- metadataCache = metadata;
82
+ siteConfigCache = siteConfig;
71
83
  // Assume function's role credentials
72
- if (metadata.role) {
73
- const credentials = await assumeSsrRole(metadata.role);
84
+ if (siteConfig.role) {
85
+ const credentials = await assumeSsrRole(siteConfig.role);
74
86
  if (credentials) {
75
87
  // refresh crecentials 1 minute before expiration
76
88
  const expireAt = credentials.Expiration.getTime() - 60000;
@@ -80,7 +92,7 @@ export const bind = (program) => program
80
92
  bindSite("iam_expired");
81
93
  }, expireAt - Date.now());
82
94
  runCommand({
83
- ...metadata.envs,
95
+ ...siteConfig.envs,
84
96
  AWS_ACCESS_KEY_ID: credentials.AccessKeyId,
85
97
  AWS_SECRET_ACCESS_KEY: credentials.SecretAccessKey,
86
98
  AWS_SESSION_TOKEN: credentials.SessionToken,
@@ -90,7 +102,7 @@ export const bind = (program) => program
90
102
  }
91
103
  // Fallback to use local IAM credentials
92
104
  runCommand({
93
- ...metadata.envs,
105
+ ...siteConfig.envs,
94
106
  ...(await localIamCredentials()),
95
107
  });
96
108
  }
@@ -101,24 +113,30 @@ export const bind = (program) => program
101
113
  ...(await localIamCredentials()),
102
114
  });
103
115
  }
104
- async function getSiteMetadata() {
105
- const { metadata } = await import("../../stacks/metadata.js");
106
- const { createSpinner } = await import("../spinner.js");
116
+ async function parseSiteConfig(metadata) {
107
117
  const { LambdaClient, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
108
118
  const { useAWSClient } = await import("../../credentials.js");
119
+ const isBindSupported = metadata.type !== "StaticSite" && metadata.type !== "SlsNextjsSite";
120
+ // Handle StaticSite
121
+ if (!isBindSupported) {
122
+ return { envs: metadata.data.environment };
123
+ }
124
+ // Get function details
125
+ const lambda = useAWSClient(LambdaClient);
126
+ const { Configuration: functionConfig } = await lambda.send(new GetFunctionCommand({
127
+ FunctionName: metadata.data.server,
128
+ }));
129
+ return {
130
+ role: functionConfig?.Role,
131
+ envs: functionConfig?.Environment?.Variables || {},
132
+ secrets: metadata.data.secrets,
133
+ };
134
+ }
135
+ async function getSiteMetadataUntilAvailable() {
136
+ const { createSpinner } = await import("../spinner.js");
109
137
  const spinner = createSpinner({});
110
138
  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
- }
139
+ const data = await getSiteMetadata();
122
140
  // Handle site metadata not found
123
141
  if (!data) {
124
142
  spinner.start(
@@ -137,25 +155,20 @@ export const bind = (program) => program
137
155
  continue;
138
156
  }
139
157
  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
- };
158
+ return data;
154
159
  }
155
160
  }
161
+ async function getSiteMetadata() {
162
+ const { metadata } = await import("../../stacks/metadata.js");
163
+ const metadataData = await metadata();
164
+ return Object.values(metadataData)
165
+ .flat()
166
+ .filter((c) => Boolean(c))
167
+ .filter((c) => c.type === "StaticSite" || Boolean(SSR_SITE_CONFIG[c.type]))
168
+ .find((c) => path.resolve(project.paths.root, c.data.path) === process.cwd());
169
+ }
156
170
  async function assumeSsrRole(roleArn) {
157
171
  const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
158
- const { Logger } = await import("../../logger.js");
159
172
  const { useAWSClient } = await import("../../credentials.js");
160
173
  const sts = useAWSClient(STSClient);
161
174
  const assumeRole = async (duration) => {
@@ -184,7 +197,7 @@ export const bind = (program) => program
184
197
  err = e;
185
198
  }
186
199
  }
187
- Colors.line(Colors.warning(`Failed to assume SSR role ${roleArn}. Falling back to using local IAM credentials.`));
200
+ Colors.line("Using local IAM credentials since `sst dev` is not running.");
188
201
  Logger.debug(`Failed to assume ${roleArn}.`, err);
189
202
  }
190
203
  async function localIamCredentials() {
@@ -26,7 +26,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
26
26
  export class NextjsSite extends SsrSite {
27
27
  constructor(scope, id, props) {
28
28
  super(scope, id, {
29
- buildCommand: "npx --yes open-next@latest build",
29
+ buildCommand: "npx --yes open-next@^0.8.0 build",
30
30
  ...props,
31
31
  });
32
32
  }
@@ -0,0 +1,29 @@
1
+ import { SSTError } from "../../util/error.js";
2
+ interface Definition {
3
+ type: string;
4
+ properties: Record<string, any>;
5
+ }
6
+ export declare class WrongActorError extends SSTError {
7
+ }
8
+ export declare function createActors<T extends Definition>(): {
9
+ useActor: () => T | {
10
+ type: "public";
11
+ properties: {};
12
+ };
13
+ provideActor: (value: T | {
14
+ type: "public";
15
+ properties: {};
16
+ }) => void;
17
+ assertActor<T_1 extends (T | {
18
+ type: "public";
19
+ properties: {};
20
+ })["type"]>(type: T_1): (Extract<T, {
21
+ type: T_1;
22
+ }> | Extract<{
23
+ type: "public";
24
+ properties: {};
25
+ }, {
26
+ type: T_1;
27
+ }>)["properties"];
28
+ };
29
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Context } from "../../context/context.js";
2
+ import { SSTError } from "../../util/error.js";
3
+ export class WrongActorError extends SSTError {
4
+ }
5
+ export function createActors() {
6
+ const ctx = Context.create();
7
+ return {
8
+ useActor: ctx.use,
9
+ provideActor: ctx.provide,
10
+ assertActor(type) {
11
+ const actor = ctx.use();
12
+ if (actor.type === type)
13
+ return actor.properties;
14
+ throw new WrongActorError(`Expected actor type "${type} but got "${actor.type}"`);
15
+ },
16
+ };
17
+ }
@@ -9,7 +9,10 @@ const ssm = new SSMClient({ region: process.env.SST_REGION });
9
9
  // }
10
10
  // }
11
11
  let allVariables = {};
12
- await parseEnvironment();
12
+ // NOTE: in some setups, top level await must be assigned to a variable,
13
+ // otherwise it would throw a top level await error.
14
+ // https://discord.com/channels/983865673656705025/1089184080534446110
15
+ const _placeholder = await parseEnvironment();
13
16
  export function createProxy(constructName) {
14
17
  return new Proxy({}, {
15
18
  get(target, prop) {
@@ -87,6 +90,14 @@ async function fetchValuesFromSSM(variablesFromSsm) {
87
90
  const variable = parseSsmFallbackPath(item.Name);
88
91
  storeVariable(variable, item.Value);
89
92
  });
93
+ // Throw error if any values are missing
94
+ const missingSecrets = fallbackResults.invalidParams
95
+ .map((name) => parseSsmFallbackPath(name))
96
+ .filter((variable) => variable.constructName === "Secret")
97
+ .map((variable) => variable.constructId);
98
+ if (missingSecrets.length > 0) {
99
+ throw new Error(`The following secrets were not found: ${missingSecrets.join(", ")}`);
100
+ }
90
101
  }
91
102
  async function loadSecrets(paths) {
92
103
  // Split paths into chunks of 10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sst",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "bin": {
5
5
  "sst": "cli/sst.js"
6
6
  },
package/sst.mjs CHANGED
@@ -7077,12 +7077,11 @@ Are you sure you want to run this stage in dev mode? [y/N] `,
7077
7077
  // src/cli/commands/bind.ts
7078
7078
  init_error();
7079
7079
  import path18 from "path";
7080
- var SITE_CONFIG = {
7080
+ var SSR_SITE_CONFIG = {
7081
7081
  NextjsSite: "next.config",
7082
7082
  AstroSite: "astro.config",
7083
7083
  RemixSite: "remix.config",
7084
7084
  SolidStartSite: "vite.config",
7085
- StaticSite: "vite.config",
7086
7085
  SlsNextjsSite: "next.config"
7087
7086
  };
7088
7087
  var bind = (program2) => program2.command(
@@ -7098,6 +7097,7 @@ var bind = (program2) => program2.command(
7098
7097
  const { useBus: useBus2 } = await Promise.resolve().then(() => (init_bus(), bus_exports));
7099
7098
  const { useIOT: useIOT2 } = await Promise.resolve().then(() => (init_iot(), iot_exports));
7100
7099
  const { Colors: Colors2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
7100
+ const { Logger: Logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
7101
7101
  if (args._[0] === "env") {
7102
7102
  Colors2.line(
7103
7103
  Colors2.warning(
@@ -7111,17 +7111,18 @@ var bind = (program2) => program2.command(
7111
7111
  const bus = useBus2();
7112
7112
  const project = useProject2();
7113
7113
  const command = args.command?.join(" ");
7114
- const isFrontend = await isRunningInFrontend();
7114
+ const isSsrSite = await isRunningInSsrSite();
7115
7115
  let p;
7116
7116
  let timer;
7117
- let metadataCache;
7117
+ let siteConfigCache;
7118
7118
  if (!command) {
7119
7119
  throw new VisibleError(
7120
- `Command is required, e.g. sst bind ${isFrontend ? "next dev" : "env"}`
7120
+ `Command is required, e.g. sst bind ${isSsrSite ? "next dev" : "env"}`
7121
7121
  );
7122
7122
  }
7123
7123
  const initialMetadata = await getSiteMetadata();
7124
- if (!initialMetadata) {
7124
+ if (!initialMetadata && !isSsrSite) {
7125
+ Logger2.debug("Running in script mode.");
7125
7126
  return await bindScript();
7126
7127
  }
7127
7128
  await bindSite("init");
@@ -7135,9 +7136,9 @@ var bind = (program2) => program2.command(
7135
7136
  );
7136
7137
  bus.subscribe("config.secret.updated", (payload) => {
7137
7138
  const secretName = payload.properties.name;
7138
- if (metadataCache?.secrets === void 0)
7139
+ if (siteConfigCache?.secrets === void 0)
7139
7140
  return;
7140
- if (!metadataCache.secrets.includes(secretName))
7141
+ if (!siteConfigCache.secrets.includes(secretName))
7141
7142
  return;
7142
7143
  Colors2.line(
7143
7144
  `
@@ -7146,21 +7147,28 @@ var bind = (program2) => program2.command(
7146
7147
  );
7147
7148
  bindSite("secrets_updated");
7148
7149
  });
7149
- async function isRunningInFrontend() {
7150
+ async function isRunningInSsrSite() {
7150
7151
  const { existsAsync: existsAsync3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
7152
+ const { readFile } = await import("fs/promises");
7151
7153
  const results = await Promise.all(
7152
- Object.values(SITE_CONFIG).map(
7153
- (config) => [".js", ".cjs", ".mjs", ".ts"].map(
7154
- (ext) => existsAsync3(`${config}${ext}`)
7155
- )
7154
+ Object.values(SSR_SITE_CONFIG).map(
7155
+ (config) => [".js", ".cjs", ".mjs", ".ts"].map(async (ext) => {
7156
+ const exists = await existsAsync3(`${config}${ext}`);
7157
+ if (exists && config === "vite.config") {
7158
+ const content = await readFile(`${config}${ext}`);
7159
+ return content.includes("solid-start");
7160
+ }
7161
+ return exists;
7162
+ })
7156
7163
  ).flat()
7157
7164
  );
7158
7165
  return results.some(Boolean);
7159
7166
  }
7160
7167
  async function bindSite(reason) {
7161
- const metadata3 = reason === "init" ? initialMetadata : await getSiteMetadata();
7168
+ const siteMetadata = reason === "init" ? initialMetadata : await getSiteMetadataUntilAvailable();
7169
+ const siteConfig = await parseSiteConfig(siteMetadata);
7162
7170
  if (reason === "metadata_updated") {
7163
- if (areEnvsSame(metadata3.envs, metadataCache?.envs || {}))
7171
+ if (areEnvsSame(siteConfig.envs, siteConfigCache?.envs || {}))
7164
7172
  return;
7165
7173
  Colors2.line(
7166
7174
  `
@@ -7168,9 +7176,9 @@ var bind = (program2) => program2.command(
7168
7176
  `SST resources have been updated. Restarting \`${command}\`...`
7169
7177
  );
7170
7178
  }
7171
- metadataCache = metadata3;
7172
- if (metadata3.role) {
7173
- const credentials = await assumeSsrRole(metadata3.role);
7179
+ siteConfigCache = siteConfig;
7180
+ if (siteConfig.role) {
7181
+ const credentials = await assumeSsrRole(siteConfig.role);
7174
7182
  if (credentials) {
7175
7183
  const expireAt = credentials.Expiration.getTime() - 6e4;
7176
7184
  clearTimeout(timer);
@@ -7183,7 +7191,7 @@ var bind = (program2) => program2.command(
7183
7191
  bindSite("iam_expired");
7184
7192
  }, expireAt - Date.now());
7185
7193
  runCommand({
7186
- ...metadata3.envs,
7194
+ ...siteConfig.envs,
7187
7195
  AWS_ACCESS_KEY_ID: credentials.AccessKeyId,
7188
7196
  AWS_SECRET_ACCESS_KEY: credentials.SecretAccessKey,
7189
7197
  AWS_SESSION_TOKEN: credentials.SessionToken
@@ -7192,7 +7200,7 @@ var bind = (program2) => program2.command(
7192
7200
  }
7193
7201
  }
7194
7202
  runCommand({
7195
- ...metadata3.envs,
7203
+ ...siteConfig.envs,
7196
7204
  ...await localIamCredentials()
7197
7205
  });
7198
7206
  }
@@ -7203,22 +7211,30 @@ var bind = (program2) => program2.command(
7203
7211
  ...await localIamCredentials()
7204
7212
  });
7205
7213
  }
7206
- async function getSiteMetadata() {
7207
- const { metadata: metadata3 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
7208
- const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_spinner(), spinner_exports));
7214
+ async function parseSiteConfig(metadata3) {
7209
7215
  const { LambdaClient: LambdaClient2, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
7210
7216
  const { useAWSClient: useAWSClient2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
7217
+ const isBindSupported = metadata3.type !== "StaticSite" && metadata3.type !== "SlsNextjsSite";
7218
+ if (!isBindSupported) {
7219
+ return { envs: metadata3.data.environment };
7220
+ }
7221
+ const lambda = useAWSClient2(LambdaClient2);
7222
+ const { Configuration: functionConfig } = await lambda.send(
7223
+ new GetFunctionCommand({
7224
+ FunctionName: metadata3.data.server
7225
+ })
7226
+ );
7227
+ return {
7228
+ role: functionConfig?.Role,
7229
+ envs: functionConfig?.Environment?.Variables || {},
7230
+ secrets: metadata3.data.secrets
7231
+ };
7232
+ }
7233
+ async function getSiteMetadataUntilAvailable() {
7234
+ const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_spinner(), spinner_exports));
7211
7235
  const spinner = createSpinner2({});
7212
7236
  while (true) {
7213
- const metadataData = await metadata3();
7214
- const data2 = Object.values(metadataData).flat().filter(
7215
- (c) => Boolean(c)
7216
- ).filter((c) => Boolean(SITE_CONFIG[c.type])).find(
7217
- (c) => path18.resolve(project.paths.root, c.data.path) === process.cwd()
7218
- );
7219
- if (!data2 && !isFrontend) {
7220
- return;
7221
- }
7237
+ const data2 = await getSiteMetadata();
7222
7238
  if (!data2) {
7223
7239
  spinner.start(
7224
7240
  "Make sure `sst dev` is running..."
@@ -7235,25 +7251,22 @@ var bind = (program2) => program2.command(
7235
7251
  continue;
7236
7252
  }
7237
7253
  spinner.isSpinning && spinner.stop().clear();
7238
- if (!isBindSupported) {
7239
- return { envs: data2.data.environment };
7240
- }
7241
- const lambda = useAWSClient2(LambdaClient2);
7242
- const { Configuration: functionConfig } = await lambda.send(
7243
- new GetFunctionCommand({
7244
- FunctionName: data2.data.server
7245
- })
7246
- );
7247
- return {
7248
- role: functionConfig?.Role,
7249
- envs: functionConfig?.Environment?.Variables || {},
7250
- secrets: data2.data.secrets
7251
- };
7254
+ return data2;
7252
7255
  }
7253
7256
  }
7257
+ async function getSiteMetadata() {
7258
+ const { metadata: metadata3 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
7259
+ const metadataData = await metadata3();
7260
+ return Object.values(metadataData).flat().filter(
7261
+ (c) => Boolean(c)
7262
+ ).filter(
7263
+ (c) => c.type === "StaticSite" || Boolean(SSR_SITE_CONFIG[c.type])
7264
+ ).find(
7265
+ (c) => path18.resolve(project.paths.root, c.data.path) === process.cwd()
7266
+ );
7267
+ }
7254
7268
  async function assumeSsrRole(roleArn) {
7255
7269
  const { STSClient: STSClient2, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
7256
- const { Logger: Logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
7257
7270
  const { useAWSClient: useAWSClient2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
7258
7271
  const sts = useAWSClient2(STSClient2);
7259
7272
  const assumeRole = async (duration) => {
@@ -7280,9 +7293,7 @@ var bind = (program2) => program2.command(
7280
7293
  }
7281
7294
  }
7282
7295
  Colors2.line(
7283
- Colors2.warning(
7284
- `Failed to assume SSR role ${roleArn}. Falling back to using local IAM credentials.`
7285
- )
7296
+ "Using local IAM credentials since `sst dev` is not running."
7286
7297
  );
7287
7298
  Logger2.debug(`Failed to assume ${roleArn}.`, err);
7288
7299
  }