Compare commits

...

20 Commits

Author SHA1 Message Date
5fa93923d2 v4.1.8 2026-03-05 12:29:11 +00:00
856099b5ed fix(unpack): catch unpack errors and add verbose unpack logging 2026-03-05 12:29:11 +00:00
7aec1548eb v4.1.7 2026-03-05 12:19:24 +00:00
c269039050 fix(fs/compiler/unpack): robustify directory removal and remove noisy diagnostic logging 2026-03-05 12:19:24 +00:00
75cb613d5e v4.1.6 2026-03-05 12:12:51 +00:00
cf05229b86 fix(mod_compiler): add diagnostic logging to report dist_ts and output directory contents after each compilation task and after import-path rewriting 2026-03-05 12:12:51 +00:00
10b8d47a55 v4.1.5 2026-03-05 11:59:18 +00:00
52e1a25948 fix(diagnostics): add diagnostic logging around compilation and unpack to aid troubleshooting 2026-03-05 11:59:18 +00:00
3b8ebd41f4 v4.1.4 2026-03-05 10:24:37 +00:00
9e839b3047 fix(deps): bump @git.zone/tspublish dependency to ^1.11.2 2026-03-05 10:24:37 +00:00
7ce22b0c4d v4.1.3 2026-03-04 22:34:06 +00:00
b2ce02a4fb fix(deps): bump dependencies: @push.rocks/smartcli, @push.rocks/smartlog, @git.zone/tstest, and @types/node to their newer versions 2026-03-04 22:34:06 +00:00
ca27db1d75 v4.1.2 2026-01-12 17:53:12 +00:00
c8a1582ec4 fix(TsPathRewriter): auto-detect all ts_* folders in project for cross-module import rewriting
Previously only mapped folders from the current compilation glob patterns.
Now scans project directory for all ts_* folders to ensure imports like
'../ts_shared/' are rewritten to '../dist_ts_shared/' even when compiling
only the main ts/ folder.
2026-01-12 17:53:06 +00:00
1e40bd5882 v4.1.1 2026-01-12 17:48:47 +00:00
4ccb7ea9d6 fix(TsPathRewriter): rewrite cross-module import paths after compilation
When compiling multiple ts_* source directories to dist_ts_* outputs,
imports like '../ts_shared/helper.js' now correctly become
'../dist_ts_shared/helper.js' in the compiled output.

Uses targeted regex patterns that only match import/export/require
statements, avoiding false positives in strings or comments.
2026-01-12 17:48:04 +00:00
0040325914 v4.1.0 2026-01-04 19:03:25 +00:00
f2980dc00f feat(docs): update README with improved docs and monorepo/tspublish guidance; namespace and extend npmextra.json with release registries; bump several dependencies 2026-01-04 19:03:25 +00:00
681f2b4c54 v4.0.2 2025-12-14 00:19:09 +00:00
fb6bd614d3 fix(TsCompiler): Clear output directories before compilation to ensure clean builds and avoid stale files 2025-12-14 00:19:09 +00:00
16 changed files with 3584 additions and 2051 deletions

View File

