sst 2.0.1 → 2.0.3

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/bootstrap.js CHANGED
@@ -55,11 +55,21 @@ async function loadCDKStatus() {
55
55
  const { Stacks: stacks } = await client.send(new DescribeStacksCommand({
56
56
  StackName: "CDKToolkit",
57
57
  }));
58
- if (stacks &&
59
- stacks.length > 0 &&
60
- ["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(stacks[0].StackStatus)) {
61
- return true;
58
+ // Check CDK bootstrap stack exists
59
+ if (!stacks || stacks.length === 0)
60
+ return false;
61
+ // Check CDK bootstrap stack deployed successfully
62
+ if (!["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(stacks[0].StackStatus)) {
63
+ return false;
62
64
  }
65
+ // Check CDK bootstrap stack is up to date
66
+ // note: there is no a programmatical way to get the minimal required version
67
+ // of CDK bootstrap stack. We are going to hardcode it to 14 for now,
68
+ // which is the latest version as of CDK v2.62.2
69
+ const output = stacks[0].Outputs?.find((o) => o.OutputKey === "BootstrapVersion");
70
+ if (!output || parseInt(output.OutputValue) < 14)
71
+ return false;
72
+ return true;
63
73
  }
64
74
  catch (e) {
65
75
  if (e.name === "ValidationError" &&
@@ -11,5 +11,5 @@ export declare const bind: (program: Program) => import("yargs").Argv<{
11
11
  } & {
12
12
  role: string | undefined;
13
13
  } & {
14
- command: string;
14
+ command: (string | number)[] | undefined;
15
15
  }>;
@@ -1,9 +1,7 @@
1
- export const bind = (program) => program.command("bind <command>", "Bind your app's resources to a command", (yargs) => yargs
2
- .positional("command", {
3
- type: "string",
4
- describe: "The command to bind to",
5
- demandOption: true,
6
- })
1
+ import { VisibleError } from "../../error.js";
2
+ export const bind = (program) => program
3
+ .command("bind <command..>", "Bind your app's resources to a command", (yargs) => yargs
4
+ .array("command")
7
5
  .example(`sst bind "vitest run"`, "Bind your resources to your tests")
8
6
  .example(`sst bind "tsx scripts/myscript.ts"`, "Bind your resources to a script"), async (args) => {
9
7
  const { Config } = await import("../../config.js");
@@ -13,7 +11,10 @@ export const bind = (program) => program.command("bind <command>", "Bind your ap
13
11
  const env = await Config.env();
14
12
  const project = useProject();
15
13
  const credentials = await useAWSCredentials();
16
- const result = spawnSync(args.command, {
14
+ const joined = args.command?.join(" ");
15
+ if (!joined)
16
+ throw new VisibleError("Command is required, e.g. sst bind env");
17
+ const result = spawnSync(joined, {
17
18
  env: {
18
19
  ...process.env,
19
20
  ...env,
@@ -26,4 +27,5 @@ export const bind = (program) => program.command("bind <command>", "Bind your ap
26
27
  shell: process.env.SHELL || true,
27
28
  });
28
29
  process.exitCode = result.status || undefined;
29
- });
30
+ })
31
+ .strict(false);
@@ -11,5 +11,5 @@ export declare const env: (program: Program) => import("yargs").Argv<{
11
11
  } & {
12
12
  role: string | undefined;
13
13
  } & {
14
- command: string;
14
+ command: (string | number)[] | undefined;
15
15
  }>;
@@ -1,15 +1,15 @@
1
- export const env = (program) => program.command("env <command>", "Load environment variables and start your frontend", (yargs) => yargs
2
- .positional("command", {
3
- type: "string",
4
- describe: "The command to start your frontend",
5
- demandOption: true,
6
- })
1
+ import { VisibleError } from "../../error.js";
2
+ export const env = (program) => program
3
+ .command("env <command..>", "Load environment variables and start your frontend", (yargs) => yargs
4
+ .array("command")
7
5
  .example(`sst env "next dev"`, "Start Next.js with your environment variables")
8
6
  .example(`sst env "vite dev"`, "Start Vite with your environment variables"), async (args) => {
9
7
  const { createSpinner } = await import("../spinner.js");
10
8
  const fs = await import("fs/promises");
11
9
  const { SiteEnv } = await import("../../site-env.js");
12
10
  const { spawnSync } = await import("child_process");
11
+ const { useAWSCredentials } = await import("../../credentials.js");
12
+ const { useProject } = await import("../../project.js");
13
13
  let spinner;
14
14
  while (true) {
15
15
  const exists = await fs
@@ -24,10 +24,19 @@ export const env = (program) => program.command("env <command>", "Load environme
24
24
  spinner?.succeed();
25
25
  const sites = await SiteEnv.values();
26
26
  const env = sites[process.cwd()] || {};
27
- const result = spawnSync(args.command, {
27
+ const project = useProject();
28
+ const credentials = await useAWSCredentials();
29
+ const joined = args.command?.join(" ");
30
+ if (!joined)
31
+ throw new VisibleError("Command is required, e.g. sst env vite dev");
32
+ const result = spawnSync(joined, {
28
33
  env: {
29
34
  ...process.env,
30
35
  ...env,
36
+ AWS_ACCESS_KEY_ID: credentials.accessKeyId,
37
+ AWS_SECRET_ACCESS_KEY: credentials.secretAccessKey,
38
+ AWS_SESSION_TOKEN: credentials.sessionToken,
39
+ AWS_REGION: project.config.region,
31
40
  },
32
41
  stdio: "inherit",
33
42
  shell: process.env.SHELL || true,
@@ -35,4 +44,5 @@ export const env = (program) => program.command("env <command>", "Load environme
35
44
  process.exitCode = result.status || undefined;
36
45
  break;
37
46
  }
38
- });
47
+ })
48
+ .strict(false);
@@ -569,7 +569,7 @@ export class ApiGatewayV1Api extends Construct {
569
569
  if (!value.userPoolIds) {
570
570
  throw new Error(`Missing "userPoolIds" for "${key}" authorizer`);
571
571
  }
572
- const userPools = value.userPoolIds.map((userPoolId) => cognito.UserPool.fromUserPoolId(this, `${key}-ImportedUserPool`, userPoolId));
572
+ const userPools = value.userPoolIds.map((userPoolId) => cognito.UserPool.fromUserPoolId(this, `${key}-${userPoolId}-ImportedUserPool`, userPoolId));
573
573
  this.authorizersData[key] = new apig.CognitoUserPoolsAuthorizer(this, key, {
574
574
  cognitoUserPools: userPools,
575
575
  authorizerName: value.name,
@@ -173,6 +173,7 @@ export declare class App extends cdk.App {
173
173
  */
174
174
  addDefaultFunctionLayers(layers: lambda.ILayerVersion[]): void;
175
175
  synth(options?: cdk.StageSynthesisOptions): cxapi.CloudAssembly;
176
+ /** @internal */
176
177
  finish(): Promise<void>;
177
178
  isRunningSSTTest(): boolean;
178
179
  getInputFilesFromEsbuildMetafile(file: string): Array<string>;
package/constructs/App.js CHANGED
@@ -227,6 +227,7 @@ export class App extends cdk.App {
227
227
  const cloudAssembly = super.synth(options);
228
228
  return cloudAssembly;
229
229
  }
230
+ /** @internal */
230
231
  async finish() {
231
232
  await useDeferredTasks().run();
232
233
  this.buildConstructsMetadata();
@@ -36,14 +36,14 @@ export class AstroSite extends SsrSite {
36
36
  super.validateBuildOutput();
37
37
  }
38
38
  createFunctionForRegional() {
39
- const { memorySize, timeout, environment, bind, cdk } = this.props;
39
+ const { runtime, memorySize, timeout, environment, bind, cdk } = this.props;
40
40
  // Create function
41
41
  const fn = new Function(this, `ServerFunction`, {
42
42
  description: "Server handler",
43
43
  handler: path.join(this.props.path, "dist", "server", "entry.handler"),
44
44
  bind,
45
45
  logRetention: "three_days",
46
- runtime: "nodejs16.x",
46
+ runtime,
47
47
  memorySize,
48
48
  timeout,
49
49
  nodejs: {
@@ -57,7 +57,7 @@ export class AstroSite extends SsrSite {
57
57
  return fn;
58
58
  }
59
59
  createFunctionForEdge() {
60
- const { timeout, memorySize, permissions, environment } = this.props;
60
+ const { runtime, timeout, memorySize, permissions, environment } = this.props;
61
61
  // Create a directory that we will use to create the bundled version
62
62
  // of the "core server build" along with our custom Lamba server handler.
63
63
  const outputPath = path.resolve(path.join(useProject().paths.artifacts, `AstroSiteFunction-${this.node.id}-${this.node.addr}`));
@@ -91,6 +91,7 @@ export class AstroSite extends SsrSite {
91
91
  scopeOverride: this,
92
92
  bundlePath: outputPath,
93
93
  handler: "entry.handler",
94
+ runtime,
94
95
  timeout,
95
96
  memorySize,
96
97
  permissions,
@@ -7,6 +7,7 @@ import { Permissions } from "./util/permission.js";
7
7
  export interface EdgeFunctionProps {
8
8
  bundlePath: string;
9
9
  handler: string;
10
+ runtime?: "nodejs14.x" | "nodejs16.x" | "nodejs18.x";
10
11
  timeout: number | Duration;
11
12
  memorySize: number | Size;
12
13
  permissions?: Permissions;
@@ -108,7 +108,7 @@ ${exports}
108
108
  return role;
109
109
  }
110
110
  createFunction(asset) {
111
- const { timeout, memorySize } = this.props;
111
+ const { runtime, timeout, memorySize } = this.props;
112
112
  const name = this.node.id;
113
113
  // Create a S3 bucket in us-east-1 to store Lambda code. Create
114
114
  // 1 bucket for all Edge functions.
@@ -122,7 +122,11 @@ ${exports}
122
122
  S3Bucket: asset.s3BucketName,
123
123
  S3Key: asset.s3ObjectKey,
124
124
  },
125
- Runtime: lambda.Runtime.NODEJS_18_X.name,
125
+ Runtime: runtime === "nodejs14.x"
126
+ ? lambda.Runtime.NODEJS_14_X.name
127
+ : runtime === "nodejs16.x"
128
+ ? lambda.Runtime.NODEJS_16_X.name
129
+ : lambda.Runtime.NODEJS_18_X.name,
126
130
  MemorySize: typeof memorySize === "string"
127
131
  ? toCdkSize(memorySize).toMebibytes()
128
132
  : memorySize,
@@ -59,7 +59,7 @@ export interface FunctionProps extends Omit<lambda.FunctionOptions, "functionNam
59
59
  * })
60
60
  *```
61
61
  */
62
- copyFiles?: FunctionBundleCopyFilesProps[];
62
+ copyFiles?: FunctionCopyFilesProps[];
63
63
  /**
64
64
  * Used to configure nodejs function properties
65
65
  */
@@ -112,15 +112,13 @@ export interface FunctionProps extends Omit<lambda.FunctionOptions, "functionNam
112
112
  */
113
113
  handler?: string;
114
114
  /**
115
- * Root directory of the project, typically where package.json is located. Set if using a monorepo with multiple subpackages
116
- *
117
- * @default Defaults to the same directory as sst.json
118
- *
115
+ * The runtime environment for the function.
116
+ * @default "nodejs16.x"
119
117
  * @example
120
118
  * ```js
121
119
  * new Function(stack, "Function", {
122
- * srcPath: "packages/backend",
123
120
  * handler: "function.handler",
121
+ * runtime: "nodejs18.x",
124
122
  * })
125
123
  *```
126
124
  */
@@ -348,7 +346,7 @@ export interface NodeJSProps {
348
346
  * @example
349
347
  * ```js
350
348
  * new Function(stack, "Function", {
351
- * bundle: {
349
+ * nodejs: {
352
350
  * loader: {
353
351
  * ".png": "file"
354
352
  * }
@@ -363,7 +361,7 @@ export interface NodeJSProps {
363
361
  * @example
364
362
  * ```js
365
363
  * new Function(stack, "Function", {
366
- * bundle: {
364
+ * nodejs: {
367
365
  * nodeModules: ["pg"]
368
366
  * }
369
367
  * })
@@ -376,7 +374,7 @@ export interface NodeJSProps {
376
374
  * @example
377
375
  * ```js
378
376
  * new Function(stack, "Function", {
379
- * bundle: {
377
+ * nodejs: {
380
378
  * banner: "console.log('Function starting')"
381
379
  * }
382
380
  * })
@@ -395,7 +393,7 @@ export interface NodeJSProps {
395
393
  * @example
396
394
  * ```js
397
395
  * new Function(stack, "Function", {
398
- * bundle: {
396
+ * nodejs: {
399
397
  * minify: false
400
398
  * }
401
399
  * })
@@ -403,14 +401,14 @@ export interface NodeJSProps {
403
401
  */
404
402
  minify?: boolean;
405
403
  /**
406
- * Configure bundle format
404
+ * Configure format
407
405
  *
408
406
  * @default "cjs"
409
407
  *
410
408
  * @example
411
409
  * ```js
412
410
  * new Function(stack, "Function", {
413
- * bundle: {
411
+ * nodejs: {
414
412
  * format: "esm"
415
413
  * }
416
414
  * })
@@ -425,8 +423,8 @@ export interface NodeJSProps {
425
423
  * @example
426
424
  * ```js
427
425
  * new Function(stack, "Function", {
428
- * bundle: {
429
- * sourcemap: true
426
+ * nodejs: {
427
+ * sourcemap: true
430
428
  * }
431
429
  * })
432
430
  * ```
@@ -445,7 +443,7 @@ export interface JavaProps {
445
443
  * @example
446
444
  * ```js
447
445
  * new Function(stack, "Function", {
448
- * bundle: {
446
+ * java: {
449
447
  * buildTask: "bundle"
450
448
  * }
451
449
  * })
@@ -460,7 +458,7 @@ export interface JavaProps {
460
458
  * @example
461
459
  * ```js
462
460
  * new Function(stack, "Function", {
463
- * bundle: {
461
+ * java: {
464
462
  * buildOutputDir: "output"
465
463
  * }
466
464
  * })
@@ -475,7 +473,7 @@ export interface JavaProps {
475
473
  * @example
476
474
  * ```js
477
475
  * new Function(stack, "Function", {
478
- * bundle: {
476
+ * java: {
479
477
  * experimentalUseProvidedRuntime: "provided.al2"
480
478
  * }
481
479
  * })
@@ -483,47 +481,17 @@ export interface JavaProps {
483
481
  */
484
482
  experimentalUseProvidedRuntime?: "provided" | "provided.al2";
485
483
  }
486
- export type FunctionBundleProp = FunctionBundlePythonProps | boolean;
487
- interface FunctionBundleBase {
488
- }
489
- /**
490
- * Used to configure Python bundling options
491
- */
492
- export interface FunctionBundlePythonProps extends FunctionBundleBase {
493
- /**
494
- * A list of commands to override the [default installing behavior](Function#bundle) for Python dependencies.
495
- *
496
- * Each string in the array is a command that'll be run. For example:
497
- *
498
- * @default "[]"
499
- *
500
- * @example
501
- * ```js
502
- * new Function(stack, "Function", {
503
- * bundle: {
504
- * installCommands: [
505
- * 'export VARNAME="my value"',
506
- * 'pip install --index-url https://domain.com/pypi/myprivatemodule/simple/ --extra-index-url https://pypi.org/simple -r requirements.txt .',
507
- * ]
508
- * }
509
- * })
510
- * ```
511
- */
512
- installCommands?: string[];
513
- }
514
484
  /**
515
485
  * Used to configure additional files to copy into the function bundle
516
486
  *
517
487
  * @example
518
488
  * ```js
519
489
  * new Function(stack, "Function", {
520
- * bundle: {
521
- * copyFiles: [{ from: "src/index.js" }]
522
- * }
490
+ * copyFiles: [{ from: "src/index.js" }]
523
491
  * })
524
492
  *```
525
493
  */
526
- export interface FunctionBundleCopyFilesProps {
494
+ export interface FunctionCopyFilesProps {
527
495
  /**
528
496
  * Source path relative to sst.json
529
497
  */
@@ -603,7 +571,6 @@ export declare class Function extends lambda.Function implements SSTConstruct {
603
571
  static normalizeMemorySize(memorySize?: number | Size): number;
604
572
  static normalizeDiskSize(diskSize?: number | Size): cdk.Size;
605
573
  static normalizeTimeout(timeout?: number | Duration): cdk.Duration;
606
- static normalizeSrcPath(srcPath: string): string;
607
574
  static handleImportedLayer(scope: Construct, layer: lambda.ILayerVersion): lambda.ILayerVersion;
608
575
  static isInlineDefinition(definition: any): definition is FunctionInlineDefinition;
609
576
  static fromDefinition(scope: Construct, id: string, definition: FunctionDefinition, inheritedProps?: FunctionProps, inheritErrorMessage?: string): Function;
@@ -366,9 +366,6 @@ export class Function extends lambda.Function {
366
366
  }
367
367
  return cdk.Duration.seconds(timeout || 10);
368
368
  }
369
- static normalizeSrcPath(srcPath) {
370
- return srcPath.replace(/\/+$/, "");
371
- }
372
369
  static handleImportedLayer(scope, layer) {
373
370
  const layerStack = Stack.of(layer);
374
371
  const currentStack = Stack.of(scope);
@@ -37,11 +37,12 @@ export class NextjsSite extends SsrSite {
37
37
  };
38
38
  }
39
39
  createFunctionForRegional() {
40
- const { timeout, memorySize, permissions, environment, cdk } = this.props;
40
+ const { runtime, timeout, memorySize, permissions, environment, cdk } = this.props;
41
41
  const ssrFn = new SsrFunction(this, `ServerFunction`, {
42
42
  description: "Server handler for Next.js",
43
43
  bundlePath: path.join(this.props.path, ".open-next", "server-function"),
44
44
  handler: "index.handler",
45
+ runtime,
45
46
  timeout,
46
47
  memorySize,
47
48
  permissions,
@@ -112,7 +112,7 @@ export class RemixSite extends SsrSite {
112
112
  return outputPath;
113
113
  }
114
114
  createFunctionForRegional() {
115
- const { timeout, memorySize, environment, cdk } = this.props;
115
+ const { runtime, timeout, memorySize, environment, cdk } = this.props;
116
116
  const bundlePath = this.createServerLambdaBundle("regional-server.js");
117
117
  return new lambda.Function(this, `ServerFunction`, {
118
118
  description: "Server handler for Remix",
@@ -122,7 +122,11 @@ export class RemixSite extends SsrSite {
122
122
  },
123
123
  logRetention: logs.RetentionDays.THREE_DAYS,
124
124
  code: lambda.Code.fromAsset(bundlePath),
125
- runtime: lambda.Runtime.NODEJS_16_X,
125
+ runtime: runtime === "nodejs14.x"
126
+ ? lambda.Runtime.NODEJS_14_X
127
+ : runtime === "nodejs16.x"
128
+ ? lambda.Runtime.NODEJS_16_X
129
+ : lambda.Runtime.NODEJS_18_X,
126
130
  memorySize: typeof memorySize === "string"
127
131
  ? toCdkSize(memorySize).toMebibytes()
128
132
  : memorySize,
@@ -134,13 +138,14 @@ export class RemixSite extends SsrSite {
134
138
  });
135
139
  }
136
140
  createFunctionForEdge() {
137
- const { timeout, memorySize, permissions, environment } = this.props;
141
+ const { runtime, timeout, memorySize, permissions, environment } = this.props;
138
142
  const bundlePath = this.createServerLambdaBundle("edge-server.js");
139
143
  return new EdgeFunction(this, `Server`, {
140
144
  scopeOverride: this,
141
145
  format: "cjs",
142
146
  bundlePath,
143
147
  handler: "server.handler",
148
+ runtime,
144
149
  timeout,
145
150
  memorySize,
146
151
  permissions,
@@ -26,7 +26,7 @@ export class SolidStartSite extends SsrSite {
26
26
  };
27
27
  }
28
28
  createFunctionForRegional() {
29
- const { timeout, memorySize, environment, cdk } = this.props;
29
+ const { runtime, timeout, memorySize, environment, cdk } = this.props;
30
30
  // Bundle code
31
31
  const handler = path.join(this.props.path, "dist", "server", "index.handler");
32
32
  // Create function
@@ -34,7 +34,7 @@ export class SolidStartSite extends SsrSite {
34
34
  description: "Server handler",
35
35
  handler,
36
36
  logRetention: "three_days",
37
- runtime: "nodejs16.x",
37
+ runtime,
38
38
  memorySize,
39
39
  timeout,
40
40
  nodejs: {
@@ -48,7 +48,7 @@ export class SolidStartSite extends SsrSite {
48
48
  return fn;
49
49
  }
50
50
  createFunctionForEdge() {
51
- const { timeout, memorySize, permissions, environment } = this.props;
51
+ const { runtime, timeout, memorySize, permissions, environment } = this.props;
52
52
  // Create a directory that we will use to create the bundled version
53
53
  // of the "core server build" along with our custom Lamba server handler.
54
54
  const outputPath = path.resolve(path.join(useProject().paths.artifacts, `SolidStartSiteFunction-${this.node.id}-${this.node.addr}`));
@@ -83,6 +83,7 @@ export class SolidStartSite extends SsrSite {
83
83
  scopeOverride: this,
84
84
  bundlePath: outputPath,
85
85
  handler: "server.handler",
86
+ runtime,
86
87
  timeout,
87
88
  memorySize,
88
89
  permissions,
@@ -7,6 +7,7 @@ import { FunctionOptions } from "aws-cdk-lib/aws-lambda";
7
7
  export interface SsrFunctionProps extends Omit<FunctionOptions, "memorySize" | "timeout" | "runtime"> {
8
8
  bundlePath: string;
9
9
  handler: string;
10
+ runtime?: "nodejs14.x" | "nodejs16.x" | "nodejs18.x";
10
11
  timeout: number | Duration;
11
12
  memorySize: number | Size;
12
13
  permissions?: Permissions;
@@ -30,7 +30,7 @@ export class SsrFunction extends Construct {
30
30
  attachPermissionsToRole(this.function.role, permissions);
31
31
  }
32
32
  createFunction() {
33
- const { timeout, memorySize, handler, bundlePath } = this.props;
33
+ const { runtime, timeout, memorySize, handler, bundlePath } = this.props;
34
34
  // Note: cannot point the bundlePath to the `.open-next/server-function`
35
35
  // b/c the folder contains node_modules. And pnpm node_modules
36
36
  // contains symlinks. CDK cannot zip symlinks correctly.
@@ -57,7 +57,11 @@ export class SsrFunction extends Construct {
57
57
  handler,
58
58
  logRetention: logs.RetentionDays.THREE_DAYS,
59
59
  code: lambda.Code.fromBucket(asset.bucket, asset.s3ObjectKey),
60
- runtime: lambda.Runtime.NODEJS_18_X,
60
+ runtime: runtime === "nodejs14.x"
61
+ ? lambda.Runtime.NODEJS_14_X
62
+ : runtime === "nodejs16.x"
63
+ ? lambda.Runtime.NODEJS_16_X
64
+ : lambda.Runtime.NODEJS_18_X,
61
65
  architecture: lambda.Architecture.ARM_64,
62
66
  memorySize: typeof memorySize === "string"
63
67
  ? toCdkSize(memorySize).toMebibytes()
@@ -94,6 +94,15 @@ export interface SsrSiteProps {
94
94
  * ```
95
95
  */
96
96
  memorySize?: number | Size;
97
+ /**
98
+ * The runtime environment for the SSR function.
99
+ * @default nodejs18.x
100
+ * @example
101
+ * ```js
102
+ * runtime: "nodejs16.x",
103
+ * ```
104
+ */
105
+ runtime?: "nodejs14.x" | "nodejs16.x" | "nodejs18.x";
97
106
  /**
98
107
  * Attaches the given list of permissions to the SSR function. Configuring this property is equivalent to calling `attachPermissions()` after the site is created.
99
108
  * @example
@@ -57,6 +57,7 @@ export class SsrSite extends Construct {
57
57
  this.props = {
58
58
  path: ".",
59
59
  waitForInvalidation: false,
60
+ runtime: "nodejs18.x",
60
61
  timeout: "10 seconds",
61
62
  memorySize: "1024 MB",
62
63
  ...props,
@@ -411,11 +412,10 @@ export class SsrSite extends Construct {
411
412
  /////////////////////
412
413
  validateCloudFrontDistributionSettings() {
413
414
  const { cdk } = this.props;
414
- const cfDistributionProps = cdk?.distribution || {};
415
- if (cfDistributionProps.certificate) {
415
+ if (cdk?.distribution?.certificate) {
416
416
  throw new Error(`Do not configure the "cfDistribution.certificate". Use the "customDomain" to configure the domain certificate.`);
417
417
  }
418
- if (cfDistributionProps.domainNames) {
418
+ if (cdk?.distribution?.domainNames) {
419
419
  throw new Error(`Do not configure the "cfDistribution.domainNames". Use the "customDomain" to configure the domain name.`);
420
420
  }
421
421
  }
@@ -320,6 +320,8 @@ export declare class StaticSite extends Construct implements SSTConstruct {
320
320
  private bundleFilenamesAsset;
321
321
  private createS3Bucket;
322
322
  private createS3Deployment;
323
+ private validateCloudFrontDistributionSettings;
324
+ protected buildDistributionDomainNames(): string[];
323
325
  private createCfDistribution;
324
326
  private createCloudFrontInvalidation;
325
327
  protected validateCustomDomainSettings(): void;
@@ -87,6 +87,7 @@ export class StaticSite extends Construct {
87
87
  // Create S3 Deployment
88
88
  const s3deployCR = this.createS3Deployment(cliLayer, assets, filenamesAsset);
89
89
  // Create CloudFront
90
+ this.validateCloudFrontDistributionSettings();
90
91
  this.distribution = this.createCfDistribution();
91
92
  this.distribution.node.addDependency(s3deployCR);
92
93
  // Invalidate CloudFront
@@ -367,21 +368,20 @@ interface ImportMeta {
367
368
  /////////////////////
368
369
  // CloudFront Distribution
369
370
  /////////////////////
370
- createCfDistribution() {
371
- const { cdk, customDomain } = this.props;
372
- const indexPage = this.props.indexPage || "index.html";
373
- const errorPage = this.props.errorPage;
374
- // Validate input
371
+ validateCloudFrontDistributionSettings() {
372
+ const { cdk, errorPage } = this.props;
375
373
  if (cdk?.distribution?.certificate) {
376
- throw new Error(`Do not configure the "cfDistribution.certificate". Use the "customDomain" to configure the StaticSite domain certificate.`);
374
+ throw new Error(`Do not configure the "cfDistribution.certificate". Use the "customDomain" to configure the domain certificate.`);
377
375
  }
378
376
  if (cdk?.distribution?.domainNames) {
379
- throw new Error(`Do not configure the "cfDistribution.domainNames". Use the "customDomain" to configure the StaticSite domain.`);
377
+ throw new Error(`Do not configure the "cfDistribution.domainNames". Use the "customDomain" to configure the domain name.`);
380
378
  }
381
379
  if (errorPage && cdk?.distribution?.errorResponses) {
382
380
  throw new Error(`Cannot configure the "cfDistribution.errorResponses" when "errorPage" is passed in. Use one or the other to configure the behavior for error pages.`);
383
381
  }
384
- // Build domainNames
382
+ }
383
+ buildDistributionDomainNames() {
384
+ const { customDomain } = this.props;
385
385
  const domainNames = [];
386
386
  if (!customDomain) {
387
387
  // no domain
@@ -397,18 +397,21 @@ interface ImportMeta {
397
397
  domainNames.push(...customDomain.alternateNames);
398
398
  }
399
399
  }
400
- // Build errorResponses
401
- const errorResponses = errorPage === "redirect_to_index_page" || errorPage === undefined
402
- ? buildErrorResponsesForRedirectToIndex(indexPage)
403
- : buildErrorResponsesFor404ErrorPage(errorPage);
400
+ return domainNames;
401
+ }
402
+ createCfDistribution() {
403
+ const { cdk, errorPage } = this.props;
404
+ const indexPage = this.props.indexPage || "index.html";
404
405
  // Create CloudFront distribution
405
406
  return new cloudfront.Distribution(this, "Distribution", {
406
407
  // these values can be overwritten by cfDistributionProps
407
408
  defaultRootObject: indexPage,
408
- errorResponses,
409
+ errorResponses: !errorPage || errorPage === "redirect_to_index_page"
410
+ ? buildErrorResponsesForRedirectToIndex(indexPage)
411
+ : buildErrorResponsesFor404ErrorPage(errorPage),
409
412
  ...cdk?.distribution,
410
413
  // these values can NOT be overwritten by cfDistributionProps
411
- domainNames,
414
+ domainNames: this.buildDistributionDomainNames(),
412
415
  certificate: this.certificate,
413
416
  defaultBehavior: {
414
417
  origin: new cfOrigins.S3Origin(this.bucket),
package/iot.js CHANGED
@@ -32,6 +32,9 @@ export const useIOT = Context.memo(async () => {
32
32
  const PREFIX = `/sst/${project.config.name}/${project.config.stage}`;
33
33
  device.subscribe(`${PREFIX}/events`);
34
34
  const fragments = new Map();
35
+ device.on("connect", () => {
36
+ Logger.debug("IoT connected");
37
+ });
35
38
  device.on("error", (err) => {
36
39
  Logger.debug("IoT error", err);
37
40
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sst",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "bin": {
5
5
  "sst": "cli/sst.js"
6
6
  },
package/sst.mjs CHANGED
@@ -793,70 +793,6 @@ var init_site_env = __esm({
793
793
  }
794
794
  });
795
795
 
796
- // src/bus.ts
797
- var bus_exports = {};
798
- __export(bus_exports, {
799
- useBus: () => useBus
800
- });
801
- import crypto from "crypto";
802
- var DO_NOT_LOG, useBus;
803
- var init_bus = __esm({
804
- "src/bus.ts"() {
805
- "use strict";
806
- init_context();
807
- init_logger();
808
- DO_NOT_LOG = /* @__PURE__ */ new Set(["stacks.metadata"]);
809
- useBus = Context.memo(() => {
810
- const subscriptions = {};
811
- function subscribers(type) {
812
- let arr = subscriptions[type];
813
- if (!arr) {
814
- arr = [];
815
- subscriptions[type] = arr;
816
- }
817
- return arr;
818
- }
819
- const sourceID = crypto.randomBytes(16).toString("hex");
820
- const result = {
821
- sourceID,
822
- publish(type, properties) {
823
- const payload = {
824
- type,
825
- properties,
826
- sourceID
827
- };
828
- if (!DO_NOT_LOG.has(type)) {
829
- Logger.debug(`Publishing event ${JSON.stringify(payload)}`);
830
- }
831
- for (const sub of subscribers(type))
832
- sub.cb(payload);
833
- },
834
- unsubscribe(sub) {
835
- const arr = subscribers(sub.type);
836
- const index = arr.indexOf(sub);
837
- if (index < 0)
838
- return;
839
- arr.splice(index, 1);
840
- },
841
- subscribe(type, cb) {
842
- const sub = {
843
- type,
844
- cb
845
- };
846
- subscribers(type).push(sub);
847
- return sub;
848
- },
849
- forward(..._types) {
850
- return (type, cb) => {
851
- return this.subscribe(type, cb);
852
- };
853
- }
854
- };
855
- return result;
856
- });
857
- }
858
- });
859
-
860
796
  // ../../node_modules/.pnpm/tslib@2.4.1/node_modules/tslib/tslib.es6.js
861
797
  var tslib_es6_exports = {};
862
798
  __export(tslib_es6_exports, {
@@ -1614,6 +1550,70 @@ var init_credentials = __esm({
1614
1550
  }
1615
1551
  });
1616
1552
 
1553
+ // src/bus.ts
1554
+ var bus_exports = {};
1555
+ __export(bus_exports, {
1556
+ useBus: () => useBus
1557
+ });
1558
+ import crypto from "crypto";
1559
+ var DO_NOT_LOG, useBus;
1560
+ var init_bus = __esm({
1561
+ "src/bus.ts"() {
1562
+ "use strict";
1563
+ init_context();
1564
+ init_logger();
1565
+ DO_NOT_LOG = /* @__PURE__ */ new Set(["stacks.metadata"]);
1566
+ useBus = Context.memo(() => {
1567
+ const subscriptions = {};
1568
+ function subscribers(type) {
1569
+ let arr = subscriptions[type];
1570
+ if (!arr) {
1571
+ arr = [];
1572
+ subscriptions[type] = arr;
1573
+ }
1574
+ return arr;
1575
+ }
1576
+ const sourceID = crypto.randomBytes(16).toString("hex");
1577
+ const result = {
1578
+ sourceID,
1579
+ publish(type, properties) {
1580
+ const payload = {
1581
+ type,
1582
+ properties,
1583
+ sourceID
1584
+ };
1585
+ if (!DO_NOT_LOG.has(type)) {
1586
+ Logger.debug(`Publishing event ${JSON.stringify(payload)}`);
1587
+ }
1588
+ for (const sub of subscribers(type))
1589
+ sub.cb(payload);
1590
+ },
1591
+ unsubscribe(sub) {
1592
+ const arr = subscribers(sub.type);
1593
+ const index = arr.indexOf(sub);
1594
+ if (index < 0)
1595
+ return;
1596
+ arr.splice(index, 1);
1597
+ },
1598
+ subscribe(type, cb) {
1599
+ const sub = {
1600
+ type,
1601
+ cb
1602
+ };
1603
+ subscribers(type).push(sub);
1604
+ return sub;
1605
+ },
1606
+ forward(..._types) {
1607
+ return (type, cb) => {
1608
+ return this.subscribe(type, cb);
1609
+ };
1610
+ }
1611
+ };
1612
+ return result;
1613
+ });
1614
+ }
1615
+ });
1616
+
1617
1617
  // src/cli/local/router.ts
1618
1618
  import * as trpc from "@trpc/server";
1619
1619
  var router2;
@@ -2475,6 +2475,9 @@ var init_iot = __esm({
2475
2475
  const PREFIX2 = `/sst/${project.config.name}/${project.config.stage}`;
2476
2476
  device.subscribe(`${PREFIX2}/events`);
2477
2477
  const fragments = /* @__PURE__ */ new Map();
2478
+ device.on("connect", () => {
2479
+ Logger.debug("IoT connected");
2480
+ });
2478
2481
  device.on("error", (err) => {
2479
2482
  Logger.debug("IoT error", err);
2480
2483
  });
@@ -2580,9 +2583,17 @@ async function loadCDKStatus() {
2580
2583
  StackName: "CDKToolkit"
2581
2584
  })
2582
2585
  );
2583
- if (stacks && stacks.length > 0 && ["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(stacks[0].StackStatus)) {
2584
- return true;
2586
+ if (!stacks || stacks.length === 0)
2587
+ return false;
2588
+ if (!["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(stacks[0].StackStatus)) {
2589
+ return false;
2585
2590
  }
2591
+ const output = stacks[0].Outputs?.find(
2592
+ (o) => o.OutputKey === "BootstrapVersion"
2593
+ );
2594
+ if (!output || parseInt(output.OutputValue) < 14)
2595
+ return false;
2596
+ return true;
2586
2597
  } catch (e) {
2587
2598
  if (e.name === "ValidationError" && e.message === "Stack with id CDKToolkit does not exist") {
2588
2599
  return false;
@@ -6220,14 +6231,11 @@ init_logger();
6220
6231
  import dotenv2 from "dotenv";
6221
6232
 
6222
6233
  // src/cli/commands/env.ts
6234
+ init_error();
6223
6235
  var env = (program2) => program2.command(
6224
- "env <command>",
6236
+ "env <command..>",
6225
6237
  "Load environment variables and start your frontend",
6226
- (yargs2) => yargs2.positional("command", {
6227
- type: "string",
6228
- describe: "The command to start your frontend",
6229
- demandOption: true
6230
- }).example(
6238
+ (yargs2) => yargs2.array("command").example(
6231
6239
  `sst env "next dev"`,
6232
6240
  "Start Next.js with your environment variables"
6233
6241
  ).example(
@@ -6239,6 +6247,8 @@ var env = (program2) => program2.command(
6239
6247
  const fs18 = await import("fs/promises");
6240
6248
  const { SiteEnv } = await Promise.resolve().then(() => (init_site_env(), site_env_exports));
6241
6249
  const { spawnSync } = await import("child_process");
6250
+ const { useAWSCredentials: useAWSCredentials2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
6251
+ const { useProject: useProject2 } = await Promise.resolve().then(() => (init_project(), project_exports));
6242
6252
  let spinner;
6243
6253
  while (true) {
6244
6254
  const exists = await fs18.access(SiteEnv.valuesFile()).then(() => true).catch(() => false);
@@ -6252,10 +6262,21 @@ var env = (program2) => program2.command(
6252
6262
  spinner?.succeed();
6253
6263
  const sites = await SiteEnv.values();
6254
6264
  const env2 = sites[process.cwd()] || {};
6255
- const result = spawnSync(args.command, {
6265
+ const project = useProject2();
6266
+ const credentials = await useAWSCredentials2();
6267
+ const joined = args.command?.join(" ");
6268
+ if (!joined)
6269
+ throw new VisibleError(
6270
+ "Command is required, e.g. sst env vite dev"
6271
+ );
6272
+ const result = spawnSync(joined, {
6256
6273
  env: {
6257
6274
  ...process.env,
6258
- ...env2
6275
+ ...env2,
6276
+ AWS_ACCESS_KEY_ID: credentials.accessKeyId,
6277
+ AWS_SECRET_ACCESS_KEY: credentials.secretAccessKey,
6278
+ AWS_SESSION_TOKEN: credentials.sessionToken,
6279
+ AWS_REGION: project.config.region
6259
6280
  },
6260
6281
  stdio: "inherit",
6261
6282
  shell: process.env.SHELL || true
@@ -6264,7 +6285,7 @@ var env = (program2) => program2.command(
6264
6285
  break;
6265
6286
  }
6266
6287
  }
6267
- );
6288
+ ).strict(false);
6268
6289
 
6269
6290
  // src/cli/commands/dev.tsx
6270
6291
  init_colors();
@@ -6570,14 +6591,11 @@ async function promptChangeMode() {
6570
6591
  }
6571
6592
 
6572
6593
  // src/cli/commands/bind.ts
6594
+ init_error();
6573
6595
  var bind = (program2) => program2.command(
6574
- "bind <command>",
6596
+ "bind <command..>",
6575
6597
  "Bind your app's resources to a command",
6576
- (yargs2) => yargs2.positional("command", {
6577
- type: "string",
6578
- describe: "The command to bind to",
6579
- demandOption: true
6580
- }).example(`sst bind "vitest run"`, "Bind your resources to your tests").example(
6598
+ (yargs2) => yargs2.array("command").example(`sst bind "vitest run"`, "Bind your resources to your tests").example(
6581
6599
  `sst bind "tsx scripts/myscript.ts"`,
6582
6600
  "Bind your resources to a script"
6583
6601
  ),
@@ -6589,7 +6607,10 @@ var bind = (program2) => program2.command(
6589
6607
  const env2 = await Config2.env();
6590
6608
  const project = useProject2();
6591
6609
  const credentials = await useAWSCredentials2();
6592
- const result = spawnSync(args.command, {
6610
+ const joined = args.command?.join(" ");
6611
+ if (!joined)
6612
+ throw new VisibleError("Command is required, e.g. sst bind env");
6613
+ const result = spawnSync(joined, {
6593
6614
  env: {
6594
6615
  ...process.env,
6595
6616
  ...env2,
@@ -6603,7 +6624,7 @@ var bind = (program2) => program2.command(
6603
6624
  });
6604
6625
  process.exitCode = result.status || void 0;
6605
6626
  }
6606
- );
6627
+ ).strict(false);
6607
6628
 
6608
6629
  // src/cli/commands/build.ts
6609
6630
  var build = (program2) => program2.command(