From 598569be8ca12b42ec6a0c820e4e38a20e8ac6e4 Mon Sep 17 00:00:00 2001 From: Mike Pokraka Date: Thu, 9 Oct 2025 18:29:51 +0100 Subject: [PATCH] Add folder exclusion --- package-lock.json | 84 +++++++++++++++++++++++++++++++++++++++++++---- package.json | 5 +-- src/cli.ts | 18 ++++++++-- test/cli.ts | 24 ++++++++++++++ test/exclusion.ts | 45 +++++++++++++++++++++++++ tsconfig.json | 1 + 6 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 test/exclusion.ts diff --git a/package-lock.json b/package-lock.json index aa5260b..acfe992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "MIT", "dependencies": { "commander": "^10.0.0", - "fast-xml-parser": "^5.0.8" + "fast-xml-parser": "^5.0.8", + "minimatch": "^10.0.3" }, "bin": { "abapmerge": "abapmerge" @@ -283,6 +284,27 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -641,6 +663,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/types": { "version": "8.25.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", @@ -682,6 +720,22 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { "version": "8.22.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", @@ -796,6 +850,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.25.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", @@ -1989,15 +2059,15 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" diff --git a/package.json b/package.json index 016041c..d1f6b8c 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,12 @@ }, "homepage": "https://github.com/larshp/abapmerge#readme", "devDependencies": { + "@eslint/compat": "^1.2.0", "@types/chai": "^4.3.20", "@types/mocha": "^10.0.9", "@types/node": "^24.3.0", "@typescript-eslint/eslint-plugin": "^8.22.0", "@typescript-eslint/parser": "^8.25.0", - "@eslint/compat": "^1.2.0", "chai": "^4.5.0", "eslint": "^9.21.0", "mocha": "^10.7.3", @@ -47,6 +47,7 @@ }, "dependencies": { "commander": "^10.0.0", - "fast-xml-parser": "^5.0.8" + "fast-xml-parser": "^5.0.8", + "minimatch": "^10.0.3" } } diff --git a/src/cli.ts b/src/cli.ts index 3ea47dd..ff2ce4e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,6 +5,7 @@ import FileList from "./file_list"; import Merge from "./merge"; import { Command } from "commander"; import PackageInfo from "../package.json"; +import { minimatch } from "minimatch"; interface ICliArgs { entryFilename: string; @@ -14,6 +15,7 @@ interface ICliArgs { allowUnused: boolean; newReportName: string; outputFile: string; + excludePattern?: string; } export class Logic { @@ -23,7 +25,13 @@ export class Logic { return Logic.textFiles.has(path.extname(filepath).toLowerCase()); } - private static readFiles(dir: string, pre = ""): FileList { + private static shouldExcludeDir(dirPath: string, excludePattern?: string): boolean { + if (!excludePattern) return false; + const dirName = path.basename(dirPath); + return minimatch(dirName, excludePattern); + } + + private static readFiles(dir: string, pre = "", excludePattern?: string): FileList { const files = fs.readdirSync(dir); const list = new FileList(); @@ -43,7 +51,9 @@ export class Logic { } } else { - list.concat(this.readFiles(filepath, path.join(pre, file))); + if (!this.shouldExcludeDir(filepath, excludePattern)) { + list.concat(this.readFiles(filepath, path.join(pre, file), excludePattern)); + } } } @@ -64,6 +74,7 @@ export class Logic { "-c, --change-report-name ", "changes report name in REPORT clause in source code", ) + .option("-e, --exclude ", "exclude directories matching pattern (supports glob patterns)") .arguments(""); commander.exitOverride((err) => { @@ -93,6 +104,7 @@ export class Logic { allowUnused: cmdOpts.allowUnused, newReportName: cmdOpts.changeReportName, outputFile: cmdOpts.output, + excludePattern: cmdOpts.exclude, }; } @@ -107,7 +119,7 @@ export class Logic { const entryObjectName = parsedArgs.entryFilename.split(".")[0]; output = Merge.merge( - Logic.readFiles(parsedArgs.entryDir), + Logic.readFiles(parsedArgs.entryDir, "", parsedArgs.excludePattern), entryObjectName, { skipFUGR: parsedArgs.skipFUGR, diff --git a/test/cli.ts b/test/cli.ts index fec5d09..48d39b4 100644 --- a/test/cli.ts +++ b/test/cli.ts @@ -41,6 +41,7 @@ describe("CLI parse arguments", () => { newReportName: undefined, allowUnused: false, outputFile: undefined, + excludePattern: undefined, }; chai.assert.isNotNull(parsedArgs); chai.assert.deepEqual(parsedArgs, parsedArgsExpected); @@ -58,6 +59,7 @@ describe("CLI parse arguments", () => { newReportName: undefined, allowUnused: false, outputFile: undefined, + excludePattern: undefined, }; chai.assert.isNotNull(parsedArgs); chai.assert.deepEqual(parsedArgs, parsedArgsExpected); @@ -76,6 +78,7 @@ describe("CLI parse arguments", () => { newReportName: undefined, allowUnused: false, outputFile: undefined, + excludePattern: undefined, }; chai.assert.isNotNull(parsedArgs); chai.assert.deepEqual(parsedArgs, parsedArgsExpected); @@ -96,6 +99,7 @@ describe("CLI parse arguments", () => { newReportName: "znewname", allowUnused: false, outputFile: undefined, + excludePattern: undefined, }; chai.assert.isNotNull(parsedArgs); chai.assert.deepEqual(parsedArgs, parsedArgsExpected); @@ -114,6 +118,26 @@ describe("CLI parse arguments", () => { newReportName: undefined, allowUnused: false, outputFile: "some.file", + excludePattern: undefined, + }; + chai.assert.isNotNull(parsedArgs); + chai.assert.deepEqual(parsedArgs, parsedArgsExpected); + }); + + it("exclude option", () => { + args.push("-e"); + args.push("test*"); + args.push(join(__dirname, "entry.abap")); + const parsedArgs = Logic.parseArgs(args); + const parsedArgsExpected = { + entryDir: __dirname, + entryFilename: "entry.abap", + skipFUGR: false, + noFooter: false, + newReportName: undefined, + allowUnused: false, + outputFile: undefined, + excludePattern: "test*", }; chai.assert.isNotNull(parsedArgs); chai.assert.deepEqual(parsedArgs, parsedArgsExpected); diff --git a/test/exclusion.ts b/test/exclusion.ts new file mode 100644 index 0000000..b5f1f4c --- /dev/null +++ b/test/exclusion.ts @@ -0,0 +1,45 @@ +import {expect} from "chai"; +import {Logic} from "../src/cli"; + +describe("Directory exclusion patterns", () => { + it("should exclude directories matching simple wildcard patterns", () => { + expect((Logic as any).shouldExcludeDir("/path/to/test", "test*")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/testing", "test*")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/production", "test*")).to.equal(false); + }); + + it("should exclude directories matching glob patterns with wildcards", () => { + expect((Logic as any).shouldExcludeDir("/path/to/temp", "*temp*")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/temporary", "*temp*")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/production", "*temp*")).to.equal(false); + }); + + it("should exclude directories matching brace expansion patterns", () => { + expect((Logic as any).shouldExcludeDir("/path/to/test", "{test*,build*}")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/build", "{test*,build*}")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/builddir", "{test*,build*}")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/production", "{test*,build*}")).to.equal(false); + }); + + it("should handle case sensitive matching", () => { + expect((Logic as any).shouldExcludeDir("/path/to/test", "test*")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/TEST", "test*")).to.equal(false); + expect((Logic as any).shouldExcludeDir("/path/to/Test", "test*")).to.equal(false); + expect((Logic as any).shouldExcludeDir("/path/to/TEST", "TEST*")).to.equal(true); + }); + + it("should not exclude when no pattern is provided", () => { + expect((Logic as any).shouldExcludeDir("/path/to/test", undefined)).to.equal(false); + expect((Logic as any).shouldExcludeDir("/path/to/test", "")).to.equal(false); + }); + + it("should handle exact directory name matches", () => { + expect((Logic as any).shouldExcludeDir("/path/to/node_modules", "node_modules")).to.equal(true); + expect((Logic as any).shouldExcludeDir("/path/to/node_modules_backup", "node_modules")).to.equal(false); + }); + + it("should only match directory basename not full path", () => { + expect((Logic as any).shouldExcludeDir("/test/path/to/production", "test*")).to.equal(false); + expect((Logic as any).shouldExcludeDir("/production/path/to/test", "test*")).to.equal(true); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index cd13dff..8168874 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "module": "commonjs", + "target": "es2020", "outDir": "build", "noFallthroughCasesInSwitch": true, "noImplicitReturns": true,