sst 2.0.0-rc.8 → 2.0.0

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.
Files changed (243) hide show
  1. package/bootstrap.d.ts +2 -4
  2. package/bootstrap.js +208 -61
  3. package/bus.d.ts +3 -3
  4. package/bus.js +1 -1
  5. package/cache.js +1 -1
  6. package/cdk/cloudformation-deployments-wrapper.d.ts +3 -0
  7. package/cdk/cloudformation-deployments-wrapper.js +117 -0
  8. package/cdk/cloudformation-deployments.d.ts +19 -4
  9. package/cdk/deploy-stack.d.ts +17 -2
  10. package/cdk/deploy-stack.js +6 -1
  11. package/cli/colors.d.ts +28 -0
  12. package/cli/colors.js +27 -0
  13. package/cli/commands/bind.d.ts +4 -0
  14. package/cli/commands/bind.js +10 -7
  15. package/cli/commands/bootstrap.d.ts +13 -0
  16. package/cli/commands/bootstrap.js +11 -0
  17. package/cli/commands/build.d.ts +6 -2
  18. package/cli/commands/build.js +13 -8
  19. package/cli/commands/console.d.ts +5 -1
  20. package/cli/commands/console.js +8 -6
  21. package/cli/commands/deploy.d.ts +4 -2
  22. package/cli/commands/deploy.js +71 -29
  23. package/cli/commands/dev.d.ts +5 -1
  24. package/cli/commands/dev.js +157 -79
  25. package/cli/commands/diff.d.ts +15 -0
  26. package/cli/commands/diff.js +61 -0
  27. package/cli/commands/env.d.ts +5 -1
  28. package/cli/commands/env.js +14 -15
  29. package/cli/commands/plugins/kysely.js +13 -5
  30. package/cli/commands/plugins/pothos.js +9 -4
  31. package/cli/commands/remove.d.ts +4 -0
  32. package/cli/commands/remove.js +26 -18
  33. package/cli/commands/secrets/get.d.ts +7 -3
  34. package/cli/commands/secrets/get.js +11 -24
  35. package/cli/commands/secrets/list.d.ts +6 -2
  36. package/cli/commands/secrets/list.js +15 -7
  37. package/cli/commands/secrets/remove.d.ts +8 -4
  38. package/cli/commands/secrets/remove.js +18 -16
  39. package/cli/commands/secrets/secrets.js +3 -3
  40. package/cli/commands/secrets/set.d.ts +8 -6
  41. package/cli/commands/secrets/set.js +16 -33
  42. package/cli/commands/telemetry.d.ts +15 -0
  43. package/cli/commands/telemetry.js +17 -0
  44. package/cli/commands/transform.d.ts +15 -0
  45. package/cli/commands/transform.js +55 -0
  46. package/cli/commands/update.d.ts +6 -2
  47. package/cli/commands/update.js +41 -33
  48. package/cli/commands/version.d.ts +13 -0
  49. package/cli/commands/version.js +7 -0
  50. package/cli/local/router.d.ts +6 -6
  51. package/cli/local/router.js +3 -3
  52. package/cli/local/server.d.ts +5 -3
  53. package/cli/local/server.js +21 -14
  54. package/cli/program.d.ts +5 -1
  55. package/cli/program.js +29 -5
  56. package/cli/spinner.js +2 -0
  57. package/cli/sst.js +35 -23
  58. package/cli/telemetry/environment.d.ts +1 -1
  59. package/cli/telemetry/environment.js +1 -1
  60. package/cli/terminal.d.ts +1 -0
  61. package/cli/terminal.js +8 -0
  62. package/cli/ui/deploy.d.ts +3 -2
  63. package/cli/ui/deploy.js +106 -106
  64. package/cli/ui/functions.d.ts +2 -0
  65. package/cli/ui/functions.js +132 -0
  66. package/cli/ui/header.d.ts +5 -0
  67. package/cli/ui/header.js +16 -0
  68. package/cli/ui/stack.d.ts +1 -0
  69. package/cli/ui/stack.js +6 -0
  70. package/config.d.ts +1 -1
  71. package/config.js +8 -8
  72. package/constructs/Api.d.ts +3 -71
  73. package/constructs/Api.js +3 -42
  74. package/constructs/ApiGatewayV1Api.d.ts +3 -3
  75. package/constructs/ApiGatewayV1Api.js +4 -3
  76. package/constructs/App.d.ts +2 -22
  77. package/constructs/App.js +12 -25
  78. package/constructs/AppSyncApi.d.ts +4 -5
  79. package/constructs/AppSyncApi.js +8 -5
  80. package/constructs/AstroSite.d.ts +2 -3
  81. package/constructs/AstroSite.js +43 -53
  82. package/constructs/Auth.js +27 -16
  83. package/constructs/BaseSite.d.ts +10 -10
  84. package/constructs/BaseSite.js +2 -2
  85. package/constructs/Cognito.js +28 -28
  86. package/constructs/EdgeFunction.d.ts +6 -4
  87. package/constructs/EdgeFunction.js +56 -59
  88. package/constructs/EventBus.js +1 -1
  89. package/constructs/Function.d.ts +4 -44
  90. package/constructs/Function.js +14 -50
  91. package/constructs/FunctionalStack.d.ts +2 -2
  92. package/constructs/Job.d.ts +23 -63
  93. package/constructs/Job.js +11 -32
  94. package/constructs/Metadata.d.ts +18 -20
  95. package/constructs/NextjsSite.d.ts +21 -5
  96. package/constructs/NextjsSite.js +67 -101
  97. package/constructs/Queue.js +10 -10
  98. package/constructs/RDS.d.ts +1 -1
  99. package/constructs/RDS.js +1 -1
  100. package/constructs/RemixSite.d.ts +0 -2
  101. package/constructs/RemixSite.js +20 -20
  102. package/constructs/Script.js +1 -1
  103. package/constructs/Secret.js +5 -3
  104. package/constructs/SolidStartSite.d.ts +2 -3
  105. package/constructs/SolidStartSite.js +42 -52
  106. package/constructs/SsrFunction.d.ts +22 -0
  107. package/constructs/SsrFunction.js +113 -0
  108. package/constructs/SsrSite.d.ts +104 -104
  109. package/constructs/SsrSite.js +196 -217
  110. package/constructs/Stack.d.ts +1 -20
  111. package/constructs/Stack.js +3 -65
  112. package/constructs/StaticSite.d.ts +33 -53
  113. package/constructs/StaticSite.js +92 -118
  114. package/constructs/Table.d.ts +1 -1
  115. package/constructs/Table.js +4 -4
  116. package/constructs/Topic.js +1 -1
  117. package/constructs/cdk/certificate-base.d.ts +18 -0
  118. package/constructs/cdk/certificate-base.js +26 -0
  119. package/constructs/cdk/dns-validated-certificate.d.ts +77 -0
  120. package/constructs/cdk/dns-validated-certificate.js +125 -0
  121. package/constructs/cdk/website-redirect.d.ts +53 -0
  122. package/constructs/cdk/website-redirect.js +77 -0
  123. package/constructs/deferred_task.d.ts +1 -1
  124. package/constructs/deprecated/NextjsSite.js +8 -6
  125. package/constructs/index.d.ts +0 -6
  126. package/constructs/index.js +0 -6
  127. package/constructs/util/apiGatewayV1AccessLog.d.ts +1 -1
  128. package/constructs/util/apiGatewayV2AccessLog.js +4 -4
  129. package/constructs/util/apiGatewayV2Domain.js +9 -2
  130. package/constructs/util/appSyncApiDomain.d.ts +1 -1
  131. package/constructs/util/appSyncApiDomain.js +9 -15
  132. package/constructs/util/duration.d.ts +1 -1
  133. package/constructs/util/functionBinding.js +1 -1
  134. package/constructs/util/permission.d.ts +3 -3
  135. package/constructs/util/permission.js +16 -18
  136. package/constructs/util/size.d.ts +1 -1
  137. package/constructs/util/warning.js +2 -2
  138. package/context/context.d.ts +2 -2
  139. package/context/context.js +4 -4
  140. package/context/handler.d.ts +1 -1
  141. package/credentials.d.ts +1 -1
  142. package/credentials.js +42 -6
  143. package/index.d.ts +1 -0
  144. package/index.js +1 -0
  145. package/iot.js +6 -2
  146. package/logger.js +3 -1
  147. package/node/api/index.d.ts +4 -3
  148. package/node/api/index.js +8 -2
  149. package/node/auth/adapter/adapter.d.ts +1 -1
  150. package/node/auth/adapter/facebook.js +1 -1
  151. package/node/auth/adapter/github.js +4 -4
  152. package/node/auth/adapter/google.d.ts +1 -1
  153. package/node/auth/adapter/google.js +2 -2
  154. package/node/auth/adapter/twitch.js +1 -1
  155. package/node/auth/session.d.ts +1 -1
  156. package/node/config/index.d.ts +2 -2
  157. package/node/config/index.js +3 -8
  158. package/node/job/index.d.ts +2 -2
  159. package/node/site/index.js +3 -5
  160. package/package.json +26 -21
  161. package/pothos.js +1 -0
  162. package/project.d.ts +47 -0
  163. package/{app.js → project.js} +60 -53
  164. package/runtime/handlers/dotnet.d.ts +1 -1
  165. package/runtime/handlers/dotnet.js +4 -4
  166. package/runtime/handlers/go.d.ts +1 -1
  167. package/runtime/handlers/go.js +3 -3
  168. package/runtime/handlers/java.d.ts +1 -1
  169. package/runtime/handlers/java.js +4 -4
  170. package/runtime/handlers/node.d.ts +1 -1
  171. package/runtime/handlers/node.js +60 -39
  172. package/runtime/handlers/python.d.ts +1 -1
  173. package/runtime/handlers/python.js +3 -3
  174. package/runtime/handlers.js +15 -12
  175. package/runtime/iot.js +3 -0
  176. package/runtime/runtime.d.ts +4 -0
  177. package/runtime/server.d.ts +3 -3
  178. package/runtime/server.js +11 -5
  179. package/runtime/workers.d.ts +4 -2
  180. package/runtime/workers.js +13 -3
  181. package/site-env.js +2 -1
  182. package/sst.mjs +6357 -20846
  183. package/stacks/app-metadata.d.ts +7 -0
  184. package/stacks/app-metadata.js +78 -0
  185. package/stacks/assembly.d.ts +1 -0
  186. package/stacks/assembly.js +4 -0
  187. package/stacks/build.d.ts +1 -1
  188. package/stacks/build.js +41 -48
  189. package/stacks/deploy.d.ts +1 -0
  190. package/stacks/deploy.js +128 -7
  191. package/stacks/diff.d.ts +8 -0
  192. package/stacks/diff.js +62 -0
  193. package/stacks/index.d.ts +3 -0
  194. package/stacks/index.js +3 -0
  195. package/stacks/metadata.d.ts +2 -0
  196. package/stacks/metadata.js +14 -12
  197. package/stacks/monitor.d.ts +8 -3
  198. package/stacks/monitor.js +38 -4
  199. package/stacks/remove.js +0 -4
  200. package/stacks/synth.d.ts +1 -0
  201. package/stacks/synth.js +27 -13
  202. package/support/base-site-archiver.mjs +1 -1
  203. package/support/bootstrap-metadata-function/index.mjs +68740 -0
  204. package/support/bridge/bridge.mjs +28 -28
  205. package/support/certificate-requestor/index.js +549 -0
  206. package/support/custom-resources/index.mjs +3824 -26567
  207. package/support/dotnet6-bootstrap/release/dotnet-bootstrap.deps.json +1 -1
  208. package/support/dotnet6-bootstrap/release/dotnet-bootstrap.runtimeconfig.json +1 -1
  209. package/support/edge-function/edge-lambda.mjs +2 -2
  210. package/support/job-invoker/index.mjs +26 -0
  211. package/support/nodejs-runtime/index.mjs +75 -72
  212. package/support/rds-migrator/index.mjs +22 -23
  213. package/support/remix-site-function/edge-server.js +1 -1
  214. package/support/script-function/index.mjs +30485 -0
  215. package/support/{edge-function-code-replacer → sls-nextjs-site-function-code-replacer}/lambda-code-updater.py +0 -0
  216. package/support/ssr-site-function-archiver.mjs +96 -0
  217. package/util/process.d.ts +0 -1
  218. package/watcher.js +1 -1
  219. package/app.d.ts +0 -36
  220. package/constructs/DebugApp.d.ts +0 -49
  221. package/constructs/DebugApp.js +0 -63
  222. package/constructs/DebugStack.d.ts +0 -34
  223. package/constructs/DebugStack.js +0 -132
  224. package/constructs/GraphQLApi.d.ts +0 -98
  225. package/constructs/GraphQLApi.js +0 -80
  226. package/constructs/ReactStaticSite.d.ts +0 -20
  227. package/constructs/ReactStaticSite.js +0 -54
  228. package/constructs/Script/cfn-response.d.ts +0 -19
  229. package/constructs/Script/cfn-response.js +0 -77
  230. package/constructs/Script/index.d.ts +0 -1
  231. package/constructs/Script/index.js +0 -78
  232. package/constructs/Script/outbound.d.ts +0 -10
  233. package/constructs/Script/outbound.js +0 -42
  234. package/constructs/Script/util.d.ts +0 -2
  235. package/constructs/Script/util.js +0 -11
  236. package/constructs/ViteStaticSite.d.ts +0 -32
  237. package/constructs/ViteStaticSite.js +0 -66
  238. package/support/astro-site-html-stub/index.html +0 -99
  239. package/support/base-site-archiver.cjs +0 -116
  240. package/support/nextjs-site-html-stub/index.html +0 -99
  241. package/support/remix-site-html-stub/index.html +0 -99
  242. package/support/solid-start-site-html-stub/index.html +0 -99
  243. package/support/static-site-stub/index.html +0 -90
