@stacksjs/ts-cloud 0.5.8 → 0.5.10

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.
@@ -99,6 +99,35 @@ export declare function getCommandRecord(config: CloudConfig, environment: Envir
99
99
  * whitespace argument parsing.
100
100
  */
101
101
  export declare function runDbQuery(config: CloudConfig, environment: EnvironmentType, sql: string): Promise<string>;
102
+ export interface ServerlessFunctionInfo {
103
+ mode: 'http' | 'queue' | 'cli';
104
+ name: string;
105
+ /** `live` alias version when provisioned concurrency is on, else '$LATEST'. */
106
+ version: string;
107
+ provisioned?: {
108
+ status: string;
109
+ allocated: number;
110
+ requested: number;
111
+ };
112
+ }
113
+ export interface ServerlessInfo {
114
+ slug: string;
115
+ environment: string;
116
+ region: string;
117
+ stackStatus: string;
118
+ endpoint?: string;
119
+ assetUrl?: string;
120
+ scheduler: 'on' | 'off' | 'sub-minute';
121
+ queues: string[];
122
+ provisionedConcurrency: number;
123
+ functions: ServerlessFunctionInfo[];
124
+ lastRelease?: {
125
+ sha: string;
126
+ timestamp: string;
127
+ };
128
+ }
129
+ /** Gather an operational summary of a deployed serverless app. */
130
+ export declare function serverlessInfo(config: CloudConfig, environment: EnvironmentType): Promise<ServerlessInfo>;
102
131
  /** Rescale the serverless Aurora cluster's min/max ACU capacity. */
103
132
  export declare function scaleServerlessDatabase(config: CloudConfig, environment: EnvironmentType, minCapacity: number, maxCapacity: number): Promise<void>;
104
133
  /**
package/dist/index.js CHANGED
@@ -26026,6 +26026,28 @@ function composeServerlessAppTemplate(opts) {
26026
26026
  addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900, undefined, app.cliTmpStorage ?? tmpStorage);
26027
26027
  if (hasQueue)
26028
26028
  addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120, undefined, app.queueTmpStorage ?? tmpStorage);
26029
+ const pc = (app.provisionedConcurrency ?? 0) > 0 ? app.provisionedConcurrency : 0;
26030
+ const fnLogicalIds = ["HttpFunction", "CliFunction", ...hasQueue ? ["QueueFunction"] : []];
26031
+ if (pc) {
26032
+ for (const L of fnLogicalIds) {
26033
+ resources[`${L}Version`] = {
26034
+ Type: "AWS::Lambda::Version",
26035
+ DeletionPolicy: "Retain",
26036
+ Properties: { FunctionName: Fn2.ref(L) }
26037
+ };
26038
+ resources[`${L}Alias`] = {
26039
+ Type: "AWS::Lambda::Alias",
26040
+ Properties: {
26041
+ FunctionName: Fn2.ref(L),
26042
+ Name: "live",
26043
+ FunctionVersion: Fn2.getAtt(`${L}Version`, "Version"),
26044
+ ProvisionedConcurrencyConfig: { ProvisionedConcurrentExecutions: pc }
26045
+ }
26046
+ };
26047
+ }
26048
+ }
26049
+ const invokeArn = (L) => pc ? Fn2.ref(`${L}Alias`) : Fn2.getAtt(L, "Arn");
26050
+ const invokeName = (L) => pc ? Fn2.ref(`${L}Alias`) : Fn2.ref(L);
26029
26051
  resources.HttpApi = {
26030
26052
  Type: "AWS::ApiGatewayV2::Api",
26031
26053
  Properties: {
@@ -26038,7 +26060,7 @@ function composeServerlessAppTemplate(opts) {
26038
26060
  Properties: {
26039
26061
  ApiId: Fn2.ref("HttpApi"),
26040
26062
  IntegrationType: "AWS_PROXY",
26041
- IntegrationUri: Fn2.getAtt("HttpFunction", "Arn"),
26063
+ IntegrationUri: invokeArn("HttpFunction"),
26042
26064
  PayloadFormatVersion: "2.0"
26043
26065
  }
26044
26066
  };
@@ -26061,7 +26083,7 @@ function composeServerlessAppTemplate(opts) {
26061
26083
  resources.HttpPermission = {
26062
26084
  Type: "AWS::Lambda::Permission",
26063
26085
  Properties: {
26064
- FunctionName: Fn2.ref("HttpFunction"),
26086
+ FunctionName: invokeName("HttpFunction"),
26065
26087
  Action: "lambda:InvokeFunction",
26066
26088
  Principal: "apigateway.amazonaws.com",
26067
26089
  SourceArn: Fn2.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*")
@@ -26152,7 +26174,7 @@ function composeServerlessAppTemplate(opts) {
26152
26174
  Type: "AWS::Lambda::EventSourceMapping",
26153
26175
  Properties: {
26154
26176
  EventSourceArn: Fn2.getAtt(qId, "Arn"),
26155
- FunctionName: Fn2.ref("QueueFunction"),
26177
+ FunctionName: invokeName("QueueFunction"),
26156
26178
  BatchSize: 1,
26157
26179
  FunctionResponseTypes: ["ReportBatchItemFailures"],
26158
26180
  ...concurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, concurrency) } } : {}
@@ -26170,7 +26192,7 @@ function composeServerlessAppTemplate(opts) {
26170
26192
  State: "ENABLED",
26171
26193
  Targets: [{
26172
26194
  Id: "cli",
26173
- Arn: Fn2.getAtt("CliFunction", "Arn"),
26195
+ Arn: invokeArn("CliFunction"),
26174
26196
  Input: JSON.stringify({ command: "schedule:run" })
26175
26197
  }]
26176
26198
  }
@@ -26178,7 +26200,7 @@ function composeServerlessAppTemplate(opts) {
26178
26200
  resources.SchedulerPermission = {
26179
26201
  Type: "AWS::Lambda::Permission",
26180
26202
  Properties: {
26181
- FunctionName: Fn2.ref("CliFunction"),
26203
+ FunctionName: invokeName("CliFunction"),
26182
26204
  Action: "lambda:InvokeFunction",
26183
26205
  Principal: "events.amazonaws.com",
26184
26206
  SourceArn: Fn2.getAtt("SchedulerRule", "Arn")
@@ -74242,6 +74264,35 @@ class LambdaClient {
74242
74264
  body: JSON.stringify(rest)
74243
74265
  });
74244
74266
  }
74267
+ async getAlias(functionName, name) {
74268
+ try {
74269
+ return await this.client.request({
74270
+ service: "lambda",
74271
+ region: this.region,
74272
+ method: "GET",
74273
+ path: `/2015-03-31/functions/${encodeURIComponent(functionName)}/aliases/${encodeURIComponent(name)}`
74274
+ });
74275
+ } catch (err) {
74276
+ if (/ResourceNotFound/i.test(String(err?.message)))
74277
+ return null;
74278
+ throw err;
74279
+ }
74280
+ }
74281
+ async getProvisionedConcurrencyConfig(functionName, qualifier) {
74282
+ try {
74283
+ return await this.client.request({
74284
+ service: "lambda",
74285
+ region: this.region,
74286
+ method: "GET",
74287
+ path: `/2019-09-30/functions/${encodeURIComponent(functionName)}/provisioned-concurrency`,
74288
+ queryParams: { Qualifier: qualifier }
74289
+ });
74290
+ } catch (err) {
74291
+ if (/ProvisionedConcurrencyConfigNotFound|ResourceNotFound/i.test(String(err?.message)))
74292
+ return null;
74293
+ throw err;
74294
+ }
74295
+ }
74245
74296
  async putProvisionedConcurrencyConfig(params) {
74246
74297
  const { FunctionName, Qualifier, ProvisionedConcurrentExecutions } = params;
74247
74298
  return this.client.request({
@@ -83439,6 +83490,14 @@ async function applyFunction(lambda2, name, env2, code) {
83439
83490
  await withConflictRetry(() => lambda2.updateFunctionCode(codeParams));
83440
83491
  await lambda2.waitForFunctionActive(name, 120);
83441
83492
  }
83493
+ async function publishAndFlip(lambda2, name) {
83494
+ const published = await withConflictRetry(() => lambda2.publishVersion({ FunctionName: name }));
83495
+ const version2 = published.Version;
83496
+ if (!version2)
83497
+ throw new Error(`publishVersion returned no version for ${name}`);
83498
+ await withConflictRetry(() => lambda2.updateAlias({ FunctionName: name, Name: "live", FunctionVersion: version2 }));
83499
+ return version2;
83500
+ }
83442
83501
  async function deployServerlessApp(config6, environment, opts = {}) {
83443
83502
  const ctx = resolveContext(config6, environment);
83444
83503
  const { app, slug, region, stackName, artifactBucket } = ctx;
@@ -83582,12 +83641,22 @@ async function deployServerlessApp(config6, environment, opts = {}) {
83582
83641
  functionEnv.queue = buildFunctionEnv(app, ctx, environment, "queue", secrets, assetUrl, primaryQueue, infraEnv);
83583
83642
  await applyFunction(lambda2, composed.functionNames.queue, functionEnv.queue, codeSource);
83584
83643
  }
83644
+ const usesProvisionedConcurrency = (app.provisionedConcurrency ?? 0) > 0;
83645
+ const functionVersions = {};
83646
+ if (usesProvisionedConcurrency) {
83647
+ step("Publishing versions + flipping live alias");
83648
+ functionVersions.http = await publishAndFlip(lambda2, composed.functionNames.http);
83649
+ functionVersions.cli = await publishAndFlip(lambda2, composed.functionNames.cli);
83650
+ if (composed.queueNames.length)
83651
+ functionVersions.queue = await publishAndFlip(lambda2, composed.functionNames.queue);
83652
+ }
83585
83653
  await writeRelease(s32, artifactBucket, slug, environment, {
83586
83654
  sha: artifactSha,
83587
83655
  code: codeSource,
83588
83656
  previousSha: prior?.sha,
83589
83657
  previousCode: prior?.code,
83590
83658
  previousFunctionEnv: prior?.functionEnv,
83659
+ ...usesProvisionedConcurrency ? { functionVersions, previousFunctionVersions: prior?.functionVersions } : {},
83591
83660
  functionEnv,
83592
83661
  functionNames: {
83593
83662
  http: composed.functionNames.http,
@@ -83646,6 +83715,9 @@ async function redeployServerlessApp(config6, environment) {
83646
83715
  continue;
83647
83716
  step(`Re-activating ${mode} (${name})`);
83648
83717
  await applyFunction(lambda2, name, release.functionEnv[mode] ?? {}, release.code);
83718
+ const version2 = release.functionVersions?.[mode];
83719
+ if (version2)
83720
+ await withConflictRetry(() => lambda2.updateAlias({ FunctionName: name, Name: "live", FunctionVersion: version2 }));
83649
83721
  }
83650
83722
  success("Redeploy complete");
83651
83723
  }
@@ -83663,15 +83735,20 @@ async function rollbackServerlessApp(config6, environment) {
83663
83735
  step(`Restoring ${mode} (${name})`);
83664
83736
  const env2 = release.previousFunctionEnv?.[mode] ?? release.functionEnv[mode] ?? {};
83665
83737
  await applyFunction(lambda2, name, env2, release.previousCode);
83738
+ const prevVersion = release.previousFunctionVersions?.[mode];
83739
+ if (prevVersion)
83740
+ await withConflictRetry(() => lambda2.updateAlias({ FunctionName: name, Name: "live", FunctionVersion: prevVersion }));
83666
83741
  }
83667
83742
  await writeRelease(s32, ctx.artifactBucket, ctx.slug, environment, {
83668
83743
  ...release,
83669
83744
  sha: release.previousSha ?? release.sha,
83670
83745
  code: release.previousCode,
83671
83746
  functionEnv: release.previousFunctionEnv ?? release.functionEnv,
83747
+ functionVersions: release.previousFunctionVersions ?? release.functionVersions,
83672
83748
  previousSha: undefined,
83673
83749
  previousCode: undefined,
83674
83750
  previousFunctionEnv: undefined,
83751
+ previousFunctionVersions: undefined,
83675
83752
  timestamp: new Date().toISOString()
83676
83753
  });
83677
83754
  success("Rollback complete");
@@ -83688,6 +83765,10 @@ async function setMaintenance(config6, environment, enabled, bypassSecret) {
83688
83765
  if (!enabled)
83689
83766
  delete env2.MAINTENANCE_BYPASS_SECRET;
83690
83767
  await withConflictRetry(() => lambda2.updateFunctionConfiguration({ FunctionName: httpName, Environment: { Variables: env2 } }));
83768
+ if ((ctx.app.provisionedConcurrency ?? 0) > 0) {
83769
+ await lambda2.waitForFunctionActive(httpName, 120);
83770
+ await publishAndFlip(lambda2, httpName);
83771
+ }
83691
83772
  success(enabled ? "Application is now in maintenance mode (503)" : "Application is live");
83692
83773
  }
83693
83774
  async function runRemoteCommand(config6, environment, command) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/ts-cloud",
3
3
  "type": "module",
4
- "version": "0.5.8",
4
+ "version": "0.5.10",
5
5
  "description": "A lightweight, performant infrastructure-as-code library and CLI for deploying both server-based (EC2) and serverless applications.",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
7
7
  "license": "MIT",
@@ -89,8 +89,8 @@
89
89
  "test": "bun test"
90
90
  },
91
91
  "dependencies": {
92
- "@ts-cloud/aws-types": "0.5.8",
93
- "@ts-cloud/core": "0.5.8",
92
+ "@ts-cloud/aws-types": "0.5.10",
93
+ "@ts-cloud/core": "0.5.10",
94
94
  "@stacksjs/ts-xml": "^0.1.0"
95
95
  },
96
96
  "devDependencies": {