sst 2.0.0-rc.41 → 2.0.0-rc.43

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
@@ -31,6 +31,7 @@ export async function initBootstrap() {
31
31
  loadCDKStatus(),
32
32
  loadSSTStatus(),
33
33
  ]);
34
+ Logger.debug("Loaded bootstrap status");
34
35
  const needToBootstrapCDK = !cdkStatus;
35
36
  const needToBootstrapSST = !sstStatus || sstStatus.version !== LATEST_VERSION;
36
37
  if (needToBootstrapCDK || needToBootstrapSST) {
@@ -56,16 +57,15 @@ async function loadCDKStatus() {
56
57
  const { Stacks: stacks } = await client.send(new DescribeStacksCommand({
57
58
  StackName: "CDKToolkit",
58
59
  }));
59
- if (stacks && stacks.length > 0 && [
60
- "CREATE_COMPLETE",
61
- "UPDATE_COMPLETE",
62
- ].includes(stacks[0].StackStatus)) {
60
+ if (stacks &&
61
+ stacks.length > 0 &&
62
+ ["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(stacks[0].StackStatus)) {
63
63
  return true;
64
64
  }
65
65
  }
66
66
  catch (e) {
67
- if (e.name === "ValidationError"
68
- && e.message === "Stack with id CDKToolkit does not exist") {
67
+ if (e.name === "ValidationError" &&
68
+ e.message === "Stack with id CDKToolkit does not exist") {
69
69
  return false;
70
70
  }
71
71
  else {
package/cli/colors.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import chalk from "chalk";
2
+ declare let last: "line" | "gap";
2
3
  export declare const Colors: {
3
4
  line: (message?: any, ...optionalParams: any[]) => void;
5
+ mode(input: typeof last): void;
4
6
  gap(): void;
5
7
  hex: (color: string) => chalk.Chalk;
6
8
  primary: chalk.Chalk;
@@ -23,3 +25,4 @@ export declare const Colors: {
23
25
  };
24
26
  prefix: string;
25
27
  };
28
+ export {};
package/cli/colors.js CHANGED
@@ -5,6 +5,9 @@ export const Colors = {
5
5
  last = "line";
6
6
  console.log(message, ...optionalParams);
7
7
  },
8
+ mode(input) {
9
+ last = input;
10
+ },
8
11
  gap() {
9
12
  if (last === "line") {
10
13
  last = "gap";
@@ -10,4 +10,6 @@ export declare const dev: (program: Program) => import("yargs").Argv<{
10
10
  verbose: boolean | undefined;
11
11
  } & {
12
12
  role: string | undefined;
13
+ } & {
14
+ "increase-timeout": boolean | undefined;
13
15
  }>;
@@ -1,7 +1,10 @@
1
1
  import chalk from "chalk";
2
2
  import { Colors } from "../colors.js";
3
3
  import { printHeader } from "../ui/header.js";
4
- export const dev = (program) => program.command(["dev", "start"], "Work on your app locally", (yargs) => yargs, async (args) => {
4
+ export const dev = (program) => program.command(["dev", "start"], "Work on your app locally", (yargs) => yargs.option("increase-timeout", {
5
+ type: "boolean",
6
+ description: "Increase function timeout",
7
+ }), async (args) => {
5
8
  const { useRuntimeWorkers } = await import("../../runtime/workers.js");
6
9
  const { useIOTBridge } = await import("../../runtime/iot.js");
7
10
  const { useRuntimeServer } = await import("../../runtime/server.js");
@@ -62,19 +65,16 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
62
65
  Colors.line(prefix(evt.properties.requestID), Colors.dim(("+" + (Date.now() - started) + "ms").padEnd(7)), Colors.dim(line));
63
66
  }
64
67
  });
65
- /*
66
68
  bus.subscribe("function.build.success", async (evt) => {
67
- console.log(
68
- bold(gray(`Built `)),
69
- useFunctions().fromID(evt.properties.functionID).handler!
70
- );
69
+ Colors.line(Colors.dim(Colors.prefix, "Built", useFunctions().fromID(evt.properties.functionID).handler));
71
70
  });
72
- */
73
71
  bus.subscribe("function.build.failed", async (evt) => {
74
- Colors.line(Colors.danger(Colors.prefix), useFunctions().fromID(evt.properties.functionID).handler);
72
+ Colors.gap();
73
+ Colors.line(Colors.danger("✖ "), "Build failed", useFunctions().fromID(evt.properties.functionID).handler);
75
74
  for (const line of evt.properties.errors) {
76
- Colors.line(Colors.danger(Colors.prefix), line);
75
+ Colors.line(" ", line);
77
76
  }
77
+ Colors.gap();
78
78
  });
79
79
  bus.subscribe("function.success", async (evt) => {
80
80
  // stdout logs sometimes come in after
@@ -88,7 +88,7 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
88
88
  setTimeout(() => {
89
89
  Colors.line(prefix(evt.properties.requestID), Colors.danger.bold("Error:"), Colors.danger.bold(evt.properties.errorMessage));
90
90
  for (const line of evt.properties.trace || []) {
91
- Colors.line(prefix(evt.properties.requestID), `${dim(line)}`);
91
+ Colors.line(" ", `${dim(line)}`);
92
92
  }
93
93
  end(evt.properties.requestID);
94
94
  }, 100);
@@ -129,9 +129,11 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
129
129
  if (!lastDeployed) {
130
130
  spinner.stop();
131
131
  spinner.clear();
132
+ Colors.mode("gap");
132
133
  }
133
134
  else {
134
135
  spinner.succeed(` Stacks built!`);
136
+ Colors.mode("gap");
135
137
  }
136
138
  pending = assembly;
137
139
  if (lastDeployed)
@@ -6,6 +6,7 @@ import { exec } from "child_process";
6
6
  import { promisify } from "util";
7
7
  const execAsync = promisify(exec);
8
8
  import path from "path";
9
+ import { Colors } from "../../colors.js";
9
10
  export const usePothosBuilder = Context.memo(() => {
10
11
  let routes = [];
11
12
  const bus = useBus();
@@ -17,11 +18,14 @@ export const usePothosBuilder = Context.memo(() => {
17
18
  await fs.writeFile(route.output, schema);
18
19
  // bus.publish("pothos.extracted", { file: route.output });
19
20
  await Promise.all(route.commands.map((cmd) => execAsync(cmd)));
20
- console.log("Done building pothos schema");
21
+ Colors.line(Colors.prefix);
22
+ Colors.line(Colors.success(`✔`), " Extracted pothos schema");
21
23
  }
22
24
  catch (ex) {
23
- console.error("Failed to extract schema from pothos");
24
- console.error(ex);
25
+ Colors.line(Colors.danger(`✖`), " Failed to extract schema from pothos:");
26
+ for (let line of ex.message.split("\n")) {
27
+ console.log(` `, line);
28
+ }
25
29
  }
26
30
  }
27
31
  bus.subscribe("file.changed", async (evt) => {
@@ -0,0 +1,15 @@
1
+ /// <reference types="yargs" />
2
+ import type { Program } from "../program.js";
3
+ export declare const updateMod: (program: Program) => import("yargs").Argv<{
4
+ stage: string | undefined;
5
+ } & {
6
+ profile: string | undefined;
7
+ } & {
8
+ region: string | undefined;
9
+ } & {
10
+ verbose: boolean | undefined;
11
+ } & {
12
+ role: string | undefined;
13
+ } & {
14
+ transform: string;
15
+ }>;
@@ -0,0 +1,53 @@
1
+ export const updateMod = (program) => program.command("update-mod <transform>", "Transforms your SST app", (yargs) => yargs.positional("transform", {
2
+ type: "string",
3
+ describe: "Name of the transform",
4
+ demandOption: true,
5
+ }), async (args) => {
6
+ const { green } = await import("colorette");
7
+ if (args.transform === "resource-binding-secrets") {
8
+ await handleSecretsMigration();
9
+ }
10
+ console.log(green(`Update transform "${args.transform}" has been applied successfully!`));
11
+ });
12
+ async function handleSecretsMigration() {
13
+ const { useProject } = await import("../../project.js");
14
+ const { useAWSClient } = await import("../../credentials.js");
15
+ const { SSMClient, GetParametersByPathCommand, PutParameterCommand } = await import("@aws-sdk/client-ssm");
16
+ const project = useProject();
17
+ const { name: app, stage } = project.config;
18
+ const ssm = useAWSClient(SSMClient);
19
+ async function* getAllPrametersByPath(prefix) {
20
+ let token;
21
+ while (true) {
22
+ const results = await ssm.send(new GetParametersByPathCommand({
23
+ Path: prefix,
24
+ WithDecryption: true,
25
+ Recursive: true,
26
+ NextToken: token,
27
+ }));
28
+ yield* results.Parameters || [];
29
+ if (!results.NextToken)
30
+ break;
31
+ token = results.NextToken;
32
+ }
33
+ }
34
+ async function migrateSecretsSSMPath(stage) {
35
+ const oldPrefix = `/sst/${app}/${stage}/secrets/`;
36
+ const newPrefix = `/sst/${app}/${stage}/Secret/`;
37
+ for await (const secret of getAllPrametersByPath(oldPrefix)) {
38
+ const name = secret.Name.split("/")[5];
39
+ // Do not migrate SST Auth secrets b/c they are no longer secrets in v1.16.
40
+ if (name === "SST_AUTH_PRIVATE" || name === "SST_AUTH_PUBLIC") {
41
+ continue;
42
+ }
43
+ await ssm.send(new PutParameterCommand({
44
+ Name: `${newPrefix}${name}/value`,
45
+ Value: secret.Value,
46
+ Type: secret.Type,
47
+ Overwrite: true,
48
+ }));
49
+ }
50
+ }
51
+ await migrateSecretsSSMPath(stage);
52
+ await migrateSecretsSSMPath(".fallback");
53
+ }
package/cli/program.js CHANGED
@@ -42,7 +42,7 @@ export const program = yargs(hideBin(process.argv))
42
42
  .fail((_, error, yargs) => {
43
43
  if (!error) {
44
44
  yargs.showHelp();
45
- return;
45
+ process.exit(1);
46
46
  }
47
47
  throw error;
48
48
  });
package/cli/spinner.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Context } from "../context/context.js";
2
2
  import ora from "ora";
3
+ import { Colors } from "./colors.js";
3
4
  export const useSpinners = Context.memo(() => {
4
5
  const spinners = [];
5
6
  return spinners;
@@ -8,5 +9,6 @@ export function createSpinner(options) {
8
9
  const spinners = useSpinners();
9
10
  const next = ora(options);
10
11
  spinners.push(next);
12
+ Colors.mode("line");
11
13
  return next;
12
14
  }
package/cli/sst.js CHANGED
@@ -15,6 +15,7 @@ import { remove } from "./commands/remove.js";
15
15
  import { consoleCommand } from "./commands/console.js";
16
16
  import { secrets } from "./commands/secrets/secrets.js";
17
17
  import { update } from "./commands/update.js";
18
+ import { updateMod } from "./commands/update-mod.js";
18
19
  import { diff } from "./commands/diff.js";
19
20
  dev(program);
20
21
  deploy(program);
@@ -24,8 +25,11 @@ env(program);
24
25
  secrets(program);
25
26
  remove(program);
26
27
  update(program);
28
+ updateMod(program);
27
29
  consoleCommand(program);
28
30
  diff(program);
31
+ // @ts-expect-error
32
+ process.setSourceMapsEnabled(true);
29
33
  process.removeAllListeners("uncaughtException");
30
34
  process.on("uncaughtException", (err) => {
31
35
  Logger.debug(err);
package/cli/ui/deploy.js CHANGED
@@ -7,31 +7,17 @@ import { Colors } from "../colors.js";
7
7
  // @ts-ignore
8
8
  const { default: Spinner } = inkSpinner;
9
9
  export const DeploymentUI = (props) => {
10
- const [stacks, setStacks] = useState(Object.fromEntries(props.stacks.map((s) => [s, ""])));
11
10
  const [resources, setResources] = useState({});
12
- const [resources2, setResources2] = useState({});
13
11
  useEffect(() => {
14
12
  Colors.gap();
15
13
  Colors.line(`${Colors.primary(`➜`)} ${Colors.bold(`Deploying...`)}`);
16
14
  Colors.gap();
17
15
  const bus = useBus();
18
- const update = bus.subscribe("stack.updated", (payload) => {
19
- setStacks((prev) => ({
20
- ...prev,
21
- [payload.properties.stackID]: "",
22
- }));
23
- });
24
- const status = bus.subscribe("stack.status", (payload) => {
25
- setStacks((prev) => ({
26
- ...prev,
27
- [payload.properties.stackID]: payload.properties.status,
28
- }));
29
- });
30
16
  const event = bus.subscribe("stack.event", (payload) => {
31
17
  const { event } = payload.properties;
32
18
  if (event.ResourceType === "AWS::CloudFormation::Stack")
33
19
  return;
34
- setResources2((previous) => {
20
+ setResources((previous) => {
35
21
  if (Stacks.isFinal(event.ResourceStatus)) {
36
22
  Colors.line(Colors.warning(Colors.prefix), Colors.dim(`${event.StackName} ${event.ResourceType} ${event.LogicalResourceId}`), Stacks.isFailed(event.ResourceStatus)
37
23
  ? Colors.danger(event.ResourceStatus)
@@ -47,8 +33,6 @@ export const DeploymentUI = (props) => {
47
33
  });
48
34
  return () => {
49
35
  bus.unsubscribe(event);
50
- bus.unsubscribe(update);
51
- bus.unsubscribe(status);
52
36
  };
53
37
  }, []);
54
38
  function color(status) {
@@ -59,7 +43,7 @@ export const DeploymentUI = (props) => {
59
43
  return "yellow";
60
44
  }
61
45
  return (React.createElement(Box, { flexDirection: "column" },
62
- Object.entries(resources2).map(([_, evt]) => {
46
+ Object.entries(resources).map(([_, evt]) => {
63
47
  return (React.createElement(Box, { key: evt.LogicalResourceId },
64
48
  React.createElement(Text, null,
65
49
  React.createElement(Spinner, null),
@@ -72,7 +56,11 @@ export const DeploymentUI = (props) => {
72
56
  " "),
73
57
  React.createElement(Text, { color: color(evt.ResourceStatus || "") }, evt.ResourceStatus)));
74
58
  }),
75
- React.createElement(Box, null)));
59
+ Object.entries(resources).length === 0 && (React.createElement(Box, null,
60
+ React.createElement(Text, null,
61
+ React.createElement(Spinner, null),
62
+ " ",
63
+ React.createElement(Text, { dimColor: true }, "Waiting for changes"))))));
76
64
  };
77
65
  export function printDeploymentResults(assembly, results) {
78
66
  Colors.gap();
@@ -129,7 +129,6 @@ export declare class Stack extends cdk.Stack {
129
129
  */
130
130
  addOutputs(outputs: Record<string, string | cdk.CfnOutputProps>): void;
131
131
  private createCustomResourceHandler;
132
- private createSecretsMigrationResource;
133
132
  private static checkForPropsIsConstruct;
134
133
  private static checkForEnvInProps;
135
134
  }
@@ -1,12 +1,9 @@
1
1
  import url from "url";
2
2
  import * as path from "path";
3
3
  import * as cdk from "aws-cdk-lib";
4
- import * as iam from "aws-cdk-lib/aws-iam";
5
4
  import * as lambda from "aws-cdk-lib/aws-lambda";
6
5
  import { Function as Fn } from "./Function.js";
7
6
  import { isConstruct } from "./Construct.js";
8
- import { useApp } from "./context.js";
9
- import { useProject } from "../project.js";
10
7
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
11
8
  /**
12
9
  * The Stack construct extends cdk.Stack. It automatically prefixes the stack names with the stage and app name to ensure that they can be deployed to multiple regions in the same AWS account. It also ensure that the stack uses the same AWS profile and region as the app. They're defined using functions that return resources that can be imported by other stacks.
@@ -51,7 +48,6 @@ export class Stack extends cdk.Stack {
51
48
  // Create a custom resource handler per stack. This handler will
52
49
  // be used by all the custom resources in the stack.
53
50
  this.customResourceHandler = this.createCustomResourceHandler();
54
- this.createSecretsMigrationResource();
55
51
  }
56
52
  /**
57
53
  * The default function props to be applied to all the Lambda functions in the stack.
@@ -203,26 +199,6 @@ export class Stack extends cdk.Stack {
203
199
  memorySize: 1024,
204
200
  });
205
201
  }
206
- createSecretsMigrationResource() {
207
- const app = useApp();
208
- // Add permissions to migrate SSM paths for secrets
209
- this.customResourceHandler.addToRolePolicy(new iam.PolicyStatement({
210
- actions: ["ssm:GetParametersByPath", "ssm:PutParameter", "sns:Publish"],
211
- resources: [
212
- `arn:${this.partition}:ssm:${app.region}:${app.account}:parameter/sst/${app.name}/${app.stage}/*`,
213
- `arn:${this.partition}:ssm:${app.region}:${app.account}:parameter/sst/${app.name}/.fallback/*`,
214
- ],
215
- }));
216
- new cdk.CustomResource(this, "SecretsMigration", {
217
- serviceToken: this.customResourceHandler.functionArn,
218
- resourceType: "Custom::SecretsMigration",
219
- properties: {
220
- App: app.name,
221
- Stage: this.stage,
222
- SSTVersion: useProject().version,
223
- },
224
- });
225
- }
226
202
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
203
  static checkForPropsIsConstruct(id, props) {
228
204
  // If a construct is passed in as stack props, let's detect it and throw a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sst",
3
- "version": "2.0.0-rc.41",
3
+ "version": "2.0.0-rc.43",
4
4
  "bin": {
5
5
  "sst": "cli/sst.js"
6
6
  },
@@ -10,6 +10,7 @@ import { useRuntimeHandlers } from "../handlers.js";
10
10
  import { useRuntimeWorkers } from "../workers.js";
11
11
  import { Context } from "../../context/context.js";
12
12
  import { VisibleError } from "../../error.js";
13
+ import { Colors } from "../../cli/colors.js";
13
14
  export const useNodeHandler = Context.memo(async () => {
14
15
  const workers = await useRuntimeWorkers();
15
16
  const handlers = useRuntimeHandlers();
@@ -102,6 +103,7 @@ export const useNodeHandler = Context.memo(async () => {
102
103
  ],
103
104
  keepNames: true,
104
105
  bundle: true,
106
+ logLevel: "silent",
105
107
  metafile: true,
106
108
  ...(isESM
107
109
  ? {
@@ -130,47 +132,60 @@ export const useNodeHandler = Context.memo(async () => {
130
132
  minify: nodejs.minify,
131
133
  ...override,
132
134
  };
133
- const result = await esbuild.build(options);
134
- // Install node_modules
135
- if (options.external?.length) {
136
- async function find(dir, target) {
137
- if (dir === "/")
138
- throw new VisibleError("Could not find a package.json file");
139
- if (await fs
140
- .access(path.join(dir, target))
141
- .then(() => true)
142
- .catch(() => false))
143
- return dir;
144
- return find(path.join(dir, ".."), target);
145
- }
146
- if (input.mode === "deploy" && nodejs.install) {
147
- const src = await find(parsed.dir, "package.json");
148
- const json = JSON.parse(await fs
149
- .readFile(path.join(src, "package.json"))
150
- .then((x) => x.toString()));
151
- fs.writeFile(path.join(input.out, "package.json"), JSON.stringify({
152
- dependencies: Object.fromEntries(nodejs.install?.map((x) => [x, json.dependencies?.[x] || "*"])),
153
- }));
154
- await new Promise((resolve) => {
155
- const process = exec("npm install", {
156
- cwd: input.out,
135
+ try {
136
+ const result = await esbuild.build(options);
137
+ // Install node_modules
138
+ if (options.external?.length) {
139
+ async function find(dir, target) {
140
+ if (dir === "/")
141
+ throw new VisibleError("Could not find a package.json file");
142
+ if (await fs
143
+ .access(path.join(dir, target))
144
+ .then(() => true)
145
+ .catch(() => false))
146
+ return dir;
147
+ return find(path.join(dir, ".."), target);
148
+ }
149
+ if (input.mode === "deploy" && nodejs.install) {
150
+ const src = await find(parsed.dir, "package.json");
151
+ const json = JSON.parse(await fs
152
+ .readFile(path.join(src, "package.json"))
153
+ .then((x) => x.toString()));
154
+ fs.writeFile(path.join(input.out, "package.json"), JSON.stringify({
155
+ dependencies: Object.fromEntries(nodejs.install?.map((x) => [x, json.dependencies?.[x] || "*"])),
156
+ }));
157
+ await new Promise((resolve) => {
158
+ const process = exec("npm install", {
159
+ cwd: input.out,
160
+ });
161
+ process.on("exit", () => resolve());
157
162
  });
158
- process.on("exit", () => resolve());
159
- });
160
- }
161
- if (input.mode === "start") {
162
- const dir = path.join(await find(parsed.dir, "package.json"), "node_modules");
163
- try {
164
- await fs.symlink(path.resolve(dir), path.resolve(path.join(input.out, "node_modules")), "dir");
165
163
  }
166
- catch { }
164
+ if (input.mode === "start") {
165
+ const dir = path.join(await find(parsed.dir, "package.json"), "node_modules");
166
+ try {
167
+ await fs.symlink(path.resolve(dir), path.resolve(path.join(input.out, "node_modules")), "dir");
168
+ }
169
+ catch { }
170
+ }
167
171
  }
172
+ cache[input.functionID] = result;
173
+ return {
174
+ type: "success",
175
+ handler,
176
+ };
177
+ }
178
+ catch (ex) {
179
+ const result = ex;
180
+ return {
181
+ type: "error",
182
+ errors: result.errors.flatMap((x) => [
183
+ Colors.bold(x.text),
184
+ x.location?.file || "",
185
+ Colors.dim(x.location?.line, "│", x.location?.lineText),
186
+ ]),
187
+ };
168
188
  }
169
- cache[input.functionID] = result;
170
- return {
171
- type: "success",
172
- handler,
173
- };
174
189
  },
175
190
  });
176
191
  });