@go-to-k/cdkd 0.219.9 → 0.220.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 +19 -0
- package/dist/cli.js +661 -34
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-DVP4vDXG.js → deploy-engine-CFaK71E9.js} +169 -7
- package/dist/deploy-engine-CFaK71E9.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DVP4vDXG.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { A as withErrorHandling, B as generateResourceNameWithFallback, C as StackHasActiveImportsError, F as getLiveRenderer, H as withStackName, I as PATTERN_B_NAME_PROPERTIES, L as PATTERN_B_RESOURCE_TYPES, M as getLogger, P as runStackBuffered, S as ResourceUpdateNotSupportedError, V as withSkipPrefix, _ as MissingCdkCliError, a as assertRegionMatch, b as ProvisioningError, f as LocalInvokeBuildError$1, i as resolveExplicitPhysicalId, k as normalizeAwsError, l as CdkdError, m as LocalStartServiceError, n as matchesCdkPath, o as disableInstanceApiTermination, p as LocalMigrateError, r as normalizeAwsTagsToCfn, s as isTerminationProtectionPropagationError, t as CDK_PATH_TAG, v as NestedStackChildDirectDestroyError, w as StackTerminationProtectionError, x as ResourceTimeoutError, y as PartialFailureError, z as generateResourceName } from "./import-helpers-wLipXr5g.js";
|
|
3
3
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-pjPwZz1r.js";
|
|
4
|
-
import { A as
|
|
4
|
+
import { $ as resolveBucketRegion, A as stringifyValue, B as resolveApp, C as DiffCalculator, D as S3StateBackend, E as LockManager, F as runDockerForeground, G as warnDeprecatedNoPrefixCliFlag, H as resolveSkipPrefix, I as runDockerStreaming, J as MIGRATE_TMP_PREFIX, K as CFN_TEMPLATE_BODY_LIMIT, L as Synthesizer, M as buildDockerImage, N as formatDockerLoginError, O as shouldRetainResource, P as getDockerCmd, R as getDefaultStateBucketName, S as applyRoleArnIfSet, T as TemplateParser, U as resolveStateBucketWithDefault, V as resolveCaptureObservedState, W as resolveStateBucketWithDefaultAndSource, X as uploadCfnTemplate, Y as findLargeInlineResources, Z as AssemblyReader, _ as collectInlinePolicyNamesManagedBySiblings, a as withRetry, b as CloudControlProvider, c as extractDeploymentEventError, d as cyan, f as gray, g as IAMRoleProvider, h as yellow, i as withResourceDeadline, j as WorkGraph, k as AssetPublisher, l as formatResourceLine, m as red, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as isRetryableTransientError, p as green, q as CFN_TEMPLATE_URL_LIMIT, r as DeployEngine, s as IMPLICIT_DELETE_DEPENDENCIES, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as bold, v as ProviderRegistry, w as DagBuilder, x as IntrinsicFunctionResolver, y as findActionableSilentDrops, z as getLegacyStateBucketName } from "./deploy-engine-CFaK71E9.js";
|
|
5
5
|
import { t as ASGProvider } from "./asg-provider-B_hrCxRx.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
@@ -1507,6 +1507,387 @@ async function promptRecreateConfirm(input) {
|
|
|
1507
1507
|
}
|
|
1508
1508
|
}
|
|
1509
1509
|
|
|
1510
|
+
//#endregion
|
|
1511
|
+
//#region src/state/deployment-events-store.ts
|
|
1512
|
+
/**
|
|
1513
|
+
* Deployment-events store (issue #808) — persists the structured
|
|
1514
|
+
* deployment events emitted by the deploy engine / destroy runner as
|
|
1515
|
+
* JSONL to the state bucket, plus a small per-stack run index:
|
|
1516
|
+
*
|
|
1517
|
+
* - Events: `{prefix}/{stackName}/{region}/deployments/{runId}.jsonl`
|
|
1518
|
+
* - Index: `{prefix}/{stackName}/{region}/deployments/index.json`
|
|
1519
|
+
*
|
|
1520
|
+
* Design constraints (all load-bearing):
|
|
1521
|
+
*
|
|
1522
|
+
* - **Best-effort, never blocking**: `record()` is synchronous and only
|
|
1523
|
+
* buffers in memory; flushes run asynchronously (debounced timer +
|
|
1524
|
+
* size threshold) and are serialized on a write chain. A failed S3
|
|
1525
|
+
* write warns once and degrades to debug-level afterwards — it can
|
|
1526
|
+
* NEVER fail or block the deploy/destroy itself.
|
|
1527
|
+
* - **No locking**: each run writes to its own unique `{runId}.jsonl`
|
|
1528
|
+
* key (no concurrent writer by construction). `index.json` is
|
|
1529
|
+
* last-writer-wins — acceptable for a derived view; the `.jsonl`
|
|
1530
|
+
* files are the source of truth and `cdkd events` can read a run
|
|
1531
|
+
* directly by id even if the index lost the race.
|
|
1532
|
+
* - **No resource properties** in events — errors + metadata only
|
|
1533
|
+
* (properties may contain secrets and already live in state.json).
|
|
1534
|
+
* - **Separate keys from state.json** — no state schema bump; event
|
|
1535
|
+
* files survive `cdkd destroy` (state deletion does not touch
|
|
1536
|
+
* `deployments/`), preserving post-mortem context.
|
|
1537
|
+
*/
|
|
1538
|
+
/** Max runs retained in `deployments/index.json` (newest first). */
|
|
1539
|
+
const DEPLOYMENT_EVENTS_MAX_INDEX_RUNS = 20;
|
|
1540
|
+
/** Debounce window between buffered events and the async S3 flush. */
|
|
1541
|
+
const FLUSH_INTERVAL_MS = 2e3;
|
|
1542
|
+
/** Flush immediately once this many events are buffered. */
|
|
1543
|
+
const FLUSH_EVENT_THRESHOLD = 50;
|
|
1544
|
+
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
1545
|
+
function getCdkdVersion() {
|
|
1546
|
+
return "0.220.0";
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Generate a time-sortable unique run id, e.g.
|
|
1550
|
+
* `20260613T012345678Z-1a2b3c4d`. The timestamp prefix keeps S3 listings
|
|
1551
|
+
* and `cdkd events` output chronologically meaningful; the random suffix
|
|
1552
|
+
* guarantees uniqueness across concurrent runs.
|
|
1553
|
+
*/
|
|
1554
|
+
function newDeploymentRunId(now = /* @__PURE__ */ new Date()) {
|
|
1555
|
+
return `${now.toISOString().replace(/[-:.]/g, "")}-${randomUUID().slice(0, 8)}`;
|
|
1556
|
+
}
|
|
1557
|
+
/** S3 key of a run's JSONL event stream. */
|
|
1558
|
+
function deploymentEventsKey(prefix, stackName, region, runId) {
|
|
1559
|
+
return `${prefix}/${stackName}/${region}/deployments/${runId}.jsonl`;
|
|
1560
|
+
}
|
|
1561
|
+
/** S3 key of a stack's run index. */
|
|
1562
|
+
function deploymentEventsIndexKey(prefix, stackName, region) {
|
|
1563
|
+
return `${prefix}/${stackName}/${region}/deployments/index.json`;
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Buffering JSONL writer for one deployment run. Implements the
|
|
1567
|
+
* {@link DeploymentEventRecorder} seam the deploy engine / destroy
|
|
1568
|
+
* runner emit through.
|
|
1569
|
+
*/
|
|
1570
|
+
var DeploymentEventsStore = class {
|
|
1571
|
+
logger = getLogger().child("DeploymentEvents");
|
|
1572
|
+
backend;
|
|
1573
|
+
stackName;
|
|
1574
|
+
region;
|
|
1575
|
+
command;
|
|
1576
|
+
runId;
|
|
1577
|
+
cdkdVersion;
|
|
1578
|
+
startedAt;
|
|
1579
|
+
/** All events recorded so far (the full JSONL body is re-PUT per flush —
|
|
1580
|
+
* S3 has no append, and metadata-only events are small). */
|
|
1581
|
+
events = [];
|
|
1582
|
+
/** Number of events already persisted by the last successful flush. */
|
|
1583
|
+
persistedCount = 0;
|
|
1584
|
+
flushTimer;
|
|
1585
|
+
/** Serializes S3 writes so flushes never interleave. */
|
|
1586
|
+
writeChain = Promise.resolve();
|
|
1587
|
+
warnedOnce = false;
|
|
1588
|
+
finalized = false;
|
|
1589
|
+
constructor(backend, options) {
|
|
1590
|
+
this.backend = backend;
|
|
1591
|
+
this.stackName = options.stackName;
|
|
1592
|
+
this.region = options.region;
|
|
1593
|
+
this.command = options.command;
|
|
1594
|
+
this.runId = options.runId ?? newDeploymentRunId();
|
|
1595
|
+
this.cdkdVersion = options.cdkdVersion ?? getCdkdVersion();
|
|
1596
|
+
this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Buffer one event (synchronous, never throws). The timestamp is
|
|
1600
|
+
* stamped here so emitters don't need to.
|
|
1601
|
+
*/
|
|
1602
|
+
record(event) {
|
|
1603
|
+
try {
|
|
1604
|
+
if (this.finalized) return;
|
|
1605
|
+
this.events.push({
|
|
1606
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1607
|
+
...event
|
|
1608
|
+
});
|
|
1609
|
+
if (this.events.length - this.persistedCount >= FLUSH_EVENT_THRESHOLD) this.scheduleFlush(0);
|
|
1610
|
+
else this.scheduleFlush(FLUSH_INTERVAL_MS);
|
|
1611
|
+
} catch {}
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Final flush + index update. Called by the owner (deploy CLI per-stack
|
|
1615
|
+
* finally / destroy runner finally) after the run reaches a terminal
|
|
1616
|
+
* state. Best-effort: never throws.
|
|
1617
|
+
*/
|
|
1618
|
+
async finalize(result) {
|
|
1619
|
+
if (this.finalized) return;
|
|
1620
|
+
this.finalized = true;
|
|
1621
|
+
if (this.flushTimer) {
|
|
1622
|
+
clearTimeout(this.flushTimer);
|
|
1623
|
+
this.flushTimer = void 0;
|
|
1624
|
+
}
|
|
1625
|
+
if (this.events.length === 0) return;
|
|
1626
|
+
await this.enqueueWrite(async () => {
|
|
1627
|
+
await this.doFlush();
|
|
1628
|
+
await this.updateIndex(result);
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
/** Await any in-flight async flushes (used by tests). */
|
|
1632
|
+
async drain() {
|
|
1633
|
+
await this.writeChain;
|
|
1634
|
+
}
|
|
1635
|
+
scheduleFlush(delayMs) {
|
|
1636
|
+
if (this.flushTimer) {
|
|
1637
|
+
if (delayMs > 0) return;
|
|
1638
|
+
clearTimeout(this.flushTimer);
|
|
1639
|
+
}
|
|
1640
|
+
this.flushTimer = setTimeout(() => {
|
|
1641
|
+
this.flushTimer = void 0;
|
|
1642
|
+
this.enqueueWrite(() => this.doFlush());
|
|
1643
|
+
}, delayMs);
|
|
1644
|
+
this.flushTimer.unref?.();
|
|
1645
|
+
}
|
|
1646
|
+
enqueueWrite(op) {
|
|
1647
|
+
const next = this.writeChain.then(op).catch((err) => {
|
|
1648
|
+
this.warnOnce(`Failed to persist deployment events for run ${this.runId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1649
|
+
});
|
|
1650
|
+
this.writeChain = next;
|
|
1651
|
+
return next;
|
|
1652
|
+
}
|
|
1653
|
+
async doFlush() {
|
|
1654
|
+
if (this.events.length === 0 || this.events.length === this.persistedCount) return;
|
|
1655
|
+
const snapshotCount = this.events.length;
|
|
1656
|
+
const body = this.events.slice(0, snapshotCount).map((e) => JSON.stringify(e)).join("\n");
|
|
1657
|
+
await this.backend.putRawObject(deploymentEventsKey(this.backend.prefix, this.stackName, this.region, this.runId), body + "\n", "application/x-ndjson");
|
|
1658
|
+
this.persistedCount = snapshotCount;
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Prepend this run's summary to `deployments/index.json`, truncated to
|
|
1662
|
+
* the last {@link DEPLOYMENT_EVENTS_MAX_INDEX_RUNS} runs. Read-modify-
|
|
1663
|
+
* write WITHOUT optimistic locking — last-writer-wins (documented
|
|
1664
|
+
* trade-off; the per-run `.jsonl` files are the source of truth).
|
|
1665
|
+
*/
|
|
1666
|
+
async updateIndex(result) {
|
|
1667
|
+
const key = deploymentEventsIndexKey(this.backend.prefix, this.stackName, this.region);
|
|
1668
|
+
let existingRuns = [];
|
|
1669
|
+
try {
|
|
1670
|
+
const raw = await this.backend.getRawObject(key);
|
|
1671
|
+
if (raw !== null) {
|
|
1672
|
+
const parsed = JSON.parse(raw);
|
|
1673
|
+
if (Array.isArray(parsed.runs)) existingRuns = parsed.runs;
|
|
1674
|
+
}
|
|
1675
|
+
} catch (err) {
|
|
1676
|
+
this.logger.debug(`Deployment-events index unreadable, rewriting: ${err instanceof Error ? err.message : String(err)}`);
|
|
1677
|
+
}
|
|
1678
|
+
const runs = [{
|
|
1679
|
+
runId: this.runId,
|
|
1680
|
+
command: this.command,
|
|
1681
|
+
cdkdVersion: this.cdkdVersion,
|
|
1682
|
+
startedAt: this.startedAt,
|
|
1683
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1684
|
+
result,
|
|
1685
|
+
eventCount: this.persistedCount
|
|
1686
|
+
}, ...existingRuns.filter((r) => r.runId !== this.runId)].slice(0, 20);
|
|
1687
|
+
const file = {
|
|
1688
|
+
indexVersion: 1,
|
|
1689
|
+
stackName: this.stackName,
|
|
1690
|
+
region: this.region,
|
|
1691
|
+
runs,
|
|
1692
|
+
lastModified: Date.now()
|
|
1693
|
+
};
|
|
1694
|
+
await this.backend.putRawObject(key, JSON.stringify(file, null, 2));
|
|
1695
|
+
}
|
|
1696
|
+
warnOnce(message) {
|
|
1697
|
+
if (this.warnedOnce) {
|
|
1698
|
+
this.logger.debug(message);
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
this.warnedOnce = true;
|
|
1702
|
+
this.logger.warn(`${message} — continuing; deployment events are best-effort and never block the run.`);
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
/**
|
|
1706
|
+
* Read side for `cdkd events`: discovers regions / runs / event streams
|
|
1707
|
+
* under `{prefix}/{stackName}/{region}/deployments/`. Region discovery
|
|
1708
|
+
* deliberately does NOT rely on state.json — event files survive
|
|
1709
|
+
* `cdkd destroy`, so a destroyed stack's runs stay readable.
|
|
1710
|
+
*/
|
|
1711
|
+
var DeploymentEventsReader = class {
|
|
1712
|
+
backend;
|
|
1713
|
+
constructor(backend) {
|
|
1714
|
+
this.backend = backend;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Regions that have a `deployments/` index or event stream for the
|
|
1718
|
+
* stack. Derived from the raw key listing under `{prefix}/{stackName}/`.
|
|
1719
|
+
*/
|
|
1720
|
+
async listRegions(stackName) {
|
|
1721
|
+
const prefix = `${this.backend.prefix}/${stackName}/`;
|
|
1722
|
+
const keys = await this.backend.listRawKeys(prefix);
|
|
1723
|
+
const regions = /* @__PURE__ */ new Set();
|
|
1724
|
+
for (const key of keys) {
|
|
1725
|
+
const segments = key.slice(prefix.length).split("/");
|
|
1726
|
+
if (segments.length === 3 && segments[1] === "deployments" && segments[0]) regions.add(segments[0]);
|
|
1727
|
+
}
|
|
1728
|
+
return [...regions].sort();
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Run summaries for `(stackName, region)`, newest first. Returns the
|
|
1732
|
+
* index file's `runs` (already newest-first); when the index is missing
|
|
1733
|
+
* or unreadable, falls back to enumerating `{runId}.jsonl` keys (sorted
|
|
1734
|
+
* descending — runIds are time-prefixed).
|
|
1735
|
+
*
|
|
1736
|
+
* In the fallback path the run's terminal result / command / version are
|
|
1737
|
+
* NOT known from the key alone, so each run's JSONL is read and its last
|
|
1738
|
+
* `RUN_FINISHED` (+ first `RUN_STARTED`) event mined for the true result,
|
|
1739
|
+
* command, version, and timestamps. A run whose JSONL has no terminal
|
|
1740
|
+
* `RUN_FINISHED` (interrupted run, or one whose index write lost the
|
|
1741
|
+
* race) is reported as `'UNKNOWN'` — it is NEVER fabricated as `'FAILED'`,
|
|
1742
|
+
* which would mislabel a successful run that merely failed to update the
|
|
1743
|
+
* derived index.
|
|
1744
|
+
*/
|
|
1745
|
+
async listRuns(stackName, region) {
|
|
1746
|
+
const key = deploymentEventsIndexKey(this.backend.prefix, stackName, region);
|
|
1747
|
+
try {
|
|
1748
|
+
const raw = await this.backend.getRawObject(key);
|
|
1749
|
+
if (raw !== null) {
|
|
1750
|
+
const parsed = JSON.parse(raw);
|
|
1751
|
+
if (Array.isArray(parsed.runs)) return parsed.runs;
|
|
1752
|
+
}
|
|
1753
|
+
} catch {}
|
|
1754
|
+
const dirPrefix = `${this.backend.prefix}/${stackName}/${region}/deployments/`;
|
|
1755
|
+
const runIds = (await this.backend.listRawKeys(dirPrefix)).filter((k) => k.endsWith(".jsonl")).map((k) => k.slice(dirPrefix.length, -6)).sort().reverse();
|
|
1756
|
+
return Promise.all(runIds.map((runId) => this.summarizeRunFromJsonl(stackName, region, runId)));
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Reconstruct a {@link DeploymentRunSummary} for the index-fallback path
|
|
1760
|
+
* by reading the run's JSONL. Derives the true result from the last
|
|
1761
|
+
* `RUN_FINISHED` event; absent that, returns `'UNKNOWN'` rather than
|
|
1762
|
+
* fabricating a definitive failure.
|
|
1763
|
+
*/
|
|
1764
|
+
async summarizeRunFromJsonl(stackName, region, runId) {
|
|
1765
|
+
const fallback = {
|
|
1766
|
+
runId,
|
|
1767
|
+
command: "deploy",
|
|
1768
|
+
cdkdVersion: "unknown",
|
|
1769
|
+
startedAt: "",
|
|
1770
|
+
finishedAt: "",
|
|
1771
|
+
result: "UNKNOWN",
|
|
1772
|
+
eventCount: 0
|
|
1773
|
+
};
|
|
1774
|
+
let events = null;
|
|
1775
|
+
try {
|
|
1776
|
+
events = await this.readRunEvents(stackName, region, runId);
|
|
1777
|
+
} catch {
|
|
1778
|
+
return fallback;
|
|
1779
|
+
}
|
|
1780
|
+
if (events === null || events.length === 0) return fallback;
|
|
1781
|
+
const started = events.find((e) => e.eventType === "RUN_STARTED");
|
|
1782
|
+
let finished;
|
|
1783
|
+
for (const e of events) if (e.eventType === "RUN_FINISHED") finished = e;
|
|
1784
|
+
return {
|
|
1785
|
+
runId,
|
|
1786
|
+
command: started?.command ?? fallback.command,
|
|
1787
|
+
cdkdVersion: started?.cdkdVersion ?? fallback.cdkdVersion,
|
|
1788
|
+
startedAt: started?.timestamp ?? fallback.startedAt,
|
|
1789
|
+
finishedAt: finished?.timestamp ?? fallback.finishedAt,
|
|
1790
|
+
result: finished?.result ?? "UNKNOWN",
|
|
1791
|
+
eventCount: events.length
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Parse one run's JSONL event stream. Returns `null` when the run file
|
|
1796
|
+
* does not exist. Malformed lines are skipped (a torn final line from
|
|
1797
|
+
* an interrupted flush must not hide the rest of the stream).
|
|
1798
|
+
*/
|
|
1799
|
+
async readRunEvents(stackName, region, runId) {
|
|
1800
|
+
const key = deploymentEventsKey(this.backend.prefix, stackName, region, runId);
|
|
1801
|
+
const raw = await this.backend.getRawObject(key);
|
|
1802
|
+
if (raw === null) return null;
|
|
1803
|
+
const events = [];
|
|
1804
|
+
for (const line of raw.split("\n")) {
|
|
1805
|
+
const trimmed = line.trim();
|
|
1806
|
+
if (!trimmed) continue;
|
|
1807
|
+
try {
|
|
1808
|
+
events.push(JSON.parse(trimmed));
|
|
1809
|
+
} catch {}
|
|
1810
|
+
}
|
|
1811
|
+
return events;
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1815
|
+
//#endregion
|
|
1816
|
+
//#region src/cli/commands/deployment-events-run.ts
|
|
1817
|
+
/**
|
|
1818
|
+
* Run-level deployment-event bracket helpers (issue #808).
|
|
1819
|
+
*
|
|
1820
|
+
* The per-resource + rollback events are emitted by `DeployEngine` /
|
|
1821
|
+
* `destroy-runner.ts`; the RUN-level events (`RUN_STARTED` /
|
|
1822
|
+
* `RUN_FINISHED`) and the recorder lifecycle (`finalize()` in a `finally`)
|
|
1823
|
+
* are owned by the `cdkd deploy` / `cdkd destroy` CLIs. That run-level
|
|
1824
|
+
* bracket was originally inline in `deploy.ts` / `destroy.ts` and could
|
|
1825
|
+
* only be exercised through the full synth → STS → work-graph pipeline.
|
|
1826
|
+
*
|
|
1827
|
+
* These thin helpers extract the bracket so it is directly unit-testable
|
|
1828
|
+
* (and shared between the two commands), with the exact contract:
|
|
1829
|
+
*
|
|
1830
|
+
* - **`--dry-run` creates NO recorder** (no events at all for a dry run).
|
|
1831
|
+
* - `RUN_STARTED` is emitted the moment the recorder is created.
|
|
1832
|
+
* - A successful run emits `RUN_FINISHED { result: 'SUCCEEDED', counts }`.
|
|
1833
|
+
* - A failed run emits `RUN_FINISHED { result: 'FAILED', error }` via
|
|
1834
|
+
* {@link extractDeploymentEventError} (error metadata only — no props).
|
|
1835
|
+
* - `finalize(result)` is always called (caller's `finally`).
|
|
1836
|
+
*
|
|
1837
|
+
* All of this is best-effort: the recorder's `record()` / `finalize()`
|
|
1838
|
+
* never throw, so these helpers never need their own try/catch.
|
|
1839
|
+
*/
|
|
1840
|
+
/**
|
|
1841
|
+
* Create a {@link DeploymentEventsStore} and immediately emit its
|
|
1842
|
+
* `RUN_STARTED` event. Returns `undefined` under `--dry-run` (no recorder,
|
|
1843
|
+
* no events). The caller wires the returned recorder into the engine /
|
|
1844
|
+
* runner and `finalize()`s it in a `finally`.
|
|
1845
|
+
*/
|
|
1846
|
+
function startRunRecorder(args) {
|
|
1847
|
+
if (args.dryRun) return void 0;
|
|
1848
|
+
const recorder = new DeploymentEventsStore(args.backend, {
|
|
1849
|
+
stackName: args.stackName,
|
|
1850
|
+
region: args.region,
|
|
1851
|
+
command: args.command,
|
|
1852
|
+
...args.runId !== void 0 && { runId: args.runId },
|
|
1853
|
+
...args.cdkdVersion !== void 0 && { cdkdVersion: args.cdkdVersion }
|
|
1854
|
+
});
|
|
1855
|
+
recorder.record({
|
|
1856
|
+
eventType: "RUN_STARTED",
|
|
1857
|
+
stackName: args.stackName,
|
|
1858
|
+
command: args.command,
|
|
1859
|
+
region: args.region,
|
|
1860
|
+
cdkdVersion: recorder.cdkdVersion
|
|
1861
|
+
});
|
|
1862
|
+
return recorder;
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Emit a success `RUN_FINISHED`. No-op when `recorder` is `undefined`
|
|
1866
|
+
* (dry-run / older state). `durationMs` is optional (destroy does not
|
|
1867
|
+
* carry one at the run level).
|
|
1868
|
+
*/
|
|
1869
|
+
function recordRunSucceeded(recorder, stackName, counts, durationMs) {
|
|
1870
|
+
recorder?.record({
|
|
1871
|
+
eventType: "RUN_FINISHED",
|
|
1872
|
+
stackName,
|
|
1873
|
+
result: "SUCCEEDED",
|
|
1874
|
+
...durationMs !== void 0 && { durationMs },
|
|
1875
|
+
counts
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Emit a failure `RUN_FINISHED` carrying the extracted error metadata.
|
|
1880
|
+
* No-op when `recorder` is `undefined`.
|
|
1881
|
+
*/
|
|
1882
|
+
function recordRunFailed(recorder, stackName, error) {
|
|
1883
|
+
recorder?.record({
|
|
1884
|
+
eventType: "RUN_FINISHED",
|
|
1885
|
+
stackName,
|
|
1886
|
+
result: "FAILED",
|
|
1887
|
+
error: extractDeploymentEventError(error)
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1510
1891
|
//#endregion
|
|
1511
1892
|
//#region src/state/export-index-store.ts
|
|
1512
1893
|
/**
|
|
@@ -33932,10 +34313,27 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
33932
34313
|
if (shouldRetainResource(resource.deletionPolicy)) {
|
|
33933
34314
|
logger.info(` ⊘ ${logicalId} (${resource.resourceType}) retained — DeletionPolicy: ${resource.deletionPolicy}`);
|
|
33934
34315
|
result.retainedCount++;
|
|
34316
|
+
ctx.eventRecorder?.record({
|
|
34317
|
+
eventType: "RESOURCE_RETAINED",
|
|
34318
|
+
stackName,
|
|
34319
|
+
operation: "DELETE",
|
|
34320
|
+
logicalId,
|
|
34321
|
+
resourceType: resource.resourceType,
|
|
34322
|
+
...resource.provisionedBy && { provisionedBy: resource.provisionedBy }
|
|
34323
|
+
});
|
|
33935
34324
|
return;
|
|
33936
34325
|
}
|
|
33937
34326
|
const baseLabel = `Deleting ${logicalId} (${resource.resourceType})`;
|
|
33938
34327
|
renderer.addTask(logicalId, baseLabel);
|
|
34328
|
+
const resourceStartedAt = Date.now();
|
|
34329
|
+
ctx.eventRecorder?.record({
|
|
34330
|
+
eventType: "RESOURCE_STARTED",
|
|
34331
|
+
stackName,
|
|
34332
|
+
operation: "DELETE",
|
|
34333
|
+
logicalId,
|
|
34334
|
+
resourceType: resource.resourceType,
|
|
34335
|
+
...resource.provisionedBy && { provisionedBy: resource.provisionedBy }
|
|
34336
|
+
});
|
|
33939
34337
|
try {
|
|
33940
34338
|
const provider = destroyProviderRegistry.getProviderFor({
|
|
33941
34339
|
resourceType: resource.resourceType,
|
|
@@ -33979,6 +34377,16 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
33979
34377
|
renderer.removeTask(logicalId);
|
|
33980
34378
|
logger.info(` ${formatResourceLine("deleted", logicalId, resource.resourceType)}`);
|
|
33981
34379
|
result.deletedCount++;
|
|
34380
|
+
ctx.eventRecorder?.record({
|
|
34381
|
+
eventType: "RESOURCE_SUCCEEDED",
|
|
34382
|
+
stackName,
|
|
34383
|
+
operation: "DELETE",
|
|
34384
|
+
logicalId,
|
|
34385
|
+
resourceType: resource.resourceType,
|
|
34386
|
+
...resource.provisionedBy && { provisionedBy: resource.provisionedBy },
|
|
34387
|
+
...resource.physicalId && { physicalId: resource.physicalId },
|
|
34388
|
+
durationMs: Date.now() - resourceStartedAt
|
|
34389
|
+
});
|
|
33982
34390
|
delete remainingResources[logicalId];
|
|
33983
34391
|
persistStateAfterDelete(logicalId);
|
|
33984
34392
|
} catch (error) {
|
|
@@ -33987,15 +34395,45 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
33987
34395
|
if (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException")) {
|
|
33988
34396
|
logger.debug(` ${logicalId} already deleted, removing from state`);
|
|
33989
34397
|
result.deletedCount++;
|
|
34398
|
+
ctx.eventRecorder?.record({
|
|
34399
|
+
eventType: "RESOURCE_SUCCEEDED",
|
|
34400
|
+
stackName,
|
|
34401
|
+
operation: "DELETE",
|
|
34402
|
+
logicalId,
|
|
34403
|
+
resourceType: resource.resourceType,
|
|
34404
|
+
...resource.provisionedBy && { provisionedBy: resource.provisionedBy },
|
|
34405
|
+
...resource.physicalId && { physicalId: resource.physicalId },
|
|
34406
|
+
durationMs: Date.now() - resourceStartedAt
|
|
34407
|
+
});
|
|
33990
34408
|
delete remainingResources[logicalId];
|
|
33991
34409
|
persistStateAfterDelete(logicalId);
|
|
33992
34410
|
} else if (error instanceof ResourceTimeoutError) {
|
|
33993
34411
|
const wrapped = new ProvisioningError(error.message, resource.resourceType, logicalId, resource.physicalId, error);
|
|
33994
34412
|
logger.error(` ✗ Failed to delete ${logicalId}:`, wrapped.message);
|
|
33995
34413
|
result.errorCount++;
|
|
34414
|
+
ctx.eventRecorder?.record({
|
|
34415
|
+
eventType: "RESOURCE_FAILED",
|
|
34416
|
+
stackName,
|
|
34417
|
+
operation: "DELETE",
|
|
34418
|
+
logicalId,
|
|
34419
|
+
resourceType: resource.resourceType,
|
|
34420
|
+
...resource.provisionedBy && { provisionedBy: resource.provisionedBy },
|
|
34421
|
+
durationMs: Date.now() - resourceStartedAt,
|
|
34422
|
+
error: extractDeploymentEventError(wrapped)
|
|
34423
|
+
});
|
|
33996
34424
|
} else {
|
|
33997
34425
|
logger.error(` ✗ Failed to delete ${logicalId}:`, String(error));
|
|
33998
34426
|
result.errorCount++;
|
|
34427
|
+
ctx.eventRecorder?.record({
|
|
34428
|
+
eventType: "RESOURCE_FAILED",
|
|
34429
|
+
stackName,
|
|
34430
|
+
operation: "DELETE",
|
|
34431
|
+
logicalId,
|
|
34432
|
+
resourceType: resource.resourceType,
|
|
34433
|
+
...resource.provisionedBy && { provisionedBy: resource.provisionedBy },
|
|
34434
|
+
durationMs: Date.now() - resourceStartedAt,
|
|
34435
|
+
error: extractDeploymentEventError(error)
|
|
34436
|
+
});
|
|
33999
34437
|
}
|
|
34000
34438
|
} finally {
|
|
34001
34439
|
renderer.removeTask(logicalId);
|
|
@@ -35097,6 +35535,14 @@ async function deployCommand(stacks, options) {
|
|
|
35097
35535
|
stackProviderRegistry.setCustomResourceResponseBucket(stateBucket, baseRegion);
|
|
35098
35536
|
if (options.allowUnsupportedTypes?.length) stackProviderRegistry.allowUnsupportedTypes(options.allowUnsupportedTypes);
|
|
35099
35537
|
if (options.allowUnsupportedProperties?.length) stackProviderRegistry.allowUnsupportedProperties(options.allowUnsupportedProperties);
|
|
35538
|
+
const eventRecorder = startRunRecorder({
|
|
35539
|
+
backend: stackStateBackend,
|
|
35540
|
+
stackName: stackInfo.stackName,
|
|
35541
|
+
region: stackRegion,
|
|
35542
|
+
command: "deploy",
|
|
35543
|
+
dryRun: options.dryRun
|
|
35544
|
+
});
|
|
35545
|
+
let runResult = "SUCCEEDED";
|
|
35100
35546
|
try {
|
|
35101
35547
|
if (skipPrefix) {
|
|
35102
35548
|
const existing = await stackStateBackend.getState(stackInfo.stackName, stackRegion);
|
|
@@ -35152,6 +35598,7 @@ async function deployCommand(stacks, options) {
|
|
|
35152
35598
|
concurrency: options.concurrency,
|
|
35153
35599
|
dryRun: options.dryRun,
|
|
35154
35600
|
noRollback: !options.rollback,
|
|
35601
|
+
...eventRecorder && { eventRecorder },
|
|
35155
35602
|
...recreateViaCcApiTargets && recreateViaCcApiTargets.size > 0 && { recreateViaCcApiTargets },
|
|
35156
35603
|
...recreateViaSdkProviderTargets && recreateViaSdkProviderTargets.size > 0 && { recreateViaSdkProviderTargets },
|
|
35157
35604
|
captureObservedState: resolveCaptureObservedState(options.captureObservedState),
|
|
@@ -35189,7 +35636,17 @@ async function deployCommand(stacks, options) {
|
|
|
35189
35636
|
}
|
|
35190
35637
|
if (options.dryRun) logger.info(`\n${green("✓")} ${bold("Dry run completed")} - no actual changes made`);
|
|
35191
35638
|
else logger.info(`\n${green("✓")} ${bold("Deployment completed successfully")}`);
|
|
35639
|
+
recordRunSucceeded(eventRecorder, stackInfo.stackName, {
|
|
35640
|
+
created: deployResult.created,
|
|
35641
|
+
updated: deployResult.updated,
|
|
35642
|
+
deleted: deployResult.deleted
|
|
35643
|
+
}, deployResult.durationMs);
|
|
35644
|
+
} catch (deployError) {
|
|
35645
|
+
runResult = "FAILED";
|
|
35646
|
+
recordRunFailed(eventRecorder, stackInfo.stackName, deployError);
|
|
35647
|
+
throw deployError;
|
|
35192
35648
|
} finally {
|
|
35649
|
+
if (eventRecorder) await eventRecorder.finalize(runResult);
|
|
35193
35650
|
stackAwsClients.destroy();
|
|
35194
35651
|
stateS3Client.destroy();
|
|
35195
35652
|
switchRegion(baseRegion);
|
|
@@ -37691,42 +38148,70 @@ async function destroyCommand(stackArgs, options) {
|
|
|
37691
38148
|
totalErrors++;
|
|
37692
38149
|
continue;
|
|
37693
38150
|
}
|
|
37694
|
-
const
|
|
37695
|
-
stateBackend,
|
|
37696
|
-
|
|
37697
|
-
|
|
37698
|
-
|
|
37699
|
-
|
|
37700
|
-
|
|
37701
|
-
|
|
37702
|
-
|
|
37703
|
-
|
|
37704
|
-
|
|
38151
|
+
const eventRecorder = startRunRecorder({
|
|
38152
|
+
backend: stateBackend,
|
|
38153
|
+
stackName,
|
|
38154
|
+
region: stackTargetRegion,
|
|
38155
|
+
command: "destroy"
|
|
38156
|
+
});
|
|
38157
|
+
let destroyRunResult = "SUCCEEDED";
|
|
38158
|
+
try {
|
|
38159
|
+
const result = await withNestedStackContext({
|
|
38160
|
+
stateBackend,
|
|
38161
|
+
lockManager,
|
|
38162
|
+
providerRegistry,
|
|
38163
|
+
parentStackName: stackName,
|
|
38164
|
+
parentRegion: stackTargetRegion,
|
|
38165
|
+
accountId,
|
|
38166
|
+
awsClients,
|
|
38167
|
+
stateBucket,
|
|
38168
|
+
exportIndexStore,
|
|
38169
|
+
destroyOptions: {
|
|
38170
|
+
...options.profile && { profile: options.profile },
|
|
38171
|
+
...options.removeProtection === true && { removeProtection: true },
|
|
38172
|
+
...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
|
|
38173
|
+
...options.resourceTimeout?.globalMs !== void 0 && { resourceTimeoutMs: options.resourceTimeout.globalMs },
|
|
38174
|
+
...options.resourceWarnAfter?.perTypeMs && { resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs },
|
|
38175
|
+
...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs }
|
|
38176
|
+
}
|
|
38177
|
+
}, () => runDestroyForStack(stackName, stateResult.state, {
|
|
38178
|
+
stateBackend,
|
|
38179
|
+
lockManager,
|
|
38180
|
+
providerRegistry,
|
|
38181
|
+
baseAwsClients: awsClients,
|
|
38182
|
+
baseRegion: region,
|
|
37705
38183
|
...options.profile && { profile: options.profile },
|
|
37706
|
-
|
|
38184
|
+
stateBucket,
|
|
38185
|
+
skipConfirmation: options.yes || options.force,
|
|
38186
|
+
removeProtection: options.removeProtection === true,
|
|
38187
|
+
exportIndexStore,
|
|
38188
|
+
...options.allowUnsupportedTypes?.length && { allowUnsupportedTypes: options.allowUnsupportedTypes },
|
|
37707
38189
|
...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
|
|
37708
38190
|
...options.resourceTimeout?.globalMs !== void 0 && { resourceTimeoutMs: options.resourceTimeout.globalMs },
|
|
37709
38191
|
...options.resourceWarnAfter?.perTypeMs && { resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs },
|
|
37710
|
-
...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs }
|
|
37711
|
-
|
|
37712
|
-
|
|
37713
|
-
|
|
37714
|
-
|
|
37715
|
-
|
|
37716
|
-
|
|
37717
|
-
|
|
37718
|
-
|
|
37719
|
-
|
|
37720
|
-
|
|
37721
|
-
|
|
37722
|
-
|
|
37723
|
-
|
|
37724
|
-
|
|
37725
|
-
|
|
37726
|
-
|
|
37727
|
-
|
|
37728
|
-
|
|
37729
|
-
|
|
38192
|
+
...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs },
|
|
38193
|
+
eventRecorder
|
|
38194
|
+
}));
|
|
38195
|
+
totalErrors += result.errorCount;
|
|
38196
|
+
destroyRunResult = result.errorCount > 0 ? "FAILED" : "SUCCEEDED";
|
|
38197
|
+
eventRecorder.record({
|
|
38198
|
+
eventType: "RUN_FINISHED",
|
|
38199
|
+
stackName,
|
|
38200
|
+
result: destroyRunResult,
|
|
38201
|
+
counts: {
|
|
38202
|
+
created: 0,
|
|
38203
|
+
updated: 0,
|
|
38204
|
+
deleted: result.deletedCount,
|
|
38205
|
+
...result.errorCount > 0 && { failed: result.errorCount }
|
|
38206
|
+
}
|
|
38207
|
+
});
|
|
38208
|
+
} catch (destroyError) {
|
|
38209
|
+
destroyRunResult = "FAILED";
|
|
38210
|
+
recordRunFailed(eventRecorder, stackName, destroyError);
|
|
38211
|
+
throw destroyError;
|
|
38212
|
+
} finally {
|
|
38213
|
+
await eventRecorder.finalize(destroyRunResult);
|
|
38214
|
+
}
|
|
37730
38215
|
}
|
|
37731
38216
|
if (totalErrors > 0) throw new PartialFailureError(`Destroy completed with ${totalErrors} resource error(s). State preserved — inspect 'cdkd state show <stack>' and re-run 'cdkd destroy' to retry.`);
|
|
37732
38217
|
} finally {
|
|
@@ -37751,6 +38236,147 @@ function createDestroyCommand() {
|
|
|
37751
38236
|
return cmd;
|
|
37752
38237
|
}
|
|
37753
38238
|
|
|
38239
|
+
//#endregion
|
|
38240
|
+
//#region src/cli/commands/events.ts
|
|
38241
|
+
/**
|
|
38242
|
+
* `cdkd events <stack>` — read back the structured deployment events
|
|
38243
|
+
* (issue #808) cdkd persists per deploy / destroy run, the local
|
|
38244
|
+
* equivalent of CloudFormation's `DescribeStackEvents`.
|
|
38245
|
+
*
|
|
38246
|
+
* Two modes:
|
|
38247
|
+
* - No `--run`: list the recorded runs for the stack, newest first
|
|
38248
|
+
* (runId, command, cdkd version, result, started/finished, event count).
|
|
38249
|
+
* - `--run <id>`: print the full ordered event stream for that one run.
|
|
38250
|
+
*
|
|
38251
|
+
* `--format json` (alias `--json`) emits machine-readable JSON for tooling
|
|
38252
|
+
* / AI-agent hand-off. Events survive `cdkd destroy` (they live under a
|
|
38253
|
+
* separate `deployments/` key family from `state.json`), so a destroyed
|
|
38254
|
+
* stack's failure history stays readable.
|
|
38255
|
+
*/
|
|
38256
|
+
async function eventsCommand(stackName, options) {
|
|
38257
|
+
const logger = getLogger();
|
|
38258
|
+
if (options.verbose) logger.setLevel("debug");
|
|
38259
|
+
warnIfDeprecatedRegion(options);
|
|
38260
|
+
const asJson = options.json === true;
|
|
38261
|
+
const awsClients = new AwsClients({
|
|
38262
|
+
...options.region && { region: options.region },
|
|
38263
|
+
...options.profile && { profile: options.profile }
|
|
38264
|
+
});
|
|
38265
|
+
setAwsClients(awsClients);
|
|
38266
|
+
try {
|
|
38267
|
+
const region = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
38268
|
+
const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
38269
|
+
const prefix = options.statePrefix ?? "cdkd";
|
|
38270
|
+
const stateBackend = new S3StateBackend(awsClients.s3, {
|
|
38271
|
+
bucket,
|
|
38272
|
+
prefix
|
|
38273
|
+
}, {
|
|
38274
|
+
region,
|
|
38275
|
+
...options.profile && { profile: options.profile }
|
|
38276
|
+
});
|
|
38277
|
+
await stateBackend.verifyBucketExists();
|
|
38278
|
+
const reader = new DeploymentEventsReader(stateBackend);
|
|
38279
|
+
const targetRegion = await resolveEventsRegion(reader, stackName, options.stackRegion);
|
|
38280
|
+
if (options.run) {
|
|
38281
|
+
const events = await reader.readRunEvents(stackName, targetRegion, options.run);
|
|
38282
|
+
if (events === null) throw new CdkdError(`No deployment-event stream found for run '${options.run}' of stack '${stackName}' in region '${targetRegion}'.`, "EVENTS_RUN_NOT_FOUND");
|
|
38283
|
+
if (asJson) {
|
|
38284
|
+
process.stdout.write(JSON.stringify(events, null, 2) + "\n");
|
|
38285
|
+
return;
|
|
38286
|
+
}
|
|
38287
|
+
printRunEvents(stackName, targetRegion, options.run, events);
|
|
38288
|
+
return;
|
|
38289
|
+
}
|
|
38290
|
+
const runs = await reader.listRuns(stackName, targetRegion);
|
|
38291
|
+
if (asJson) {
|
|
38292
|
+
process.stdout.write(JSON.stringify({
|
|
38293
|
+
stackName,
|
|
38294
|
+
region: targetRegion,
|
|
38295
|
+
runs
|
|
38296
|
+
}, null, 2) + "\n");
|
|
38297
|
+
return;
|
|
38298
|
+
}
|
|
38299
|
+
printRunList(stackName, targetRegion, runs);
|
|
38300
|
+
} finally {
|
|
38301
|
+
awsClients.destroy();
|
|
38302
|
+
}
|
|
38303
|
+
}
|
|
38304
|
+
/**
|
|
38305
|
+
* Pick the region whose `deployments/` key family holds this stack's run
|
|
38306
|
+
* history. When `--stack-region` is supplied it is honored verbatim;
|
|
38307
|
+
* otherwise the single discovered region is used, and an ambiguous (>1)
|
|
38308
|
+
* or missing (0) history surfaces an actionable error.
|
|
38309
|
+
*/
|
|
38310
|
+
async function resolveEventsRegion(reader, stackName, explicitRegion) {
|
|
38311
|
+
if (explicitRegion) return explicitRegion;
|
|
38312
|
+
const regions = await reader.listRegions(stackName);
|
|
38313
|
+
if (regions.length === 0) throw new CdkdError(`No deployment-event history found for stack '${stackName}'. Events are recorded by 'cdkd deploy' / 'cdkd destroy' (issue #808); a stack deployed by an older cdkd version has none.`, "EVENTS_NOT_FOUND");
|
|
38314
|
+
if (regions.length > 1) throw new CdkdError(`Stack '${stackName}' has deployment-event history in multiple regions: ${regions.join(", ")}. Re-run with '--stack-region <region>' to disambiguate.`, "EVENTS_REGION_AMBIGUOUS");
|
|
38315
|
+
return regions[0];
|
|
38316
|
+
}
|
|
38317
|
+
/** Human-readable run listing (newest first). */
|
|
38318
|
+
function printRunList(stackName, region, runs) {
|
|
38319
|
+
const logger = getLogger();
|
|
38320
|
+
logger.info(`${bold("Deployment runs for")} ${cyan(stackName)} ${gray(`(${region})`)}`);
|
|
38321
|
+
if (runs.length === 0) {
|
|
38322
|
+
logger.info(gray(" (no runs recorded)"));
|
|
38323
|
+
return;
|
|
38324
|
+
}
|
|
38325
|
+
for (const run of runs) {
|
|
38326
|
+
const resultColored = run.result === "SUCCEEDED" ? green(run.result) : run.result === "UNKNOWN" ? gray(run.result) : red(run.result);
|
|
38327
|
+
logger.info(` ${cyan(run.runId)} ${run.command} ${resultColored} ${gray(run.startedAt || "?")} -> ${gray(run.finishedAt || "?")} ${gray(`cdkd ${run.cdkdVersion}`)} ${gray(`${run.eventCount} events`)}`);
|
|
38328
|
+
}
|
|
38329
|
+
logger.info(gray(`\nUse 'cdkd events ${stackName} --run <runId>' to read one run's events.`));
|
|
38330
|
+
}
|
|
38331
|
+
/** Human-readable single-run event stream (in recorded order). */
|
|
38332
|
+
function printRunEvents(stackName, region, runId, events) {
|
|
38333
|
+
const logger = getLogger();
|
|
38334
|
+
logger.info(`${bold("Events for run")} ${cyan(runId)} ${gray(`(${stackName}, ${region})`)}`);
|
|
38335
|
+
if (events.length === 0) {
|
|
38336
|
+
logger.info(gray(" (no events)"));
|
|
38337
|
+
return;
|
|
38338
|
+
}
|
|
38339
|
+
for (const e of events) {
|
|
38340
|
+
const parts = [gray(e.timestamp), colorizeEventType(e.eventType)];
|
|
38341
|
+
if (e.logicalId) parts.push(`${e.logicalId}${e.resourceType ? ` (${e.resourceType})` : ""}`);
|
|
38342
|
+
if (e.operation) parts.push(gray(e.operation));
|
|
38343
|
+
if (e.provisionedBy) parts.push(gray(`[${e.provisionedBy}]`));
|
|
38344
|
+
if (e.command) parts.push(gray(e.command));
|
|
38345
|
+
if (e.region) parts.push(gray(e.region));
|
|
38346
|
+
if (e.cdkdVersion) parts.push(gray(`cdkd ${e.cdkdVersion}`));
|
|
38347
|
+
if (e.result) parts.push(e.result === "SUCCEEDED" ? green(e.result) : red(e.result));
|
|
38348
|
+
if (typeof e.durationMs === "number") parts.push(gray(`${e.durationMs}ms`));
|
|
38349
|
+
if (e.counts) parts.push(gray(`+${e.counts.created}/~${e.counts.updated}/-${e.counts.deleted}` + (e.counts.failed ? ` !${e.counts.failed}` : "")));
|
|
38350
|
+
logger.info(` ${parts.join(" ")}`);
|
|
38351
|
+
if (e.error) {
|
|
38352
|
+
const code = e.error.awsErrorCode ? ` (${e.error.awsErrorCode})` : "";
|
|
38353
|
+
const reqId = e.error.requestId ? gray(` requestId=${e.error.requestId}`) : "";
|
|
38354
|
+
logger.info(` ${red(`${e.error.name}${code}: ${e.error.message}`)}${reqId}`);
|
|
38355
|
+
}
|
|
38356
|
+
}
|
|
38357
|
+
}
|
|
38358
|
+
/** Color the event-type token by its lifecycle phase. */
|
|
38359
|
+
function colorizeEventType(eventType) {
|
|
38360
|
+
if (eventType.endsWith("FAILED")) return red(eventType);
|
|
38361
|
+
if (eventType.endsWith("SUCCEEDED") || eventType === "RUN_FINISHED") return green(eventType);
|
|
38362
|
+
if (eventType.startsWith("ROLLBACK")) return yellow(eventType);
|
|
38363
|
+
return cyan(eventType);
|
|
38364
|
+
}
|
|
38365
|
+
/**
|
|
38366
|
+
* Create the `cdkd events` command.
|
|
38367
|
+
*/
|
|
38368
|
+
function createEventsCommand() {
|
|
38369
|
+
const cmd = new Command("events").description("Read back structured deployment events (cdkd's DescribeStackEvents equivalent, issue #808)").argument("<stack>", "Stack name (physical CloudFormation name)").option("--run <runId>", "Read a single run's full event stream instead of the run listing").option("--stack-region <region>", "Disambiguate a stack with event history in multiple regions").option("--json", "Output as JSON", false).option("--format <format>", "Output format ('json' is equivalent to --json)").action(withErrorHandling((stack, options) => {
|
|
38370
|
+
return eventsCommand(stack, {
|
|
38371
|
+
...options,
|
|
38372
|
+
json: options.json === true || options.format === "json"
|
|
38373
|
+
});
|
|
38374
|
+
}));
|
|
38375
|
+
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
38376
|
+
cmd.addOption(deprecatedRegionOption);
|
|
38377
|
+
return cmd;
|
|
38378
|
+
}
|
|
38379
|
+
|
|
37754
38380
|
//#endregion
|
|
37755
38381
|
//#region src/cli/cdk-path.ts
|
|
37756
38382
|
/**
|
|
@@ -52914,7 +53540,7 @@ function reorderArgs(argv) {
|
|
|
52914
53540
|
async function main() {
|
|
52915
53541
|
installPipeCloseHandler();
|
|
52916
53542
|
const program = new Command();
|
|
52917
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
53543
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.220.0");
|
|
52918
53544
|
program.addCommand(createBootstrapCommand());
|
|
52919
53545
|
program.addCommand(createSynthCommand());
|
|
52920
53546
|
program.addCommand(createListCommand());
|
|
@@ -52922,6 +53548,7 @@ async function main() {
|
|
|
52922
53548
|
program.addCommand(createDiffCommand());
|
|
52923
53549
|
program.addCommand(createDriftCommand());
|
|
52924
53550
|
program.addCommand(createDestroyCommand());
|
|
53551
|
+
program.addCommand(createEventsCommand());
|
|
52925
53552
|
program.addCommand(createOrphanCommand());
|
|
52926
53553
|
program.addCommand(createImportCommand());
|
|
52927
53554
|
program.addCommand(createPublishAssetsCommand());
|