cdk-agc 1.2.0 → 1.3.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
@@ -7,7 +7,7 @@
7
7
  `cdk-agc` is a fast CLI tool that scans your AWS CDK cloud assembly directory and helps you reclaim disk space:
8
8
 
9
9
  - **Clean `cdk.out` directories**: Remove unused assets while protecting referenced files
10
- - Only deletes unreferenced `asset.*` directories - all other files are automatically protected
10
+ - Only deletes unreferenced `asset.*` directories and files - all other files are automatically protected
11
11
  - Protects recently modified files (configurable with `-k/--keep-hours`)
12
12
 
13
13
  - **Clean temporary directories** (`-t/--cleanup-tmp`): Delete accumulated temporary CDK directories in `$TMPDIR`
@@ -85,6 +85,7 @@ npx cdk-agc -t
85
85
  ### Deletion Candidates
86
86
 
87
87
  - `asset.{hash}/` - Unreferenced asset directories
88
+ - `asset.{hash}.{ext}` - Unreferenced asset files (e.g., `.txt`, `.zip`)
88
89
 
89
90
  ### Always Protected
90
91
 
@@ -0,0 +1,13 @@
1
+ //#region src/asset-cleanup.d.ts
2
+ interface CleanupOptions {
3
+ outdir: string;
4
+ dryRun: boolean;
5
+ keepHours: number;
6
+ }
7
+ /**
8
+ * Clean up cdk.out directory
9
+ */
10
+ declare function cleanupAssets(options: CleanupOptions): Promise<void>;
11
+ //#endregion
12
+ export { CleanupOptions, cleanupAssets };
13
+ //# sourceMappingURL=asset-cleanup.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-cleanup.d.mts","names":[],"sources":["../src/asset-cleanup.ts"],"mappings":";UAIiB,cAAA;EACf,MAAA;EACA,MAAA;EACA,SAAA;AAAA;;;;iBAMoB,aAAA,CAAc,OAAA,EAAS,cAAA,GAAiB,OAAA"}
@@ -0,0 +1,99 @@
1
+ import { calculateSize, formatSize } from "./utils.mjs";
2
+ import { promises } from "fs";
3
+ import path from "path";
4
+
5
+ //#region src/asset-cleanup.ts
6
+ /**
7
+ * Clean up cdk.out directory
8
+ */
9
+ async function cleanupAssets(options) {
10
+ const { outdir, dryRun, keepHours } = options;
11
+ const fullPath = path.resolve(outdir);
12
+ console.log(`Scanning ${fullPath}`);
13
+ console.log(keepHours > 0 ? `Keeping files modified within ${keepHours} hours\n` : "");
14
+ try {
15
+ await promises.access(outdir);
16
+ } catch {
17
+ throw new Error(`Directory not found: ${fullPath}`);
18
+ }
19
+ const activePaths = await collectAssetPaths(outdir);
20
+ const entries = await promises.readdir(outdir);
21
+ const itemsToDelete = (await Promise.all(entries.filter((entry) => entry.startsWith("asset.")).map(async (entry) => {
22
+ const itemPath = path.join(outdir, entry);
23
+ if (await isProtected(itemPath, activePaths, keepHours)) return null;
24
+ return {
25
+ path: itemPath,
26
+ size: await calculateSize(itemPath)
27
+ };
28
+ }))).filter((item) => item !== null);
29
+ if (itemsToDelete.length === 0) {
30
+ console.log(`✓ No unused assets found.`);
31
+ return;
32
+ }
33
+ console.log(`Found ${itemsToDelete.length} unused item(s):\n`);
34
+ itemsToDelete.forEach((item) => {
35
+ const relativePath = path.relative(outdir, item.path);
36
+ console.log(` - ${relativePath} (${formatSize(item.size)})`);
37
+ });
38
+ const totalSize = itemsToDelete.reduce((sum, item) => sum + item.size, 0);
39
+ console.log(`\nTotal size to reclaim: ${formatSize(totalSize)}\n`);
40
+ if (dryRun) console.log("Dry-run mode: No files were deleted.");
41
+ else {
42
+ await Promise.all(itemsToDelete.map((item) => promises.rm(item.path, {
43
+ recursive: true,
44
+ force: true
45
+ })));
46
+ console.log("✓ Cleanup completed successfully.");
47
+ }
48
+ }
49
+ /**
50
+ * Recursively collect asset paths from *.assets.json files
51
+ */
52
+ async function collectAssetPaths(dirPath) {
53
+ const activePaths = /* @__PURE__ */ new Set();
54
+ const items = await promises.readdir(dirPath, { withFileTypes: true });
55
+ for (const item of items) {
56
+ const itemPath = path.join(dirPath, item.name);
57
+ if (item.isDirectory()) {
58
+ (await collectAssetPaths(itemPath)).forEach((p) => activePaths.add(p));
59
+ continue;
60
+ }
61
+ if (!item.name.endsWith(".assets.json")) continue;
62
+ try {
63
+ const content = await promises.readFile(itemPath, "utf-8");
64
+ const assets = JSON.parse(content);
65
+ if (assets.files) for (const fileEntry of Object.values(assets.files)) {
66
+ const entry = fileEntry;
67
+ if (entry.source?.path) {
68
+ const assetPath = path.join(path.dirname(itemPath), entry.source.path);
69
+ activePaths.add(assetPath);
70
+ }
71
+ }
72
+ if (assets.dockerImages) for (const imageEntry of Object.values(assets.dockerImages)) {
73
+ const entry = imageEntry;
74
+ if (entry.source?.directory) {
75
+ const assetPath = path.join(path.dirname(itemPath), entry.source.directory);
76
+ activePaths.add(assetPath);
77
+ }
78
+ }
79
+ } catch (error) {
80
+ console.warn(`Warning: Failed to parse ${item.name}:`, error);
81
+ }
82
+ }
83
+ return activePaths;
84
+ }
85
+ /**
86
+ * Check if file/directory should be protected from deletion
87
+ */
88
+ async function isProtected(itemPath, activePaths, keepHours) {
89
+ if (activePaths.has(itemPath)) return true;
90
+ if (keepHours > 0) {
91
+ const stats = await promises.stat(itemPath);
92
+ if ((Date.now() - stats.mtimeMs) / (1e3 * 60 * 60) <= keepHours) return true;
93
+ }
94
+ return false;
95
+ }
96
+
97
+ //#endregion
98
+ export { cleanupAssets };
99
+ //# sourceMappingURL=asset-cleanup.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-cleanup.mjs","names":["fs"],"sources":["../src/asset-cleanup.ts"],"sourcesContent":["import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport { calculateSize, formatSize } from \"./utils.js\";\n\nexport interface CleanupOptions {\n outdir: string;\n dryRun: boolean;\n keepHours: number;\n}\n\n/**\n * Clean up cdk.out directory\n */\nexport async function cleanupAssets(options: CleanupOptions): Promise<void> {\n const { outdir, dryRun, keepHours } = options;\n\n const fullPath = path.resolve(outdir);\n console.log(`Scanning ${fullPath}`);\n console.log(keepHours > 0 ? `Keeping files modified within ${keepHours} hours\\n` : \"\");\n\n // Check directory exists\n try {\n await fs.access(outdir);\n } catch {\n throw new Error(`Directory not found: ${fullPath}`);\n }\n\n // Collect asset paths referenced in *.assets.json files\n const activePaths = await collectAssetPaths(outdir);\n\n // Scan directory items\n const entries = await fs.readdir(outdir);\n\n const itemsToDelete = (\n await Promise.all(\n entries\n .filter((entry) => entry.startsWith(\"asset.\"))\n .map(async (entry) => {\n const itemPath = path.join(outdir, entry);\n\n if (await isProtected(itemPath, activePaths, keepHours)) {\n return null;\n }\n\n const size = await calculateSize(itemPath);\n return { path: itemPath, size };\n }),\n )\n ).filter((item): item is { path: string; size: number } => item !== null);\n\n // Display results\n if (itemsToDelete.length === 0) {\n console.log(`✓ No unused assets found.`);\n return;\n }\n\n console.log(`Found ${itemsToDelete.length} unused item(s):\\n`);\n itemsToDelete.forEach((item) => {\n const relativePath = path.relative(outdir, item.path);\n console.log(` - ${relativePath} (${formatSize(item.size)})`);\n });\n\n const totalSize = itemsToDelete.reduce((sum, item) => sum + item.size, 0);\n console.log(`\\nTotal size to reclaim: ${formatSize(totalSize)}\\n`);\n\n if (dryRun) {\n console.log(\"Dry-run mode: No files were deleted.\");\n } else {\n // Delete in parallel\n await Promise.all(\n itemsToDelete.map((item) => fs.rm(item.path, { recursive: true, force: true })),\n );\n console.log(\"✓ Cleanup completed successfully.\");\n }\n}\n\n/**\n * Recursively collect asset paths from *.assets.json files\n */\nasync function collectAssetPaths(dirPath: string): Promise<Set<string>> {\n const activePaths = new Set<string>();\n const items = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const item of items) {\n const itemPath = path.join(dirPath, item.name);\n\n // Recursively scan subdirectories (e.g., assembly-MyStage/)\n if (item.isDirectory()) {\n const subPaths = await collectAssetPaths(itemPath);\n subPaths.forEach((p) => activePaths.add(p));\n continue;\n }\n\n // Only process *.assets.json files\n if (!item.name.endsWith(\".assets.json\")) {\n continue;\n }\n\n // Parse assets.json file\n try {\n const content = await fs.readFile(itemPath, \"utf-8\");\n const assets = JSON.parse(content);\n\n // Collect asset paths from files object\n if (assets.files) {\n for (const fileEntry of Object.values(assets.files)) {\n const entry = fileEntry as { source?: { path?: string } };\n if (entry.source?.path) {\n const assetPath = path.join(path.dirname(itemPath), entry.source.path);\n activePaths.add(assetPath);\n }\n }\n }\n\n // Collect asset paths from dockerImages object\n if (assets.dockerImages) {\n for (const imageEntry of Object.values(assets.dockerImages)) {\n const entry = imageEntry as { source?: { directory?: string } };\n if (entry.source?.directory) {\n const assetPath = path.join(path.dirname(itemPath), entry.source.directory);\n activePaths.add(assetPath);\n }\n }\n }\n } catch (error) {\n // Skip malformed asset files\n console.warn(`Warning: Failed to parse ${item.name}:`, error);\n }\n }\n\n return activePaths;\n}\n\n/**\n * Check if file/directory should be protected from deletion\n */\nasync function isProtected(\n itemPath: string,\n activePaths: Set<string>,\n keepHours: number,\n): Promise<boolean> {\n // Protect assets referenced in *.assets.json files\n if (activePaths.has(itemPath)) {\n return true;\n }\n\n // Protect files/directories within retention period\n if (keepHours > 0) {\n const stats = await fs.stat(itemPath);\n const ageHours = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60);\n if (ageHours <= keepHours) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":"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"}
package/dist/cli.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { cleanupAssets, cleanupTempDirectories } from "./cleanup.mjs";
2
+ import { cleanupAssets } from "./asset-cleanup.mjs";
3
+ import { cleanupTempDirectories } from "./temp-cleanup.mjs";
3
4
  import { Command } from "commander";