@@ -0,0 +1,7 @@
1
+ interface Metadata {
2
+ mode: "deploy" | "dev";
3
+ }
4
+ export declare function saveAppMetadata(data: Metadata): Promise<void>;
5
+ export declare function clearAppMetadata(): Promise<void>;
6
+ export declare const useAppMetadata: () => Promise<Metadata | undefined>;
7
+ export {};
@@ -0,0 +1,78 @@
1
+ import { useBootstrap } from "../bootstrap.js";
2
+ import { useAWSCredentials } from "../credentials.js";
3
+ import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, } from "@aws-sdk/client-s3";
4
+ import { json } from "stream/consumers";
5
+ import { Context } from "../context/context.js";
6
+ import { Logger } from "../logger.js";
7
+ import { useProject } from "../project.js";
8
+ async function metadata() {
9
+ Logger.debug("Fetching app metadata");
10
+ const [project, credentials, bootstrap] = await Promise.all([
11
+ useProject(),
12
+ useAWSCredentials(),
13
+ useBootstrap(),
14
+ ]);
15
+ const s3 = new S3Client({
16
+ region: project.config.region,
17
+ credentials,
18
+ });
19
+ try {
20
+ const result = await s3
21
+ .send(new GetObjectCommand({
22
+ Key: useS3Key(),
23
+ Bucket: bootstrap.bucket,
24
+ }))
25
+ .then((result) => json(result.Body));
26
+ return result;
27
+ }
28
+ catch (ex) {
29
+ Logger.debug("Fetching app metadata: not found");
30
+ }
31
+ }
32
+ export async function saveAppMetadata(data) {
33
+ Logger.debug("Saving app metadata");
34
+ const [project, credentials, bootstrap] = await Promise.all([
35
+ useProject(),
36
+ useAWSCredentials(),
37
+ useBootstrap(),
38
+ ]);
39
+ const s3 = new S3Client({
40
+ region: project.config.region,
41
+ credentials,
42
+ });
43
+ try {
44
+ await s3.send(new PutObjectCommand({
45
+ Key: useS3Key(),
46
+ Bucket: bootstrap.bucket,
47
+ Body: JSON.stringify(data),
48
+ }));
49
+ }
50
+ catch (ex) {
51
+ Logger.debug("Saving app metadata: not found");
52
+ }
53
+ }
54
+ export async function clearAppMetadata() {
55
+ Logger.debug("Clearing app metadata");
56
+ const [project, credentials, bootstrap] = await Promise.all([
57
+ useProject(),
58
+ useAWSCredentials(),
59
+ useBootstrap(),
60
+ ]);
61
+ const s3 = new S3Client({
62
+ region: project.config.region,
63
+ credentials,
64
+ });
65
+ await s3.send(new DeleteObjectCommand({
66
+ Key: useS3Key(),
67
+ Bucket: bootstrap.bucket,
68
+ }));
69
+ }
70
+ function useS3Key() {
71
+ const project = useProject();
72
+ return `appMetadata/app.${project.config.name}/stage.${project.config.stage}.json`;
73
+ }
74
+ const MetadataContext = Context.create(async () => {
75
+ const data = await metadata();
76
+ return data;
77
+ });
78
+ export const useAppMetadata = MetadataContext.use;
@@ -0,0 +1 @@
1
+ export declare function loadAssembly(from: string): Promise<import("aws-cdk-lib/cx-api").CloudAssembly>;
@@ -0,0 +1,4 @@
1
+ export async function loadAssembly(from) {
2
+ const { CloudAssembly } = await import("aws-cdk-lib/cx-api");
3
+ return new CloudAssembly(from);
4
+ }
package/stacks/build.d.ts CHANGED
@@ -6,4 +6,4 @@ declare module "../bus.js" {
6
6
  };
