sst 2.28.6 → 2.29.2

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.
@@ -2,8 +2,8 @@ import * as cxapi from "@aws-cdk/cx-api";
2
2
  import { Tag } from "sst-aws-cdk/lib/cdk-toolkit.js";
3
3
  import { AssetManifestBuilder } from "sst-aws-cdk/lib/util/asset-manifest-builder.js";
4
4
  import { ISDK, SdkProvider } from "sst-aws-cdk/lib/api/aws-auth/index.js";
5
+ import { EnvironmentResources } from "sst-aws-cdk/lib/api/environment-resources.js";
5
6
  import { HotswapMode } from "sst-aws-cdk/lib/api/hotswap/common.js";
6
- import { ToolkitInfo } from "sst-aws-cdk/lib/api/toolkit-info.js";
7
7
  import { ResourcesToImport } from "sst-aws-cdk/lib/api/util/cloudformation.js";
8
8
  import { StackActivityProgress } from "sst-aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.js";
9
9
  type TemplateBodyParameter = {
@@ -56,7 +56,7 @@ export interface DeployStackOptions {
56
56
  /**
57
57
  * Information about the bootstrap stack found in the target environment
58
58
  */
59
- readonly toolkitInfo: ToolkitInfo;
59
+ readonly envResources: EnvironmentResources;
60
60
  /**
61
61
  * Role to pass to CloudFormation to execute the change set
62
62
  *
@@ -200,14 +200,14 @@ export declare function deployStack(options: DeployStackOptions): Promise<Deploy
200
200
  * @param stack the synthesized stack that provides the CloudFormation template
201
201
  * @param toolkitInfo information about the toolkit stack
202
202
  */
203
- export declare function makeBodyParameter(stack: cxapi.CloudFormationStackArtifact, resolvedEnvironment: cxapi.Environment, assetManifest: AssetManifestBuilder, toolkitInfo: ToolkitInfo, sdk: ISDK, overrideTemplate?: any): Promise<TemplateBodyParameter>;
203
+ export declare function makeBodyParameter(stack: cxapi.CloudFormationStackArtifact, resolvedEnvironment: cxapi.Environment, assetManifest: AssetManifestBuilder, resources: EnvironmentResources, sdk: ISDK, overrideTemplate?: any): Promise<TemplateBodyParameter>;
204
204
  /**
205
205
  * Prepare a body parameter for CFN, performing the upload
206
206
  *
207
207
  * Return it as-is if it is small enough to pass in the API call,
208
208
  * upload to S3 and return the coordinates if it is not.
209
209
  */
210
- export declare function makeBodyParameterAndUpload(stack: cxapi.CloudFormationStackArtifact, resolvedEnvironment: cxapi.Environment, toolkitInfo: ToolkitInfo, sdkProvider: SdkProvider, sdk: ISDK, overrideTemplate?: any): Promise<TemplateBodyParameter>;
210
+ export declare function makeBodyParameterAndUpload(stack: cxapi.CloudFormationStackArtifact, resolvedEnvironment: cxapi.Environment, resources: EnvironmentResources, sdkProvider: SdkProvider, sdk: ISDK, overrideTemplate?: any): Promise<TemplateBodyParameter>;
211
211
  export interface DestroyStackOptions {
212
212
  /**
213
213
  * The stack to be destroyed
@@ -37,7 +37,7 @@ export async function deployStack(options) {
37
37
  // an ad-hoc asset manifest, while passing their locations via template
38
38
  // parameters.
39
39
  const legacyAssets = new AssetManifestBuilder();
40
- const assetParams = await addMetadataAssetsToManifest(stackArtifact, legacyAssets, options.toolkitInfo, options.reuseAssets);
40
+ const assetParams = await addMetadataAssetsToManifest(stackArtifact, legacyAssets, options.envResources, options.reuseAssets);
41
41
  const finalParameterValues = { ...options.parameters, ...assetParams };
42
42
  const templateParams = TemplateParameters.fromTemplate(stackArtifact.template);
43
43
  const stackParams = options.usePreviousParameters
@@ -58,7 +58,7 @@ export async function deployStack(options) {
58
58
  else {
59
59
  debug(`${deployName}: deploying...`);
60
60
  }
61
- const bodyParameter = await makeBodyParameter(stackArtifact, options.resolvedEnvironment, legacyAssets, options.toolkitInfo, options.sdk, options.overrideTemplate);
61
+ const bodyParameter = await makeBodyParameter(stackArtifact, options.resolvedEnvironment, legacyAssets, options.envResources, options.sdk, options.overrideTemplate);
62
62
  await publishAssets(legacyAssets.toManifest(stackArtifact.assembly.directory), options.sdkProvider, stackEnv, {
63
63
  parallel: options.assetParallelism,
64
64
  });
@@ -352,7 +352,7 @@ class FullCloudFormationDeployment {
352
352
  * @param stack the synthesized stack that provides the CloudFormation template
353
353
  * @param toolkitInfo information about the toolkit stack
354
354
  */
355
- export async function makeBodyParameter(stack, resolvedEnvironment, assetManifest, toolkitInfo, sdk, overrideTemplate) {
355
+ export async function makeBodyParameter(stack, resolvedEnvironment, assetManifest, resources, sdk, overrideTemplate) {
356
356
  // If the template has already been uploaded to S3, just use it from there.
357
357
  if (stack.stackTemplateAssetObjectUrl && !overrideTemplate) {
358
358
  return {
@@ -364,6 +364,7 @@ export async function makeBodyParameter(stack, resolvedEnvironment, assetManifes
364
364
  if (templateJson.length <= LARGE_TEMPLATE_SIZE_KB * 1024) {
365
365
  return { TemplateBody: templateJson };
366
366
  }
367
+ const toolkitInfo = await resources.lookupToolkit();
367
368
  if (!toolkitInfo.found) {
368
369
  error(`The template for stack "${stack.displayName}" is ${Math.round(templateJson.length / 1024)}KiB. ` +
369
370
  `Templates larger than ${LARGE_TEMPLATE_SIZE_KB}KiB must be uploaded to S3.\n` +
@@ -394,14 +395,14 @@ export async function makeBodyParameter(stack, resolvedEnvironment, assetManifes
394
395
  * Return it as-is if it is small enough to pass in the API call,
395
396
  * upload to S3 and return the coordinates if it is not.
396
397
  */
397
- export async function makeBodyParameterAndUpload(stack, resolvedEnvironment, toolkitInfo, sdkProvider, sdk, overrideTemplate) {
398
+ export async function makeBodyParameterAndUpload(stack, resolvedEnvironment, resources, sdkProvider, sdk, overrideTemplate) {
398
399
  // We don't have access to the actual asset manifest here, so pretend that the
399
400
  // stack doesn't have a pre-published URL.
400
401
  const forceUploadStack = Object.create(stack, {
401
402
  stackTemplateAssetObjectUrl: { value: undefined },
402
403
  });
403
404
  const builder = new AssetManifestBuilder();
404
- const bodyparam = await makeBodyParameter(forceUploadStack, resolvedEnvironment, builder, toolkitInfo, sdk, overrideTemplate);
405
+ const bodyparam = await makeBodyParameter(forceUploadStack, resolvedEnvironment, builder, resources, sdk, overrideTemplate);
405
406
  const manifest = builder.toManifest(stack.assembly.directory);
406
407
  await publishAssets(manifest, sdkProvider, resolvedEnvironment, {
407
408
  quiet: true,
@@ -3,7 +3,6 @@ import { AssetManifest } from "cdk-assets";
3
3
  import { debug } from "sst-aws-cdk/lib/logging.js";
4
4
  import { CloudFormationStack, TemplateParameters, waitForStackDelete, } from "sst-aws-cdk/lib/api/util/cloudformation.js";
5
5
  import { Mode } from "sst-aws-cdk/lib/api/aws-auth/credentials.js";
6
- import { ToolkitInfo } from "sst-aws-cdk/lib/api/toolkit-info.js";
7
6
  import { addMetadataAssetsToManifest } from "sst-aws-cdk/lib/assets.js";
8
7
  import { publishAssets } from "sst-aws-cdk/lib/util/asset-publishing.js";
9
8
  import { AssetManifestBuilder } from "sst-aws-cdk/lib/util/asset-manifest-builder.js";
@@ -11,7 +10,7 @@ import { Deployments, } from "./deployments.js";
11
10
  import { makeBodyParameter } from "./deploy-stack.js";
12
11
  import { lazy } from "../util/lazy.js";
13
12
  export async function publishDeployAssets(sdkProvider, options) {
14
- const { deployment, toolkitInfo, stackSdk, resolvedEnvironment, cloudFormationRoleArn, } = await useDeployment().get(sdkProvider, options);
13
+ const { deployment, envResources, stackSdk, resolvedEnvironment, cloudFormationRoleArn, } = await useDeployment().get(sdkProvider, options);
15
14
  // TODO
16
15
  // old
17
16
  //await deployment.publishStackAssets(options.stack, toolkitInfo, {
@@ -44,7 +43,7 @@ export async function publishDeployAssets(sdkProvider, options) {
44
43
  sdkProvider,
45
44
  roleArn: cloudFormationRoleArn,
46
45
  reuseAssets: options.reuseAssets,
47
- toolkitInfo,
46
+ envResources,
48
47
  tags: options.tags,
49
48
  deploymentMethod: options.deploymentMethod,
50
49
  force: options.force,
@@ -67,13 +66,12 @@ const useDeployment = lazy(() => {
67
66
  const region = options.stack.environment.region;
68
67
  if (!state.has(region)) {
69
68
  const deployment = new Deployments({ sdkProvider });
70
- const { stackSdk, resolvedEnvironment, cloudFormationRoleArn } = await deployment.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
71
- const toolkitInfo = await ToolkitInfo.lookup(resolvedEnvironment, stackSdk, options.toolkitStackName);
69
+ const { stackSdk, resolvedEnvironment, cloudFormationRoleArn, envResources, } = await deployment.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
72
70
  // Do a verification of the bootstrap stack version
73
- await deployment.validateBootstrapStackVersion(options.stack.stackName, options.stack.requiresBootstrapStackVersion, options.stack.bootstrapStackVersionSsmParameter, toolkitInfo);
71
+ await deployment.validateBootstrapStackVersion(options.stack.stackName, options.stack.requiresBootstrapStackVersion, options.stack.bootstrapStackVersionSsmParameter, envResources);
74
72
  state.set(region, {
75
73
  deployment,
76
- toolkitInfo,
74
+ envResources,
77
75
  stackSdk,
78
76
  resolvedEnvironment,
79
77
  cloudFormationRoleArn,
@@ -106,13 +104,13 @@ async function deployStack(options) {
106
104
  // an ad-hoc asset manifest, while passing their locations via template
107
105
  // parameters.
108
106
  const legacyAssets = new AssetManifestBuilder();
109
- const assetParams = await addMetadataAssetsToManifest(stackArtifact, legacyAssets, options.toolkitInfo, options.reuseAssets);
107
+ const assetParams = await addMetadataAssetsToManifest(stackArtifact, legacyAssets, options.envResources, options.reuseAssets);
110
108
  const finalParameterValues = { ...options.parameters, ...assetParams };
111
109
  const templateParams = TemplateParameters.fromTemplate(stackArtifact.template);
112
110
  const stackParams = options.usePreviousParameters
113
111
  ? templateParams.updateExisting(finalParameterValues, cloudFormationStack.parameters)
114
112
  : templateParams.supplyAll(finalParameterValues);
115
- const bodyParameter = await makeBodyParameter(stackArtifact, options.resolvedEnvironment, legacyAssets, options.toolkitInfo, options.sdk, options.overrideTemplate);
113
+ const bodyParameter = await makeBodyParameter(stackArtifact, options.resolvedEnvironment, legacyAssets, options.envResources, options.sdk, options.overrideTemplate);
116
114
  await publishAssets(legacyAssets.toManifest(stackArtifact.assembly.directory), options.sdkProvider, stackEnv, {
117
115
  parallel: options.assetParallelism,
118
116
  });
@@ -6,7 +6,7 @@ import { Mode } from "sst-aws-cdk/lib/api/aws-auth/credentials.js";
6
6
  import { ISDK } from "sst-aws-cdk/lib/api/aws-auth/sdk.js";
7
7
  import { SdkProvider } from "sst-aws-cdk/lib/api/aws-auth/sdk-provider.js";
8
8
  import { DeployStackResult, DeploymentMethod } from "./deploy-stack.js";
9
- import { ToolkitInfo } from "sst-aws-cdk/lib/api/toolkit-info.js";
9
+ import { EnvironmentResources } from "sst-aws-cdk/lib/api/environment-resources.js";
10
10
  import { Template, ResourcesToImport, ResourceIdentifierSummaries } from "sst-aws-cdk/lib/api/util/cloudformation.js";
11
11
  import { StackActivityProgress } from "sst-aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.js";
12
12
  import { HotswapMode } from "sst-aws-cdk/lib/api/hotswap/common.js";
@@ -31,6 +31,10 @@ export interface PreparedSdkWithLookupRoleForEnvironment {
31
31
  * the default credentials (not the assume role credentials)
32
32
  */
33
33
  readonly didAssumeRole: boolean;
34
+ /**
35
+ * An object for accessing the bootstrap resources in this environment
36
+ */
37
+ readonly envResources: EnvironmentResources;
34
38
  }
35
39
  export interface DeployStackOptions {
36
40
  /**
@@ -212,6 +216,7 @@ export interface StackExistsOptions {
212
216
  }
213
217
  export interface DeploymentsProps {
214
218
  sdkProvider: SdkProvider;
219
+ readonly toolkitStackName?: string;
215
220
  readonly quiet?: boolean;
216
221
  }
217
222
  /**
@@ -234,6 +239,10 @@ export interface PreparedSdkForEnvironment {
234
239
  * @default - no execution role is used
235
240
  */
236
241
  readonly cloudFormationRoleArn?: string;
242
+ /**
243
+ * Access class for environmental resources to help the deployment
244
+ */
245
+ readonly envResources: EnvironmentResources;
237
246
  }
238
247
  /**
239
248
  * Scope for a single set of deployments from a set of Cloud Assembly Artifacts
@@ -243,13 +252,13 @@ export interface PreparedSdkForEnvironment {
243
252
  export declare class Deployments {
244
253
  private readonly props;
245
254
  private readonly sdkProvider;
246
- private readonly toolkitInfoCache;
247
255
  private readonly sdkCache;
248
256
  private readonly publisherCache;
257
+ private readonly environmentResources;
249
258
  constructor(props: DeploymentsProps);
250
259
  readCurrentTemplateWithNestedStacks(rootStackArtifact: cxapi.CloudFormationStackArtifact, retrieveProcessedTemplate?: boolean): Promise<Template>;
251
260
  readCurrentTemplate(stackArtifact: cxapi.CloudFormationStackArtifact): Promise<Template>;
252
- resourceIdentifierSummaries(stackArtifact: cxapi.CloudFormationStackArtifact, toolkitStackName?: string): Promise<ResourceIdentifierSummaries>;
261
+ resourceIdentifierSummaries(stackArtifact: cxapi.CloudFormationStackArtifact): Promise<ResourceIdentifierSummaries>;
253
262
  deployStack(options: DeployStackOptions): Promise<DeployStackResult | undefined>;
254
263
  destroyStack(options: DestroyStackOptions): Promise<void>;
255
264
  stackExists(options: StackExistsOptions): Promise<boolean>;
@@ -283,10 +292,6 @@ export declare class Deployments {
283
292
  * function can then decide to use them or fallback to another role.
284
293
  */
285
294
  prepareSdkWithLookupRoleFor(stack: cxapi.CloudFormationStackArtifact): Promise<PreparedSdkWithLookupRoleForEnvironment>;
286
- /**
287
- * Look up the toolkit for a given environment, using a given SDK
288
- */
289
- lookupToolkit(resolvedEnvironment: cxapi.Environment, sdk: ISDK, toolkitStackName?: string): Promise<ToolkitInfo>;
290
295
  private prepareAndValidateAssets;
291
296
  /**
292
297
  * Build all assets in a manifest
@@ -314,8 +319,10 @@ export declare class Deployments {
314
319
  isSingleAssetPublished(assetManifest: AssetManifest, asset: IManifestEntry, options: PublishStackAssetsOptions): Promise<boolean>;
315
320
  /**
316
321
  * Validate that the bootstrap stack has the right version for this stack
322
+ *
323
+ * Call into envResources.validateVersion, but prepend the stack name in case of failure.
317
324
  */
318
- validateBootstrapStackVersion(stackName: string, requiresBootstrapStackVersion: number | undefined, bootstrapStackVersionSsmParameter: string | undefined, toolkitInfo: ToolkitInfo): Promise<void>;
325
+ validateBootstrapStackVersion(stackName: string, requiresBootstrapStackVersion: number | undefined, bootstrapStackVersionSsmParameter: string | undefined, envResources: EnvironmentResources): Promise<void>;
319
326
  private cachedSdkForEnvironment;
320
327
  private cachedPublisher;
321
328
  }
@@ -6,11 +6,10 @@ import { debug, warning } from "sst-aws-cdk/lib/logging.js";
6
6
  import { buildAssets, publishAssets, PublishingAws, EVENT_TO_LOGGER, } from "sst-aws-cdk/lib/util/asset-publishing.js";
7
7
  import { Mode } from "sst-aws-cdk/lib/api/aws-auth/credentials.js";
8
8
  import { deployStack, destroyStack, makeBodyParameterAndUpload, } from "./deploy-stack.js";
9
- import { loadCurrentTemplateWithNestedStacks, loadCurrentTemplate, } from "sst-aws-cdk/lib/api/nested-stack-helpers.js";
10
- import { ToolkitInfo } from "sst-aws-cdk/lib/api/toolkit-info.js";
9
+ import { EnvironmentResourcesRegistry, } from "sst-aws-cdk/lib/api/environment-resources.js";
10
+ import { loadCurrentTemplateWithNestedStacks, loadCurrentTemplate, flattenNestedStackNames, } from "sst-aws-cdk/lib/api/nested-stack-helpers.js";
11
11
  import { CloudFormationStack, } from "sst-aws-cdk/lib/api/util/cloudformation.js";
12
12
  import { replaceEnvPlaceholders } from "sst-aws-cdk/lib/api/util/placeholders.js";
13
- import { callWithRetry } from "./util.js";
14
13
  /**
15
14
  * Scope for a single set of deployments from a set of Cloud Assembly Artifacts
16
15
  *
@@ -19,17 +18,22 @@ import { callWithRetry } from "./util.js";
19
18
  export class Deployments {
20
19
  props;
21
20
  sdkProvider;
22
- toolkitInfoCache = new Map();
23
21
  sdkCache = new Map();
24
22
  publisherCache = new Map();
23
+ environmentResources;
25
24
  constructor(props) {
26
25
  this.props = props;
27
26
  this.sdkProvider = props.sdkProvider;
27
+ this.environmentResources = new EnvironmentResourcesRegistry(props.toolkitStackName);
28
28
  }
29
29
  async readCurrentTemplateWithNestedStacks(rootStackArtifact, retrieveProcessedTemplate = false) {
30
30
  const sdk = (await this.prepareSdkWithLookupOrDeployRole(rootStackArtifact))
31
31
  .stackSdk;
32
- return (await loadCurrentTemplateWithNestedStacks(rootStackArtifact, sdk, retrieveProcessedTemplate)).deployedTemplate;
32
+ const templateWithNestedStacks = await loadCurrentTemplateWithNestedStacks(rootStackArtifact, sdk, retrieveProcessedTemplate);
33
+ return {
34
+ deployedTemplate: templateWithNestedStacks.deployedTemplate,
35
+ nestedStackCount: flattenNestedStackNames(templateWithNestedStacks.nestedStackNames).length,
36
+ };
33
37
  }
34
38
  async readCurrentTemplate(stackArtifact) {
35
39
  debug(`Reading existing template for stack ${stackArtifact.displayName}.`);
@@ -37,15 +41,14 @@ export class Deployments {
37
41
  .stackSdk;
38
42
  return loadCurrentTemplate(stackArtifact, sdk);
39
43
  }
40
- async resourceIdentifierSummaries(stackArtifact, toolkitStackName) {
44
+ async resourceIdentifierSummaries(stackArtifact) {
41
45
  debug(`Retrieving template summary for stack ${stackArtifact.displayName}.`);
42
46
  // Currently, needs to use `deploy-role` since it may need to read templates in the staging
43
47
  // bucket which have been encrypted with a KMS key (and lookup-role may not read encrypted things)
44
- const { stackSdk, resolvedEnvironment } = await this.prepareSdkFor(stackArtifact, undefined, Mode.ForReading);
48
+ const { stackSdk, resolvedEnvironment, envResources } = await this.prepareSdkFor(stackArtifact, undefined, Mode.ForReading);
45
49
  const cfn = stackSdk.cloudFormation();
46
- const toolkitInfo = await this.lookupToolkit(resolvedEnvironment, stackSdk, toolkitStackName);
47
50
  // Upload the template, if necessary, before passing it to CFN
48
- const cfnParam = await makeBodyParameterAndUpload(stackArtifact, resolvedEnvironment, toolkitInfo, this.sdkProvider, stackSdk);
51
+ const cfnParam = await makeBodyParameterAndUpload(stackArtifact, resolvedEnvironment, envResources, this.sdkProvider, stackSdk);
49
52
  const response = await cfn.getTemplateSummary(cfnParam).promise();
50
53
  if (!response.ResourceIdentifierSummaries) {
51
54
  debug('GetTemplateSummary API call did not return "ResourceIdentifierSummaries"');
@@ -64,10 +67,9 @@ export class Deployments {
64
67
  execute: options.execute,
65
68
  };
66
69
  }
67
- const { stackSdk, resolvedEnvironment, cloudFormationRoleArn } = await this.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
68
- const toolkitInfo = await callWithRetry(() => this.lookupToolkit(resolvedEnvironment, stackSdk, options.toolkitStackName));
70
+ const { stackSdk, resolvedEnvironment, cloudFormationRoleArn, envResources, } = await this.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
69
71
  // Do a verification of the bootstrap stack version
70
- await this.validateBootstrapStackVersion(options.stack.stackName, options.stack.requiresBootstrapStackVersion, options.stack.bootstrapStackVersionSsmParameter, toolkitInfo);
72
+ await this.validateBootstrapStackVersion(options.stack.stackName, options.stack.requiresBootstrapStackVersion, options.stack.bootstrapStackVersionSsmParameter, envResources);
71
73
  // Deploy assets
72
74
  const assetArtifacts = options.stack.dependencies.filter(cxapi.AssetManifestArtifact.isAssetManifestArtifact);
73
75
  for (const asset of assetArtifacts) {
@@ -89,7 +91,7 @@ export class Deployments {
89
91
  sdkProvider: this.sdkProvider,
90
92
  roleArn: cloudFormationRoleArn,
91
93
  reuseAssets: options.reuseAssets,
92
- toolkitInfo,
94
+ envResources,
93
95
  tags: options.tags,
94
96
  deploymentMethod,
95
97
  force: options.force,
@@ -129,6 +131,7 @@ export class Deployments {
129
131
  return {
130
132
  resolvedEnvironment: result.resolvedEnvironment,
131
133
  stackSdk: result.sdk,
134
+ envResources: result.envResources,
132
135
  };
133
136
  }
134
137
  }
@@ -164,6 +167,7 @@ export class Deployments {
164
167
  stackSdk: stackSdk.sdk,
165
168
  resolvedEnvironment,
166
169
  cloudFormationRoleArn: arns.cloudFormationRoleArn,
170
+ envResources: this.environmentResources.for(resolvedEnvironment, stackSdk.sdk),
167
171
  };
168
172
  }
169
173
  /**
@@ -198,11 +202,12 @@ export class Deployments {
198
202
  assumeRoleArn: arns.lookupRoleArn,
199
203
  assumeRoleExternalId: stack.lookupRole?.assumeRoleExternalId,
200
204
  });
205
+ const envResources = this.environmentResources.for(resolvedEnvironment, stackSdk.sdk);
201
206
  // if we succeed in assuming the lookup role, make sure we have the correct bootstrap stack version
202
207
  if (stackSdk.didAssumeRole &&
203
208
  stack.lookupRole?.bootstrapStackVersionSsmParameter &&
204
209
  stack.lookupRole.requiresBootstrapStackVersion) {
205
- const version = await ToolkitInfo.versionFromSsmParameter(stackSdk.sdk, stack.lookupRole.bootstrapStackVersionSsmParameter);
210
+ const version = await envResources.versionFromSsmParameter(stack.lookupRole.bootstrapStackVersionSsmParameter);
206
211
  if (version < stack.lookupRole.requiresBootstrapStackVersion) {
207
212
  throw new Error(`Bootstrap stack version '${stack.lookupRole.requiresBootstrapStackVersion}' is required, found version '${version}'.`);
208
213
  }
@@ -213,7 +218,7 @@ export class Deployments {
213
218
  stack.lookupRole?.requiresBootstrapStackVersion) {
214
219
  warning(upgradeMessage);
215
220
  }
216
- return { ...stackSdk, resolvedEnvironment };
221
+ return { ...stackSdk, resolvedEnvironment, envResources };
217
222
  }
218
223
  catch (e) {
219
224
  debug(e);
@@ -226,26 +231,11 @@ export class Deployments {
226
231
  throw e;
227
232
  }
228
233
  }
229
- /**
230
- * Look up the toolkit for a given environment, using a given SDK
231
- */
232
- async lookupToolkit(resolvedEnvironment, sdk, toolkitStackName) {
233
- const key = `${resolvedEnvironment.account}:${resolvedEnvironment.region}:${toolkitStackName}`;
234
- const existing = this.toolkitInfoCache.get(key);
235
- if (existing) {
236
- return existing;
237
- }
238
- const ret = await ToolkitInfo.lookup(resolvedEnvironment, sdk, toolkitStackName);
239
- this.toolkitInfoCache.set(key, ret);
240
- return ret;
241
- }
242
234
  async prepareAndValidateAssets(asset, options) {
243
- const { stackSdk, resolvedEnvironment } = await this.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
244
- const toolkitInfo = await this.lookupToolkit(resolvedEnvironment, stackSdk, options.toolkitStackName);
245
- const stackEnv = await this.sdkProvider.resolveEnvironment(options.stack.environment);
246
- await this.validateBootstrapStackVersion(options.stack.stackName, asset.requiresBootstrapStackVersion, asset.bootstrapStackVersionSsmParameter, toolkitInfo);
235
+ const { envResources } = await this.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
236
+ await this.validateBootstrapStackVersion(options.stack.stackName, asset.requiresBootstrapStackVersion, asset.bootstrapStackVersionSsmParameter, envResources);
247
237
  const manifest = AssetManifest.fromFile(asset.file);
248
- return { manifest, stackEnv };
238
+ return { manifest, stackEnv: envResources.environment };
249
239
  }
250
240
  /**
251
241
  * Build all assets in a manifest
@@ -270,10 +260,9 @@ export class Deployments {
270
260
  */
271
261
  // eslint-disable-next-line max-len
272
262
  async buildSingleAsset(assetArtifact, assetManifest, asset, options) {
273
- const { stackSdk, resolvedEnvironment: stackEnv } = await this.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
274
- const toolkitInfo = await this.lookupToolkit(stackEnv, stackSdk, options.toolkitStackName);
275
- await this.validateBootstrapStackVersion(options.stack.stackName, assetArtifact.requiresBootstrapStackVersion, assetArtifact.bootstrapStackVersionSsmParameter, toolkitInfo);
276
- const publisher = this.cachedPublisher(assetManifest, stackEnv, options.stackName);
263
+ const { resolvedEnvironment, envResources } = await this.prepareSdkFor(options.stack, options.roleArn, Mode.ForWriting);
264
+ await this.validateBootstrapStackVersion(options.stack.stackName, assetArtifact.requiresBootstrapStackVersion, assetArtifact.bootstrapStackVersionSsmParameter, envResources);
265
+ const publisher = this.cachedPublisher(assetManifest, resolvedEnvironment, options.stackName);
277
266
  await publisher.buildEntry(asset);
278
267
  }
279
268
  /**
@@ -296,13 +285,12 @@ export class Deployments {
296
285
  }
297
286
  /**
298
287
  * Validate that the bootstrap stack has the right version for this stack
288
+ *
289
+ * Call into envResources.validateVersion, but prepend the stack name in case of failure.
299
290
  */
300
- async validateBootstrapStackVersion(stackName, requiresBootstrapStackVersion, bootstrapStackVersionSsmParameter, toolkitInfo) {
301
- if (requiresBootstrapStackVersion === undefined) {
302
- return;
303
- }
291
+ async validateBootstrapStackVersion(stackName, requiresBootstrapStackVersion, bootstrapStackVersionSsmParameter, envResources) {
304
292
  try {
305
- await toolkitInfo.validateVersion(requiresBootstrapStackVersion, bootstrapStackVersionSsmParameter);
293
+ await envResources.validateVersion(requiresBootstrapStackVersion, bootstrapStackVersionSsmParameter);
306
294
  }
307
295
  catch (e) {
308
296
  throw new Error(`${stackName}: ${e.message}`);
@@ -81,13 +81,10 @@ export const bind = (program) => program
81
81
  });
82
82
  async function buildApp() {
83
83
  const [_metafile, sstConfig] = await Stacks.load(project.paths.config);
84
- const cwd = process.cwd();
85
- process.chdir(project.paths.root);
86
84
  await Stacks.synth({
87
85
  fn: sstConfig.stacks,
88
86
  mode: "remove",
89
87
  });
90
- process.chdir(cwd);
91
88
  }
92
89
  function isInSsrSite() {
93
90
  const cwd = process.cwd();
package/cli/sst.js CHANGED
@@ -3,7 +3,6 @@ import { blue, red } from "colorette";
3
3
  import { program } from "./program.js";
4
4
  import { SilentError, VisibleError } from "../error.js";
5
5
  import { useSpinners } from "./spinner.js";
6
- import { Logger } from "../logger.js";
7
6
  import dotenv from "dotenv";
8
7
  dotenv.config({
9
8
  override: true,
@@ -48,7 +47,6 @@ if ("setSourceMapsEnabled" in process) {
48
47
  }
49
48
  process.removeAllListeners("uncaughtException");
50
49
  process.on("uncaughtException", (err) => {
51
- Logger.debug(err);
52
50
  const spinners = useSpinners();
53
51
  for (const spinner of spinners) {
54
52
  if (spinner.isSpinning)
@@ -68,7 +66,7 @@ process.on("uncaughtException", (err) => {
68
66
  });
69
67
  // Check Node version
70
68
  const nodeVersion = process.versions.node;
71
- if (Number(nodeVersion.split(".")[0]) < 16) {
72
- throw new VisibleError(`Node.js version ${nodeVersion} is not supported by SST. Please upgrade to Node.js 16 or later.`);
69
+ if (Number(nodeVersion.split(".")[0]) < 18) {
70
+ throw new VisibleError(`Node.js version ${nodeVersion} is not supported by SST. Please upgrade to Node.js 18 or later.`);
73
71
  }
74
72
  program.parse();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sideEffects": false,
3
3
  "name": "sst",
4
- "version": "2.28.6",
4
+ "version": "2.29.2",
5
5
  "bin": {
6
6
  "sst": "cli/sst.js"
7
7
  },
@@ -87,7 +87,7 @@
87
87
  "ora": "^6.1.2",
88
88
  "react": "18.2.0",
89
89
  "remeda": "^1.3.0",
90
- "sst-aws-cdk": "2.91.0",
90
+ "sst-aws-cdk": "2.95.1",
91
91
  "tree-kill": "^1.2.2",
92
92
  "undici": "^5.12.0",
93
93
  "uuid": "^9.0.0",
@@ -119,7 +119,7 @@
119
119
  "@types/ws": "^8.5.3",
120
120
  "@types/yargs": "^17.0.13",
121
121
  "archiver": "^5.3.1",
122
- "astro-sst": "2.28.6",
122
+ "astro-sst": "2.29.2",
123
123
  "tsx": "^3.12.1",
124
124
  "typescript": "^5.2.2",
125
125
  "vitest": "^0.33.0"
@@ -114,6 +114,7 @@ export const useRuntimeHandlers = lazy(() => {
114
114
  export const useFunctionBuilder = lazy(() => {
115
115
  const artifacts = new Map();
116
116
  const handlers = useRuntimeHandlers();
117
+ const semaphore = new Semaphore(4);
117
118
  const result = {
118
119
  artifact: (functionID) => {
119
120
  if (artifacts.has(functionID))
@@ -121,13 +122,19 @@ export const useFunctionBuilder = lazy(() => {
121
122
  return result.build(functionID);
122
123
  },
123
124
  build: async (functionID) => {
124
- const result = await handlers.build(functionID, "start");
125
- if (!result)
126
- return;
127
- if (result.type === "error")
128
- return;
129
- artifacts.set(functionID, result);
130
- return artifacts.get(functionID);
125
+ const unlock = await semaphore.lock();
126
+ try {
127
+ const result = await handlers.build(functionID, "start");
128
+ if (!result)
129
+ return;
130
+ if (result.type === "error")
131
+ return;
132
+ artifacts.set(functionID, result);
133
+ return artifacts.get(functionID);
134
+ }
135
+ finally {
136
+ unlock();
137
+ }
131
138
  },
132
139
  };
133
140
  const watcher = useWatcher();
@@ -149,3 +156,30 @@ export const useFunctionBuilder = lazy(() => {
149
156
  });
150
157
  return result;
151
158
  });
159
+ class Semaphore {
160
+ queue = [];
161
+ locked = 0;
162
+ maxLocks;
163
+ constructor(maxLocks = 1) {
164
+ this.maxLocks = maxLocks;
165
+ }
166
+ lock() {
167
+ return new Promise((resolve) => {
168
+ const unlock = () => {
169
+ this.locked--;
170
+ const next = this.queue.shift();
171
+ if (next) {
172
+ this.locked++;
173
+ next(unlock);
174
+ }
175
+ };
176
+ if (this.locked < this.maxLocks) {
177
+ this.locked++;
178
+ resolve(unlock);
179
+ }
180
+ else {
181
+ this.queue.push(unlock);
182
+ }
183
+ });
184
+ }
185
+ }
package/stacks/build.js CHANGED
@@ -14,7 +14,13 @@ export async function load(input, shallow) {
14
14
  const root = await findAbove(input, "package.json");
15
15
  if (!root)
16
16
  throw new VisibleError("Could not find a package.json file");
17
- const outfile = path.join(parsed.dir, `.${parsed.name}.${Date.now()}.mjs`);
17
+ // Create the output file in the current working directory. This is because
18
+ // the sst cli command might not always run from where `sst.config.ts` is. When
19
+ // running from another workspace (ie. running `sst bind` in frontend workspace),
20
+ // the "sst" package imported from within the output file might resolve to a
21
+ // different "sst" package than the one resolved by the sst cli command. This
22
+ // will cause issues like "Project not initialized".
23
+ const outfile = path.join(process.cwd(), `.${parsed.name}.${Date.now()}.mjs`);
18
24
  const pkg = JSON.parse(await fs.readFile(path.join(root, "package.json")).then((x) => x.toString()));
19
25
  try {
20
26
  // Logger.debug("running esbuild on", input);
package/stacks/synth.js CHANGED
@@ -11,62 +11,75 @@ export async function synth(opts) {
11
11
  const cxapi = await import("@aws-cdk/cx-api");
12
12
  const { Configuration } = await import("sst-aws-cdk/lib/settings.js");
13
13
  const project = useProject();
14
- const identity = await useSTSIdentity();
15
- opts = {
16
- ...opts,
17
- buildDir: opts.buildDir || path.join(project.paths.out, "dist"),
18
- };
19
- await fs.rm(opts.buildDir, { recursive: true, force: true });
20
- await fs.mkdir(opts.buildDir, { recursive: true });
21
- /*
22
- console.log(JSON.stringify(cfg.context));
23
- const executable = new CloudExecutable({
24
- sdkProvider: await useAWSProvider(),
25
- configuration: cfg,
26
- synthesizer: async () => app.synth() as any
27
- });
28
- const { assembly } = await executable.synthesize(true);
29
- */
30
- const cfg = new Configuration();
31
- await cfg.load();
32
- let previous = new Set();
33
- while (true) {
34
- const app = new App({
35
- account: identity.Account,
36
- stage: project.config.stage,
37
- name: project.config.name,
38
- region: project.config.region,
39
- mode: opts.mode,
40
- debugIncreaseTimeout: opts.increaseTimeout,
41
- debugScriptVersion: opts.scriptVersion,
42
- isActiveStack: opts.isActiveStack,
43
- }, {
44
- outdir: opts.buildDir,
45
- context: {
46
- ...cfg.context.all,
47
- [cxapi.PATH_METADATA_ENABLE_CONTEXT]: project.config.cdk?.pathMetadata ?? false,
48
- },
49
- });
50
- await opts.fn(app);
51
- await app.finish();
52
- const assembly = app.synth();
53
- Logger.debug(assembly.manifest.missing);
54
- const { missing } = assembly.manifest;
55
- const provider = await useAWSProvider();
56
- if (missing && missing.length) {
57
- const next = missing.map((x) => x.key);
58
- if (next.length === previous.size && next.every((x) => previous.has(x)))
59
- throw new VisibleError(formatErrorMessage(next.join("")));
60
- Logger.debug("Looking up context for:", next, "Previous:", previous);
61
- previous = new Set(next);
62
- await contextproviders.provideContextValues(missing, cfg.context, provider);
63
- if (cfg.context.keys.length) {
64
- await cfg.saveContext();
14
+ const cwd = process.cwd();
15
+ process.chdir(project.paths.root);
16
+ try {
17
+ return await synthInRoot();
18
+ }
19
+ catch (e) {
20
+ throw e;
21
+ }
22
+ finally {
23
+ process.chdir(cwd);
24
+ }
25
+ async function synthInRoot() {
26
+ const identity = await useSTSIdentity();
27
+ opts = {
28
+ ...opts,
29
+ buildDir: opts.buildDir || path.join(project.paths.out, "dist"),
30
+ };
31
+ await fs.rm(opts.buildDir, { recursive: true, force: true });
32
+ await fs.mkdir(opts.buildDir, { recursive: true });
33
+ /*
34
+ console.log(JSON.stringify(cfg.context));
35
+ const executable = new CloudExecutable({
36
+ sdkProvider: await useAWSProvider(),
37
+ configuration: cfg,
38
+ synthesizer: async () => app.synth() as any
39
+ });
40
+ const { assembly } = await executable.synthesize(true);
41
+ */
42
+ const cfg = new Configuration();
43
+ await cfg.load();
44
+ let previous = new Set();
45
+ while (true) {
46
+ const app = new App({
47
+ account: identity.Account,
48
+ stage: project.config.stage,
49
+ name: project.config.name,
50
+ region: project.config.region,
51
+ mode: opts.mode,
52
+ debugIncreaseTimeout: opts.increaseTimeout,
53
+ debugScriptVersion: opts.scriptVersion,
54
+ isActiveStack: opts.isActiveStack,
55
+ }, {
56
+ outdir: opts.buildDir,
57
+ context: {
58
+ ...cfg.context.all,
59
+ [cxapi.PATH_METADATA_ENABLE_CONTEXT]: project.config.cdk?.pathMetadata ?? false,
60
+ },
61
+ });
62
+ await opts.fn(app);
63
+ await app.finish();
64
+ const assembly = app.synth();
65
+ Logger.debug(assembly.manifest.missing);
66
+ const { missing } = assembly.manifest;
67
+ const provider = await useAWSProvider();
68
+ if (missing && missing.length) {
69
+ const next = missing.map((x) => x.key);
70
+ if (next.length === previous.size && next.every((x) => previous.has(x)))
71
+ throw new VisibleError(formatErrorMessage(next.join("")));
72
+ Logger.debug("Looking up context for:", next, "Previous:", previous);
73
+ previous = new Set(next);
74
+ await contextproviders.provideContextValues(missing, cfg.context, provider);
75
+ if (cfg.context.keys.length) {
76
+ await cfg.saveContext();
77
+ }
78
+ continue;
65
79
  }
66
- continue;
80
+ Logger.debug("Finished synthesizing");
81
+ return assembly;
67
82
  }
68
- Logger.debug("Finished synthesizing");
69
- return assembly;
70
83
  }
71
84
  }
72
85
  function formatErrorMessage(message) {