From 7e8007be571d518dd7ca637ccd7cf6df4857c77f Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:04:12 +0300 Subject: [PATCH 01/16] test: add artifacts and module integrity tests --- tests/artifacts.test.ts | 117 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/artifacts.test.ts diff --git a/tests/artifacts.test.ts b/tests/artifacts.test.ts new file mode 100644 index 0000000..303cdd0 --- /dev/null +++ b/tests/artifacts.test.ts @@ -0,0 +1,117 @@ +import { describe, it, expect } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as vm from 'node:vm'; +import { execSync } from 'node:child_process'; + +describe('Package Artifacts & Module Integrity', () => { + const rootDir = path.resolve(__dirname, '..'); + const packageJsonPath = path.join(rootDir, 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + it('should have all entry point files existing on disk and non-empty', () => { + const entryPoints = [ + { field: 'main', path: packageJson.main }, + { field: 'module', path: packageJson.module }, + { field: 'types', path: packageJson.types }, + { field: 'unpkg', path: packageJson.unpkg }, + { field: 'jsdelivr', path: packageJson.jsdelivr }, + { field: 'exports.import.default', path: packageJson.exports['.'].import.default }, + { field: 'exports.import.types', path: packageJson.exports['.'].import.types }, + { field: 'exports.require.default', path: packageJson.exports['.'].require.default }, + { field: 'exports.require.types', path: packageJson.exports['.'].require.types }, + ]; + + for (const entry of entryPoints) { + const absolutePath = path.resolve(rootDir, entry.path); + expect(fs.existsSync(absolutePath), `File specified in ${entry.field} (${entry.path}) must exist`).toBe( + true, + ); + const stats = fs.statSync(absolutePath); + expect(stats.size, `File specified in ${entry.field} (${entry.path}) must not be empty`).toBeGreaterThan( + 0, + ); + } + }); + + it('should have dist/esm/package.json declaring type: module and sideEffects: false', () => { + const esmPkgPath = path.join(rootDir, 'dist/esm/package.json'); + expect(fs.existsSync(esmPkgPath)).toBe(true); + const esmPkg = JSON.parse(fs.readFileSync(esmPkgPath, 'utf8')); + expect(esmPkg.type).toBe('module'); + expect(esmPkg.sideEffects).toBe(false); + }); + + it('should support standalone CommonJS require() in isolated Node.js process', () => { + const script = ` + const { removeTashkeel, toOldArabic, ArabicServices } = require('./dist'); + const res1 = removeTashkeel('مَرْحَبًا'); + const res2 = ArabicServices.removeTatweel('رائـــــع'); + console.log(JSON.stringify({ res1, res2, hasToOldArabic: typeof toOldArabic === 'function' })); + `; + const stdout = execSync(`node -e "${script.replace(/\n/g, ' ')}"`, { + cwd: rootDir, + encoding: 'utf8', + }); + const result = JSON.parse(stdout.trim()); + expect(result.res1).toBe('مرحبا'); + expect(result.res2).toBe('رائع'); + expect(result.hasToOldArabic).toBe(true); + }); + + it('should support standalone ES Module import in isolated Node.js process', () => { + const script = ` + import { removeTashkeel, toOldArabic, ArabicServices } from './dist/esm/index.js'; + const res1 = removeTashkeel('مَرْحَبًا'); + const res2 = ArabicServices.removeTatweel('رائـــــع'); + console.log(JSON.stringify({ res1, res2, hasToOldArabic: typeof toOldArabic === 'function' })); + `; + const stdout = execSync(`node --input-type=module -e "${script.replace(/\n/g, ' ')}"`, { + cwd: rootDir, + encoding: 'utf8', + }); + const result = JSON.parse(stdout.trim()); + expect(result.res1).toBe('مرحبا'); + expect(result.res2).toBe('رائع'); + expect(result.hasToOldArabic).toBe(true); + }); + + it('should execute browser UMD bundle inside a simulated browser window context', () => { + const umdPath = path.join(rootDir, packageJson.unpkg); + const umdCode = fs.readFileSync(umdPath, 'utf8'); + + const sandbox: { window: Record; ArabicServices?: Record } = { + window: {}, + }; + // Establish circular reference standard in browser environments (window.window === window) + sandbox.window.window = sandbox.window; + + const context = vm.createContext(sandbox); + vm.runInContext(umdCode, context); + + const arabicServices = (sandbox.window.ArabicServices || sandbox.ArabicServices) as Record< + string, + (...args: unknown[]) => unknown + >; + + expect(arabicServices).toBeDefined(); + expect(typeof arabicServices.removeTashkeel).toBe('function'); + expect(typeof arabicServices.toOldArabic).toBe('function'); + expect(typeof arabicServices.removeTatweel).toBe('function'); + expect(typeof arabicServices.tashfeer).toBe('function'); + + expect(arabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ')).toBe('الخيل والليل'); + expect(arabicServices.removeTatweel('كتــــاب')).toBe('كتاب'); + }); + + it('should execute purely as Isomorphic/Universal functions without DOM dependencies', () => { + // Import all scripts in current process (where window and document are undefined) + expect(typeof window).toBe('undefined'); + expect(typeof document).toBe('undefined'); + + const dist = require(path.join(rootDir, 'dist')); + expect(dist.removeTashkeel('فَحَصَ')).toBe('فحص'); + expect(dist.removeTatweel('مـلـف')).toBe('ملف'); + expect(dist.toOldArabic('بَيْت')).toBe('ٮىٮ'); + }); +}); From 73af4388d34d184b75c3f8a322a1257e84eb4a2f Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:06:40 +0300 Subject: [PATCH 02/16] test: add Arabic NLP edge cases and ReDoS tests --- tests/edge-cases.test.ts | 154 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 tests/edge-cases.test.ts diff --git a/tests/edge-cases.test.ts b/tests/edge-cases.test.ts new file mode 100644 index 0000000..5725651 --- /dev/null +++ b/tests/edge-cases.test.ts @@ -0,0 +1,154 @@ +import { describe, it, expect } from 'vitest'; +import { + removeTashkeel, + removeTatweel, + toOldArabic, + toOldArabicAndTashfeerBannedWords, + tashfeer, + tashfeerBannedWords, + wordToLetters, + removeArabicAffixes, + BANNED_WORDS, +} from '../src'; + +describe('Arabic NLP Edge Cases & Robustness', () => { + describe('Empty, Whitespace & Boundary Inputs', () => { + it('should gracefully handle empty strings across all functions', () => { + expect(removeTashkeel('')).toBe(''); + expect(removeTatweel('')).toBe(''); + expect(toOldArabic('')).toBe(''); + expect(toOldArabicAndTashfeerBannedWords('')).toBe(''); + expect(tashfeer('')).toBe(''); + expect(tashfeerBannedWords('')).toBe(''); + expect(wordToLetters('')).toBe(''); + expect(removeArabicAffixes('')).toBe(''); + }); + + it('should trim surrounding whitespace properly', () => { + expect(removeTashkeel(' مَرْحَبًا ')).toBe('مرحبا'); + expect(removeTatweel(' رائــــع ')).toBe('رائع'); + expect(toOldArabic(' كِتَابٌ ')).toBe('کٮاٮ'); + expect(wordToLetters(' حب ')).toBe('حاء باء'); + }); + + it('should handle words that are shorter than standard affix length', () => { + // 'و' is not in ARABIC_PREFIXES so it stays unchanged + expect(removeArabicAffixes('و')).toBe('و'); + // 'ال' is an exact 2-letter prefix and is removed completely + expect(removeArabicAffixes('ال')).toBe(''); + // single letter prefix 'ي' + expect(removeArabicAffixes('يقرأ')).toBe('قرأ'); + }); + + it('should handle sentences with irregular spacing', () => { + const sentence = 'ذهب الموظفون إلى العمل'; + const res = removeArabicAffixes(sentence); + expect(res).toContain('موظف'); + expect(res).toContain('عمل'); + }); + }); + + describe('Mixed Scripts & Special Characters Preservation', () => { + it('should preserve Latin characters, numbers, and emojis while stripping Tashkeel', () => { + const text = '🎉 Hello 123! الخَيْلُ وَاللّيْلُ (Test 456) ✨'; + const result = removeTashkeel(text); + expect(result).toBe('🎉 Hello 123! الخيل والليل (Test 456) ✨'); + }); + + it('should preserve non-Arabic characters in toOldArabic', () => { + const text = 'User123: كَتَبَ email@example.com!'; + const result = toOldArabic(text); + expect(result).toBe('User123: کٮٮ email@example.com!'); + }); + + it('should preserve non-Arabic characters in tashfeer without corruption', () => { + const text = 'Code 99: سلاح'; + const result = tashfeer(text); + expect(result.startsWith('Code 99:')).toBe(true); + expect(result.length).toBeGreaterThanOrEqual(text.length); + }); + }); + + describe('Complex Diacritics & Orthography', () => { + it('should normalize Wasla Alef (ٱ) to standard Alef', () => { + expect(removeTashkeel('ٱسْمُ')).toBe('اسم'); + }); + + it('should remove stacked diacritics (Shaddah with Fatha/Kasra/Damma/Tanween)', () => { + const stacked = 'مُحَمَّدٌ وَعَلِيٌّ'; + expect(removeTashkeel(stacked)).toBe('محمد وعلي'); + }); + + it('should handle consecutive Tatweel characters cleanly', () => { + const excessiveTatweel = 'قـــــــــــــــــال'; + expect(removeTatweel(excessiveTatweel)).toBe('قال'); + }); + + it('should handle Tatweel combined with Tashkeel', () => { + const tatweelWithTashkeel = 'رَائـِـــــعٌ'; + const noTashkeel = removeTashkeel(tatweelWithTashkeel); + const clean = removeTatweel(noTashkeel); + expect(clean).toBe('رائع'); + }); + }); + + describe('Tashfeer Variants and Word Length Boundaries', () => { + it('should handle Tashfeer on words with length >= 8 characters', () => { + const longWord = 'المسؤوليات'; + const result = tashfeer(longWord, 1); + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result.length).toBeGreaterThan(0); + }); + + it('should handle Tashfeer with Alef, Waw, and Yaa variants', () => { + const sentence = 'أحمد يؤمن بالرئيس'; + const result = tashfeer(sentence, 2); + expect(result).toBeDefined(); + expect(result.length).toBeGreaterThan(0); + }); + + it('should obfuscate banned words using tashfeerBannedWords', () => { + const text = 'هذا السلاح ممنوع'; + const result = tashfeerBannedWords(text); + expect(result).not.toBe(text); + expect(result).toContain('هذا'); + expect(result).toContain('ممنوع'); + }); + + it('should handle toOldArabicAndTashfeerBannedWords on mixed sentences', () => { + const input = 'جيش العدو يقتل الأطفال'; + const result = toOldArabicAndTashfeerBannedWords(input, 1); + expect(result).toBeDefined(); + expect(result.split(' ').length).toBeGreaterThanOrEqual(4); + }); + }); + + describe('Performance & ReDoS Resilience', () => { + it('should process large texts (50,000+ chars) in under 150ms without catastrophic backtracking', () => { + const sampleUnit = 'الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني، وَالسّيفُ وَالرّمحُ والقرْطاسُ وَالقَلَـمُ! '; + const largeText = sampleUnit.repeat(600); // ~54,000 characters + expect(largeText.length).toBeGreaterThan(50000); + + const startTashkeel = performance.now(); + const noTashkeel = removeTashkeel(largeText); + const tashkeelDuration = performance.now() - startTashkeel; + + const startTatweel = performance.now(); + const noTatweel = removeTatweel(largeText); + const tatweelDuration = performance.now() - startTatweel; + + const startOldArabic = performance.now(); + const oldArabic = toOldArabic(largeText); + const oldArabicDuration = performance.now() - startOldArabic; + + expect(noTashkeel.length).toBeLessThan(largeText.length); + expect(noTatweel.length).toBeLessThan(largeText.length); + expect(oldArabic.length).toBeGreaterThan(0); + + expect(tashkeelDuration, 'removeTashkeel should process large text within 150ms').toBeLessThan(150); + expect(tatweelDuration, 'removeTatweel should process large text within 150ms').toBeLessThan(150); + expect(oldArabicDuration, 'toOldArabic should process large text within 150ms').toBeLessThan(150); + }); + }); +}); From add168b19ef6a39c742ade17a1738381bc22e16d Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:07:04 +0300 Subject: [PATCH 03/16] ci: test across Node.js 18, 20, 22, and 24 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2955b3e..36ea42f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - node-version: [22.x] + node-version: [18.x, 20.x, 22.x, 24.x] steps: - name: Checkout repository From 4d06a42eac3e65b373159c8e885ad994e1ca6038 Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:11:24 +0300 Subject: [PATCH 04/16] refactor(tests): fix ESLint no-require-imports --- tests/artifacts.test.ts | 12 +++++------- tests/edge-cases.test.ts | 4 ++-- tests/tree-shaking.test.ts | 6 +++--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/artifacts.test.ts b/tests/artifacts.test.ts index 303cdd0..4def519 100644 --- a/tests/artifacts.test.ts +++ b/tests/artifacts.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect } from 'vitest'; +import { execSync } from 'node:child_process'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as vm from 'node:vm'; -import { execSync } from 'node:child_process'; +import { describe, expect, it } from 'vitest'; describe('Package Artifacts & Module Integrity', () => { const rootDir = path.resolve(__dirname, '..'); @@ -28,9 +28,7 @@ describe('Package Artifacts & Module Integrity', () => { true, ); const stats = fs.statSync(absolutePath); - expect(stats.size, `File specified in ${entry.field} (${entry.path}) must not be empty`).toBeGreaterThan( - 0, - ); + expect(stats.size, `File specified in ${entry.field} (${entry.path}) must not be empty`).toBeGreaterThan(0); } }); @@ -104,12 +102,12 @@ describe('Package Artifacts & Module Integrity', () => { expect(arabicServices.removeTatweel('كتــــاب')).toBe('كتاب'); }); - it('should execute purely as Isomorphic/Universal functions without DOM dependencies', () => { + it('should execute purely as Isomorphic/Universal functions without DOM dependencies', async () => { // Import all scripts in current process (where window and document are undefined) expect(typeof window).toBe('undefined'); expect(typeof document).toBe('undefined'); - const dist = require(path.join(rootDir, 'dist')); + const dist = await import('../dist'); expect(dist.removeTashkeel('فَحَصَ')).toBe('فحص'); expect(dist.removeTatweel('مـلـف')).toBe('ملف'); expect(dist.toOldArabic('بَيْت')).toBe('ٮىٮ'); diff --git a/tests/edge-cases.test.ts b/tests/edge-cases.test.ts index 5725651..a8f74f3 100644 --- a/tests/edge-cases.test.ts +++ b/tests/edge-cases.test.ts @@ -8,7 +8,6 @@ import { tashfeerBannedWords, wordToLetters, removeArabicAffixes, - BANNED_WORDS, } from '../src'; describe('Arabic NLP Edge Cases & Robustness', () => { @@ -126,7 +125,8 @@ describe('Arabic NLP Edge Cases & Robustness', () => { describe('Performance & ReDoS Resilience', () => { it('should process large texts (50,000+ chars) in under 150ms without catastrophic backtracking', () => { - const sampleUnit = 'الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني، وَالسّيفُ وَالرّمحُ والقرْطاسُ وَالقَلَـمُ! '; + const sampleUnit = + 'الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني، وَالسّيفُ وَالرّمحُ والقرْطاسُ وَالقَلَـمُ! '; const largeText = sampleUnit.repeat(600); // ~54,000 characters expect(largeText.length).toBeGreaterThan(50000); diff --git a/tests/tree-shaking.test.ts b/tests/tree-shaking.test.ts index c1c13d2..222ffc7 100644 --- a/tests/tree-shaking.test.ts +++ b/tests/tree-shaking.test.ts @@ -1,3 +1,4 @@ +import { execSync } from 'node:child_process'; import ArabicServicesDefault, { ArabicServices, removeTashkeel, @@ -34,10 +35,9 @@ describe('Tree Shaking & Named Exports', () => { }); it('should support native Node.js ESM import with named exports', () => { - const { execSync } = require('child_process'); const stdout = execSync( - 'node --input-type=module -e "import(\'./dist/esm/index.js\').then(m => console.log(JSON.stringify({ hasRemoveTashkeel: typeof m.removeTashkeel, hasArabicServices: typeof m.ArabicServices, sampleResult: m.removeTashkeel(\'مَرْحَبًا\') })))"', - { encoding: 'utf8' } + "node --input-type=module -e \"import('./dist/esm/index.js').then(m => console.log(JSON.stringify({ hasRemoveTashkeel: typeof m.removeTashkeel, hasArabicServices: typeof m.ArabicServices, sampleResult: m.removeTashkeel('مَرْحَبًا') })))\"", + { encoding: 'utf8' }, ); const result = JSON.parse(stdout); expect(result.hasRemoveTashkeel).toBe('function'); From 8aa594d796c5414270af24025482936a890ab6eb Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:15:51 +0300 Subject: [PATCH 05/16] test: add in-process CJS test with createRequire --- tests/artifacts.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/artifacts.test.ts b/tests/artifacts.test.ts index 4def519..80980d5 100644 --- a/tests/artifacts.test.ts +++ b/tests/artifacts.test.ts @@ -1,5 +1,6 @@ import { execSync } from 'node:child_process'; import * as fs from 'node:fs'; +import { createRequire } from 'node:module'; import * as path from 'node:path'; import * as vm from 'node:vm'; import { describe, expect, it } from 'vitest'; @@ -102,6 +103,18 @@ describe('Package Artifacts & Module Integrity', () => { expect(arabicServices.removeTatweel('كتــــاب')).toBe('كتاب'); }); + it('should support in-process CommonJS loading via Node.js createRequire()', () => { + const nodeRequire = createRequire(import.meta.url); + const cjsDist = nodeRequire('../dist'); + + expect(cjsDist).toBeDefined(); + expect(typeof cjsDist.removeTashkeel).toBe('function'); + expect(typeof cjsDist.toOldArabic).toBe('function'); + expect(typeof cjsDist.ArabicServices).toBe('object'); + expect(cjsDist.removeTashkeel('مَرْحَبًا')).toBe('مرحبا'); + expect(cjsDist.ArabicServices.removeTatweel('رائـــــع')).toBe('رائع'); + }); + it('should execute purely as Isomorphic/Universal functions without DOM dependencies', async () => { // Import all scripts in current process (where window and document are undefined) expect(typeof window).toBe('undefined'); From 6aff36b4a4a456ac06244dc00414b34043b4edc3 Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:19:40 +0300 Subject: [PATCH 06/16] ci: align test matrix with Vitest 5 engine --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36ea42f..4a3d7aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x, 22.x, 24.x] + node-version: [22.x, 24.x] steps: - name: Checkout repository From 38619e59a250c4dbd40c03cc7f7ef6375d234564 Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:30:02 +0300 Subject: [PATCH 07/16] ci: run lint on entire workspace in CI workflow --- .github/workflows/test.yml | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a3d7aa..2377231 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,5 +32,5 @@ jobs: - name: Install dependencies run: npm ci - - name: Run build and tests - run: npm run build && npm test + - name: Run lint, build, and tests + run: npm run lint && npm run build && npm test diff --git a/package.json b/package.json index 56e8075..88c554f 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "test:watch": "vitest", "prepublishOnly": "npm run build", "format": "npx prettier --write . --ignore-path .gitignore", - "lint": "eslint src --quiet", - "lint:fix": "eslint src --fix", + "lint": "eslint . --quiet", + "lint:fix": "eslint . --fix", "bundle": "webpack" }, "repository": { From f0d3db3c68978545bdd1e81b8c4ef505ee20df8e Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:30:40 +0300 Subject: [PATCH 08/16] docs: update AGENT.md to reference Vitest --- AGENT.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AGENT.md b/AGENT.md index ed3ca6e..3f5597b 100644 --- a/AGENT.md +++ b/AGENT.md @@ -34,8 +34,11 @@ This repository contains `arabic-services`: a zero-runtime-dependency TypeScript - `src/utils/`: - `string.util.ts`: Internal string manipulation and regex utility functions. - `tests/`: - - `scripts.test.ts`: Jest unit tests covering all text operations. + - `scripts.test.ts`: Vitest unit tests covering all text operations. - `constants.test.ts`: Sanity checks for character mappings and constants. + - `artifacts.test.ts`: Module integrity tests for CJS, ESM, and browser UMD bundles. + - `edge-cases.test.ts`: Boundary values, orthography variations, and ReDoS resilience tests. + - `tree-shaking.test.ts`: Verification of named exports and dead code elimination. - `webpack.config.cjs`: Bundler configuration generating UMD (`dist/umd/`) and ESM (`dist/esm/`) bundles for browser distribution. - `tsconfig.json`: TypeScript compiler configuration targeting ES5 / CommonJS for the main npm distribution. @@ -54,7 +57,7 @@ This repository contains `arabic-services`: a zero-runtime-dependency TypeScript ## Build, Test, and Development Commands - `npm run build`: Compile TypeScript into `dist/` (generates `.js` and `.d.ts`). -- `npm test`: Run Jest unit tests and generate coverage report. **Note**: Run `npm run build` before `npm test`, as tests import from the compiled `./dist` directory. +- `npm test`: Run Vitest test suite and generate coverage report. **Note**: Run `npm run build` before `npm test`, as tests import from the compiled `./dist` directory. - `npm test -- -t "test name"`: Run a specific test case by name. - `npm run dev`: Quick execution runner using `ts-node src/index.ts`. - `npm run bundle`: Run Webpack to bundle production UMD (`arabic-services.umd.js`) and ESM (`arabic-services.esm.js`) files for browser/CDN distribution. From b7a4df341210101f10d21d4b2d5029e2587a511d Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:31:29 +0300 Subject: [PATCH 09/16] docs: document CDN usage and tree shaking imports --- README.md | 59 +++++++++++++++++++++++++++++++-------------------- README_AR.md | 60 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index ba4162b..60fa4f6 100644 --- a/README.md +++ b/README.md @@ -36,15 +36,15 @@ Can be used in Node.js and the browser. ## 📋 Features -- **Tashkeel Removal**: Easily remove Tashkeel from Arabic text. -- **Tatweel Removal**: Remove Tatweel character from Arabic phrases. -- **Convert To Old Arabic**: Transform Arabic text into old script. -- **Convert To Old Arabic And Tashfeer Banned Words**: Transform Arabic text into old script and replace Banned Arabic text with visually similar characters for encoding purposes. (Banned words are words that considered as hate speech in social media) -- **Tashfeer**: Replaces Arabic text with visually similar characters for encoding purposes. -- **Tashfeer Banned Words**: Replaces Banned Arabic text with visually similar characters for encoding purposes. (Banned words are words that considered as hate speech in social media) -- **Remove Arabic Affixes**: Removes predefined affixes (prefixes and suffixes) from an Arabic word if it starts or ends with those affixes. -- **Word To Letters**: Convert Arabic word to its pronounced letters. -- ... and more! +- **Tashkeel Removal**: Easily remove Tashkeel from Arabic text. +- **Tatweel Removal**: Remove Tatweel character from Arabic phrases. +- **Convert To Old Arabic**: Transform Arabic text into old script. +- **Convert To Old Arabic And Tashfeer Banned Words**: Transform Arabic text into old script and replace Banned Arabic text with visually similar characters for encoding purposes. (Banned words are words that considered as hate speech in social media) +- **Tashfeer**: Replaces Arabic text with visually similar characters for encoding purposes. +- **Tashfeer Banned Words**: Replaces Banned Arabic text with visually similar characters for encoding purposes. (Banned words are words that considered as hate speech in social media) +- **Remove Arabic Affixes**: Removes predefined affixes (prefixes and suffixes) from an Arabic word if it starts or ends with those affixes. +- **Word To Letters**: Convert Arabic word to its pronounced letters. +- ... and more!

➡️ Check out the usage and detailed documentation on each feature ⬅️

@@ -54,38 +54,51 @@ Arabic-Services was designed to work in both the browser and NodeJS applications ### Browser -We provide both ESM and UMD bundles for use in the browser. -You can find it with the latest release files. +You can use the library directly in the browser via CDN using **jsdelivr** or **unpkg**: ```html - + + + + + + ``` -### NodeJS +### NodeJS & Modern Bundlers -Both CommonJS and ES Module libraries are provided. +Install the package via npm: ```bash npm install arabic-services ``` -**CommonJS:** +#### ES Module & Tree Shaking (Recommended): + +Import only the functions you need for maximum bundle size reduction: ```javascript -const { ArabicServices } = require('arabic-services'); -console.log(ArabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); +import { removeTashkeel, removeTatweel, toOldArabic } from 'arabic-services'; + +console.log(removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); // Output: 'الخيل والليل والبيداء تعرفني' ``` -**ES Module:** +Or import the full object: ```javascript import { ArabicServices } from 'arabic-services'; -console.log(ArabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); +``` + +#### CommonJS: + +```javascript +const { removeTashkeel, ArabicServices } = require('arabic-services'); +console.log(removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); // Output: 'الخيل والليل والبيداء تعرفني' ``` @@ -93,8 +106,8 @@ console.log(ArabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ Arabic Services is made publicly available to provide a well tested and well documented library for processing and transforming Arabic text to all developers. We accept feature contributions provided that they are properly documented and include the appropriate unit tests. -- 🚀 If you want to contribute to this project and make it better with new ideas, your pull request is very welcomed. -- 📝 If you find any issue just put it in the repository issue section, thank you. +- 🚀 If you want to contribute to this project and make it better with new ideas, your pull request is very welcomed. +- 📝 If you find any issue just put it in the repository issue section, thank you. ## 🌟 Star this repository @@ -106,5 +119,5 @@ This project is licensed under the GPL-3.0 License. See the [LICENSE](./LICENSE) ## 💬 Contact -- 📩 Email: [SeenDevelopment@gmail.com](mailto:SeenDevelopment@gmail.com) -- 🌎 [Website](https://seen-arabic.github.io/) +- 📩 Email: [SeenDevelopment@gmail.com](mailto:SeenDevelopment@gmail.com) +- 🌎 [Website](https://seen-arabic.github.io/) diff --git a/README_AR.md b/README_AR.md index b8f3f44..5aa2e44 100644 --- a/README_AR.md +++ b/README_AR.md @@ -33,15 +33,15 @@ ## 📋 الميزات -- **إزالة التشكيل:** إزالة التشكيل من النص العربي. -- **إزالة التطويل:** إزالة أحرف التطويل غير الضرورية من العبارات العربية. -- **التحويل إلى العربية القديمة:** تحويل النص العربي إلى الخط العربي القديم. -- **التحويل إلى العربية القديمة وتشفير الكلمات المحظورة:** تحويل النص العربي إلى الخط العربي القديم واستبدال النصوص العربية المحظورة بأحرف مشابهة بصريًا لأغراض الترميز. (الكلمات المحظورة هي الكلمات التي تعتبر كلمات مسيئة في وسائل التواصل الاجتماعي) -- **التشفير:** استبدال النص العربي بأحرف مشابهة بصريًا لأغراض الترميز. -- **تشفير الكلمات المحظورة:** استبدال النصوص العربية المحظورة بأحرف مشابهة بصريًا لأغراض الترميز. (الكلمات المحظورة هي الكلمات التي تعتبر كلمات مسيئة في وسائل التواصل الاجتماعي) -- **إزالة اللواحق العربية:** إزالة اللواحق المحددة (البادئات واللاحقات) من كلمة عربية إذا بدأت أو انتهت بهذه اللواحق. -- **الكلمة إلى حروفها:** تحويل الكلمة العربية إلى حروفها المنطوقة. -- ...والمزيد! +- **إزالة التشكيل:** إزالة التشكيل من النص العربي. +- **إزالة التطويل:** إزالة أحرف التطويل غير الضرورية من العبارات العربية. +- **التحويل إلى العربية القديمة:** تحويل النص العربي إلى الخط العربي القديم. +- **التحويل إلى العربية القديمة وتشفير الكلمات المحظورة:** تحويل النص العربي إلى الخط العربي القديم واستبدال النصوص العربية المحظورة بأحرف مشابهة بصريًا لأغراض الترميز. (الكلمات المحظورة هي الكلمات التي تعتبر كلمات مسيئة في وسائل التواصل الاجتماعي) +- **التشفير:** استبدال النص العربي بأحرف مشابهة بصريًا لأغراض الترميز. +- **تشفير الكلمات المحظورة:** استبدال النصوص العربية المحظورة بأحرف مشابهة بصريًا لأغراض الترميز. (الكلمات المحظورة هي الكلمات التي تعتبر كلمات مسيئة في وسائل التواصل الاجتماعي) +- **إزالة اللواحق العربية:** إزالة اللواحق المحددة (البادئات واللاحقات) من كلمة عربية إذا بدأت أو انتهت بهذه اللواحق. +- **الكلمة إلى حروفها:** تحويل الكلمة العربية إلى حروفها المنطوقة. +- ...والمزيد!

➡️ شاهد استخدام كل ميزة من المكتبة وطريقة استخدامها في الكود ⬅️

@@ -49,39 +49,53 @@ تم تصميم خدمات العربية لتعمل في كل من المتصفح وتطبيقات NodeJS. -### المتصفح +### المتصفح (Browser) -نوفر حزم ESM و UMD للاستخدام في المتصفح. يمكن العثور عليها مع ملفات الإصدار الأخير. +يمكنك استخدام المكتبة مباشرة في المتصفح عبر شبكات الـ CDN العالمية (**jsdelivr** أو **unpkg**): ```html - + + + + + + ``` -### NodeJS +### NodeJS وحزم العمل الحديثة (Modern Bundlers) -تم توفير مكتبات CommonJS و ES Module. +تثبيت الحزمة عبر npm: ```bash npm install arabic-services ``` -**CommonJS:** +#### نظام ES Module ودعم Tree Shaking (موصى به): + +استيراد الدوال التي تحتاجها فقط لتقليل حجم الحزمة إلى أدنى حد (أقل من 400 بايت للدالة): ```javascript -const { ArabicServices } = require('arabic-services'); -console.log(ArabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); +import { removeTashkeel, removeTatweel, toOldArabic } from 'arabic-services'; + +console.log(removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); // Output: 'الخيل والليل والبيداء تعرفني' ``` -**ES Module:** +أو استيراد الكائن كاملاً: ```javascript import { ArabicServices } from 'arabic-services'; -console.log(ArabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); +``` + +#### نظام CommonJS: + +```javascript +const { removeTashkeel, ArabicServices } = require('arabic-services'); +console.log(removeTashkeel('الخَيْلُ وَاللّيْلُ وَالبَيْداءُ تَعرِفُني')); // Output: 'الخيل والليل والبيداء تعرفني' ``` @@ -89,8 +103,8 @@ console.log(ArabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ تم إتاحة خدمات اللغة العربية للجمهور لتوفير مكتبة مُجربة جيدًا وموثقة بشكل جيد لمعالجة وتحويل النصوص العربية لجميع المطورين. نحن نقبل مساهمات الميزات بشرط أن تكون موثقة بشكل صحيح وتشمل الاختبارات المناسبة. -- 🚀 إذا كنت ترغب في المساهمة في هذا المشروع وتحسينه بأفكار جديدة، فبإمكانك عمل pull request وطلبك مرحب به للغاية. -- 📝 إذا وجدت أي مشكلة، فقط ضعها في قسم الـIssues في المستودع، شكرًا لك. +- 🚀 إذا كنت ترغب في المساهمة في هذا المشروع وتحسينه بأفكار جديدة، فبإمكانك عمل pull request وطلبك مرحب به للغاية. +- 📝 إذا وجدت أي مشكلة، فقط ضعها في قسم الـIssues في المستودع، شكرًا لك.

🌟 ضع نجمة على هذا المستودع 🌟

@@ -107,5 +121,5 @@ console.log(ArabicServices.removeTashkeel('الخَيْلُ وَاللّيْلُ ## 💬 الاتصال -- 📩 البريد الإلكتروني: [SeenDevelopment@gmail.com](mailto:SeenDevelopment@gmail.com) -- 🌎 [الموقع الإلكتروني](https://seen-arabic.github.io/) +- 📩 البريد الإلكتروني: [SeenDevelopment@gmail.com](mailto:SeenDevelopment@gmail.com) +- 🌎 [الموقع الإلكتروني](https://seen-arabic.github.io/) From bc74df1e6a5300e1221ab28e45ade8125609dcf9 Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:31:57 +0300 Subject: [PATCH 10/16] fix(templates): fix environment in bug report --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0d2d0bc..1bba01b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,9 +26,9 @@ assignees: '' يرجى توضيح البيئة التي كنت تستخدمها عند حدوث المشكلة. يمكن ذلك أن يشمل: -- نسخة لغة البرمجة (مثل Python 3.8) -- نظام التشغيل (مثل Windows 10 أو Ubuntu 20.04) -- المكتبات البرمجية المعنية وإصداراتها +- بيئة التشغيل (مثل Node.js 22.x، أو المتصفح Chrome، أو Next.js / Vite) +- إصدار الحزمة arabic-services (مثل 1.1.0) +- نظام التشغيل (مثل Windows أو Linux أو macOS) ## السلوك المتوقع (Expected Behavior) From 4cf19ba444f1fb5e47667202f51f55f01e6c3cb7 Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 22:37:46 +0300 Subject: [PATCH 11/16] ci: rename test workflow to CI --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2377231..f71cd5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Test +name: CI on: push: From 1aa3043c8f5ffffe14ebeb51cf37c214e02f9b81 Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 23:10:29 +0300 Subject: [PATCH 12/16] docs: link GitHub Releases downloads in README --- README.md | 2 +- README_AR.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60fa4f6..e1d6cc9 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Arabic-Services was designed to work in both the browser and NodeJS applications ### Browser -You can use the library directly in the browser via CDN using **jsdelivr** or **unpkg**: +You can use the library directly in the browser via CDN using **jsdelivr** or **unpkg**, or download standalone bundle files from the latest [GitHub Releases](https://github.com/Seen-Arabic/Arabic-Services-JavaScript/releases): ```html diff --git a/README_AR.md b/README_AR.md index 5aa2e44..bb3d0fb 100644 --- a/README_AR.md +++ b/README_AR.md @@ -51,7 +51,7 @@ ### المتصفح (Browser) -يمكنك استخدام المكتبة مباشرة في المتصفح عبر شبكات الـ CDN العالمية (**jsdelivr** أو **unpkg**): +يمكنك استخدام المكتبة مباشرة في المتصفح عبر شبكات الـ CDN العالمية (**jsdelivr** أو **unpkg**)، أو تنزيل ملفات الـ Bundles الجاهزة من صفحة [GitHub Releases](https://github.com/Seen-Arabic/Arabic-Services-JavaScript/releases): ```html From 598035ac182873f69cbe4b9459cf6fcec37f2e7f Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 23:11:04 +0300 Subject: [PATCH 13/16] ci: ensure release assets upload if build passes --- .github/workflows/npm-publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 3588c22..2629d23 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -31,6 +31,7 @@ jobs: # Step 4: Build package and bundles - name: Build package and bundles + id: build run: npm run build # Step 5: Publish the package to npm using Trusted Publishing (OIDC) @@ -39,6 +40,7 @@ jobs: # Step 6: Upload bundle assets to release - name: Upload bundle assets to release + if: always() && steps.build.outcome == 'success' uses: softprops/action-gh-release@v2 with: files: | From 2718e3a2a6d7fda3f70a62fd3fbbe5c32a0a0bf5 Mon Sep 17 00:00:00 2001 From: Abdelrahman Bayoumi Date: Wed, 14 Oct 2026 23:11:47 +0300 Subject: [PATCH 14/16] chore: move example.html to examples/browser/ --- example.html => examples/browser/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename example.html => examples/browser/index.html (97%) diff --git a/example.html b/examples/browser/index.html similarity index 97% rename from example.html rename to examples/browser/index.html index 2941132..fa671ae 100644 --- a/example.html +++ b/examples/browser/index.html @@ -12,7 +12,7 @@ - +