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 +2 -1
- package/dist/asset-cleanup.d.mts +13 -0
- package/dist/asset-cleanup.d.mts.map +1 -0
- package/dist/asset-cleanup.mjs +99 -0
- package/dist/asset-cleanup.mjs.map +1 -0
- package/dist/cli.mjs +2 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/temp-cleanup.d.mts +12 -0
- package/dist/temp-cleanup.d.mts.map +1 -0
- package/dist/temp-cleanup.mjs +70 -0
- package/dist/temp-cleanup.mjs.map +1 -0
- package/dist/utils.d.mts +12 -0
- package/dist/utils.d.mts.map +1 -0
- package/dist/utils.mjs +38 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/cleanup.d.mts +0 -21
- package/dist/cleanup.d.mts.map +0 -1
- package/dist/cleanup.mjs +0 -200
- package/dist/cleanup.mjs.map +0 -1
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
|
|
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
|
|
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"}
|
package/dist/utils.d.mts
ADDED
|
@@ -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
package/dist/cleanup.d.mts
DELETED
|
@@ -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
|
package/dist/cleanup.d.mts.map
DELETED
|
@@ -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
|
package/dist/cleanup.mjs.map
DELETED
|
@@ -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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"}
|