@go-to-k/cdkd 0.227.1 → 0.228.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 +4 -1
- package/dist/cli.js +265 -9
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-Bl_Ka0hj.js → deploy-engine-BRZdKXDs.js} +33 -2
- package/dist/deploy-engine-BRZdKXDs.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-Bl_Ka0hj.js.map +0 -1
package/README.md
CHANGED
|
@@ -588,12 +588,15 @@ stream to S3 — cdkd's local equivalent of CloudFormation's
|
|
|
588
588
|
cdkd events MyStack # list runs, newest first
|
|
589
589
|
cdkd events MyStack --run <runId> # one run's full event stream
|
|
590
590
|
cdkd events MyStack --format json # machine-readable (AI-agent hand-off)
|
|
591
|
+
cdkd events prune MyStack --all # purge event history (reclaim S3 space)
|
|
591
592
|
```
|
|
592
593
|
|
|
593
594
|
Events are persisted as JSONL under a `deployments/` key family separate
|
|
594
595
|
from `state.json` (no state schema bump), so a destroyed stack's failure
|
|
595
596
|
history stays readable. Recording is best-effort and never blocks the
|
|
596
|
-
run; events carry error + metadata only (never resource properties).
|
|
597
|
+
run; events carry error + metadata only (never resource properties). The
|
|
598
|
+
store self-bounds to the last 20 runs, and `cdkd events prune` purges old
|
|
599
|
+
history on demand (`--keep N` / `--older-than <dur>` / `--all`). See
|
|
597
600
|
**[docs/deployment-events.md](docs/deployment-events.md)** for the full
|
|
598
601
|
reference.
|
|
599
602
|
|
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 { $ as findLargeInlineResources, A as shouldRetainResource, B as getDockerImageBySourceHash, C as applyRoleArnIfSet, D as LockManager, E as TemplateParser, F as formatDockerLoginError, G as resolveCaptureObservedState, H as getDefaultStateBucketName, I as getDockerCmd, J as resolveStateBucketWithDefaultAndSource, K as resolveSkipPrefix, L as runDockerForeground, M as stringifyValue, N as WorkGraph, O as S3StateBackend, P as buildDockerImage, Q as MIGRATE_TMP_PREFIX, R as runDockerStreaming, S as IntrinsicFunctionResolver, T as DagBuilder, U as getLegacyStateBucketName, V as Synthesizer, W as resolveApp, X as CFN_TEMPLATE_BODY_LIMIT, Y as warnDeprecatedNoPrefixCliFlag, Z as CFN_TEMPLATE_URL_LIMIT, _ as IAMRoleProvider, a as withRetry, b as findActionableSilentDrops, c as computeImplicitDeleteEdges, d as bold, et as uploadCfnTemplate, f as cyan, g as yellow, h as red, i as withResourceDeadline, j as AssetPublisher, k as rebuildClientForBucketRegion, l as extractDeploymentEventError, m as green, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as isRetryableTransientError, p as gray, q as resolveStateBucketWithDefault, r as DeployEngine, rt as resolveBucketRegion, s as IMPLICIT_DELETE_DEPENDENCIES, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as AssemblyReader, u as formatResourceLine, v as collectInlinePolicyNamesManagedBySiblings, w as DiffCalculator, x as CloudControlProvider, y as ProviderRegistry, z as AssetManifestLoader } from "./deploy-engine-
|
|
4
|
+
import { $ as findLargeInlineResources, A as shouldRetainResource, B as getDockerImageBySourceHash, C as applyRoleArnIfSet, D as LockManager, E as TemplateParser, F as formatDockerLoginError, G as resolveCaptureObservedState, H as getDefaultStateBucketName, I as getDockerCmd, J as resolveStateBucketWithDefaultAndSource, K as resolveSkipPrefix, L as runDockerForeground, M as stringifyValue, N as WorkGraph, O as S3StateBackend, P as buildDockerImage, Q as MIGRATE_TMP_PREFIX, R as runDockerStreaming, S as IntrinsicFunctionResolver, T as DagBuilder, U as getLegacyStateBucketName, V as Synthesizer, W as resolveApp, X as CFN_TEMPLATE_BODY_LIMIT, Y as warnDeprecatedNoPrefixCliFlag, Z as CFN_TEMPLATE_URL_LIMIT, _ as IAMRoleProvider, a as withRetry, b as findActionableSilentDrops, c as computeImplicitDeleteEdges, d as bold, et as uploadCfnTemplate, f as cyan, g as yellow, h as red, i as withResourceDeadline, j as AssetPublisher, k as rebuildClientForBucketRegion, l as extractDeploymentEventError, m as green, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as isRetryableTransientError, p as gray, q as resolveStateBucketWithDefault, r as DeployEngine, rt as resolveBucketRegion, s as IMPLICIT_DELETE_DEPENDENCIES, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as AssemblyReader, u as formatResourceLine, v as collectInlinePolicyNamesManagedBySiblings, w as DiffCalculator, x as CloudControlProvider, y as ProviderRegistry, z as AssetManifestLoader } from "./deploy-engine-BRZdKXDs.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";
|
|
@@ -1543,6 +1543,10 @@ async function promptRecreateConfirm(input) {
|
|
|
1543
1543
|
* - **Separate keys from state.json** — no state schema bump; event
|
|
1544
1544
|
* files survive `cdkd destroy` (state deletion does not touch
|
|
1545
1545
|
* `deployments/`), preserving post-mortem context.
|
|
1546
|
+
* - **Bounded growth** (issue #885): `finalize()` prunes `{runId}.jsonl`
|
|
1547
|
+
* streams that fell out of the index window so the `deployments/`
|
|
1548
|
+
* prefix stays bounded; `cdkd events prune` (via
|
|
1549
|
+
* {@link DeploymentEventsReader.pruneRuns}) is the explicit purge.
|
|
1546
1550
|
*/
|
|
1547
1551
|
/** Max runs retained in `deployments/index.json` (newest first). */
|
|
1548
1552
|
const DEPLOYMENT_EVENTS_MAX_INDEX_RUNS = 20;
|
|
@@ -1552,7 +1556,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
1552
1556
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
1553
1557
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
1554
1558
|
function getCdkdVersion() {
|
|
1555
|
-
return "0.
|
|
1559
|
+
return "0.228.0";
|
|
1556
1560
|
}
|
|
1557
1561
|
/**
|
|
1558
1562
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -1571,6 +1575,29 @@ function deploymentEventsKey(prefix, stackName, region, runId) {
|
|
|
1571
1575
|
function deploymentEventsIndexKey(prefix, stackName, region) {
|
|
1572
1576
|
return `${prefix}/${stackName}/${region}/deployments/index.json`;
|
|
1573
1577
|
}
|
|
1578
|
+
/** S3 key prefix under which a stack's `deployments/` artifacts live. */
|
|
1579
|
+
function deploymentsDirPrefix(prefix, stackName, region) {
|
|
1580
|
+
return `${prefix}/${stackName}/${region}/deployments/`;
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Parse the wall-clock time encoded in a run id's leading timestamp
|
|
1584
|
+
* (e.g. `20260613T012345678Z-1a2b3c4d`) back to epoch milliseconds.
|
|
1585
|
+
* Returns `null` for any id that does not start with the canonical
|
|
1586
|
+
* compact-ISO prefix — the age-based pruner treats an unparseable id as
|
|
1587
|
+
* "do not delete on age grounds", which is the safe direction.
|
|
1588
|
+
*/
|
|
1589
|
+
function runIdTimestampMs(runId) {
|
|
1590
|
+
const m = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(\d{3})Z/.exec(runId);
|
|
1591
|
+
if (!m) return null;
|
|
1592
|
+
const iso = `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}.${m[7]}Z`;
|
|
1593
|
+
const ms = Date.parse(iso);
|
|
1594
|
+
return Number.isNaN(ms) ? null : ms;
|
|
1595
|
+
}
|
|
1596
|
+
/** Extract the `{runId}` from a `.../{runId}.jsonl` event-stream key. */
|
|
1597
|
+
function runIdFromJsonlKey(key, dirPrefix) {
|
|
1598
|
+
if (!key.startsWith(dirPrefix) || !key.endsWith(".jsonl")) return null;
|
|
1599
|
+
return key.slice(dirPrefix.length, -6);
|
|
1600
|
+
}
|
|
1574
1601
|
/**
|
|
1575
1602
|
* Buffering JSONL writer for one deployment run. Implements the
|
|
1576
1603
|
* {@link DeploymentEventRecorder} seam the deploy engine / destroy
|
|
@@ -1634,7 +1661,8 @@ var DeploymentEventsStore = class {
|
|
|
1634
1661
|
if (this.events.length === 0) return;
|
|
1635
1662
|
await this.enqueueWrite(async () => {
|
|
1636
1663
|
await this.doFlush();
|
|
1637
|
-
await this.updateIndex(result);
|
|
1664
|
+
const keptRunIds = await this.updateIndex(result);
|
|
1665
|
+
await this.pruneSupersededRunFiles(keptRunIds);
|
|
1638
1666
|
});
|
|
1639
1667
|
}
|
|
1640
1668
|
/** Await any in-flight async flushes (used by tests). */
|
|
@@ -1671,6 +1699,10 @@ var DeploymentEventsStore = class {
|
|
|
1671
1699
|
* the last {@link DEPLOYMENT_EVENTS_MAX_INDEX_RUNS} runs. Read-modify-
|
|
1672
1700
|
* write WITHOUT optimistic locking — last-writer-wins (documented
|
|
1673
1701
|
* trade-off; the per-run `.jsonl` files are the source of truth).
|
|
1702
|
+
*
|
|
1703
|
+
* Returns the run ids retained in the index (newest-first), which the
|
|
1704
|
+
* caller feeds to {@link pruneSupersededRunFiles} so the `.jsonl` files
|
|
1705
|
+
* stay bounded to the same window as the index.
|
|
1674
1706
|
*/
|
|
1675
1707
|
async updateIndex(result) {
|
|
1676
1708
|
const key = deploymentEventsIndexKey(this.backend.prefix, this.stackName, this.region);
|
|
@@ -1701,6 +1733,35 @@ var DeploymentEventsStore = class {
|
|
|
1701
1733
|
lastModified: Date.now()
|
|
1702
1734
|
};
|
|
1703
1735
|
await this.backend.putRawObject(key, JSON.stringify(file, null, 2));
|
|
1736
|
+
return runs.map((r) => r.runId);
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Self-bounding prune (issue #885): delete `{runId}.jsonl` streams that
|
|
1740
|
+
* have fallen out of the index window so the `deployments/` prefix does
|
|
1741
|
+
* not grow without bound. Best-effort — runs inside the same write-chain
|
|
1742
|
+
* link as the flush + index write, so a failure here is caught + warned
|
|
1743
|
+
* once by {@link enqueueWrite} and never blocks the deploy / destroy.
|
|
1744
|
+
*
|
|
1745
|
+
* Concurrency-tolerant by construction: the cutoff is the OLDEST retained
|
|
1746
|
+
* run id, and only `.jsonl` files strictly below it are deleted. Run ids
|
|
1747
|
+
* are time-sortable, so a concurrent NEWER run's id sorts ABOVE every
|
|
1748
|
+
* retained id and is never touched. A concurrent run that STARTED earlier
|
|
1749
|
+
* but is still writing could in theory fall below the cutoff if 20+ newer
|
|
1750
|
+
* runs finalized while it ran — an extreme edge that self-heals anyway,
|
|
1751
|
+
* since the whole JSONL body is re-PUT on that run's next flush / finalize
|
|
1752
|
+
* (S3 has no append; each flush rewrites the full stream).
|
|
1753
|
+
*/
|
|
1754
|
+
async pruneSupersededRunFiles(keptRunIds) {
|
|
1755
|
+
if (keptRunIds.length < 20) return;
|
|
1756
|
+
const cutoff = keptRunIds.reduce((min, id) => id < min ? id : min, keptRunIds[0]);
|
|
1757
|
+
const dirPrefix = deploymentsDirPrefix(this.backend.prefix, this.stackName, this.region);
|
|
1758
|
+
const stale = (await this.backend.listRawKeys(dirPrefix)).filter((k) => {
|
|
1759
|
+
const runId = runIdFromJsonlKey(k, dirPrefix);
|
|
1760
|
+
return runId !== null && runId < cutoff;
|
|
1761
|
+
});
|
|
1762
|
+
if (stale.length === 0) return;
|
|
1763
|
+
await this.backend.deleteRawObjects(stale);
|
|
1764
|
+
this.logger.debug(`Pruned ${stale.length} superseded deployment-event stream(s) for ${this.stackName} (${this.region})`);
|
|
1704
1765
|
}
|
|
1705
1766
|
warnOnce(message) {
|
|
1706
1767
|
if (this.warnedOnce) {
|
|
@@ -1819,6 +1880,102 @@ var DeploymentEventsReader = class {
|
|
|
1819
1880
|
}
|
|
1820
1881
|
return events;
|
|
1821
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Prune a stack's recorded deployment-event runs (issue #885 — `cdkd
|
|
1885
|
+
* events prune`). Deletes `{runId}.jsonl` streams beyond a retention
|
|
1886
|
+
* window and rewrites (or removes) `index.json` to match.
|
|
1887
|
+
*
|
|
1888
|
+
* Retention semantics (see {@link DeploymentEventsPruneOptions}):
|
|
1889
|
+
* - `all` — delete every run + the index (full purge).
|
|
1890
|
+
* - `keep N` — retain the newest N runs, delete the rest.
|
|
1891
|
+
* - `olderThanMs`— delete runs whose run-id timestamp is older than the
|
|
1892
|
+
* cutoff; a run id with no parseable timestamp is kept.
|
|
1893
|
+
* - `keep` + `olderThanMs` — a run is deleted only when it is BOTH
|
|
1894
|
+
* beyond the newest-N window AND older than the cutoff
|
|
1895
|
+
* (the most conservative combination).
|
|
1896
|
+
* - none of the above — defaults to `keep DEPLOYMENT_EVENTS_MAX_INDEX_RUNS`
|
|
1897
|
+
* (matches the index window the writer self-bounds to).
|
|
1898
|
+
*
|
|
1899
|
+
* Unlike the writer's best-effort auto-prune, this is user-initiated and
|
|
1900
|
+
* surfaces errors to the caller (the command reports / exits non-zero).
|
|
1901
|
+
*/
|
|
1902
|
+
async pruneRuns(stackName, region, opts = {}) {
|
|
1903
|
+
const dirPrefix = deploymentsDirPrefix(this.backend.prefix, stackName, region);
|
|
1904
|
+
const runIdsDesc = (await this.backend.listRawKeys(dirPrefix)).map((k) => runIdFromJsonlKey(k, dirPrefix)).filter((id) => id !== null).sort().reverse();
|
|
1905
|
+
const indexKey = deploymentEventsIndexKey(this.backend.prefix, stackName, region);
|
|
1906
|
+
if (opts.all === true) {
|
|
1907
|
+
const toDelete = runIdsDesc.map((id) => deploymentEventsKey(this.backend.prefix, stackName, region, id));
|
|
1908
|
+
await this.backend.deleteRawObjects([...toDelete, indexKey]);
|
|
1909
|
+
return {
|
|
1910
|
+
deletedRunIds: runIdsDesc,
|
|
1911
|
+
remainingRunIds: [],
|
|
1912
|
+
indexDeleted: true
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
const noGuards = opts.keep === void 0 && opts.olderThanMs === void 0;
|
|
1916
|
+
const keep = opts.keep ?? (noGuards ? 20 : void 0);
|
|
1917
|
+
const protectedByCount = keep === void 0 ? /* @__PURE__ */ new Set() : new Set(runIdsDesc.slice(0, keep));
|
|
1918
|
+
let candidates = runIdsDesc.filter((id) => !protectedByCount.has(id));
|
|
1919
|
+
if (opts.olderThanMs !== void 0) {
|
|
1920
|
+
const cutoff = (opts.now ?? /* @__PURE__ */ new Date()).getTime() - opts.olderThanMs;
|
|
1921
|
+
candidates = candidates.filter((id) => {
|
|
1922
|
+
const t = runIdTimestampMs(id);
|
|
1923
|
+
return t !== null && t < cutoff;
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
if (candidates.length === 0) return {
|
|
1927
|
+
deletedRunIds: [],
|
|
1928
|
+
remainingRunIds: runIdsDesc,
|
|
1929
|
+
indexDeleted: false
|
|
1930
|
+
};
|
|
1931
|
+
const deletedSet = new Set(candidates);
|
|
1932
|
+
const deleteKeys = candidates.map((id) => deploymentEventsKey(this.backend.prefix, stackName, region, id));
|
|
1933
|
+
const remainingRunIds = runIdsDesc.filter((id) => !deletedSet.has(id));
|
|
1934
|
+
const indexDeleted = await this.rewriteIndexAfterPrune(indexKey, stackName, region, deletedSet, remainingRunIds.length === 0);
|
|
1935
|
+
await this.backend.deleteRawObjects(deleteKeys);
|
|
1936
|
+
return {
|
|
1937
|
+
deletedRunIds: candidates,
|
|
1938
|
+
remainingRunIds,
|
|
1939
|
+
indexDeleted
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Drop the pruned run ids from `index.json`, or delete the index entirely
|
|
1944
|
+
* when no `.jsonl` streams remain. A corrupt / unreadable index is left
|
|
1945
|
+
* untouched (the `.jsonl` files are the source of truth; `cdkd events`
|
|
1946
|
+
* falls back to key enumeration). Returns whether the index was deleted.
|
|
1947
|
+
*/
|
|
1948
|
+
async rewriteIndexAfterPrune(indexKey, stackName, region, deletedRunIds, noRunsRemain) {
|
|
1949
|
+
if (noRunsRemain) {
|
|
1950
|
+
await this.backend.deleteRawObjects([indexKey]);
|
|
1951
|
+
return true;
|
|
1952
|
+
}
|
|
1953
|
+
let raw;
|
|
1954
|
+
try {
|
|
1955
|
+
raw = await this.backend.getRawObject(indexKey);
|
|
1956
|
+
} catch {
|
|
1957
|
+
return false;
|
|
1958
|
+
}
|
|
1959
|
+
if (raw === null) return false;
|
|
1960
|
+
let parsed;
|
|
1961
|
+
try {
|
|
1962
|
+
parsed = JSON.parse(raw);
|
|
1963
|
+
} catch {
|
|
1964
|
+
return false;
|
|
1965
|
+
}
|
|
1966
|
+
if (!Array.isArray(parsed.runs)) return false;
|
|
1967
|
+
const remaining = parsed.runs.filter((r) => !deletedRunIds.has(r.runId));
|
|
1968
|
+
if (remaining.length === parsed.runs.length) return false;
|
|
1969
|
+
const file = {
|
|
1970
|
+
indexVersion: 1,
|
|
1971
|
+
stackName,
|
|
1972
|
+
region,
|
|
1973
|
+
runs: remaining,
|
|
1974
|
+
lastModified: Date.now()
|
|
1975
|
+
};
|
|
1976
|
+
await this.backend.putRawObject(indexKey, JSON.stringify(file, null, 2));
|
|
1977
|
+
return false;
|
|
1978
|
+
}
|
|
1822
1979
|
};
|
|
1823
1980
|
|
|
1824
1981
|
//#endregion
|
|
@@ -37350,7 +37507,7 @@ async function retireCloudFormationStack(options) {
|
|
|
37350
37507
|
}
|
|
37351
37508
|
} else ({body: newBody, modified, format} = injectRetainPolicies(tpl.TemplateBody, cfnStackName));
|
|
37352
37509
|
if (!yes) {
|
|
37353
|
-
if (!await confirmPrompt$
|
|
37510
|
+
if (!await confirmPrompt$6(`Set DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource in CloudFormation stack '${cfnStackName}', then delete the stack? AWS resources will NOT be deleted (cdkd state has been written).`)) {
|
|
37354
37511
|
logger.info("CloudFormation stack retirement cancelled. cdkd state is unaffected.");
|
|
37355
37512
|
for (const cleanup of nestedCleanups) try {
|
|
37356
37513
|
await cleanup();
|
|
@@ -37727,7 +37884,7 @@ async function walkCfnStackTree(stackName, physicalId, cfnClient) {
|
|
|
37727
37884
|
nested
|
|
37728
37885
|
};
|
|
37729
37886
|
}
|
|
37730
|
-
async function confirmPrompt$
|
|
37887
|
+
async function confirmPrompt$6(prompt) {
|
|
37731
37888
|
const rl = readline$1.createInterface({
|
|
37732
37889
|
input: process.stdin,
|
|
37733
37890
|
output: process.stdout
|
|
@@ -38896,7 +39053,7 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
|
|
|
38896
39053
|
return;
|
|
38897
39054
|
}
|
|
38898
39055
|
if (!options.yes) {
|
|
38899
|
-
if (!await confirmPrompt$
|
|
39056
|
+
if (!await confirmPrompt$5(`Update cdkd state with the AWS-current values shown above?`)) {
|
|
38900
39057
|
logger.info("Aborted.");
|
|
38901
39058
|
return;
|
|
38902
39059
|
}
|
|
@@ -39022,7 +39179,7 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
|
|
|
39022
39179
|
return;
|
|
39023
39180
|
}
|
|
39024
39181
|
if (!options.yes) {
|
|
39025
|
-
if (!await confirmPrompt$
|
|
39182
|
+
if (!await confirmPrompt$5(`Push cdkd state values back into AWS for the resources shown above?`)) {
|
|
39026
39183
|
logger.info("Aborted.");
|
|
39027
39184
|
return;
|
|
39028
39185
|
}
|
|
@@ -39130,7 +39287,7 @@ async function runWithConcurrency(tasks, concurrency) {
|
|
|
39130
39287
|
})());
|
|
39131
39288
|
await Promise.all(workers);
|
|
39132
39289
|
}
|
|
39133
|
-
async function confirmPrompt$
|
|
39290
|
+
async function confirmPrompt$5(prompt) {
|
|
39134
39291
|
const rl = readline$1.createInterface({
|
|
39135
39292
|
input: process.stdin,
|
|
39136
39293
|
output: process.stdout
|
|
@@ -39592,6 +39749,86 @@ async function resolveEventsRegion(reader, stackName, explicitRegion) {
|
|
|
39592
39749
|
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");
|
|
39593
39750
|
return regions[0];
|
|
39594
39751
|
}
|
|
39752
|
+
/**
|
|
39753
|
+
* `cdkd events prune <stack>` — reclaim S3 space by deleting old per-run
|
|
39754
|
+
* `{runId}.jsonl` event streams (issue #885). `cdkd destroy` deliberately
|
|
39755
|
+
* keeps event history as post-mortem context, so this is the explicit way
|
|
39756
|
+
* to purge it; the deploy/destroy writer also self-bounds to the last
|
|
39757
|
+
* {@link DEPLOYMENT_EVENTS_MAX_INDEX_RUNS} runs automatically.
|
|
39758
|
+
*
|
|
39759
|
+
* Retention selection:
|
|
39760
|
+
* - `--all` purge every run + the index.
|
|
39761
|
+
* - `--keep <N>` retain the newest N runs.
|
|
39762
|
+
* - `--older-than <dur>` delete runs older than the duration.
|
|
39763
|
+
* - both keep+older-than: delete runs that are BOTH beyond newest-N AND
|
|
39764
|
+
* older than the cutoff.
|
|
39765
|
+
* - none of the above: default to keeping the newest
|
|
39766
|
+
* {@link DEPLOYMENT_EVENTS_MAX_INDEX_RUNS}.
|
|
39767
|
+
*/
|
|
39768
|
+
async function eventsPruneCommand(stackName, options) {
|
|
39769
|
+
const logger = getLogger();
|
|
39770
|
+
if (options.verbose) logger.setLevel("debug");
|
|
39771
|
+
warnIfDeprecatedRegion(options);
|
|
39772
|
+
if (options.all === true && (options.keep !== void 0 || options.olderThan !== void 0)) throw new CdkdError("'--all' purges every run and cannot be combined with '--keep' / '--older-than'.", "EVENTS_PRUNE_BAD_FLAGS");
|
|
39773
|
+
const olderThanMs = options.olderThan !== void 0 ? parseDuration(options.olderThan) : void 0;
|
|
39774
|
+
const awsClients = new AwsClients({
|
|
39775
|
+
...options.region && { region: options.region },
|
|
39776
|
+
...options.profile && { profile: options.profile }
|
|
39777
|
+
});
|
|
39778
|
+
setAwsClients(awsClients);
|
|
39779
|
+
try {
|
|
39780
|
+
const region = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
39781
|
+
const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
39782
|
+
const prefix = options.statePrefix ?? "cdkd";
|
|
39783
|
+
const stateBackend = new S3StateBackend(awsClients.s3, {
|
|
39784
|
+
bucket,
|
|
39785
|
+
prefix
|
|
39786
|
+
}, {
|
|
39787
|
+
region,
|
|
39788
|
+
...options.profile && { profile: options.profile }
|
|
39789
|
+
});
|
|
39790
|
+
await stateBackend.verifyBucketExists();
|
|
39791
|
+
const reader = new DeploymentEventsReader(stateBackend);
|
|
39792
|
+
const targetRegion = await resolveEventsRegion(reader, stackName, options.stackRegion);
|
|
39793
|
+
const totalRuns = (await reader.listRuns(stackName, targetRegion)).length;
|
|
39794
|
+
const scope = options.all ? `ALL ${totalRuns} run(s)` : options.keep !== void 0 && olderThanMs !== void 0 ? `runs beyond the newest ${options.keep} AND older than ${options.olderThan}` : options.keep !== void 0 ? `runs beyond the newest ${options.keep}` : olderThanMs !== void 0 ? `runs older than ${options.olderThan}` : `runs beyond the newest ${20}`;
|
|
39795
|
+
if (options.yes !== true) {
|
|
39796
|
+
if (!process.stdin.isTTY) {
|
|
39797
|
+
logger.info(gray(`Refusing to prune deployment-event history for ${stackName} (${targetRegion}) without confirmation on a non-interactive terminal. Re-run with --yes to proceed.`));
|
|
39798
|
+
return;
|
|
39799
|
+
}
|
|
39800
|
+
if (!await confirmPrompt$4(`Prune deployment-event history for ${cyan(stackName)} ${gray(`(${targetRegion})`)}: ${scope}?`)) {
|
|
39801
|
+
logger.info(gray("Aborted; nothing was deleted."));
|
|
39802
|
+
return;
|
|
39803
|
+
}
|
|
39804
|
+
}
|
|
39805
|
+
const result = await reader.pruneRuns(stackName, targetRegion, {
|
|
39806
|
+
...options.all === true && { all: true },
|
|
39807
|
+
...options.keep !== void 0 && { keep: options.keep },
|
|
39808
|
+
...olderThanMs !== void 0 && { olderThanMs }
|
|
39809
|
+
});
|
|
39810
|
+
if (result.deletedRunIds.length === 0) {
|
|
39811
|
+
logger.info(gray(result.indexDeleted ? `Removed the empty deployment-event index for ${stackName} (${targetRegion}); no run streams to delete.` : `No runs matched the prune criteria for ${stackName} (${targetRegion}).`));
|
|
39812
|
+
return;
|
|
39813
|
+
}
|
|
39814
|
+
logger.info(`${green("Pruned")} ${result.deletedRunIds.length} deployment-event run(s) for ${cyan(stackName)} ${gray(`(${targetRegion})`)}; ${result.remainingRunIds.length} retained` + (result.indexDeleted ? gray(" (index removed)") : "") + ".");
|
|
39815
|
+
} finally {
|
|
39816
|
+
awsClients.destroy();
|
|
39817
|
+
}
|
|
39818
|
+
}
|
|
39819
|
+
/** Minimal `(y/N)` confirmation prompt. */
|
|
39820
|
+
async function confirmPrompt$4(prompt) {
|
|
39821
|
+
const rl = readline$1.createInterface({
|
|
39822
|
+
input: process.stdin,
|
|
39823
|
+
output: process.stdout
|
|
39824
|
+
});
|
|
39825
|
+
try {
|
|
39826
|
+
const ans = await rl.question(`${prompt} [y/N] `);
|
|
39827
|
+
return /^y(es)?$/i.test(ans.trim());
|
|
39828
|
+
} finally {
|
|
39829
|
+
rl.close();
|
|
39830
|
+
}
|
|
39831
|
+
}
|
|
39595
39832
|
/** Human-readable run listing (newest first). */
|
|
39596
39833
|
function printRunList(stackName, region, runs) {
|
|
39597
39834
|
const logger = getLogger();
|
|
@@ -39652,8 +39889,27 @@ function createEventsCommand() {
|
|
|
39652
39889
|
}));
|
|
39653
39890
|
[...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
|
|
39654
39891
|
cmd.addOption(deprecatedRegionOption);
|
|
39892
|
+
cmd.addCommand(createEventsPruneCommand());
|
|
39655
39893
|
return cmd;
|
|
39656
39894
|
}
|
|
39895
|
+
/**
|
|
39896
|
+
* Create the `cdkd events prune <stack>` subcommand (issue #885).
|
|
39897
|
+
*
|
|
39898
|
+
* Only the prune-SPECIFIC options (`--keep` / `--older-than` / `--all`) are
|
|
39899
|
+
* declared here. The shared option blocks (`commonOptions` + `stateOptions`
|
|
39900
|
+
* + the deprecated region option) and `--stack-region` are inherited from
|
|
39901
|
+
* the parent `events` command — declaring the same flag on BOTH parent and
|
|
39902
|
+
* child makes Commander route a post-subcommand flag (`events prune X --yes`)
|
|
39903
|
+
* to the PARENT's storage, leaving the child's value at its default. The
|
|
39904
|
+
* action therefore reads the merged view via `command.optsWithGlobals()`.
|
|
39905
|
+
*/
|
|
39906
|
+
function createEventsPruneCommand() {
|
|
39907
|
+
return new Command("prune").description("Delete old per-run deployment-event streams to reclaim S3 space").argument("<stack>", "Stack name (physical CloudFormation name)").addOption(new Option("--keep <N>", "Retain only the newest N runs").argParser((v) => {
|
|
39908
|
+
const n = parseInt(v, 10);
|
|
39909
|
+
if (!Number.isInteger(n) || n < 0) throw new Error(`Invalid --keep value "${v}": expected a non-negative integer.`);
|
|
39910
|
+
return n;
|
|
39911
|
+
})).option("--older-than <duration>", "Delete runs older than this duration (e.g. 24h, 90m)").option("--all", "Delete every recorded run and the index (full purge)", false).action(withErrorHandling((stack, _options, command) => eventsPruneCommand(stack, command.optsWithGlobals())));
|
|
39912
|
+
}
|
|
39657
39913
|
|
|
39658
39914
|
//#endregion
|
|
39659
39915
|
//#region src/cli/cdk-path.ts
|
|
@@ -54696,7 +54952,7 @@ function reorderArgs(argv) {
|
|
|
54696
54952
|
async function main() {
|
|
54697
54953
|
installPipeCloseHandler();
|
|
54698
54954
|
const program = new Command();
|
|
54699
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
54955
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.228.0");
|
|
54700
54956
|
program.addCommand(createBootstrapCommand());
|
|
54701
54957
|
program.addCommand(createSynthCommand());
|
|
54702
54958
|
program.addCommand(createListCommand());
|