4
5
  import { createRequire } from "module";
5
6
 
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { createRequire } from \"module\";\nimport { cleanupAssets, cleanupTempDirectories } from \"./cleanup.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\");\n\nconst program = new Command();\n\nprogram\n .name(\"cdk-agc\")\n .description(\"CDK Assembly Garbage Collector - Clean up unused assets in cdk.out\")\n .version(pkg.version)\n .option(\"-o, --outdir <path>\", \"CDK output directory\", \"cdk.out\")\n .option(\"-d, --dry-run\", \"Show what would be deleted without actually deleting\", false)\n .option(\"-k, --keep-hours <number>\", \"Protect files modified within this many hours\", \"0\")\n .option(\"-t, --cleanup-tmp\", \"Clean up all temporary cdk.out directories in $TMPDIR\", false)\n .action(async (options) => {\n try {\n const keepHours = parseInt(options.keepHours, 10);\n if (isNaN(keepHours) || keepHours < 0) {\n console.error(\"Error: --keep-hours must be a non-negative number\");\n process.exit(1);\n }\n\n // Check for conflicting options\n if (options.cleanupTmp && options.outdir !== \"cdk.out\") {\n console.error(\"Error: --cleanup-tmp and --outdir cannot be used together\");\n process.exit(1);\n }\n\n if (options.cleanupTmp) {\n await cleanupTempDirectories({\n dryRun: options.dryRun,\n keepHours,\n });\n } else {\n await cleanupAssets({\n outdir: options.outdir,\n dryRun: options.dryRun,\n keepHours,\n });\n }\n } catch (error) {\n console.error(\"Error:\", error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":";;;;;;AAMA,MAAM,MADU,cAAc,OAAO,KAAK,IAAI,CAC1B,kBAAkB;AAEtC,MAAM,UAAU,IAAI,SAAS;AAE7B,QACG,KAAK,UAAU,CACf,YAAY,qEAAqE,CACjF,QAAQ,IAAI,QAAQ,CACpB,OAAO,uBAAuB,wBAAwB,UAAU,CAChE,OAAO,iBAAiB,wDAAwD,MAAM,CACtF,OAAO,6BAA6B,iDAAiD,IAAI,CACzF,OAAO,qBAAqB,yDAAyD,MAAM,CAC3F,OAAO,OAAO,YAAY;AACzB,KAAI;EACF,MAAM,YAAY,SAAS,QAAQ,WAAW,GAAG;AACjD,MAAI,MAAM,UAAU,IAAI,YAAY,GAAG;AACrC,WAAQ,MAAM,oDAAoD;AAClE,WAAQ,KAAK,EAAE;;AAIjB,MAAI,QAAQ,cAAc,QAAQ,WAAW,WAAW;AACtD,WAAQ,MAAM,4DAA4D;AAC1E,WAAQ,KAAK,EAAE;;AAGjB,MAAI,QAAQ,WACV,OAAM,uBAAuB;GAC3B,QAAQ,QAAQ;GAChB;GACD,CAAC;MAEF,OAAM,cAAc;GAClB,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB;GACD,CAAC;UAEG,OAAO;AACd,UAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;AAC/E,UAAQ,KAAK,EAAE;;EAEjB;AAEJ,QAAQ,OAAO"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { createRequire } from \"module\";\nimport { cleanupAssets } from \"./asset-cleanup.js\";\nimport { cleanupTempDirectories } from \"./temp-cleanup.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\");\n\nconst program = new Command();\n\nprogram\n .name(\"cdk-agc\")\n .description(\"CDK Assembly Garbage Collector - Clean up unused assets in cdk.out\")\n .version(pkg.version)\n .option(\"-o, --outdir <path>\", \"CDK output directory\", \"cdk.out\")\n .option(\"-d, --dry-run\", \"Show what would be deleted without actually deleting\", false)\n .option(\"-k, --keep-hours <number>\", \"Protect files modified within this many hours\", \"0\")\n .option(\"-t, --cleanup-tmp\", \"Clean up all temporary cdk.out directories in $TMPDIR\", false)\n .action(async (options) => {\n try {\n const keepHours = parseInt(options.keepHours, 10);\n if (isNaN(keepHours) || keepHours < 0) {\n console.error(\"Error: --keep-hours must be a non-negative number\");\n process.exit(1);\n }\n\n // Check for conflicting options\n if (options.cleanupTmp && options.outdir !== \"cdk.out\") {\n console.error(\"Error: --cleanup-tmp and --outdir cannot be used together\");\n process.exit(1);\n }\n\n if (options.cleanupTmp) {\n await cleanupTempDirectories({\n dryRun: options.dryRun,\n keepHours,\n });\n } else {\n await cleanupAssets({\n outdir: options.outdir,\n dryRun: options.dryRun,\n keepHours,\n });\n }\n } catch (error) {\n console.error(\"Error:\", error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":";;;;;;;AAOA,MAAM,MADU,cAAc,OAAO,KAAK,IAAI,CAC1B,kBAAkB;AAEtC,MAAM,UAAU,IAAI,SAAS;AAE7B,QACG,KAAK,UAAU,CACf,YAAY,qEAAqE,CACjF,QAAQ,IAAI,QAAQ,CACpB,OAAO,uBAAuB,wBAAwB,UAAU,CAChE,OAAO,iBAAiB,wDAAwD,MAAM,CACtF,OAAO,6BAA6B,iDAAiD,IAAI,CACzF,OAAO,qBAAqB,yDAAyD,MAAM,CAC3F,OAAO,OAAO,YAAY;AACzB,KAAI;EACF,MAAM,YAAY,SAAS,QAAQ,WAAW,GAAG;AACjD,MAAI,MAAM,UAAU,IAAI,YAAY,GAAG;AACrC,WAAQ,MAAM,oDAAoD;AAClE,WAAQ,KAAK,EAAE;;AAIjB,MAAI,QAAQ,cAAc,QAAQ,WAAW,WAAW;AACtD,WAAQ,MAAM,4DAA4D;AAC1E,WAAQ,KAAK,EAAE;;AAGjB,MAAI,QAAQ,WACV,OAAM,uBAAuB;GAC3B,QAAQ,QAAQ;GAChB;GACD,CAAC;MAEF,OAAM,cAAc;GAClB,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB;GACD,CAAC;UAEG,OAAO;AACd,UAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;AAC/E,UAAQ,KAAK,EAAE;;EAEjB;AAEJ,QAAQ,OAAO"}
@@ -0,0 +1,12 @@
1
+ //#region src/temp-cleanup.d.ts
2
+ interface TempCleanupOptions {
3
+ dryRun: boolean;
4
+ keepHours: number;
5
+ }
6
+ /**
7
+ * Clean up all temporary CDK output directories
8
+ */
9
+ declare function cleanupTempDirectories(options: TempCleanupOptions): Promise<void>;
10
+ //#endregion
11
+ export { TempCleanupOptions, cleanupTempDirectories };
12
+ //# sourceMappingURL=temp-cleanup.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temp-cleanup.d.mts","names":[],"sources":["../src/temp-cleanup.ts"],"mappings":";UAKiB,kBAAA;EACf,MAAA;EACA,SAAA;AAAA;;;AAMF;iBAAsB,sBAAA,CAAuB,OAAA,EAAS,kBAAA,GAAqB,OAAA"}
@@ -0,0 +1,70 @@
1
+ import { calculateSize, formatSize } from "./utils.mjs";
2
+ import { promises } from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+
6
+ //#region src/temp-cleanup.ts
7
+ /**
8
+ * Clean up all temporary CDK output directories
9
+ */
10
+ async function cleanupTempDirectories(options) {
11
+ const { dryRun, keepHours } = options;
12
+ const tmpdir = os.tmpdir();
13
+ console.log(`Scanning ${tmpdir}`);
14
+ console.log(keepHours > 0 ? `Keeping directories modified within ${keepHours} hours\n` : "");
15
+ const directories = await findTempDirectories();
16
+ if (directories.length === 0) {
17
+ console.log("✓ No temporary CDK directories found.");
18
+ return;
19
+ }
20
+ let totalCleaned = 0;
21
+ let totalSize = 0;
22
+ for (const dir of directories) try {
23
+ if (await shouldProtectDirectory(dir, keepHours)) continue;
24
+ const size = await calculateSize(dir);
25
+ totalSize += size;
26
+ if (!dryRun) await promises.rm(dir, {
27
+ recursive: true,
28
+ force: true
29
+ });
30
+ totalCleaned++;
31
+ } catch {
32
+ continue;
33
+ }
34
+ if (totalCleaned === 0) {
35
+ console.log("✓ No temporary CDK directories to clean.");
36
+ return;
37
+ }
38
+ console.log(`Found ${totalCleaned} temporary CDK directory(ies)\n`);
39
+ console.log(`Total size to reclaim: ${formatSize(totalSize)}\n`);
40
+ if (dryRun) console.log("Dry-run mode: No files were deleted.");
41
+ else console.log("✓ Cleanup completed successfully.");
42
+ }
43
+ /**
44
+ * Find all cdk.out temporary directories in $TMPDIR
45
+ */
46
+ async function findTempDirectories() {
47
+ const tmpdir = os.tmpdir();
48
+ try {
49
+ return (await promises.readdir(tmpdir, { withFileTypes: true })).filter((item) => item.isDirectory() && (item.name.startsWith("cdk.out") || item.name.startsWith("cdk-") || item.name.startsWith(".cdk"))).map((item) => path.join(tmpdir, item.name));
50
+ } catch (error) {
51
+ console.warn(`Warning: Failed to scan $TMPDIR (${tmpdir}):`, error);
52
+ return [];
53
+ }
54
+ }
55
+ /**
56
+ * Check if directory should be protected based on age
57
+ */
58
+ async function shouldProtectDirectory(dirPath, keepHours) {
59
+ if (keepHours <= 0) return false;
60
+ try {
61
+ const stats = await promises.stat(dirPath);
62
+ return (Date.now() - stats.mtimeMs) / (1e3 * 60 * 60) <= keepHours;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ //#endregion
69
+ export { cleanupTempDirectories };
70
+ //# sourceMappingURL=temp-cleanup.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temp-cleanup.mjs","names":["fs"],"sources":["../src/temp-cleanup.ts"],"sourcesContent":["import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport os from \"os\";\nimport { calculateSize, formatSize } from \"./utils.js\";\n\nexport interface TempCleanupOptions {\n dryRun: boolean;\n keepHours: number;\n}\n\n/**\n * Clean up all temporary CDK output directories\n */\nexport async function cleanupTempDirectories(options: TempCleanupOptions): Promise<void> {\n const { dryRun, keepHours } = options;\n const tmpdir = os.tmpdir();\n\n console.log(`Scanning ${tmpdir}`);\n console.log(keepHours > 0 ? `Keeping directories modified within ${keepHours} hours\\n` : \"\");\n\n const directories = await findTempDirectories();\n\n if (directories.length === 0) {\n console.log(\"✓ No temporary CDK directories found.\");\n return;\n }\n\n let totalCleaned = 0;\n let totalSize = 0;\n\n for (const dir of directories) {\n try {\n // Check if directory should be protected by age\n if (await shouldProtectDirectory(dir, keepHours)) {\n continue;\n }\n\n // Calculate size before deletion\n const size = await calculateSize(dir);\n totalSize += size;\n\n if (!dryRun) {\n await fs.rm(dir, { recursive: true, force: true });\n }\n\n totalCleaned++;\n } catch {\n // Silently continue on error\n continue;\n }\n }\n\n if (totalCleaned === 0) {\n console.log(\"✓ No temporary CDK directories to clean.\");\n return;\n }\n\n console.log(`Found ${totalCleaned} temporary CDK directory(ies)\\n`);\n console.log(`Total size to reclaim: ${formatSize(totalSize)}\\n`);\n\n if (dryRun) {\n console.log(\"Dry-run mode: No files were deleted.\");\n } else {\n console.log(\"✓ Cleanup completed successfully.\");\n }\n}\n\n/**\n * Find all cdk.out temporary directories in $TMPDIR\n */\nasync function findTempDirectories(): Promise<string[]> {\n const tmpdir = os.tmpdir();\n\n try {\n const items = await fs.readdir(tmpdir, { withFileTypes: true });\n\n return items\n .filter(\n (item) =>\n item.isDirectory() &&\n (item.name.startsWith(\"cdk.out\") ||\n item.name.startsWith(\"cdk-\") ||\n item.name.startsWith(\".cdk\")),\n )\n .map((item) => path.join(tmpdir, item.name));\n } catch (error) {\n console.warn(`Warning: Failed to scan $TMPDIR (${tmpdir}):`, error);\n return [];\n }\n}\n\n/**\n * Check if directory should be protected based on age\n */\nasync function shouldProtectDirectory(dirPath: string, keepHours: number): Promise<boolean> {\n if (keepHours <= 0) {\n return false;\n }\n\n try {\n const stats = await fs.stat(dirPath);\n const ageHours = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60);\n return ageHours <= keepHours;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;AAaA,eAAsB,uBAAuB,SAA4C;CACvF,MAAM,EAAE,QAAQ,cAAc;CAC9B,MAAM,SAAS,GAAG,QAAQ;AAE1B,SAAQ,IAAI,YAAY,SAAS;AACjC,SAAQ,IAAI,YAAY,IAAI,uCAAuC,UAAU,YAAY,GAAG;CAE5F,MAAM,cAAc,MAAM,qBAAqB;AAE/C,KAAI,YAAY,WAAW,GAAG;AAC5B,UAAQ,IAAI,wCAAwC;AACpD;;CAGF,IAAI,eAAe;CACnB,IAAI,YAAY;AAEhB,MAAK,MAAM,OAAO,YAChB,KAAI;AAEF,MAAI,MAAM,uBAAuB,KAAK,UAAU,CAC9C;EAIF,MAAM,OAAO,MAAM,cAAc,IAAI;AACrC,eAAa;AAEb,MAAI,CAAC,OACH,OAAMA,SAAG,GAAG,KAAK;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAGpD;SACM;AAEN;;AAIJ,KAAI,iBAAiB,GAAG;AACtB,UAAQ,IAAI,2CAA2C;AACvD;;AAGF,SAAQ,IAAI,SAAS,aAAa,iCAAiC;AACnE,SAAQ,IAAI,0BAA0B,WAAW,UAAU,CAAC,IAAI;AAEhE,KAAI,OACF,SAAQ,IAAI,uCAAuC;KAEnD,SAAQ,IAAI,oCAAoC;;;;;AAOpD,eAAe,sBAAyC;CACtD,MAAM,SAAS,GAAG,QAAQ;AAE1B,KAAI;AAGF,UAFc,MAAMA,SAAG,QAAQ,QAAQ,EAAE,eAAe,MAAM,CAAC,EAG5D,QACE,SACC,KAAK,aAAa,KACjB,KAAK,KAAK,WAAW,UAAU,IAC9B,KAAK,KAAK,WAAW,OAAO,IAC5B,KAAK,KAAK,WAAW,OAAO,EACjC,CACA,KAAK,SAAS,KAAK,KAAK,QAAQ,KAAK,KAAK,CAAC;UACvC,OAAO;AACd,UAAQ,KAAK,oCAAoC,OAAO,KAAK,MAAM;AACnE,SAAO,EAAE;;;;;;AAOb,eAAe,uBAAuB,SAAiB,WAAqC;AAC1F,KAAI,aAAa,EACf,QAAO;AAGT,KAAI;EACF,MAAM,QAAQ,MAAMA,SAAG,KAAK,QAAQ;AAEpC,UADkB,KAAK,KAAK,GAAG,MAAM,YAAY,MAAO,KAAK,OAC1C;SACb;AACN,SAAO"}
@@ -0,0 +1,12 @@
1
+ //#region src/utils.d.ts
2
+ /**
3
+ * Calculate size of file or directory
4
+ */
5
+ declare function calculateSize(itemPath: string): Promise<number>;
6
+ /**
7
+ * Format bytes to human-readable string
8
+ */
9
+ declare function formatSize(bytes: number): string;
10
+ //#endregion
11
+ export { calculateSize, formatSize };
12
+ //# sourceMappingURL=utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"mappings":";;AAMA;;iBAAsB,aAAA,CAAc,QAAA,WAAmB,OAAA;;;AAqBvD;iBAAgB,UAAA,CAAW,KAAA"}
package/dist/utils.mjs ADDED
@@ -0,0 +1,38 @@
1
+ import { promises } from "fs";
2
+ import path from "path";
3
+
4
+ //#region src/utils.ts
5
+ /**
6
+ * Calculate size of file or directory
7
+ */
8
+ async function calculateSize(itemPath) {
9
+ const stats = await promises.stat(itemPath);
10
+ if (stats.isFile()) return stats.size;
11
+ if (stats.isDirectory()) {
12
+ const entries = await promises.readdir(itemPath);
13
+ return (await Promise.all(entries.map((entry) => calculateSize(path.join(itemPath, entry))))).reduce((sum, size) => sum + size, 0);
14
+ }
15
+ return 0;
16
+ }
17
+ /**
18
+ * Format bytes to human-readable string
19
+ */
20
+ function formatSize(bytes) {
21
+ const units = [
22
+ "B",
23
+ "KB",
24
+ "MB",
25
+ "GB"
26
+ ];
27
+ let size = bytes;
28
+ let unitIndex = 0;
29
+ while (size >= 1024 && unitIndex < units.length - 1) {
30
+ size /= 1024;
31
+ unitIndex++;
32
+ }
33
+ return `${size.toFixed(2)} ${units[unitIndex]}`;
34
+ }
35
+
36
+ //#endregion
37
+ export { calculateSize, formatSize };
38
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.mjs","names":["fs"],"sources":["../src/utils.ts"],"sourcesContent":["import { promises as fs } from \"fs\";\nimport path from \"path\";\n\n/**\n * Calculate size of file or directory\n */\nexport async function calculateSize(itemPath: string): Promise<number> {\n const stats = await fs.stat(itemPath);\n\n if (stats.isFile()) {\n return stats.size;\n }\n\n if (stats.isDirectory()) {\n const entries = await fs.readdir(itemPath);\n const sizes = await Promise.all(\n entries.map((entry) => calculateSize(path.join(itemPath, entry))),\n );\n return sizes.reduce((sum, size) => sum + size, 0);\n }\n\n return 0;\n}\n\n/**\n * Format bytes to human-readable string\n */\nexport function formatSize(bytes: number): string {\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n return `${size.toFixed(2)} ${units[unitIndex]}`;\n}\n"],"mappings":";;;;;;;AAMA,eAAsB,cAAc,UAAmC;CACrE,MAAM,QAAQ,MAAMA,SAAG,KAAK,SAAS;AAErC,KAAI,MAAM,QAAQ,CAChB,QAAO,MAAM;AAGf,KAAI,MAAM,aAAa,EAAE;EACvB,MAAM,UAAU,MAAMA,SAAG,QAAQ,SAAS;AAI1C,UAHc,MAAM,QAAQ,IAC1B,QAAQ,KAAK,UAAU,cAAc,KAAK,KAAK,UAAU,MAAM,CAAC,CAAC,CAClE,EACY,QAAQ,KAAK,SAAS,MAAM,MAAM,EAAE;;AAGnD,QAAO;;;;;AAMT,SAAgB,WAAW,OAAuB;CAChD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAK;CACrC,IAAI,OAAO;CACX,IAAI,YAAY;AAEhB,QAAO,QAAQ,QAAQ,YAAY,MAAM,SAAS,GAAG;AACnD,UAAQ;AACR;;AAGF,QAAO,GAAG,KAAK,QAAQ,EAAE,CAAC,GAAG,MAAM"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdk-agc",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "CDK Assembly Garbage Collector - Clean up unused assets in cdk.out",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,21 +0,0 @@
1
- //#region src/cleanup.d.ts
2
- interface CleanupOptions {
3
- outdir: string;
4
- dryRun: boolean;
5
- keepHours: number;
6
- }
7
- interface TempCleanupOptions {
8
- dryRun: boolean;
9
- keepHours: number;
10
- }
11
- /**
12
- * Clean up cdk.out directory
13
- */
14
- declare function cleanupAssets(options: CleanupOptions): Promise<void>;
15
- /**
16
- * Clean up all temporary CDK output directories
17
- */
18
- declare function cleanupTempDirectories(options: TempCleanupOptions): Promise<void>;
19
- //#endregion
20
- export { CleanupOptions, TempCleanupOptions, cleanupAssets, cleanupTempDirectories };
21
- //# sourceMappingURL=cleanup.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cleanup.d.mts","names":[],"sources":["../src/cleanup.ts"],"mappings":";UAIiB,cAAA;EACf,MAAA;EACA,MAAA;EACA,SAAA;AAAA;AAAA,UAGe,kBAAA;EACf,MAAA;EACA,SAAA;AAAA;;AAFF;;iBA4IsB,aAAA,CAAc,OAAA,EAAS,cAAA,GAAiB,OAAA;;;AAA9D;iBAgHsB,sBAAA,CAAuB,OAAA,EAAS,kBAAA,GAAqB,OAAA"}
package/dist/cleanup.mjs DELETED
@@ -1,200 +0,0 @@
1
- import { promises } from "fs";
2
- import path from "path";
3
- import os from "os";
4
-
5
- //#region src/cleanup.ts
6
- /**
7
- * Recursively collect asset paths from *.assets.json files
8
- */
9
- async function collectAssetPaths(dirPath) {
10
- const activePaths = /* @__PURE__ */ new Set();
11
- const items = await promises.readdir(dirPath, { withFileTypes: true });
12
- for (const item of items) {
13
- const itemPath = path.join(dirPath, item.name);
14
- if (item.isDirectory()) {
15
- (await collectAssetPaths(itemPath)).forEach((p) => activePaths.add(p));
16
- continue;
17
- }
18
- if (!item.name.endsWith(".assets.json")) continue;
19
- try {
20
- const content = await promises.readFile(itemPath, "utf-8");
21
- const assets = JSON.parse(content);
22
- if (assets.files) for (const fileEntry of Object.values(assets.files)) {
23
- const entry = fileEntry;
24
- if (entry.source?.path) {
25
- const assetPath = path.join(path.dirname(itemPath), entry.source.path);
26
- activePaths.add(assetPath);
27
- }
28
- }
29
- if (assets.dockerImages) for (const imageEntry of Object.values(assets.dockerImages)) {
30
- const entry = imageEntry;
31
- if (entry.source?.directory) {
32
- const assetPath = path.join(path.dirname(itemPath), entry.source.directory);
33
- activePaths.add(assetPath);
34
- }
35
- }
36
- } catch (error) {
37
- console.warn(`Warning: Failed to parse ${item.name}:`, error);
38
- }
39
- }
40
- return activePaths;
41
- }
42
- /**
43
- * Check if file/directory should be protected from deletion
44
- */
45
- async function isProtected(itemPath, activePaths, keepHours) {
46
- if (activePaths.has(itemPath)) return true;
47
- if (keepHours > 0) {
48
- const stats = await promises.stat(itemPath);
49
- if ((Date.now() - stats.mtimeMs) / (1e3 * 60 * 60) <= keepHours) return true;
50
- }
51
- return false;
52
- }
53
- /**
54
- * Calculate size of file or directory
55
- */
56
- async function calculateSize(itemPath) {
57
- const stats = await promises.stat(itemPath);
58
- if (stats.isFile()) return stats.size;
59
- if (stats.isDirectory()) {
60
- const entries = await promises.readdir(itemPath);
61
- return (await Promise.all(entries.map((entry) => calculateSize(path.join(itemPath, entry))))).reduce((sum, size) => sum + size, 0);
62
- }
63
- return 0;
64
- }
65
- /**
66
- * Recursively remove file or directory
67
- */
68
- async function remove(itemPath) {
69
- if ((await promises.stat(itemPath)).isDirectory()) await promises.rm(itemPath, {
70
- recursive: true,
71
- force: true
72
- });
73
- else await promises.unlink(itemPath);
74
- }
75
- /**
76
- * Format bytes to human-readable string
77
- */
78
- function formatSize(bytes) {
79
- const units = [
80
- "B",
81
- "KB",
82
- "MB",
83
- "GB"
84
- ];
85
- let size = bytes;
86
- let unitIndex = 0;
87
- while (size >= 1024 && unitIndex < units.length - 1) {
88
- size /= 1024;
89
- unitIndex++;
90
- }
91
- return `${size.toFixed(2)} ${units[unitIndex]}`;
92
- }
93
- /**
94
- * Clean up cdk.out directory
95
- */
96
- async function cleanupAssets(options) {
97
- const { outdir, dryRun, keepHours } = options;
98
- console.log(`Scanning ${outdir}...`);
99
- console.log(`Protection policy: Referenced assets + files modified within ${keepHours} hours\n`);
100
- try {
101
- await promises.access(outdir);
102
- } catch {
103
- throw new Error(`Directory not found: ${outdir}`);
104
- }
105
- const activePaths = await collectAssetPaths(outdir);
106
- const entries = await promises.readdir(outdir);
107
- const itemsToDelete = [];
108
- await Promise.all(entries.map(async (entry) => {
109
- const itemPath = path.join(outdir, entry);
110
- if (!entry.startsWith("asset.")) return;
111
- if (await isProtected(itemPath, activePaths, keepHours)) return;
112
- const size = await calculateSize(itemPath);
113
- itemsToDelete.push({
114
- path: itemPath,
115
- size
116
- });
117
- }));
118
- if (itemsToDelete.length === 0) {
119
- console.log(`✓ No unused assets found.`);
120
- return;
121
- }
122
- const totalSize = itemsToDelete.reduce((sum, item) => sum + item.size, 0);
123
- console.log(`Found ${itemsToDelete.length} unused item(s):\n`);
124
- itemsToDelete.forEach((item) => {
125
- const relativePath = path.relative(outdir, item.path);
126
- console.log(` - ${relativePath} (${formatSize(item.size)})`);
127
- });
128
- console.log(`\nTotal size to reclaim: ${formatSize(totalSize)}\n`);
129
- if (dryRun) console.log("Dry-run mode: No files were deleted.");
130
- else {
131
- await Promise.all(itemsToDelete.map((item) => remove(item.path)));
132
- console.log("✓ Cleanup completed successfully.");
133
- }
134
- }
135
- /**
136
- * Find all cdk.out temporary directories in $TMPDIR
137
- */
138
- async function findTempDirectories() {
139
- const tmpdir = os.tmpdir();
140
- const directories = [];
141
- try {
142
- const items = await promises.readdir(tmpdir, { withFileTypes: true });
143
- for (const item of items) if (item.isDirectory() && (item.name.startsWith("cdk.out") || item.name.startsWith("cdk-") || item.name.startsWith(".cdk"))) {
144
- const dirPath = path.join(tmpdir, item.name);
145
- directories.push(dirPath);
146
- }
147
- } catch (error) {
148
- console.warn(`Warning: Failed to scan $TMPDIR (${tmpdir}):`, error);
149
- }
150
- return directories;
151
- }
152
- /**
153
- * Check if directory should be protected based on age
154
- */
155
- async function shouldProtectDirectory(dirPath, keepHours) {
156
- if (keepHours <= 0) return false;
157
- try {
158
- const stats = await promises.stat(dirPath);
159
- return (Date.now() - stats.mtimeMs) / (1e3 * 60 * 60) <= keepHours;
160
- } catch {
161
- return false;
162
- }
163
- }
164
- /**
165
- * Clean up all temporary CDK output directories
166
- */
167
- async function cleanupTempDirectories(options) {
168
- const { dryRun, keepHours } = options;
169
- const tmpdir = os.tmpdir();
170
- console.log(`Scanning ${tmpdir}...`);
171
- console.log(`Protection policy: Time-based (directories modified within ${keepHours} hours)\n`);
172
- const directories = await findTempDirectories();
173
- if (directories.length === 0) {
174
- console.log("✓ No temporary CDK directories found.");
175
- return;
176
- }
177
- let totalCleaned = 0;
178
- let totalSize = 0;
179
- for (const dir of directories) try {
180
- if (await shouldProtectDirectory(dir, keepHours)) continue;
181
- const size = await calculateSize(dir);
182
- totalSize += size;
183
- if (!dryRun) await remove(dir);
184
- totalCleaned++;
185
- } catch {
186
- continue;
187
- }
188
- if (totalCleaned === 0) {
189
- console.log("✓ No temporary CDK directories to clean.");
190
- return;
191
- }
192
- console.log(`Found ${totalCleaned} temporary CDK directory(ies)\n`);
193
- console.log(`Total size to reclaim: ${formatSize(totalSize)}\n`);
194
- if (dryRun) console.log("Dry-run mode: No files were deleted.");
195
- else console.log("✓ Cleanup completed successfully.");
196
- }
197
-
198
- //#endregion
199
- export { cleanupAssets, cleanupTempDirectories };
200
- //# sourceMappingURL=cleanup.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cleanup.mjs","names":["fs"],"sources":["../src/cleanup.ts"],"sourcesContent":["import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport os from \"os\";\n\nexport interface CleanupOptions {\n outdir: string;\n dryRun: boolean;\n keepHours: number;\n}\n\nexport interface TempCleanupOptions {\n dryRun: boolean;\n keepHours: number;\n}\n\n/**\n * Recursively collect asset paths from *.assets.json files\n */\nasync function collectAssetPaths(dirPath: string): Promise<Set<string>> {\n const activePaths = new Set<string>();\n const items = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const item of items) {\n const itemPath = path.join(dirPath, item.name);\n\n // Recursively scan subdirectories (e.g., assembly-MyStage/)\n if (item.isDirectory()) {\n const subPaths = await collectAssetPaths(itemPath);\n subPaths.forEach((p) => activePaths.add(p));\n continue;\n }\n\n // Only process *.assets.json files\n if (!item.name.endsWith(\".assets.json\")) {\n continue;\n }\n\n // Parse assets.json file\n try {\n const content = await fs.readFile(itemPath, \"utf-8\");\n const assets = JSON.parse(content);\n\n // Collect asset paths from files object\n if (assets.files) {\n for (const fileEntry of Object.values(assets.files)) {\n const entry = fileEntry as { source?: { path?: string } };\n if (entry.source?.path) {\n const assetPath = path.join(path.dirname(itemPath), entry.source.path);\n activePaths.add(assetPath);\n }\n }\n }\n\n // Collect asset paths from dockerImages object\n if (assets.dockerImages) {\n for (const imageEntry of Object.values(assets.dockerImages)) {\n const entry = imageEntry as { source?: { directory?: string } };\n if (entry.source?.directory) {\n const assetPath = path.join(path.dirname(itemPath), entry.source.directory);\n activePaths.add(assetPath);\n }\n }\n }\n } catch (error) {\n // Skip malformed asset files\n console.warn(`Warning: Failed to parse ${item.name}:`, error);\n }\n }\n\n return activePaths;\n}\n\n/**\n * Check if file/directory should be protected from deletion\n */\nasync function isProtected(\n itemPath: string,\n activePaths: Set<string>,\n keepHours: number,\n): Promise<boolean> {\n // Protect assets referenced in *.assets.json files\n if (activePaths.has(itemPath)) {\n return true;\n }\n\n // Protect files/directories within retention period\n if (keepHours > 0) {\n const stats = await fs.stat(itemPath);\n const ageHours = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60);\n if (ageHours <= keepHours) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Calculate size of file or directory\n */\nasync function calculateSize(itemPath: string): Promise<number> {\n const stats = await fs.stat(itemPath);\n\n if (stats.isFile()) {\n return stats.size;\n }\n\n if (stats.isDirectory()) {\n const entries = await fs.readdir(itemPath);\n const sizes = await Promise.all(\n entries.map((entry) => calculateSize(path.join(itemPath, entry))),\n );\n return sizes.reduce((sum, size) => sum + size, 0);\n }\n\n return 0;\n}\n\n/**\n * Recursively remove file or directory\n */\nasync function remove(itemPath: string): Promise<void> {\n const stats = await fs.stat(itemPath);\n\n if (stats.isDirectory()) {\n await fs.rm(itemPath, { recursive: true, force: true });\n } else {\n await fs.unlink(itemPath);\n }\n}\n\n/**\n * Format bytes to human-readable string\n */\nfunction formatSize(bytes: number): string {\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n return `${size.toFixed(2)} ${units[unitIndex]}`;\n}\n\n/**\n * Clean up cdk.out directory\n */\nexport async function cleanupAssets(options: CleanupOptions): Promise<void> {\n const { outdir, dryRun, keepHours } = options;\n\n console.log(`Scanning ${outdir}...`);\n console.log(`Protection policy: Referenced assets + files modified within ${keepHours} hours\\n`);\n\n // Check directory exists\n try {\n await fs.access(outdir);\n } catch {\n throw new Error(`Directory not found: ${outdir}`);\n }\n\n // Collect asset paths referenced in *.assets.json files\n const activePaths = await collectAssetPaths(outdir);\n\n // Scan directory items\n const entries = await fs.readdir(outdir);\n const itemsToDelete: Array<{ path: string; size: number }> = [];\n\n await Promise.all(\n entries.map(async (entry) => {\n const itemPath = path.join(outdir, entry);\n\n // Only consider asset.* files/directories for deletion\n if (!entry.startsWith(\"asset.\")) {\n return;\n }\n\n if (await isProtected(itemPath, activePaths, keepHours)) {\n return;\n }\n\n const size = await calculateSize(itemPath);\n itemsToDelete.push({ path: itemPath, size });\n }),\n );\n\n // Display results\n if (itemsToDelete.length === 0) {\n console.log(`✓ No unused assets found.`);\n return;\n }\n\n const totalSize = itemsToDelete.reduce((sum, item) => sum + item.size, 0);\n\n console.log(`Found ${itemsToDelete.length} unused item(s):\\n`);\n itemsToDelete.forEach((item) => {\n const relativePath = path.relative(outdir, item.path);\n console.log(` - ${relativePath} (${formatSize(item.size)})`);\n });\n\n console.log(`\\nTotal size to reclaim: ${formatSize(totalSize)}\\n`);\n\n if (dryRun) {\n console.log(\"Dry-run mode: No files were deleted.\");\n } else {\n // Delete in parallel\n await Promise.all(itemsToDelete.map((item) => remove(item.path)));\n console.log(\"✓ Cleanup completed successfully.\");\n }\n}\n\n/**\n * Find all cdk.out temporary directories in $TMPDIR\n */\nasync function findTempDirectories(): Promise<string[]> {\n const tmpdir = os.tmpdir();\n const directories: string[] = [];\n\n try {\n const items = await fs.readdir(tmpdir, { withFileTypes: true });\n\n for (const item of items) {\n // Match directories starting with \"cdk.out\", \"cdk-\", or \".cdk\"\n if (\n item.isDirectory() &&\n (item.name.startsWith(\"cdk.out\") ||\n item.name.startsWith(\"cdk-\") ||\n item.name.startsWith(\".cdk\"))\n ) {\n const dirPath = path.join(tmpdir, item.name);\n directories.push(dirPath);\n }\n }\n } catch (error) {\n console.warn(`Warning: Failed to scan $TMPDIR (${tmpdir}):`, error);\n }\n\n return directories;\n}\n\n/**\n * Check if directory should be protected based on age\n */\nasync function shouldProtectDirectory(dirPath: string, keepHours: number): Promise<boolean> {\n if (keepHours <= 0) {\n return false;\n }\n\n try {\n const stats = await fs.stat(dirPath);\n const ageHours = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60);\n return ageHours <= keepHours;\n } catch {\n return false;\n }\n}\n\n/**\n * Clean up all temporary CDK output directories\n */\nexport async function cleanupTempDirectories(options: TempCleanupOptions): Promise<void> {\n const { dryRun, keepHours } = options;\n const tmpdir = os.tmpdir();\n\n console.log(`Scanning ${tmpdir}...`);\n console.log(`Protection policy: Time-based (directories modified within ${keepHours} hours)\\n`);\n\n const directories = await findTempDirectories();\n\n if (directories.length === 0) {\n console.log(\"✓ No temporary CDK directories found.\");\n return;\n }\n\n let totalCleaned = 0;\n let totalSize = 0;\n\n for (const dir of directories) {\n try {\n // Check if directory should be protected by age\n if (await shouldProtectDirectory(dir, keepHours)) {\n continue;\n }\n\n // Calculate size before deletion\n const size = await calculateSize(dir);\n totalSize += size;\n\n if (!dryRun) {\n await remove(dir);\n }\n\n totalCleaned++;\n } catch {\n // Silently continue on error\n continue;\n }\n }\n\n if (totalCleaned === 0) {\n console.log(\"✓ No temporary CDK directories to clean.\");\n return;\n }\n\n console.log(`Found ${totalCleaned} temporary CDK directory(ies)\\n`);\n console.log(`Total size to reclaim: ${formatSize(totalSize)}\\n`);\n\n if (dryRun) {\n console.log(\"Dry-run mode: No files were deleted.\");\n } else {\n console.log(\"✓ Cleanup completed successfully.\");\n }\n}\n"],"mappings":"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"}