repo-anon 0.1.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 +53 -0
- package/bin/repo-anon.js +20 -0
- package/eslint.config.mjs +11 -0
- package/lib/processor.js +65 -0
- package/package.json +26 -0
- package/tests/processor.test.js +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# repo-anon
|
|
2
|
+
|
|
3
|
+
A Node.js CLI tool to anonymize and de-anonymize files in a repository based on a `.phrases` configuration file. Perfect for preparing repositories for AI processing while keeping sensitive info, such as company or brand names, protected.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Anonymize**: Replaces sensitive phrases with configured placeholders.
|
|
8
|
+
- **De-anonymize**: Restores original phrases from placeholders.
|
|
9
|
+
- **Recursive**: Traverses through all project directories (ignoring `node_modules`, `.git`, etc.).
|
|
10
|
+
- **CI/CD Ready**: Includes GitLab pipeline configuration for publishing to the GitLab package registry.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @your_gitlab_namespace/repo-anon
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
*(Note: Replace `@your_gitlab_namespace` with your actual GitLab namespace).*
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
1. Create a `.phrases` file in the current working directory:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"CompanyA": "ANON_COMPANY_A",
|
|
27
|
+
"BrandX": "ANON_BRAND_X"
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. Run the anonymization command:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
repo-anon anonymize
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. Revert changes (if needed):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
repo-anon deanonymize
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
- **Tests**: Run unit tests using `npm test`.
|
|
46
|
+
- **Linting**: Lint the project using `npm run lint`.
|
|
47
|
+
|
|
48
|
+
## CI/CD Deployment
|
|
49
|
+
|
|
50
|
+
The project includes a `.gitlab-ci.yml` configured to automatically publish new versions to the GitLab package registry when a tag is pushed.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
Built with ❤️.
|
package/bin/repo-anon.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const { anonymize, deanonymize } = require('./../lib/processor');
|
|
5
|
+
|
|
6
|
+
program
|
|
7
|
+
.version('1.0.0')
|
|
8
|
+
.description('Repository Anonymizer CLI');
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.command('anonymize')
|
|
12
|
+
.description('Anonymize project based on .phrases file')
|
|
13
|
+
.action(anonymize);
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('deanonymize')
|
|
17
|
+
.description('De-anonymize project based on .phrases file')
|
|
18
|
+
.action(deanonymize);
|
|
19
|
+
|
|
20
|
+
program.parse(process.argv);
|
package/lib/processor.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const PHRASES_FILE = '.phrases';
|
|
5
|
+
|
|
6
|
+
function loadPhrases() {
|
|
7
|
+
if (!fs.existsSync(PHRASES_FILE)) {
|
|
8
|
+
throw new Error('No .phrases file found.');
|
|
9
|
+
}
|
|
10
|
+
return JSON.parse(fs.readFileSync(PHRASES_FILE, 'utf8'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function processContent(content, phrases, reverse = false) {
|
|
14
|
+
let changed = false;
|
|
15
|
+
let newContent = content;
|
|
16
|
+
|
|
17
|
+
for (const [original, placeholder] of Object.entries(phrases)) {
|
|
18
|
+
const search = reverse ? placeholder : original;
|
|
19
|
+
const replace = reverse ? original : placeholder;
|
|
20
|
+
if (newContent.includes(search)) {
|
|
21
|
+
newContent = newContent.split(search).join(replace);
|
|
22
|
+
changed = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return changed ? newContent : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function walk(dir, callback) {
|
|
29
|
+
const files = fs.readdirSync(dir);
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
if (['node_modules', '.git', '.phrases', 'package.json', 'package-lock.json', 'bin', 'tests', '.gitlab-ci.yml'].includes(file)) continue;
|
|
32
|
+
const filePath = path.join(dir, file);
|
|
33
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
34
|
+
await walk(filePath, callback);
|
|
35
|
+
} else {
|
|
36
|
+
callback(filePath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function anonymize() {
|
|
42
|
+
const phrases = loadPhrases();
|
|
43
|
+
walk(process.cwd(), (filePath) => {
|
|
44
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
45
|
+
const newContent = processContent(content, phrases);
|
|
46
|
+
if (newContent) {
|
|
47
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
48
|
+
console.log(`Updated: ${filePath}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function deanonymize() {
|
|
54
|
+
const phrases = loadPhrases();
|
|
55
|
+
walk(process.cwd(), (filePath) => {
|
|
56
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
57
|
+
const newContent = processContent(content, phrases, true);
|
|
58
|
+
if (newContent) {
|
|
59
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
60
|
+
console.log(`Updated: ${filePath}`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { anonymize, deanonymize, processContent };
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "repo-anon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to anonymize/de-anonymize repositories.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"repo-anon": "bin/repo-anon.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"commander": "^13.1.0"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"lint": "eslint ."
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"type": "commonjs",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@eslint/js": "^10.0.1",
|
|
22
|
+
"eslint": "^10.0.2",
|
|
23
|
+
"globals": "^17.4.0",
|
|
24
|
+
"jest": "^30.2.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { processContent } = require('../lib/processor');
|
|
2
|
+
|
|
3
|
+
describe('repo-anon processor', () => {
|
|
4
|
+
test('should anonymize content', () => {
|
|
5
|
+
const phrases = { "company": "anon" };
|
|
6
|
+
const content = "my company is here";
|
|
7
|
+
const expected = "my anon is here";
|
|
8
|
+
const result = processContent(content, phrases);
|
|
9
|
+
expect(result).toBe(expected);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should deanonymize content', () => {
|
|
13
|
+
const phrases = { "company": "anon" };
|
|
14
|
+
const content = "my anon is here";
|
|
15
|
+
const expected = "my company is here";
|
|
16
|
+
// reverse = true
|
|
17
|
+
const result = processContent(content, phrases, true);
|
|
18
|
+
expect(result).toBe(expected);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should return null if no change', () => {
|
|
22
|
+
const phrases = { "company": "anon" };
|
|
23
|
+
const content = "nothing here";
|
|
24
|
+
const result = processContent(content, phrases);
|
|
25
|
+
expect(result).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|