@stacksjs/ts-cloud 0.5.7 → 0.5.8

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.
@@ -43,6 +43,8 @@ export declare function resolveServerlessFunctions(config: CloudConfig, environm
43
43
  cli: string;
44
44
  };
45
45
  };
46
+ /** Resolve secrets from Secrets Manager into a flat env map. */
47
+ export declare function resolveSecrets(app: ServerlessAppConfig, region: string): Promise<Record<string, string>>;
46
48
  /**
47
49
  * Derive framework env vars (DB/Redis hosts) from the deployed stack outputs so
48
50
  * a Laravel app connects to the Aurora/RDS-Proxy/ElastiCache resources the
@@ -72,6 +74,24 @@ export declare function rollbackServerlessApp(config: CloudConfig, environment:
72
74
  export declare function setMaintenance(config: CloudConfig, environment: EnvironmentType, enabled: boolean, bypassSecret?: string): Promise<void>;
73
75
  /** Invoke the CLI function with an arbitrary command (e.g. `cloud command "migrate"`). */
74
76
  export declare function runRemoteCommand(config: CloudConfig, environment: EnvironmentType, command: string): Promise<string>;
77
+ /** One recorded `cloud command` invocation. */
78
+ export interface CommandRecord {
79
+ id: number;
80
+ command: string;
81
+ timestamp: string;
82
+ status: 'ok' | 'error';
83
+ output: string;
84
+ }
85
+ /** Run a command on the CLI function AND record it to the history log (best-effort). */
86
+ export declare function runAndRecordCommand(config: CloudConfig, environment: EnvironmentType, command: string): Promise<{
87
+ id: number;
88
+ status: 'ok' | 'error';
89
+ output: string;
90
+ }>;
91
+ /** Return the recorded command history (most-recent last). */
92
+ export declare function listCommandHistory(config: CloudConfig, environment: EnvironmentType): Promise<CommandRecord[]>;
93
+ /** Look up one history record by id (or the most recent when id is omitted). */
94
+ export declare function getCommandRecord(config: CloudConfig, environment: EnvironmentType, id?: number): Promise<CommandRecord | undefined>;
75
95
  /**
76
96
  * Run a SQL statement against a (private, in-VPC) serverless database via the CLI
77
97
  * function — no bastion needed. Requires the `tscloud/serverless` PHP bridge
package/dist/index.js CHANGED
@@ -26187,34 +26187,39 @@ function composeServerlessAppTemplate(opts) {
26187
26187
  }
26188
26188
  if (app.warm && app.warm > 0) {
26189
26189
  const TARGETS_PER_RULE = 5;
26190
- const ruleCount = Math.ceil(app.warm / TARGETS_PER_RULE);
26191
- let warmed = 0;
26192
- for (let r = 0;r < ruleCount; r++) {
26193
- const targets = Math.min(TARGETS_PER_RULE, app.warm - warmed);
26194
- resources[`WarmerRule${r}`] = {
26195
- Type: "AWS::Events::Rule",
26190
+ const fnResource = { http: "HttpFunction", queue: "QueueFunction", cli: "CliFunction" };
26191
+ const warmModes = (app.warmFunctions ?? ["http"]).filter((m) => m !== "queue" || hasQueue);
26192
+ for (const mode of warmModes) {
26193
+ const cap = mode.charAt(0).toUpperCase() + mode.slice(1);
26194
+ const ruleCount = Math.ceil(app.warm / TARGETS_PER_RULE);
26195
+ let warmed = 0;
26196
+ for (let r = 0;r < ruleCount; r++) {
26197
+ const targets = Math.min(TARGETS_PER_RULE, app.warm - warmed);
26198
+ resources[`Warmer${cap}Rule${r}`] = {
26199
+ Type: "AWS::Events::Rule",
26200
+ Properties: {
26201
+ Name: `${slug}-${environment}-warmer-${mode}-${r}`,
26202
+ ScheduleExpression: "rate(5 minutes)",
26203
+ State: "ENABLED",
26204
+ Targets: Array.from({ length: targets }, (_, i) => ({
26205
+ Id: `warm-${mode}-${r}-${i}`,
26206
+ Arn: Fn2.getAtt(fnResource[mode], "Arn"),
26207
+ Input: JSON.stringify({ warmer: true })
26208
+ }))
26209
+ }
26210
+ };
26211
+ warmed += targets;
26212
+ }
26213
+ resources[`Warmer${cap}Permission`] = {
26214
+ Type: "AWS::Lambda::Permission",
26196
26215
  Properties: {
26197
- Name: `${slug}-${environment}-warmer-${r}`,
26198
- ScheduleExpression: "rate(5 minutes)",
26199
- State: "ENABLED",
26200
- Targets: Array.from({ length: targets }, (_, i) => ({
26201
- Id: `warm-${r}-${i}`,
26202
- Arn: Fn2.getAtt("HttpFunction", "Arn"),
26203
- Input: JSON.stringify({ warmer: true })
26204
- }))
26216
+ FunctionName: Fn2.ref(fnResource[mode]),
26217
+ Action: "lambda:InvokeFunction",
26218
+ Principal: "events.amazonaws.com",
26219
+ SourceArn: Fn2.sub(`arn:aws:events:\${AWS::Region}:\${AWS::AccountId}:rule/${slug}-${environment}-warmer-${mode}-*`)
26205
26220
  }
26206
26221
  };
26207
- warmed += targets;
26208
26222
  }
26209
- resources.WarmerPermission = {
26210
- Type: "AWS::Lambda::Permission",
26211
- Properties: {
26212
- FunctionName: Fn2.ref("HttpFunction"),
26213
- Action: "lambda:InvokeFunction",
26214
- Principal: "events.amazonaws.com",
26215
- SourceArn: Fn2.sub(`arn:aws:events:\${AWS::Region}:\${AWS::AccountId}:rule/${slug}-${environment}-warmer-*`)
26216
- }
26217
- };
26218
26223
  }
26219
26224
  if (cacheEnabled) {
26220
26225
  resources.CacheTable = {
@@ -26807,6 +26812,8 @@ function parseRecordBody(body) {
26807
26812
  function createQueueHandler(handler8) {
26808
26813
  return async (event) => {
26809
26814
  const batchItemFailures = [];
26815
+ if (event.warmer)
26816
+ return { batchItemFailures };
26810
26817
  if (!handler8)
26811
26818
  return { batchItemFailures };
26812
26819
  for (const record of event.Records ?? []) {
@@ -26821,6 +26828,8 @@ function createQueueHandler(handler8) {
26821
26828
  }
26822
26829
  function createCliHandler(handler8) {
26823
26830
  return async (event) => {
26831
+ if (event.warmer)
26832
+ return { statusCode: 0, output: "warm" };
26824
26833
  if (!handler8)
26825
26834
  return { statusCode: 501, output: "No CLI handler configured" };
26826
26835
  return handler8(event);
@@ -58022,6 +58031,9 @@ function colorize(text, color) {
58022
58031
  function success(message) {
58023
58032
  console.log(`${colors.green}✓${colors.reset} ${message}`);
58024
58033
  }
58034
+ function error(message) {
58035
+ console.error(`${colors.red}✗${colors.reset} ${message}`);
58036
+ }
58025
58037
  function warn(message) {
58026
58038
  console.warn(`${colors.yellow}⚠${colors.reset} ${message}`);
58027
58039
  }
@@ -83301,6 +83313,16 @@ async function resolveSecrets(app, region) {
83301
83313
  const sm = new SecretsManagerClient(region);
83302
83314
  const out = {};
83303
83315
  const entries = Array.isArray(app.secrets) ? app.secrets.map((name) => ({ envName: name.split("/").pop().toUpperCase().replace(/[^A-Z0-9_]/g, "_"), secretId: name })) : Object.entries(app.secrets).map(([envName, secretId]) => ({ envName, secretId }));
83316
+ if (Array.isArray(app.secrets)) {
83317
+ const byName = new Map;
83318
+ for (const { envName, secretId } of entries)
83319
+ byName.set(envName, [...byName.get(envName) ?? [], secretId]);
83320
+ const clashes = [...byName.entries()].filter(([, ids]) => ids.length > 1);
83321
+ if (clashes.length) {
83322
+ const detail = clashes.map(([name, ids]) => `${name} ← ${ids.join(", ")}`).join("; ");
83323
+ throw new Error(`serverless secrets: multiple secrets map to the same env var (${detail}). Use the map form { ENV_NAME: 'secret/id' } to disambiguate.`);
83324
+ }
83325
+ }
83304
83326
  for (const { envName, secretId } of entries) {
83305
83327
  const value = await sm.getSecretValue({ SecretId: secretId });
83306
83328
  const str = value.SecretString ?? "";
@@ -83586,6 +83608,8 @@ async function deployServerlessApp(config6, environment, opts = {}) {
83586
83608
  LogType: "Tail"
83587
83609
  });
83588
83610
  if (res.FunctionError) {
83611
+ error("Deploy hook failed — the new build is ALREADY LIVE.");
83612
+ info(` Roll back with: cloud serverless:rollback --env ${environment}`);
83589
83613
  throw new Error(`Deploy hook failed (${command}): ${res.Payload}`);
83590
83614
  }
83591
83615
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/ts-cloud",
3
3
  "type": "module",
4
- "version": "0.5.7",
4
+ "version": "0.5.8",
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.7",
93
- "@ts-cloud/core": "0.5.7",
92
+ "@ts-cloud/aws-types": "0.5.8",
93
+ "@ts-cloud/core": "0.5.8",
94
94
  "@stacksjs/ts-xml": "^0.1.0"
95
95
  },
96
96
  "devDependencies": {