awscfn 1.4.0 → 1.5.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.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
10
10
  [![AutoRel](https://img.shields.io/badge/%F0%9F%9A%80%20AutoRel-2D4DDE)](https://github.com/mhweiner/autorel)
11
11
 
12
- Deploy CloudFormation stacks without the usual suffering.
12
+ Deploy CloudFormation stacks with confidence.
13
13
 
14
14
  ## Why awscfn?
15
15
 
@@ -104,6 +104,28 @@ List all CloudFormation stacks in the current region (name, status, creation dat
104
104
  awscfn list-stacks
105
105
  ```
106
106
 
107
+ ### 🔎 inspect-stack
108
+
109
+ Inspect a **deployed** stack in a **read-only pager view** (TUI-like), including:
110
+
111
+ - full deployed template body
112
+ - current parameters and outputs
113
+ - stack metadata (raw JSON details)
114
+ - event timeline (chronological)
115
+ - failure events and likely root-cause errors
116
+
117
+ ```bash
118
+ awscfn inspect-stack -n <STACK_NAME>
119
+ ```
120
+
121
+ | Flag | Description |
122
+ |------|-------------|
123
+ | `--name`, `-n` | Stack name |
124
+ | `--events`, `-e` | Max stack events to fetch (`0` = all available events). Default `500` |
125
+ | `--pager` / `--no-pager` | Use a view-only pager (default true). Disable to print directly |
126
+
127
+ This command performs read-only CloudFormation API calls (`DescribeStacks`, `GetTemplate`, `DescribeStackEvents`) and makes no stack changes.
128
+
107
129
  ### 🚀 create-stack
108
130
 
109
131
  ```bash
package/bin/awscfn CHANGED
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs');
4
+ const os = require('os');
4
5
  const path = require('path');
6
+ const { spawnSync } = require('node:child_process');
5
7
  const yargs = require('yargs');
6
8
 
7
9
  const { createStack } = require('../dist/createStack');
8
10
  const { deleteStack } = require('../dist/deleteStack');
9
11
  const { listStacks } = require('../dist/listStacks');
12
+ const { inspectStack } = require('../dist/inspectStack');
10
13
  const { previewStack } = require('../dist/previewStack');
11
14
  const { redeployStack } = require('../dist/redeployStack');
12
15
  const { updateStack } = require('../dist/updateStack');
@@ -16,6 +19,9 @@ const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'
16
19
  const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
17
20
 
18
21
  const FILE_FLAGS = ['--template', '--params', '-t', '-p'];
22
+ const ASCII_LOGO = [
23
+ 'awscfn',
24
+ ];
19
25
 
20
26
  function completeFile(current) {
21
27
  const dir = path.dirname(current) || '.';
@@ -49,6 +55,65 @@ function logError(msg) {
49
55
  console.log(`\x1b[31mError: ${msg}\x1b[0m`);
50
56
  }
51
57
 
58
+ function maybeColorize(text, colorCode) {
59
+ if (!process.stdout.isTTY || process.env.NO_COLOR) {
60
+ return text;
61
+ }
62
+ return `${colorCode}${text}\x1b[0m`;
63
+ }
64
+
65
+ function runImageCommand(command, args) {
66
+ const result = spawnSync(command, args, { stdio: 'inherit' });
67
+ return !result.error && result.status === 0;
68
+ }
69
+
70
+ function tryPrintTerminalLogo() {
71
+ if (!process.stdout.isTTY || process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true') {
72
+ return false;
73
+ }
74
+
75
+ if (process.env.TERM_PROGRAM === 'vscode') {
76
+ return false;
77
+ }
78
+
79
+ const logoPath = path.resolve(__dirname, '../docs/awscfn-logo-light.svg');
80
+ if (!fs.existsSync(logoPath)) {
81
+ return false;
82
+ }
83
+
84
+ // Try native terminal image commands (best effort).
85
+ if (process.env.WEZTERM_PANE && runImageCommand('wezterm', ['imgcat', '--width', '40%', logoPath])) {
86
+ return true;
87
+ }
88
+
89
+ if (process.env.KITTY_WINDOW_ID && runImageCommand('kitty', ['+kitten', 'icat', '--align', 'left', logoPath])) {
90
+ return true;
91
+ }
92
+
93
+ if (process.env.ITERM_SESSION_ID && runImageCommand('imgcat', [logoPath])) {
94
+ return true;
95
+ }
96
+
97
+ return false;
98
+ }
99
+
100
+ function printLandingHeader() {
101
+ const boldWhite = '\x1b[1;37m';
102
+ const gray = '\x1b[90m';
103
+ const tagline = 'Deploy CloudFormation stacks with confidence.';
104
+ const hr = maybeColorize('─'.repeat(tagline.length), gray);
105
+
106
+ if (!tryPrintTerminalLogo()) {
107
+ console.log(maybeColorize(ASCII_LOGO.join('\n'), boldWhite));
108
+ }
109
+
110
+ console.log(hr);
111
+ console.log(tagline);
112
+ console.log(hr);
113
+ console.log(maybeColorize('Quick start: awscfn list-stacks | awscfn inspect-stack -n <stack-name>', gray));
114
+ console.log('');
115
+ }
116
+
52
117
  function logErrorDetails(data) {
53
118
  if (data) {
54
119
  console.log('\nError details:');
@@ -56,13 +121,162 @@ function logErrorDetails(data) {
56
121
  }
57
122
  }
58
123
 
124
+ function getProfileAwareCommand(baseCommand) {
125
+ const profile = process.env.AWS_PROFILE;
126
+ if (!profile) return baseCommand;
127
+
128
+ return `${baseCommand} --profile ${profile}`;
129
+ }
130
+
131
+ function getAwsConfigPath() {
132
+ if (process.env.AWS_CONFIG_FILE) return process.env.AWS_CONFIG_FILE;
133
+
134
+ return path.join(os.homedir(), '.aws', 'config');
135
+ }
136
+
137
+ function getAwsCredentialsPath() {
138
+ if (process.env.AWS_SHARED_CREDENTIALS_FILE) return process.env.AWS_SHARED_CREDENTIALS_FILE;
139
+
140
+ return path.join(os.homedir(), '.aws', 'credentials');
141
+ }
142
+
143
+ function readIniSections(filePath) {
144
+ try {
145
+ const content = fs.readFileSync(filePath, 'utf8');
146
+
147
+ return content
148
+ .split('\n')
149
+ .map((line) => line.trim())
150
+ .filter((line) => line.startsWith('[') && line.endsWith(']'))
151
+ .map((line) => line.slice(1, -1).trim());
152
+ } catch {
153
+ return [];
154
+ }
155
+ }
156
+
157
+ function getConfiguredProfiles() {
158
+ const configSections = readIniSections(getAwsConfigPath())
159
+ .map((section) => section.startsWith('profile ') ? section.slice('profile '.length) : section);
160
+ const credentialSections = readIniSections(getAwsCredentialsPath());
161
+
162
+ return new Set([...configSections, ...credentialSections]);
163
+ }
164
+
165
+ function hasCredentialHints() {
166
+ if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
167
+ return true;
168
+ }
169
+
170
+ const configuredProfiles = getConfiguredProfiles();
171
+ const requestedProfile = process.env.AWS_PROFILE;
172
+
173
+ if (requestedProfile) return configuredProfiles.has(requestedProfile);
174
+
175
+ return configuredProfiles.has('default');
176
+ }
177
+
178
+ function formatRegionOnlyHelp() {
179
+ const configureCmd = getProfileAwareCommand('aws configure');
180
+ const showRegionCmd = getProfileAwareCommand('aws configure get region');
181
+
182
+ return [
183
+ 'AWS region is not configured.',
184
+ [
185
+ 'Set a region:',
186
+ ' • export AWS_REGION=us-east-1',
187
+ ` • ${configureCmd}`,
188
+ ].join('\n'),
189
+ [
190
+ 'Check current region:',
191
+ ` • ${showRegionCmd}`,
192
+ ].join('\n'),
193
+ 'Tip: if you use named profiles, set AWS_PROFILE first.',
194
+ ].join('\n\n');
195
+ }
196
+
197
+ function formatAuthSetupHelp(profileMissing) {
198
+ const profile = process.env.AWS_PROFILE;
199
+ const showRegionCmd = getProfileAwareCommand('aws configure get region');
200
+ const configureSsoCmd = getProfileAwareCommand('aws configure sso');
201
+ const ssoLoginCmd = getProfileAwareCommand('aws sso login');
202
+ const stsCheckCmd = getProfileAwareCommand('aws sts get-caller-identity');
203
+ const configureCmd = getProfileAwareCommand('aws configure');
204
+ const profileHint = profile ? `Using AWS profile '${profile}'.` : 'Using default AWS profile.';
205
+
206
+ const profileSection = profileMissing && profile
207
+ ? [
208
+ `Profile '${profile}' was not found.`,
209
+ `Create it first: ${configureSsoCmd}`,
210
+ ].join('\n')
211
+ : profileHint;
212
+
213
+ return [
214
+ 'AWS authentication is not configured for this command yet.',
215
+ profileSection,
216
+ 'Choose one authentication method:',
217
+ [
218
+ 'AWS SSO:',
219
+ ` • ${configureSsoCmd}`,
220
+ ` • ${ssoLoginCmd}`,
221
+ ].join('\n'),
222
+ [
223
+ 'Access keys:',
224
+ ` • ${configureCmd} # adds keys + default region`,
225
+ ].join('\n'),
226
+ [
227
+ 'Then verify identity:',
228
+ ` • ${stsCheckCmd}`,
229
+ ].join('\n'),
230
+ [
231
+ 'If region is still missing:',
232
+ ' • export AWS_REGION=us-east-1',
233
+ ` • ${showRegionCmd}`,
234
+ ].join('\n'),
235
+ ].filter(Boolean).join('\n\n');
236
+ }
237
+
238
+ function formatAuthorizationHelp() {
239
+ const stsCheckCmd = getProfileAwareCommand('aws sts get-caller-identity');
240
+
241
+ return [
242
+ 'AWS authentication succeeded, but this identity does not have enough permissions.',
243
+ [
244
+ 'Confirm which identity is active:',
245
+ ` • ${stsCheckCmd}`,
246
+ ].join('\n'),
247
+ 'Then ask for the required CloudFormation permissions for this account/role.',
248
+ ].join('\n\n');
249
+ }
250
+
59
251
  function clarifyAwsError(msg) {
60
- if (/region\s+is\s+missing|configuration.*region|Missing required key 'region'/i.test(msg)) {
61
- return 'AWS region is not set. Set AWS_REGION or AWS_DEFAULT_REGION, or run `aws configure` (or `aws configure sso` if your org uses SSO).';
252
+ const isRegionMissing = /region\s+is\s+missing|configuration.*region|Missing required key 'region'/i.test(msg);
253
+ const isCredentialsMissing = /Unable to locate credentials|Could not load credentials|NoCredentialProviders/i.test(msg)
254
+ || /Token is expired|ExpiredToken|security token included in the request is expired/i.test(msg)
255
+ || /sso.*expired|refresh failed|SSO session.*invalid|aws sso login/i.test(msg)
256
+ || /resolved credential object is not valid|credential.*could not be loaded/i.test(msg);
257
+ const requestedProfile = process.env.AWS_PROFILE;
258
+ const requestedProfileMissing = Boolean(
259
+ requestedProfile && !getConfiguredProfiles().has(requestedProfile),
260
+ );
261
+ const isProfileMissing = /config profile .* could not be found|profile.*not found/i.test(msg)
262
+ || requestedProfileMissing;
263
+
264
+ if (isRegionMissing && hasCredentialHints() && !isProfileMissing) {
265
+ return formatRegionOnlyHelp();
62
266
  }
63
- if (/credentials|Unable to locate credentials|Token is expired/i.test(msg)) {
64
- return 'AWS credentials not found or expired. Set AWS_PROFILE, AWS_ACCESS_KEY_ID/secret, or run `aws configure` (or `aws configure sso` if your org uses SSO).';
267
+
268
+ if (
269
+ isRegionMissing
270
+ || isCredentialsMissing
271
+ || isProfileMissing
272
+ ) {
273
+ return formatAuthSetupHelp(isProfileMissing);
274
+ }
275
+
276
+ if (/AccessDenied|UnauthorizedOperation|is not authorized to perform|AccessDeniedException/i.test(msg)) {
277
+ return formatAuthorizationHelp();
65
278
  }
279
+
66
280
  return msg;
67
281
  }
68
282
 
@@ -70,11 +284,17 @@ async function runCommand(fn) {
70
284
  try {
71
285
  await fn();
72
286
  } catch (err) {
73
- const message = clarifyAwsError(err.message);
74
- logError(message);
75
- if (message !== err.message) {
76
- console.log(`\x1b[90m(original: ${err.message})\x1b[0m`);
287
+ const originalError = err?.message || String(err);
288
+ const message = clarifyAwsError(originalError);
289
+
290
+ logError(originalError);
291
+
292
+ if (message !== originalError) {
293
+ console.log('');
294
+ console.log(maybeColorize('Suggested next steps:', '\x1b[36m'));
295
+ console.log(message);
77
296
  }
297
+
78
298
  if (getOutputConfig().verbose) {
79
299
  logErrorDetails(err.data);
80
300
  }
@@ -113,8 +333,31 @@ const createIfMissingOpt = {
113
333
  const confirmOpt = {
114
334
  confirm: { type: 'string', alias: 'c', describe: 'Repeat stack name to confirm deletion', demandOption: true },
115
335
  };
336
+ const eventsLimitOpt = {
337
+ events: {
338
+ type: 'number',
339
+ alias: 'e',
340
+ describe: 'Max stack events to fetch (0 means all available events)',
341
+ demandOption: false,
342
+ default: 500,
343
+ },
344
+ };
345
+ const pagerOpt = {
346
+ pager: {
347
+ type: 'boolean',
348
+ describe: 'Open output in a view-only pager (use --no-pager to print directly)',
349
+ demandOption: false,
350
+ default: true,
351
+ },
352
+ };
116
353
 
117
- yargs
354
+ const cli = yargs
355
+ .scriptName('awscfn')
356
+ .usage('Usage:\n $0 <command> [options]')
357
+ .example('$0 list-stacks', 'List active stacks in the current AWS region')
358
+ .example('$0 inspect-stack -n my-stack', 'Open a read-only stack inspection view')
359
+ .example('$0 update-stack -n my-stack -t ./template.yaml -p ./params.yaml', 'Update an existing stack')
360
+ .epilog('Tip: run `awscfn <command> --help` for command-specific help.')
118
361
  .option('ci', {
119
362
  type: 'boolean',
120
363
  alias: 'C',
@@ -188,6 +431,14 @@ yargs
188
431
  runCommand(() => previewStack(name, template, params, require('../dist/cli/parseParamOverrides').parseParamOverrides(set).overrides));
189
432
  }
190
433
  )
434
+ .command(
435
+ 'inspect-stack',
436
+ 'Inspect a deployed stack in a read-only pager view',
437
+ (cmd) => cmd.options({ ...nameOpt, ...eventsLimitOpt, ...pagerOpt }),
438
+ ({ name, events, pager }) => {
439
+ runCommand(() => inspectStack(name, { eventsLimit: Number(events), usePager: Boolean(pager) }));
440
+ }
441
+ )
191
442
  .completion(
192
443
  'completion',
193
444
  'Generate shell completion script',
@@ -210,5 +461,12 @@ yargs
210
461
  .help()
211
462
  .alias('h', 'help')
212
463
  .version(pkg.version)
213
- .alias('v', 'version')
214
- .argv;
464
+ .alias('v', 'version');
465
+
466
+ if (process.argv.length <= 2) {
467
+ printLandingHeader();
468
+ cli.showHelp();
469
+ process.exit(0);
470
+ }
471
+
472
+ cli.argv;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './lib/cfn';
2
2
  export * from './lib/output';
3
3
  export { previewStack } from './previewStack';
4
+ export { inspectStack } from './inspectStack';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -14,9 +14,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.previewStack = void 0;
17
+ exports.inspectStack = exports.previewStack = void 0;
18
18
  __exportStar(require("./lib/cfn"), exports);
19
19
  __exportStar(require("./lib/output"), exports);
20
20
  var previewStack_1 = require("./previewStack");
21
21
  Object.defineProperty(exports, "previewStack", { enumerable: true, get: function () { return previewStack_1.previewStack; } });
22
+ var inspectStack_1 = require("./inspectStack");
23
+ Object.defineProperty(exports, "inspectStack", { enumerable: true, get: function () { return inspectStack_1.inspectStack; } });
22
24
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,4CAA0B;AAC1B,+CAA6B;AAC7B,+CAA4C;AAApC,4GAAA,YAAY,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,4CAA0B;AAC1B,+CAA6B;AAC7B,+CAA4C;AAApC,4GAAA,YAAY,OAAA;AACpB,+CAA4C;AAApC,4GAAA,YAAY,OAAA"}
@@ -0,0 +1,6 @@
1
+ export interface InspectStackOptions {
2
+ eventsLimit: number;
3
+ usePager: boolean;
4
+ }
5
+ export declare function inspectStack(stackName: string, options?: Partial<InspectStackOptions>): Promise<void>;
6
+ //# sourceMappingURL=inspectStack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspectStack.d.ts","sourceRoot":"","sources":["../src/inspectStack.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,OAAO,CAAA;CACpB;AAwND,wBAAsB,YAAY,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.inspectStack = inspectStack;
37
+ const cfn = __importStar(require("./lib/cfn"));
38
+ const viewText_1 = require("./lib/viewText");
39
+ const DEFAULT_OPTIONS = {
40
+ eventsLimit: 500,
41
+ usePager: true,
42
+ };
43
+ const GENERIC_FAILURE_PATTERNS = [
44
+ /resource creation cancelled/i,
45
+ /resource update cancelled/i,
46
+ /the following resource\(s\) failed/i,
47
+ /user initiated/i,
48
+ ];
49
+ function toIso(value) {
50
+ if (!value) {
51
+ return '—';
52
+ }
53
+ return value.toISOString();
54
+ }
55
+ function toTimestamp(event) {
56
+ return event.Timestamp?.getTime() ?? 0;
57
+ }
58
+ function toChronological(events) {
59
+ return events.slice().sort((left, right) => toTimestamp(left) - toTimestamp(right));
60
+ }
61
+ function isFailureStatus(status) {
62
+ return status.includes('FAILED') || status.includes('ROLLBACK');
63
+ }
64
+ function isGenericFailureReason(reason) {
65
+ return GENERIC_FAILURE_PATTERNS.some((pattern) => pattern.test(reason));
66
+ }
67
+ function getFailureEvents(events) {
68
+ const out = [];
69
+ for (const event of events) {
70
+ const status = event.ResourceStatus ?? '';
71
+ const reason = event.ResourceStatusReason ?? '';
72
+ if (!isFailureStatus(status) || !reason)
73
+ continue;
74
+ out.push({
75
+ timestamp: toIso(event.Timestamp),
76
+ resource: `${event.LogicalResourceId ?? 'Unknown'} (${event.ResourceType ?? 'Unknown'})`,
77
+ status,
78
+ reason,
79
+ });
80
+ }
81
+ return out;
82
+ }
83
+ function uniqueRootCauseCandidates(failures) {
84
+ const seenReasons = new Set();
85
+ const rootCauses = [];
86
+ for (const failure of failures) {
87
+ if (isGenericFailureReason(failure.reason) || seenReasons.has(failure.reason))
88
+ continue;
89
+ seenReasons.add(failure.reason);
90
+ rootCauses.push(failure);
91
+ }
92
+ return rootCauses;
93
+ }
94
+ function formatSection(title, body) {
95
+ return `${title}\n${'-'.repeat(title.length)}\n${body}`;
96
+ }
97
+ function formatParameters(stack) {
98
+ const params = stack.Parameters ?? [];
99
+ if (params.length === 0)
100
+ return 'No parameters on stack.';
101
+ return params
102
+ .map((param) => `${param.ParameterKey ?? 'Unknown'}=${param.ParameterValue ?? '(no value returned)'}`)
103
+ .join('\n');
104
+ }
105
+ function formatOutputs(stack) {
106
+ const outputs = stack.Outputs ?? [];
107
+ if (outputs.length === 0)
108
+ return 'No outputs on stack.';
109
+ return outputs.map((output) => `${output.OutputKey ?? 'Unknown'}=${output.OutputValue ?? ''}`).join('\n');
110
+ }
111
+ function formatRootCauses(failures) {
112
+ const rootCauses = uniqueRootCauseCandidates(failures).slice(0, 10);
113
+ if (rootCauses.length === 0)
114
+ return 'No specific root-cause errors detected.';
115
+ return rootCauses
116
+ .map((failure) => `[${failure.timestamp}] ${failure.status} ${failure.resource}\n Reason: ${failure.reason}`)
117
+ .join('\n\n');
118
+ }
119
+ function formatFailureEvents(failures) {
120
+ if (failures.length === 0)
121
+ return 'No failure events found.';
122
+ return failures
123
+ .slice(-50)
124
+ .map((failure) => `[${failure.timestamp}] ${failure.status} ${failure.resource}\n Reason: ${failure.reason}`)
125
+ .join('\n\n');
126
+ }
127
+ function formatEvent(event) {
128
+ const timestamp = toIso(event.Timestamp);
129
+ const status = event.ResourceStatus ?? 'UNKNOWN';
130
+ const logical = event.LogicalResourceId ?? 'Unknown';
131
+ const type = event.ResourceType ?? 'Unknown';
132
+ const reason = event.ResourceStatusReason ? `\n Reason: ${event.ResourceStatusReason}` : '';
133
+ return `[${timestamp}] ${status} ${logical} (${type})${reason}`;
134
+ }
135
+ function formatEvents(events, eventsLimit) {
136
+ if (events.length === 0)
137
+ return 'No stack events found.';
138
+ const chronological = toChronological(events);
139
+ const header = eventsLimit > 0
140
+ ? `Showing up to ${eventsLimit} events in chronological order.`
141
+ : 'Showing all events in chronological order.';
142
+ return `${header}\n\n${chronological.map((event) => formatEvent(event)).join('\n\n')}`;
143
+ }
144
+ function stackDetailsJson(stack) {
145
+ return JSON.stringify(stack, (_key, value) => value instanceof Date ? value.toISOString() : value, 2);
146
+ }
147
+ function stackSummary(stack) {
148
+ const lines = [
149
+ `StackName: ${stack.StackName ?? '—'}`,
150
+ `StackId: ${stack.StackId ?? '—'}`,
151
+ `Status: ${stack.StackStatus ?? '—'}`,
152
+ `StatusReason: ${stack.StackStatusReason ?? '—'}`,
153
+ `CreatedAt: ${toIso(stack.CreationTime)}`,
154
+ `UpdatedAt: ${toIso(stack.LastUpdatedTime)}`,
155
+ `DriftStatus: ${stack.DriftInformation?.StackDriftStatus ?? '—'}`,
156
+ `EnableTerminationProtection: ${stack.EnableTerminationProtection ?? false}`,
157
+ ];
158
+ return lines.join('\n');
159
+ }
160
+ function buildReport(stack, templateBody, events, options) {
161
+ const failures = getFailureEvents(toChronological(events));
162
+ const sections = [
163
+ formatSection('Stack Summary', stackSummary(stack)),
164
+ formatSection('Stack Details (Raw JSON)', stackDetailsJson(stack)),
165
+ formatSection('Parameters', formatParameters(stack)),
166
+ formatSection('Outputs', formatOutputs(stack)),
167
+ formatSection('Likely Root Causes', formatRootCauses(failures)),
168
+ formatSection('Failure Events', formatFailureEvents(failures)),
169
+ formatSection('Events', formatEvents(events, options.eventsLimit)),
170
+ formatSection('Full Deployed Template', templateBody || 'Template body is empty.'),
171
+ ];
172
+ return ['awscfn inspect-stack', '='.repeat(20), '', ...sections].join('\n\n');
173
+ }
174
+ async function inspectStack(stackName, options) {
175
+ cfn.initCloudFormationClient();
176
+ const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };
177
+ const stack = await cfn.getStackByName(stackName, true);
178
+ if (!stack)
179
+ throw new Error(`stack ${stackName} does not exist`);
180
+ const [templateBody, events] = await Promise.all([
181
+ cfn.getStackTemplateBody(stackName),
182
+ cfn.listStackEvents(stackName, resolvedOptions.eventsLimit),
183
+ ]);
184
+ const report = buildReport(stack, templateBody, events, resolvedOptions);
185
+ (0, viewText_1.viewText)(`inspect-${stackName}`, report, { usePager: resolvedOptions.usePager });
186
+ }
187
+ //# sourceMappingURL=inspectStack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspectStack.js","sourceRoot":"","sources":["../src/inspectStack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function getStackTemplateBody(stackName: string): Promise<string>;
2
+ //# sourceMappingURL=getStackTemplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getStackTemplate.d.ts","sourceRoot":"","sources":["../../../src/lib/cfn/getStackTemplate.ts"],"names":[],"mappings":"AAiCA,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY7E"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStackTemplateBody = getStackTemplateBody;
4
+ const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
5
+ const _1 = require(".");
6
+ function toTemplateString(templateBody) {
7
+ if (typeof templateBody === 'string') {
8
+ return templateBody;
9
+ }
10
+ if (!templateBody) {
11
+ return '';
12
+ }
13
+ return JSON.stringify(templateBody, null, 2);
14
+ }
15
+ async function fetchTemplate(stage, stackName) {
16
+ const cf = (0, _1.getCfClient)();
17
+ const response = await cf.send(new client_cloudformation_1.GetTemplateCommand({
18
+ StackName: stackName,
19
+ TemplateStage: stage,
20
+ }));
21
+ return toTemplateString(response.TemplateBody);
22
+ }
23
+ async function getStackTemplateBody(stackName) {
24
+ const original = await fetchTemplate('Original', stackName);
25
+ if (original) {
26
+ return original;
27
+ }
28
+ return fetchTemplate('Processed', stackName);
29
+ }
30
+ //# sourceMappingURL=getStackTemplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getStackTemplate.js","sourceRoot":"","sources":["../../../src/lib/cfn/getStackTemplate.ts"],"names":[],"mappings":";;AAiCA,oDAYC;AA7CD,0EAAkE;AAClE,wBAA8B;AAE9B,SAAS,gBAAgB,CAAC,YAAqB;IAE3C,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QAEnC,OAAO,YAAY,CAAC;IAExB,CAAC;IAED,IAAI,CAAC,YAAY,EAAE,CAAC;QAEhB,OAAO,EAAE,CAAC;IAEd,CAAC;IAED,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAEjD,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,KAA6B,EAAE,SAAiB;IAEzE,MAAM,EAAE,GAAG,IAAA,cAAW,GAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,0CAAkB,CAAC;QAClD,SAAS,EAAE,SAAS;QACpB,aAAa,EAAE,KAAK;KACvB,CAAC,CAAC,CAAC;IAEJ,OAAO,gBAAgB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAEnD,CAAC;AAEM,KAAK,UAAU,oBAAoB,CAAC,SAAiB;IAExD,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAE5D,IAAI,QAAQ,EAAE,CAAC;QAEX,OAAO,QAAQ,CAAC;IAEpB,CAAC;IAED,OAAO,aAAa,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AAEjD,CAAC"}
@@ -18,4 +18,6 @@ export * from './isStackTerminal';
18
18
  export * from './waitUntilStackTerminal';
19
19
  export * from './getParamFromStack';
20
20
  export * from './streamStackEvents';
21
+ export * from './getStackTemplate';
22
+ export * from './listStackEvents';
21
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/cfn/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,gCAAgC,CAAC;AAEpE,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACjD,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,cAAc,IAAI;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,CAAC,CAAA;CACZ,GAAG,MAAM,CAAC;AAIX,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAED,wBAAgB,WAAW,IAAI,oBAAoB,CAMlD;AAED,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/cfn/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,gCAAgC,CAAC;AAEpE,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACjD,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,cAAc,IAAI;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,CAAC,CAAA;CACZ,GAAG,MAAM,CAAC;AAIX,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAED,wBAAgB,WAAW,IAAI,oBAAoB,CAMlD;AAED,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
@@ -38,4 +38,6 @@ __exportStar(require("./isStackTerminal"), exports);
38
38
  __exportStar(require("./waitUntilStackTerminal"), exports);
39
39
  __exportStar(require("./getParamFromStack"), exports);
40
40
  __exportStar(require("./streamStackEvents"), exports);
41
+ __exportStar(require("./getStackTemplate"), exports);
42
+ __exportStar(require("./listStackEvents"), exports);
41
43
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/cfn/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAUA,4DAIC;AAED,kCAMC;AAtBD,0EAAoE;AAQpE,IAAI,EAAkC,CAAC;AAEvC,SAAgB,wBAAwB;IAEpC,EAAE,GAAG,IAAI,4CAAoB,EAAE,CAAC;AAEpC,CAAC;AAED,SAAgB,WAAW;IAEvB,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAElE,OAAO,EAAE,CAAC;AAEd,CAAC;AAED,gDAA8B;AAC9B,mDAAiC;AACjC,+CAA6B;AAC7B,qDAAmC;AACnC,gDAA8B;AAC9B,gDAA8B;AAC9B,qDAAmC;AACnC,sDAAoC;AACpC,oDAAkC;AAClC,2DAAyC;AACzC,sDAAoC;AACpC,sDAAoC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/cfn/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAUA,4DAIC;AAED,kCAMC;AAtBD,0EAAoE;AAQpE,IAAI,EAAkC,CAAC;AAEvC,SAAgB,wBAAwB;IAEpC,EAAE,GAAG,IAAI,4CAAoB,EAAE,CAAC;AAEpC,CAAC;AAED,SAAgB,WAAW;IAEvB,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAElE,OAAO,EAAE,CAAC;AAEd,CAAC;AAED,gDAA8B;AAC9B,mDAAiC;AACjC,+CAA6B;AAC7B,qDAAmC;AACnC,gDAA8B;AAC9B,gDAA8B;AAC9B,qDAAmC;AACnC,sDAAoC;AACpC,oDAAkC;AAClC,2DAAyC;AACzC,sDAAoC;AACpC,sDAAoC;AACpC,qDAAmC;AACnC,oDAAkC"}
@@ -0,0 +1,3 @@
1
+ import { StackEvent } from '@aws-sdk/client-cloudformation';
2
+ export declare function listStackEvents(stackName: string, limit?: number): Promise<StackEvent[]>;
3
+ //# sourceMappingURL=listStackEvents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listStackEvents.d.ts","sourceRoot":"","sources":["../../../src/lib/cfn/listStackEvents.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,UAAU,EAAC,MAAM,gCAAgC,CAAC;AAetF,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAY,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAoBnG"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listStackEvents = listStackEvents;
4
+ const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
5
+ const _1 = require(".");
6
+ function trimToLimit(events, limit) {
7
+ if (limit <= 0) {
8
+ return events;
9
+ }
10
+ return events.slice(0, limit);
11
+ }
12
+ async function listStackEvents(stackName, limit = 500) {
13
+ const cf = (0, _1.getCfClient)();
14
+ const events = [];
15
+ let nextToken;
16
+ do {
17
+ const response = await cf.send(new client_cloudformation_1.DescribeStackEventsCommand({
18
+ StackName: stackName,
19
+ NextToken: nextToken,
20
+ }));
21
+ events.push(...(response.StackEvents ?? []));
22
+ nextToken = response.NextToken;
23
+ } while (nextToken && (limit <= 0 || events.length < limit));
24
+ return trimToLimit(events, limit);
25
+ }
26
+ //# sourceMappingURL=listStackEvents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listStackEvents.js","sourceRoot":"","sources":["../../../src/lib/cfn/listStackEvents.ts"],"names":[],"mappings":";;AAeA,0CAoBC;AAnCD,0EAAsF;AACtF,wBAA8B;AAE9B,SAAS,WAAW,CAAC,MAAoB,EAAE,KAAa;IAEpD,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QAEb,OAAO,MAAM,CAAC;IAElB,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAElC,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,SAAiB,EAAE,QAAgB,GAAG;IAExE,MAAM,EAAE,GAAG,IAAA,cAAW,GAAE,CAAC;IACzB,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,SAA2B,CAAC;IAEhC,GAAG,CAAC;QAEA,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,kDAA0B,CAAC;YAC1D,SAAS,EAAE,SAAS;YACpB,SAAS,EAAE,SAAS;SACvB,CAAC,CAAC,CAAC;QAEJ,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7C,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;IAEnC,CAAC,QAAQ,SAAS,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,EAAE;IAE7D,OAAO,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAEtC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface ViewTextOptions {
2
+ usePager: boolean;
3
+ }
4
+ export declare function viewText(reportName: string, content: string, options: ViewTextOptions): void;
5
+ //# sourceMappingURL=viewText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewText.d.ts","sourceRoot":"","sources":["../../src/lib/viewText.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC5B,QAAQ,EAAE,OAAO,CAAA;CACpB;AA2DD,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAyB5F"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.viewText = viewText;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_child_process_1 = require("node:child_process");
11
+ function runViewerCommand(command, args) {
12
+ const result = (0, node_child_process_1.spawnSync)(command, args, { stdio: 'inherit' });
13
+ return !result.error && result.status === 0;
14
+ }
15
+ function runPagerExpression(pagerExpression, filePath) {
16
+ const result = (0, node_child_process_1.spawnSync)(`${pagerExpression} "${filePath}"`, {
17
+ shell: true,
18
+ stdio: 'inherit',
19
+ });
20
+ return !result.error && result.status === 0;
21
+ }
22
+ function openWithPager(filePath) {
23
+ const pagerExpression = process.env.GIT_PAGER ?? process.env.PAGER;
24
+ if (pagerExpression && runPagerExpression(pagerExpression, filePath)) {
25
+ return true;
26
+ }
27
+ return runViewerCommand('less', ['-R', filePath]) || runViewerCommand('more', [filePath]);
28
+ }
29
+ function writeTempReport(reportName, content) {
30
+ const tempDir = (0, node_fs_1.mkdtempSync)(node_path_1.default.join((0, node_os_1.tmpdir)(), 'awscfn-'));
31
+ const safeName = reportName.replace(/[^a-zA-Z0-9-]/g, '-');
32
+ const filePath = node_path_1.default.join(tempDir, `${safeName}.txt`);
33
+ (0, node_fs_1.writeFileSync)(filePath, content, 'utf8');
34
+ return filePath;
35
+ }
36
+ function readAndPrint(filePath) {
37
+ console.log((0, node_fs_1.readFileSync)(filePath, 'utf8'));
38
+ }
39
+ function shouldPrintInline(usePager) {
40
+ return !usePager || !process.stdout.isTTY || process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
41
+ }
42
+ function viewText(reportName, content, options) {
43
+ if (shouldPrintInline(options.usePager)) {
44
+ console.log(content);
45
+ return;
46
+ }
47
+ const filePath = writeTempReport(reportName, content);
48
+ try {
49
+ if (!openWithPager(filePath)) {
50
+ readAndPrint(filePath);
51
+ }
52
+ }
53
+ finally {
54
+ (0, node_fs_1.rmSync)(node_path_1.default.dirname(filePath), { recursive: true, force: true });
55
+ }
56
+ }
57
+ //# sourceMappingURL=viewText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewText.js","sourceRoot":"","sources":["../../src/lib/viewText.ts"],"names":[],"mappings":";;;;;AAkEA,4BAyBC;AA3FD,qCAAyE;AACzE,qCAA+B;AAC/B,0DAA6B;AAC7B,2DAA6C;AAM7C,SAAS,gBAAgB,CAAC,OAAe,EAAE,IAAc;IAErD,MAAM,MAAM,GAAG,IAAA,8BAAS,EAAC,OAAO,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;IAE5D,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;AAEhD,CAAC;AAED,SAAS,kBAAkB,CAAC,eAAuB,EAAE,QAAgB;IAEjE,MAAM,MAAM,GAAG,IAAA,8BAAS,EAAC,GAAG,eAAe,KAAK,QAAQ,GAAG,EAAE;QACzD,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,SAAS;KACnB,CAAC,CAAC;IAEH,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;AAEhD,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IAEnC,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;IAEnE,IAAI,eAAe,IAAI,kBAAkB,CAAC,eAAe,EAAE,QAAQ,CAAC,EAAE,CAAC;QAEnE,OAAO,IAAI,CAAC;IAEhB,CAAC;IAED,OAAO,gBAAgB,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAE9F,CAAC;AAED,SAAS,eAAe,CAAC,UAAkB,EAAE,OAAe;IAExD,MAAM,OAAO,GAAG,IAAA,qBAAW,EAAC,mBAAI,CAAC,IAAI,CAAC,IAAA,gBAAM,GAAE,EAAE,SAAS,CAAC,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,mBAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,QAAQ,MAAM,CAAC,CAAC;IAEvD,IAAA,uBAAa,EAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAEzC,OAAO,QAAQ,CAAC;AAEpB,CAAC;AAED,SAAS,YAAY,CAAC,QAAgB;IAElC,OAAO,CAAC,GAAG,CAAC,IAAA,sBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhD,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAiB;IAExC,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,MAAM,CAAC;AAEpH,CAAC;AAED,SAAgB,QAAQ,CAAC,UAAkB,EAAE,OAAe,EAAE,OAAwB;IAElF,IAAI,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAEtC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO;IAEX,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAEtD,IAAI,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;YAE3B,YAAY,CAAC,QAAQ,CAAC,CAAC;QAE3B,CAAC;IAEL,CAAC;YAAS,CAAC;QAEP,IAAA,gBAAM,EAAC,mBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAC,CAAC,CAAC;IAEnE,CAAC;AAEL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awscfn",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "CLI and TypeScript SDK for managing AWS CloudFormation stacks with simple YAML parameters and real-time event streaming.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",