7
7
  }
8
8
  }
9
- export declare function build(): Promise<any>;
9
+ export declare function load(input: string): Promise<readonly [esbuild.Metafile, any]>;
package/stacks/build.js CHANGED
@@ -1,60 +1,53 @@
1
1
  import esbuild from "esbuild";
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
- import { Logger } from "../logger.js";
5
- import { useBus } from "../bus.js";
6
- import { useProject } from "../app.js";
7
4
  import { dynamicImport } from "../util/module.js";
8
- export async function build() {
9
- const project = useProject();
10
- const bus = useBus();
11
- const pkg = JSON.parse(await fs
12
- .readFile(path.join(project.paths.root, "package.json"))
13
- .then((x) => x.toString()));
14
- const outfile = path.join(project.paths.out, `stacks.${Math.random()}.mjs`);
15
- Logger.debug("Running esbuild on", project.main, "to", outfile);
16
- const result = await esbuild.build({
17
- keepNames: true,
18
- bundle: true,
19
- sourcemap: "inline",
20
- platform: "node",
21
- target: "esnext",
22
- metafile: true,
23
- format: "esm",
24
- external: [
25
- "aws-cdk-lib",
26
- "sst",
27
- ...Object.keys({
28
- ...pkg.devDependencies,
29
- ...pkg.dependencies,
30
- ...pkg.peerDependencies,
31
- }),
32
- ],
33
- absWorkingDir: project.paths.root,
34
- outfile,
35
- banner: {
36
- js: [
37
- `import { createRequire as topLevelCreateRequire } from 'module';`,
38
- `const require = topLevelCreateRequire(import.meta.url);`,
39
- ].join(""),
40
- },
41
- // The entry can have any file name (ie. "stacks/anything.ts"). We want the
42
- // build output to be always named "lib/index.js". This allow us to always
43
- // import from "buildDir" without needing to pass "anything" around.
44
- entryPoints: [project.main],
45
- });
46
- Logger.debug("Finished esbuild");
47
- Logger.debug("Sourcing stacks");
5
+ import { findAbove } from "../util/fs.js";
6
+ import { VisibleError } from "../error.js";
7
+ export async function load(input) {
8
+ const parsed = path.parse(input);
9
+ const root = await findAbove(input, "package.json");
10
+ const outfile = path.join(parsed.dir, `.${parsed.name}.${Date.now()}.mjs`);
11
+ const pkg = JSON.parse(await fs.readFile(path.join(root, "package.json")).then((x) => x.toString()));
48
12
  try {
13
+ const result = await esbuild.build({
14
+ keepNames: true,
15
+ bundle: true,
16
+ sourcemap: "inline",
17
+ platform: "node",
18
+ target: "esnext",
19
+ metafile: true,
20
+ format: "esm",
21
+ logLevel: "silent",
22
+ external: [
23
+ "aws-cdk-lib",
24
+ "sst",
25
+ ...Object.keys({
26
+ ...pkg.devDependencies,
27
+ ...pkg.dependencies,
28
+ ...pkg.peerDependencies,
29
+ }),
30
+ ],
31
+ absWorkingDir: root,
32
+ outfile,
33
+ banner: {
34
+ js: [
35
+ `import { createRequire as topLevelCreateRequire } from 'module';`,
36
+ `const require = topLevelCreateRequire(import.meta.url);`,
37
+ ].join(""),
38
+ },
39
+ // The entry can have any file name (ie. "stacks/anything.ts"). We want the
40
+ // build output to be always named "lib/index.js". This allow us to always
41
+ // import from "buildDir" without needing to pass "anything" around.
42
+ entryPoints: [input],
43
+ });
49
44
  const mod = await dynamicImport(outfile);
50
- Logger.debug("Finished sourcing stacks");
51
45
  await fs.rm(outfile, {
52
46
  force: true,
53
47
  });
54
- bus.publish("stack.built", {
55
- metafile: result.metafile,
56
- });
57
- return mod.default;
48
+ if (!mod.default?.config)
49
+ throw new VisibleError(`The config file is improperly formatted.`, `Example:`, `export default {`, ` config() {`, ` return {`, ` name: "my-app",`, ` region: "us-east-1"`, ` }`, ` },`, ` stacks(app) {`, ` }`, `}`);
50
+ return [result.metafile, mod.default];
58
51
  }
59
52
  catch (e) {
60
53
  await fs.rm(outfile, {
@@ -1,5 +1,6 @@
1
1
  import type { CloudFormationStackArtifact } from "aws-cdk-lib/cx-api";
2
2
  import { StackDeploymentResult } from "./monitor.js";
3
+ export declare function publishAssets(stacks: CloudFormationStackArtifact[]): Promise<Record<string, any>>;
3
4
  export declare function deployMany(stacks: CloudFormationStackArtifact[]): Promise<Record<string, {
4
5
  status: string;
5
6
  outputs: Record<string, string>;
package/stacks/deploy.js CHANGED
@@ -1,7 +1,24 @@
1
1
  import { useBus } from "../bus.js";
2
- import { useAWSProvider } from "../credentials.js";
2
+ import { useAWSClient, useAWSProvider } from "../credentials.js";
3
3
  import { Logger } from "../logger.js";
4
- import { isFailed, monitor } from "./monitor.js";
4
+ import { filterOutputs, isFailed, monitor, } from "./monitor.js";
5
+ export async function publishAssets(stacks) {
6
+ Logger.debug("Publishing assets");
7
+ const provider = await useAWSProvider();
8
+ const { publishDeployAssets } = await import("../cdk/cloudformation-deployments-wrapper.js");
9
+ const results = {};
10
+ for (const stack of stacks) {
11
+ const result = await publishDeployAssets(provider, {
12
+ stack: stack,
13
+ quiet: false,
14
+ deploymentMethod: {
15
+ method: "direct",
16
+ },
17
+ });
18
+ results[stack.stackName] = result;
19
+ }
20
+ return results;
21
+ }
5
22
  export async function deployMany(stacks) {
6
23
  Logger.debug("Deploying stacks", stacks.map((s) => s.stackName));
7
24
  const { CloudFormationStackArtifact } = await import("aws-cdk-lib/cx-api");
@@ -9,11 +26,7 @@ export async function deployMany(stacks) {
9
26
  const bus = useBus();
10
27
  const complete = new Set();
11
28
  const todo = new Set(stacks.map((s) => s.id));
12
- const pending = new Set();
13
29
  const results = {};
14
- bus.subscribe("stack.updated", (evt) => {
15
- pending.add(evt.properties.stackID);
16
- });
17
30
  return new Promise((resolve) => {
18
31
  async function trigger() {
19
32
  for (const stack of stacks) {
@@ -62,6 +75,7 @@ export async function deploy(stack) {
62
75
  sdkProvider: provider,
63
76
  });
64
77
  try {
78
+ await addInUseExports(stack);
65
79
  const result = await deployment.deployStack({
66
80
  stack: stack,
67
81
  quiet: true,
@@ -76,7 +90,7 @@ export async function deploy(stack) {
76
90
  });
77
91
  return {
78
92
  errors: {},
79
- outputs: result.outputs,
93
+ outputs: filterOutputs(result.outputs),
80
94
  status: "SKIPPED",
81
95
  };
82
96
  }
@@ -103,3 +117,110 @@ export async function deploy(stack) {
103
117
  };
104
118
  }
105
119
  }
120
+ async function addInUseExports(stack) {
121
+ // Get old outputs
122
+ const oldOutputs = await getCloudFormationStackOutputs(stack);
123
+ if (!oldOutputs)
124
+ return;
125
+ // Get new exports
126
+ // note: that we only want to handle outputs exported by CDK.
127
+ // ie.
128
+ // "Outputs": {
129
+ // "ExportsOutputRefauthUserPoolA78B038B8D9965B5": {
130
+ // "Value": {
131
+ // "Ref": "authUserPoolA78B038B"
132
+ // },
133
+ // "Export": {
134
+ // "Name": "frank-acme-auth:ExportsOutputRefauthUserPoolA78B038B8D9965B5"
135
+ // }
136
+ // },
137
+ const newTemplate = JSON.parse(await getLocalTemplate(stack));
138
+ const newOutputs = newTemplate.Outputs || {};
139
+ const newExportNames = Object.keys(newOutputs)
140
+ .filter((outputKey) => outputKey.startsWith("ExportsOutput"))
141
+ .filter((outputKey) => newOutputs[outputKey].Export)
142
+ .map((outputKey) => newOutputs[outputKey].Export.Name);
143
+ // Add missing exports
144
+ // ie.
145
+ // Outputs [{
146
+ // OutputKey: (String)
147
+ // OutputValue: (String)
148
+ // Description: (String)
149
+ // ExportName: (String)
150
+ // }]
151
+ let isDirty = false;
152
+ await Promise.all(oldOutputs
153
+ .filter((output) => output.OutputKey?.startsWith("ExportsOutput"))
154
+ .filter((output) => output.ExportName)
155
+ // filter exports not in the new template (ie. CloudFormation will be removing)
156
+ .filter((output) => !newExportNames.includes(output.ExportName))
157
+ // filter the exports still in-use by other stacks
158
+ .map(async (output) => {
159
+ const imports = await listImports(output.ExportName);
160
+ // update template
161
+ if (imports.length > 0) {
162
+ Logger.debug(`deploy stack: addInUseExports: export ${output.ExportName} used in ${imports.join(", ")}`);
163
+ newTemplate.Outputs = newTemplate.Outputs || {};
164
+ newTemplate.Outputs[output.OutputKey] = {
165
+ Description: `Output added by SST b/c exported value still used in ${imports.join(", ")}`,
166
+ Value: output.OutputValue,
167
+ Export: {
168
+ Name: output.ExportName,
169
+ },
170
+ };
171
+ isDirty = true;
172
+ }
173
+ }));
174
+ // Save new template
175
+ if (isDirty) {
176
+ await saveLocalTemplate(stack, JSON.stringify(newTemplate, null, 2));
177
+ }
178
+ }
179
+ async function getCloudFormationStackOutputs(stack) {
180
+ const { CloudFormationClient, DescribeStacksCommand } = await import("@aws-sdk/client-cloudformation");
181
+ const client = useAWSClient(CloudFormationClient);
182
+ try {
183
+ const { Stacks: stacks } = await client.send(new DescribeStacksCommand({
184
+ StackName: stack.id,
185
+ }));
186
+ if (!stacks || stacks.length === 0)
187
+ return;
188
+ return stacks[0].Outputs || [];
189
+ }
190
+ catch (e) {
191
+ if (e.name === "ValidationError" &&
192
+ e.message.includes("Stack with id") &&
193
+ e.message.includes("does not exist")) {
194
+ return;
195
+ }
196
+ else {
197
+ throw e;
198
+ }
199
+ }
200
+ }
201
+ async function listImports(exportName) {
202
+ const { CloudFormationClient, ListImportsCommand } = await import("@aws-sdk/client-cloudformation");
203
+ const client = useAWSClient(CloudFormationClient);
204
+ try {
205
+ const ret = await client.send(new ListImportsCommand({
206
+ ExportName: exportName,
207
+ }));
208
+ return ret.Imports || [];
209
+ }
210
+ catch (e) {
211
+ if (e.name === "ValidationError" &&
212
+ e.message.includes("is not imported by any stack")) {
213
+ return [];
214
+ }
215
+ throw e;
216
+ }
217
+ }
218
+ async function getLocalTemplate(stack) {
219
+ const fs = await import("fs/promises");
220
+ const fileContent = await fs.readFile(stack.templateFullPath);
221
+ return fileContent.toString();
222
+ }
223
+ async function saveLocalTemplate(stack, content) {
224
+ const fs = await import("fs/promises");
225
+ await fs.writeFile(stack.templateFullPath, content);
226
+ }
@@ -0,0 +1,8 @@
1
+ import type { CloudFormationStackArtifact } from "aws-cdk-lib/cx-api";
2
+ export declare function diff(stack: CloudFormationStackArtifact, oldTemplate: any): Promise<{
3
+ count: number;
4
+ diff?: undefined;
5
+ } | {
6
+ count: number;
7
+ diff: string;
8
+ }>;
package/stacks/diff.js ADDED
@@ -0,0 +1,62 @@
1
+ export async function diff(stack, oldTemplate) {
2
+ const { diffTemplate, formatDifferences, TemplateDiff } = await import("@aws-cdk/cloudformation-diff");
3
+ // Generate diff
4
+ const diff = diffTemplate(oldTemplate, stack.template);
5
+ if (diff.isEmpty) {
6
+ return { count: 0 };
7
+ }
8
+ // Only display resource and output changes
9
+ // @ts-ignore
10
+ diff.iamChanges = { hasChanges: false };
11
+ // @ts-ignore
12
+ diff.securityGroupChanges = { hasChanges: false };
13
+ // @ts-ignore
14
+ diff.awsTemplateFormatVersion = false;
15
+ // @ts-ignore
16
+ diff.transform = false;
17
+ // @ts-ignore
18
+ diff.description = false;
19
+ // @ts-ignore
20
+ diff.parameters = { differenceCount: 0 };
21
+ // @ts-ignore
22
+ diff.metadata = { differenceCount: 0 };
23
+ // @ts-ignore
24
+ diff.mappings = { differenceCount: 0 };
25
+ // @ts-ignore
26
+ diff.conditions = { differenceCount: 0 };
27
+ // @ts-ignore
28
+ diff.unknown = { differenceCount: 0 };
29
+ // Filter out SST internal diffs
30
+ // @ts-ignore
31
+ delete diff.outputs.diffs?.["SSTMetadata"];
32
+ // Format diff
33
+ const output = [];
34
+ const stream = {
35
+ write(chunk) {
36
+ output.push(` ${chunk}`);
37
+ },
38
+ };
39
+ const pathMap = await buildLogicalToPathMap(stack);
40
+ formatDifferences(stream, diff, pathMap);
41
+ // Remove trailing newline
42
+ while (true) {
43
+ if (output[output.length - 1].match(/^\s*$/)) {
44
+ output.pop();
45
+ }
46
+ else {
47
+ break;
48
+ }
49
+ }
50
+ return {
51
+ count: diff.outputs.differenceCount + diff.resources.differenceCount,
52
+ diff: output.join(""),
53
+ };
54
+ }
55
+ async function buildLogicalToPathMap(stack) {
56
+ const { ArtifactMetadataEntryType } = await import("@aws-cdk/cloud-assembly-schema");
57
+ const map = {};
58
+ for (const md of stack.findMetadataByType(ArtifactMetadataEntryType.LOGICAL_ID)) {
59
+ map[md.data] = md.path;
60
+ }
61
+ return map;
62
+ }
package/stacks/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ export * from "./app-metadata.js";
2
+ export * from "./assembly.js";
1
3
  export * from "./build.js";
2
4
  export * from "./deploy.js";
5
+ export * from "./diff.js";
3
6
  export * from "./metadata.js";
4
7
  export * from "./synth.js";
5
8
  export * from "./monitor.js";
package/stacks/index.js CHANGED
@@ -1,5 +1,8 @@
1
+ export * from "./app-metadata.js";
2
+ export * from "./assembly.js";
1
3
  export * from "./build.js";
2
4
  export * from "./deploy.js";
5
+ export * from "./diff.js";
3
6
  export * from "./metadata.js";
4
7
  export * from "./synth.js";
5
8
  export * from "./monitor.js";
@@ -2,6 +2,8 @@ import type { Metadata } from "../constructs/Metadata.js";
2
2
  declare module "../bus.js" {
3
3
  interface Events {
4
4
  "stacks.metadata": Awaited<ReturnType<typeof metadata>>;
5
+ "stacks.metadata.updated": {};
6
+ "stacks.metadata.deleted": {};
5
7
  }
6
8
  }
7
9
  export declare function metadata(): Promise<Record<string, Metadata[]>>;
@@ -5,9 +5,8 @@ import { json } from "stream/consumers";
5
5
  import { useCache } from "../cache.js";
6
6
  import { Context } from "../context/context.js";
7
7
  import { useBus } from "../bus.js";
8
- import { Stacks } from "./index.js";
9
8
  import { Logger } from "../logger.js";
10
- import { useProject } from "../app.js";
9
+ import { useProject } from "../project.js";
11
10
  export async function metadata() {
12
11
  Logger.debug("Fetching all metadata");
13
12
  const project = useProject();
@@ -16,10 +15,10 @@ export async function metadata() {
16
15
  useBootstrap(),
17
16
  ]);
18
17
  const s3 = new S3Client({
19
- region: project.region,
18
+ region: project.config.region,
20
19
  credentials: credentials,
21
20
  });
22
- const key = `stackMetadata/app.${project.name}/stage.${project.stage}/`;
21
+ const key = `stackMetadata/app.${project.config.name}/stage.${project.config.stage}/`;
23
22
  const list = await s3.send(new ListObjectsV2Command({
24
23
  Prefix: key,
25
24
  Bucket: bootstrap.bucket,
@@ -44,10 +43,10 @@ export async function metadataForStack(stackID) {
44
43
  useBootstrap(),
45
44
  ]);
46
45
  const s3 = new S3Client({
47
- region: project.region,
46
+ region: project.config.region,
48
47
  credentials: credentials,
49
48
  });
50
- const key = `stackMetadata/app.${project.name}/stage.${project.stage}/stack.${stackID}.json`;
49
+ const key = `stackMetadata/app.${project.config.name}/stage.${project.config.stage}/stack.${stackID}.json`;
51
50
  Logger.debug("Getting metadata", key, "from", bootstrap.bucket);
52
51
  try {
53
52
  const result = await s3
@@ -67,12 +66,15 @@ const MetadataContext = Context.create(async () => {
67
66
  const bus = useBus();
68
67
  const cache = await useCache();
69
68
  const data = await metadata();
70
- bus.subscribe("stack.status", async (evt) => {
71
- if (!Stacks.isSuccess(evt.properties.status))
72
- return;
73
- const meta = await metadataForStack(evt.properties.stackID);
74
- Logger.debug("Got metadata", meta);
75
- data[evt.properties.stackID] = meta;
69
+ bus.publish("stacks.metadata", data);
70
+ bus.subscribe("stacks.metadata.updated", async () => {
71
+ const data = await metadata();
72
+ await cache.write(`metadata.json`, JSON.stringify(data));
73
+ bus.publish("stacks.metadata", data);
74
+ MetadataContext.provide(Promise.resolve(data));
75
+ });
76
+ bus.subscribe("stacks.metadata.deleted", async () => {
77
+ const data = await metadata();
76
78
  await cache.write(`metadata.json`, JSON.stringify(data));
77
79
  bus.publish("stacks.metadata", data);
78
80
  MetadataContext.provide(Promise.resolve(data));
@@ -1,4 +1,4 @@
1
- import { DescribeStackResourcesOutput } from "@aws-sdk/client-cloudformation";
1
+ import { DescribeStackResourcesOutput, StackEvent } from "@aws-sdk/client-cloudformation";
2
2
  declare module "../bus.js" {
3
3
  interface Events {
4
4
  "stack.updated": {
@@ -6,12 +6,16 @@ declare module "../bus.js" {
6
6
  };
7
7
  "stack.status": {
8
8
  stackID: string;
9
- status: typeof STATUSES[number];
9
+ status: (typeof STATUSES)[number];
10
10
  };
11
11
  "stack.resources": {
12
12
  stackID: string;
13
13
  resources: DescribeStackResourcesOutput["StackResources"];
14
14
  };
15
+ "stack.event": {
16
+ stackID: string;
17
+ event: StackEvent;
18
+ };
15
19
  }
16
20
  }
17
21
  export declare const STATUSES: readonly ["CREATE_IN_PROGRESS", "DELETE_IN_PROGRESS", "REVIEW_IN_PROGRESS", "ROLLBACK_IN_PROGRESS", "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", "UPDATE_IN_PROGRESS", "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS", "UPDATE_ROLLBACK_IN_PROGRESS", "CREATE_COMPLETE", "UPDATE_COMPLETE", "DELETE_COMPLETE", "SKIPPED", "CREATE_FAILED", "DELETE_FAILED", "ROLLBACK_FAILED", "ROLLBACK_COMPLETE", "UPDATE_FAILED", "UPDATE_ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_FAILED", "DEPENDENCY_FAILED"];
@@ -24,4 +28,5 @@ export declare function monitor(stack: string): Promise<{
24
28
  outputs: Record<string, string>;
25
29
  errors: Record<string, string>;
26
30
  }>;
27
- export declare type StackDeploymentResult = Awaited<ReturnType<typeof monitor>>;
31
+ export declare function filterOutputs(input: Record<string, string>): Record<string, string>;
32
+ export type StackDeploymentResult = Awaited<ReturnType<typeof monitor>>;
package/stacks/monitor.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CloudFormationClient, DescribeStackResourcesCommand, DescribeStacksCommand, } from "@aws-sdk/client-cloudformation";
1
+ import { CloudFormationClient, DescribeStackResourcesCommand, DescribeStacksCommand, DescribeStackEventsCommand, } from "@aws-sdk/client-cloudformation";
2
2
  import { useBus } from "../bus.js";
3
3
  import { useAWSClient } from "../credentials.js";
4
4
  import { Logger } from "../logger.js";
@@ -53,23 +53,51 @@ export async function monitor(stack) {
53
53
  ]);
54
54
  let lastStatus;
55
55
  const errors = {};
56
+ let lastEvent;
56
57
  while (true) {
57
58
  try {
58
- const [describe, resources] = await Promise.all([
59
+ const [describe, resources, events] = await Promise.all([
59
60
  cfn.send(new DescribeStacksCommand({
60
61
  StackName: stack,
61
62
  })),
62
63
  cfn.send(new DescribeStackResourcesCommand({
63
64
  StackName: stack,
64
65
  })),
66
+ cfn.send(new DescribeStackEventsCommand({
67
+ StackName: stack,
68
+ })),
65
69
  ]);
66
70
  Logger.debug("Stack description", describe);
71
+ if (lastEvent) {
72
+ const eventsReversed = [...(events.StackEvents ?? [])].reverse();
73
+ for (const event of eventsReversed) {
74
+ if (!event.Timestamp)
75
+ continue;
76
+ if (event.Timestamp.getTime() > lastEvent.getTime()) {
77
+ bus.publish("stack.event", {
78
+ event: event,
79
+ stackID: stack,
80
+ });
81
+ if (event.ResourceStatusReason) {
82
+ if (event.ResourceStatusReason.includes("Resource creation cancelled") ||
83
+ event.ResourceStatusReason.includes("Resource update cancelled") ||
84
+ event.ResourceStatusReason?.includes("Resource creation Initiated"))
85
+ continue;
86
+ errors[event.LogicalResourceId] = event.ResourceStatusReason;
87
+ }
88
+ }
89
+ }
90
+ Logger.debug("Last event set to", lastEvent);
91
+ }
92
+ lastEvent = events.StackEvents?.at(0)?.Timestamp;
67
93
  bus.publish("stack.resources", {
68
94
  stackID: stack,
69
95
  resources: resources.StackResources,
70
96
  });
71
97
  for (const resource of resources.StackResources || []) {
72
- if (resource.ResourceStatusReason?.includes("Resource creation cancelled"))
98
+ if (resource.ResourceStatusReason?.includes("Resource creation cancelled") ||
99
+ resource.ResourceStatusReason?.includes("Resource update cancelled") ||
100
+ resource.ResourceStatusReason?.includes("Resource creation Initiated"))
73
101
  continue;
74
102
  if (resource.ResourceStatusReason)
75
103
  errors[resource.LogicalResourceId] = resource.ResourceStatusReason;
@@ -86,7 +114,7 @@ export async function monitor(stack) {
86
114
  if (isFinal(first.StackStatus)) {
87
115
  return {
88
116
  status: first.StackStatus,
89
- outputs: Object.fromEntries(first.Outputs?.map((o) => [o.OutputKey, o.OutputValue]) || []),
117
+ outputs: pipe(first.Outputs || [], map((o) => [o.OutputKey, o.OutputValue]), Object.fromEntries, filterOutputs),
90
118
  errors: isFailed(first.StackStatus) ? errors : {},
91
119
  };
92
120
  }
@@ -109,3 +137,9 @@ export async function monitor(stack) {
109
137
  await new Promise((resolve) => setTimeout(resolve, 1000));
110
138
  }
111
139
  }
140
+ import { map, omitBy, pipe } from "remeda";
141
+ export function filterOutputs(input) {
142
+ return pipe(input, omitBy((_, key) => {
143
+ return key.startsWith("Export") || key === "SSTMetadata";
144
+ }));
145
+ }
package/stacks/remove.js CHANGED
@@ -8,11 +8,7 @@ export async function removeMany(stacks) {
8
8
  const bus = useBus();
9
9
  const complete = new Set();
10
10
  const todo = new Set(stacks.map((s) => s.id));
11
- const pending = new Set();
12
11
  const results = {};
13
- bus.subscribe("stack.updated", (evt) => {
14
- pending.add(evt.properties.stackID);
15
- });
16
12
  return new Promise((resolve) => {
17
13
  async function trigger() {
18
14
  for (const stack of stacks) {
package/stacks/synth.d.ts CHANGED
@@ -3,6 +3,7 @@ interface SynthOptions {
3
3
  buildDir?: string;
4
4
  outDir?: string;
5
5
  skipBuild?: boolean;
6
+ increaseTimeout?: boolean;
6
7
  mode: App["mode"];
7
8
  fn: (app: App) => Promise<void> | void;
8
9
  }