@@ -1,5 +1,66 @@
# Changelog
## 2026-03-05 - 4.1.8 - fix(unpack)
catch unpack errors and add verbose unpack logging
- Wrap performUnpack call in the compiler to catch and log exceptions so unpack failures don't crash the process
- Log the number of directories and files being unpacked when moving nested contents to aid debugging
## 2026-03-05 - 4.1.7 - fix(fs/compiler/unpack)
robustify directory removal and remove noisy diagnostic logging
- Use fs.promises.rm with force, maxRetries and retryDelay in removeDirectory to reduce intermittent failures during removals.
- Handle ENOTEMPTY in removeEmptyDirectory by falling back to a recursive rm and ignore ENOENT to avoid errors from transient filesystem metadata lag.
- Remove several diagnostic/verbose console logs in the compiler and unpacker paths to reduce noisy output and simplify flow.
- Short-circuit unpack logic when no nesting is detected to avoid unnecessary work and logs.
- No public API or behavior-breaking changes; suitable for a patch release.
## 2026-03-05 - 4.1.6 - fix(mod_compiler)
add diagnostic logging to report dist_ts and output directory contents after each compilation task and after import-path rewriting
- Adds DIAG-CROSSTASK logs to inspect dist_ts subdirectories after each task when not in quiet or JSON mode
- Adds DIAG-FINAL logs to report directory names and file counts for each successful output dir after import-path rewriting
- Diagnostics use dynamic fs import and are non-intrusive: gated by isQuiet/isJson and errors are ignored
## 2026-03-05 - 4.1.5 - fix(diagnostics)
add diagnostic logging around compilation and unpack to aid troubleshooting
- Enable TypeScript CompilerOptions.listEmittedFiles to surface emitted file info
- Log destination directory contents and emitted file/error counts after compile and after unpack (only when not quiet and not json)
- Add unpack-specific diagnostic logs: shouldUnpack skip, detectNesting result and nestedPath, and nested/destination directory listings before removing sibling directories
- All logs use console.log and are wrapped in try/catch to avoid throwing; changes are informational and non-breaking
## 2026-03-05 - 4.1.4 - fix(deps)
bump @git.zone/tspublish dependency to ^1.11.2
- Updated @git.zone/tspublish from ^1.11.0 to ^1.11.2 in package.json
## 2026-03-04 - 4.1.3 - fix(deps)
bump dependencies: @push.rocks/smartcli, @push.rocks/smartlog, @git.zone/tstest, and @types/node to their newer versions
- @push.rocks/smartcli: ^4.0.19 -> ^4.0.20
- @push.rocks/smartlog: ^3.1.10 -> ^3.2.1
- @git.zone/tstest: ^3.1.4 -> ^3.2.0
- @types/node: ^25.0.3 -> ^25.3.3
- Non-breaking dependency version bumps
## 2026-01-04 - 4.1.0 - feat(docs)
update README with improved docs and monorepo/tspublish guidance; namespace and extend npmextra.json with release registries; bump several dependencies
- Bumped dependencies: @git.zone/tspublish ^1.10.3 → ^1.11.0, @push.rocks/smartfs ^1.2.0 → ^1.3.1, @git.zone/tstest ^3.1.3 → ^3.1.4, @types/node ^25.0.1 → ^25.0.3
- npmextra.json reorganized: replaced legacy keys with namespaced entries (@git.zone/cli, @git.zone/tsdoc) and added @ship.zone/szci; added release configuration with registries [https://verdaccio.lossless.digital, https://registry.npmjs.org] and public accessLevel
- README rewritten: improved formatting, added emojis and tables, new 'Issue Reporting and Security' section, monorepo/tspublish usage docs, auto-unpack and decorator guidance, CI/CD and troubleshooting improvements
- Non-breaking changes; recommended next semantic version bump is minor
## 2025-12-14 - 4.0.2 - fix(TsCompiler)
Clear output directories before compilation to ensure clean builds and avoid stale files
- TsCompiler.compileGlob now clears the destination directory (if it exists) before compiling each glob pattern.
- Clearing is logged unless --quiet or --json flags are set (e.g. "🧹 Clearing output directory: <dest>").
- Uses FsHelpers.removeDirectory to remove previous output, preventing stale or duplicate emitted files.
- Documentation (readme.md) updated to advertise automatic output directory management / clean builds.
- Removed stale compiled test artifacts from test/assets/output to avoid interference with tests.
## 2025-12-13 - 3.1.3 - fix(npmextra)
Align npmextra.json package name with package.json (@git.zone/tsbuild)

View File

@@ -1,9 +1,5 @@
{
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"gitzone": {
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
@@ -23,9 +19,19 @@
"development",
"API"
]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"tsdoc": {
"@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@ship.zone/szci": {
"npmGlobalTools": []
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@git.zone/tsbuild",
"version": "4.0.1",
"version": "4.1.8",
"private": false,
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
"main": "dist_ts/index.js",
@@ -36,21 +36,21 @@
},
"homepage": "https://code.foss.global/git.zone/tsbuild#README",
"dependencies": {
"@git.zone/tspublish": "^1.10.3",
"@git.zone/tspublish": "^1.11.2",
"@push.rocks/early": "^4.0.4",
"@push.rocks/smartcli": "^4.0.19",
"@push.rocks/smartcli": "^4.0.20",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartfile": "^13.1.2",
"@push.rocks/smartfs": "^1.2.0",
"@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartfs": "^1.3.1",
"@push.rocks/smartlog": "^3.2.1",
"@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3",
"typescript": "5.9.3"
},
"devDependencies": {
"@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.1.3",
"@types/node": "^25.0.1"
"@git.zone/tstest": "^3.2.0",
"@types/node": "^25.3.3"
},
"files": [
"ts/**/*",

4284
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

949
readme.md

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,17 +0,0 @@
console.log('test');
console.log('test2');
import * as early from '@push.rocks/early';
early.start();
early.stop();
import { anExportedString } from './tocompile2.js';
console.log(anExportedString);
class test2 {
test = [];
constructor() {
console.log('hi');
}
}
const run = async () => {
return 'hi';
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9jb21waWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdG9jb21waWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDcEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUVyQixPQUFPLEtBQUssS0FBSyxNQUFNLG1CQUFtQixDQUFDO0FBRTNDLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztBQUNkLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUViLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ25ELE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUU5QixNQUFNLEtBQUs7SUFDVCxJQUFJLEdBQWEsRUFBRSxDQUFDO0lBQ3BCO1FBQ0UsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNwQixDQUFDO0NBQ0Y7QUFFRCxNQUFNLEdBQUcsR0FBRyxLQUFLLElBQXFCLEVBQUU7SUFDdEMsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDLENBQUMifQ==

View File

@@ -1 +0,0 @@
export declare const anExportedString = "exported string";

View File

@@ -1,3 +0,0 @@
console.log('hello');
export const anExportedString = 'exported string';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9jb21waWxlMi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RvY29tcGlsZTIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUNyQixNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxpQkFBaUIsQ0FBQyJ9

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/tsbuild',
version: '3.1.3',
version: '4.1.8',
description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.'
}

View File

@@ -5,6 +5,7 @@ early.start('tsbuild');
export * from './mod_fs/index.js';
export * from './mod_config/index.js';
export * from './mod_unpack/index.js';
export * from './mod_pathrewrite/index.js';
export * from './mod_compiler/index.js';
export * from './mod_cli/index.js';

View File

@@ -7,6 +7,7 @@ import * as smartpath from '@push.rocks/smartpath';
import { TsConfig } from '../mod_config/index.js';
import { FsHelpers } from '../mod_fs/index.js';
import { performUnpack } from '../mod_unpack/index.js';
import { TsPathRewriter } from '../mod_pathrewrite/index.js';
/**
* Interface for error summary data
@@ -307,6 +308,7 @@ export class TsCompiler {
): Promise<ICompileResult> {
const emittedFiles: string[] = [];
const errorSummaries: IErrorSummary[] = [];
const successfulOutputDirs: string[] = [];
const totalTasks = Object.keys(globPatterns).length;
let currentTask = 0;
@@ -335,10 +337,19 @@ export class TsCompiler {
// Get destination directory as absolute path
const destDir = smartpath.transform.toAbsolute(destPath, this.cwd) as string;
// Clear the destination directory before compilation if it exists
if (await FsHelpers.directoryExists(destDir)) {
if (!isQuiet && !isJson) {
console.log(`🧹 Clearing output directory: ${destPath}`);
}
await FsHelpers.removeDirectory(destDir);
}
// Update compiler options with the output directory
const options: CompilerOptions = {
...customOptions,
outDir: destDir,
listEmittedFiles: true,
};
currentTask++;
@@ -356,7 +367,28 @@ export class TsCompiler {
// Perform unpack if compilation succeeded
if (result.errorSummary.totalErrors === 0) {
await performUnpack(pattern, destDir, this.cwd);
try {
await performUnpack(pattern, destDir, this.cwd);
} catch (unpackErr: any) {
console.error(` ⚠️ Unpack error for ${destPath}: ${unpackErr.message}`);
}
successfulOutputDirs.push(destDir);
}
}
// Rewrite import paths in all output directories to handle cross-module references
// This must happen after ALL compilations so all destination folders exist
// Use fromProjectDirectory to detect ALL ts_* folders, not just the ones being compiled
if (successfulOutputDirs.length > 0) {
const rewriter = await TsPathRewriter.fromProjectDirectory(this.cwd);
let totalRewritten = 0;
for (const outputDir of successfulOutputDirs) {
totalRewritten += await rewriter.rewriteDirectory(outputDir);
}
if (totalRewritten > 0 && !isQuiet && !isJson) {
console.log(` 🔄 Rewrote import paths in ${totalRewritten} file${totalRewritten !== 1 ? 's' : ''}`);
}
}

View File

@@ -125,7 +125,7 @@ export class FsHelpers {
* Remove a directory recursively
*/
public static async removeDirectory(dirPath: string): Promise<void> {
await fs.promises.rm(dirPath, { recursive: true });
await fs.promises.rm(dirPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
}
/**
@@ -139,6 +139,15 @@ export class FsHelpers {
* Remove an empty directory
*/
public static async removeEmptyDirectory(dirPath: string): Promise<void> {
await fs.promises.rmdir(dirPath);
try {
await fs.promises.rmdir(dirPath);
} catch (err: any) {
if (err.code === 'ENOTEMPTY') {
// Filesystem metadata lag — use recursive rm as fallback
await fs.promises.rm(dirPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
} else if (err.code !== 'ENOENT') {
throw err;
}
}
}
}

View File

@@ -0,0 +1,233 @@
import * as fs from 'fs';
import * as path from 'path';
import { FsHelpers } from '../mod_fs/index.js';
/**
* Interface for folder mapping between source and destination
*/
export interface IFolderMapping {
sourceFolder: string; // e.g., 'ts_shared'
destFolder: string; // e.g., 'dist_ts_shared'
}
/**
* TsPathRewriter handles rewriting import paths in compiled JavaScript files.
*
* When TypeScript compiles files that import from sibling directories like:
* import { helper } from '../ts_shared/helper.js';
*
* This class rewrites them to point to the compiled output directories:
* import { helper } from '../dist_ts_shared/helper.js';
*
* This is necessary because the unpack feature flattens nested output structures,
* changing the relative paths between modules.
*/
export class TsPathRewriter {
private mappings: IFolderMapping[];
constructor(mappings: IFolderMapping[]) {
this.mappings = mappings;
}
/**
* Create a TsPathRewriter from glob patterns used in compilation
*
* @param globPatterns - Map of source patterns to destination directories
* e.g., { './ts_core/**\/*.ts': './dist_ts_core', './ts_shared/**\/*.ts': './dist_ts_shared' }
* @returns TsPathRewriter instance with extracted folder mappings
*/
public static fromGlobPatterns(globPatterns: Record<string, string>): TsPathRewriter {
const mappings: IFolderMapping[] = [];
for (const [sourcePattern, destDir] of Object.entries(globPatterns)) {
const sourceFolder = FsHelpers.extractSourceFolder(sourcePattern);
const destFolder = FsHelpers.extractSourceFolder(destDir);
if (sourceFolder && destFolder) {
mappings.push({ sourceFolder, destFolder });
}
}
return new TsPathRewriter(mappings);
}
/**
* Create a TsPathRewriter by auto-detecting all ts_* folders in the project directory.
* This ensures that cross-module imports are rewritten even when compiling a single module.
*
* For example, if the project has ts/, ts_shared/, ts_web/ folders:
* - ts → dist_ts
* - ts_shared → dist_ts_shared
* - ts_web → dist_ts_web
*
* @param cwd - The project root directory
* @returns TsPathRewriter instance with all detected folder mappings
*/
public static async fromProjectDirectory(cwd: string): Promise<TsPathRewriter> {
const mappings: IFolderMapping[] = [];
try {
const entries = await FsHelpers.listDirectory(cwd);
for (const entry of entries) {
// Match folders starting with 'ts' (ts, ts_shared, ts_web, ts_core, etc.)
if (entry.isDirectory && /^ts(_|$)/.test(entry.name)) {
const sourceFolder = entry.name;
const destFolder = `dist_${sourceFolder}`;
mappings.push({ sourceFolder, destFolder });
}
}
} catch {
// Directory listing failed, return empty mappings
}
return new TsPathRewriter(mappings);
}
/**
* Get the current folder mappings
*/
public getMappings(): IFolderMapping[] {
return [...this.mappings];
}
/**
* Rewrite import paths in a single file
*
* @param filePath - Absolute path to the file to rewrite
* @returns true if file was modified, false otherwise
*/
public async rewriteFile(filePath: string): Promise<boolean> {
// Only process .js and .d.ts files
if (!filePath.endsWith('.js') && !filePath.endsWith('.d.ts')) {
return false;
}
try {
const content = await fs.promises.readFile(filePath, 'utf8');
const rewritten = this.rewriteContent(content);
if (rewritten !== content) {
await fs.promises.writeFile(filePath, rewritten, 'utf8');
return true;
}
return false;
} catch (error) {
// File doesn't exist or other error
return false;
}
}
/**
* Rewrite import paths in all .js and .d.ts files in a directory (recursively)
*
* @param dirPath - Absolute path to the directory
* @returns Number of files modified
*/
public async rewriteDirectory(dirPath: string): Promise<number> {
let modifiedCount = 0;
const processDir = async (currentDir: string): Promise<void> => {
const entries = await FsHelpers.listDirectory(currentDir);
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory) {
await processDir(fullPath);
} else if (entry.isFile && (entry.name.endsWith('.js') || entry.name.endsWith('.d.ts'))) {
const wasModified = await this.rewriteFile(fullPath);
if (wasModified) {
modifiedCount++;
}
}
}
};
if (await FsHelpers.directoryExists(dirPath)) {
await processDir(dirPath);
}
return modifiedCount;
}
/**
* Rewrite import paths in content string
*
* This method only modifies actual import/export/require statements,
* not arbitrary strings or comments that might contain similar patterns.
*
* @param content - File content to process
* @returns Rewritten content
*/
public rewriteContent(content: string): string {
if (this.mappings.length === 0) {
return content;
}
let result = content;
for (const { sourceFolder, destFolder } of this.mappings) {
// Skip if source and dest are the same
if (sourceFolder === destFolder) {
continue;
}
// Pattern 1: ES Module imports/exports with 'from'
// Matches: from '../ts_shared/...' or from '../../ts_shared/...'
// Also handles: export { x } from '../ts_shared/...'
const fromPattern = new RegExp(
`(from\\s*['"])((?:\\.\\.\\/)+)${this.escapeRegex(sourceFolder)}(\\/[^'"]*['"])`,
'g'
);
result = result.replace(fromPattern, `$1$2${destFolder}$3`);
// Pattern 2: Dynamic imports
// Matches: import('../ts_shared/...')
const dynamicImportPattern = new RegExp(
`(import\\s*\\(\\s*['"])((?:\\.\\.\\/)+)${this.escapeRegex(sourceFolder)}(\\/[^'"]*['"]\\s*\\))`,
'g'
);
result = result.replace(dynamicImportPattern, `$1$2${destFolder}$3`);
// Pattern 3: CommonJS require
// Matches: require('../ts_shared/...')
const requirePattern = new RegExp(
`(require\\s*\\(\\s*['"])((?:\\.\\.\\/)+)${this.escapeRegex(sourceFolder)}(\\/[^'"]*['"]\\s*\\))`,
'g'
);
result = result.replace(requirePattern, `$1$2${destFolder}$3`);
}
return result;
}
/**
* Escape special regex characters in a string
*/
private escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
}
/**
* Convenience function to rewrite import paths in output directories
*
* @param globPatterns - Map of source patterns to destination directories
* @param outputDirs - List of output directories to process
* @returns Total number of files modified
*/
export async function rewriteImportPaths(
globPatterns: Record<string, string>,
outputDirs: string[]
): Promise<number> {
const rewriter = TsPathRewriter.fromGlobPatterns(globPatterns);
let totalModified = 0;
for (const dir of outputDirs) {
totalModified += await rewriter.rewriteDirectory(dir);
}
return totalModified;
}

View File

@@ -0,0 +1 @@
export * from './classes.tspathrewriter.js';

View File

@@ -128,6 +128,9 @@ export class TsUnpacker {
private async moveNestedContentsUp(): Promise<void> {
const nestedPath = this.getNestedPath();
const entries = await FsHelpers.listDirectory(nestedPath);
const dirCount = entries.filter(e => e.isDirectory).length;
const fileCount = entries.filter(e => e.isFile).length;
console.log(` 📦 Unpacking ${this.sourceFolderName}/: ${dirCount} directories, ${fileCount} files`);
for (const entry of entries) {
const src = path.join(nestedPath, entry.name);
const dest = path.join(this.destDir, entry.name);