@edcalderon/versioning 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 +48 -10
- package/dist/extensions/{cleanup-repo-extension.d.ts → cleanup-repo/index.d.ts} +2 -2
- package/dist/extensions/{cleanup-repo-extension.js → cleanup-repo/index.js} +40 -32
- package/dist/extensions/lifecycle-hooks/index.d.ts +4 -0
- package/dist/extensions/{lifecycle-hooks.js → lifecycle-hooks/index.js} +1 -1
- package/dist/extensions/npm-publish/index.d.ts +4 -0
- package/dist/extensions/{npm-publish.js → npm-publish/index.js} +1 -1
- package/dist/extensions/reentry-status/config-manager.js +3 -1
- package/dist/extensions/reentry-status/constants.d.ts +1 -1
- package/dist/extensions/reentry-status/constants.js +1 -1
- package/dist/extensions/reentry-status/extension.d.ts +4 -0
- package/dist/extensions/{reentry-status-extension.js → reentry-status/extension.js} +14 -14
- package/dist/extensions/reentry-status/index.d.ts +2 -0
- package/dist/extensions/reentry-status/index.js +5 -0
- package/dist/extensions/sample-extension/index.d.ts +4 -0
- package/dist/extensions/{sample-extension.js → sample-extension/index.js} +1 -1
- package/dist/extensions/secrets-check/index.d.ts +16 -0
- package/dist/extensions/secrets-check/index.js +264 -0
- package/dist/extensions.js +27 -15
- package/dist/versioning.d.ts +1 -0
- package/package.json +1 -1
- package/dist/extensions/lifecycle-hooks.d.ts +0 -4
- package/dist/extensions/npm-publish.d.ts +0 -4
- package/dist/extensions/reentry-status-extension.d.ts +0 -4
- package/dist/extensions/sample-extension.d.ts +0 -4
package/README.md
CHANGED
|
@@ -11,7 +11,9 @@ A comprehensive versioning and changelog management tool designed for monorepos
|
|
|
11
11
|
- 📦 NPM publishable
|
|
12
12
|
- 🏷️ Git tagging and committing
|
|
13
13
|
- ✅ Validation of version sync
|
|
14
|
-
- 🔌 **Extensible plugin system** for
|
|
14
|
+
- 🔌 **Extensible plugin system** for subdirectory-based extensions
|
|
15
|
+
- 🔒 **Security Checks** with automatic Husky integration
|
|
16
|
+
- 🧹 **Repository Cleanup** to keep root directory organized
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -33,7 +35,7 @@ The versioning tool supports a **composable extension system** that allows you t
|
|
|
33
35
|
- Implement custom versioning strategies
|
|
34
36
|
|
|
35
37
|
Extensions are loaded automatically from:
|
|
36
|
-
- Built-in extensions in
|
|
38
|
+
- Built-in extensions in subdirectories of `src/extensions/` (e.g. `src/extensions/reentry-status/index.ts`)
|
|
37
39
|
- External packages listed in `versioning.config.json`
|
|
38
40
|
|
|
39
41
|
### Creating Extensions
|
|
@@ -172,20 +174,56 @@ versioning cleanup scan # Dry-run scan of root
|
|
|
172
174
|
versioning cleanup move # Move files to configured destinations
|
|
173
175
|
versioning cleanup restore # Restore a moved file
|
|
174
176
|
versioning cleanup config # View/manage configuration
|
|
175
|
-
versioning cleanup husky # Setup git hook
|
|
177
|
+
versioning cleanup husky # Setup git hook (scan-only by default)
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
Configuration (`versioning.config.json`):
|
|
179
181
|
|
|
180
182
|
```json
|
|
181
183
|
{
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"
|
|
188
|
-
|
|
184
|
+
"extensionConfig": {
|
|
185
|
+
"cleanup-repo": {
|
|
186
|
+
"enabled": true,
|
|
187
|
+
"defaultDestination": "docs",
|
|
188
|
+
"allowlist": ["CHANGELOG.md"],
|
|
189
|
+
"routes": {
|
|
190
|
+
".sh": "scripts",
|
|
191
|
+
".json": "config"
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Secrets Check Extension
|
|
199
|
+
|
|
200
|
+
Prevents sensitive data (private keys, tokens, mnemonics) from being committed to the repository.
|
|
201
|
+
|
|
202
|
+
Features:
|
|
203
|
+
- **Pre-defined Patterns:** Detects AWS, GitHub, NPM tokens, private keys, and EVM mnemonics.
|
|
204
|
+
- **Allowlist:** Ignore false positives or specific test keys.
|
|
205
|
+
- **Husky Integration:** Easy CLI setup to block commits containing secrets.
|
|
206
|
+
|
|
207
|
+
Commands:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
versioning check-secrets # Scan staged files for secrets
|
|
211
|
+
versioning check-secrets husky # Add blocking secrets check to pre-commit hook
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Configuration (`versioning.config.json`):
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"extensionConfig": {
|
|
219
|
+
"secrets-check": {
|
|
220
|
+
"enabled": true,
|
|
221
|
+
"patterns": [
|
|
222
|
+
"CUSTOM_API_KEY=[0-9a-f]{32}"
|
|
223
|
+
],
|
|
224
|
+
"allowlist": [
|
|
225
|
+
"ETHERSCAN_API_KEY=YOUR_KEY"
|
|
226
|
+
]
|
|
189
227
|
}
|
|
190
228
|
}
|
|
191
229
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VersioningExtension } from '
|
|
1
|
+
import { VersioningExtension } from '../../extensions';
|
|
2
2
|
/**
|
|
3
3
|
* The canonical cleanup configuration schema.
|
|
4
4
|
* Everything the plugin needs lives here.
|
|
@@ -46,4 +46,4 @@ declare function loadCleanupConfig(rootConfig: any): CleanupRepoConfig;
|
|
|
46
46
|
declare const extension: VersioningExtension;
|
|
47
47
|
export { loadCleanupConfig, BUILTIN_ALLOWLIST, DEFAULT_EXTENSIONS, DEFAULT_ROUTES };
|
|
48
48
|
export default extension;
|
|
49
|
-
//# sourceMappingURL=
|
|
49
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -124,7 +124,7 @@ exports.DEFAULT_ROUTES = DEFAULT_ROUTES;
|
|
|
124
124
|
// CONFIG LOADER
|
|
125
125
|
// ─────────────────────────────────────────────────────────────────
|
|
126
126
|
function loadCleanupConfig(rootConfig) {
|
|
127
|
-
const raw = rootConfig?.cleanup ?? {};
|
|
127
|
+
const raw = rootConfig?.extensionConfig?.['cleanup-repo'] ?? rootConfig?.cleanup ?? {};
|
|
128
128
|
return {
|
|
129
129
|
enabled: raw.enabled !== false,
|
|
130
130
|
defaultDestination: typeof raw.defaultDestination === 'string' ? raw.defaultDestination : 'docs',
|
|
@@ -262,7 +262,7 @@ const extension = {
|
|
|
262
262
|
return;
|
|
263
263
|
}
|
|
264
264
|
console.log('\n🔍 Repository Root Cleanup Scan\n');
|
|
265
|
-
console.log(` Config: versioning.config.json → cleanup`);
|
|
265
|
+
console.log(` Config: versioning.config.json → extensionConfig['cleanup-repo']`);
|
|
266
266
|
console.log(` Default destination: ${cfg.defaultDestination}/`);
|
|
267
267
|
console.log(` Extensions monitored: ${cfg.extensions.join(', ')}`);
|
|
268
268
|
console.log(` Allowlist (custom): ${cfg.allowlist.length > 0 ? cfg.allowlist.join(', ') : '—'}`);
|
|
@@ -390,23 +390,27 @@ const extension = {
|
|
|
390
390
|
if (await fs.pathExists(configPath)) {
|
|
391
391
|
rawCfg = await fs.readJson(configPath);
|
|
392
392
|
}
|
|
393
|
-
// Ensure
|
|
394
|
-
if (!rawCfg.
|
|
395
|
-
rawCfg.
|
|
396
|
-
if (!
|
|
397
|
-
rawCfg.cleanup
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (!
|
|
401
|
-
|
|
402
|
-
if (!
|
|
403
|
-
|
|
393
|
+
// Ensure extensionConfig section exists
|
|
394
|
+
if (!rawCfg.extensionConfig)
|
|
395
|
+
rawCfg.extensionConfig = {};
|
|
396
|
+
if (!rawCfg.extensionConfig['cleanup-repo']) {
|
|
397
|
+
rawCfg.extensionConfig['cleanup-repo'] = rawCfg.cleanup || {};
|
|
398
|
+
}
|
|
399
|
+
const extensionCfg = rawCfg.extensionConfig['cleanup-repo'];
|
|
400
|
+
if (!Array.isArray(extensionCfg.allowlist))
|
|
401
|
+
extensionCfg.allowlist = [];
|
|
402
|
+
if (!Array.isArray(extensionCfg.denylist))
|
|
403
|
+
extensionCfg.denylist = [];
|
|
404
|
+
if (!extensionCfg.routes)
|
|
405
|
+
extensionCfg.routes = { ...DEFAULT_ROUTES };
|
|
406
|
+
if (!extensionCfg.extensions)
|
|
407
|
+
extensionCfg.extensions = [...DEFAULT_EXTENSIONS];
|
|
404
408
|
let modified = false;
|
|
405
409
|
// ── --allow
|
|
406
410
|
if (options.allow) {
|
|
407
411
|
const file = String(options.allow).trim();
|
|
408
|
-
if (!
|
|
409
|
-
|
|
412
|
+
if (!extensionCfg.allowlist.includes(file)) {
|
|
413
|
+
extensionCfg.allowlist.push(file);
|
|
410
414
|
modified = true;
|
|
411
415
|
console.log(`✅ Added "${file}" to cleanup.allowlist`);
|
|
412
416
|
}
|
|
@@ -417,8 +421,8 @@ const extension = {
|
|
|
417
421
|
// ── --deny
|
|
418
422
|
if (options.deny) {
|
|
419
423
|
const file = String(options.deny).trim();
|
|
420
|
-
if (!
|
|
421
|
-
|
|
424
|
+
if (!extensionCfg.denylist.includes(file)) {
|
|
425
|
+
extensionCfg.denylist.push(file);
|
|
422
426
|
modified = true;
|
|
423
427
|
console.log(`✅ Added "${file}" to cleanup.denylist`);
|
|
424
428
|
}
|
|
@@ -429,9 +433,9 @@ const extension = {
|
|
|
429
433
|
// ── --unallow
|
|
430
434
|
if (options.unallow) {
|
|
431
435
|
const file = String(options.unallow).trim();
|
|
432
|
-
const idx =
|
|
436
|
+
const idx = extensionCfg.allowlist.indexOf(file);
|
|
433
437
|
if (idx !== -1) {
|
|
434
|
-
|
|
438
|
+
extensionCfg.allowlist.splice(idx, 1);
|
|
435
439
|
modified = true;
|
|
436
440
|
console.log(`✅ Removed "${file}" from cleanup.allowlist`);
|
|
437
441
|
}
|
|
@@ -442,9 +446,9 @@ const extension = {
|
|
|
442
446
|
// ── --undeny
|
|
443
447
|
if (options.undeny) {
|
|
444
448
|
const file = String(options.undeny).trim();
|
|
445
|
-
const idx =
|
|
449
|
+
const idx = extensionCfg.denylist.indexOf(file);
|
|
446
450
|
if (idx !== -1) {
|
|
447
|
-
|
|
451
|
+
extensionCfg.denylist.splice(idx, 1);
|
|
448
452
|
modified = true;
|
|
449
453
|
console.log(`✅ Removed "${file}" from cleanup.denylist`);
|
|
450
454
|
}
|
|
@@ -464,15 +468,15 @@ const extension = {
|
|
|
464
468
|
const dest = mapping.slice(eqIdx + 1).trim();
|
|
465
469
|
if (!ext.startsWith('.'))
|
|
466
470
|
ext = `.${ext}`;
|
|
467
|
-
|
|
471
|
+
extensionCfg.routes[ext] = dest;
|
|
468
472
|
modified = true;
|
|
469
473
|
console.log(`✅ Added route: ${ext} → ${dest}/`);
|
|
470
474
|
}
|
|
471
475
|
// ── --set-dest
|
|
472
476
|
if (options.setDest) {
|
|
473
|
-
|
|
477
|
+
extensionCfg.defaultDestination = String(options.setDest).trim();
|
|
474
478
|
modified = true;
|
|
475
|
-
console.log(`✅ Default destination set to "${
|
|
479
|
+
console.log(`✅ Default destination set to "${extensionCfg.defaultDestination}"`);
|
|
476
480
|
}
|
|
477
481
|
// Write config if modified
|
|
478
482
|
if (modified) {
|
|
@@ -626,16 +630,20 @@ async function updateHuskyConfig(configPath, enabled, hook, mode) {
|
|
|
626
630
|
if (await fs.pathExists(fullPath)) {
|
|
627
631
|
rawCfg = await fs.readJson(fullPath);
|
|
628
632
|
}
|
|
629
|
-
if (!rawCfg.
|
|
630
|
-
rawCfg.
|
|
631
|
-
if (!rawCfg.cleanup
|
|
632
|
-
rawCfg.cleanup
|
|
633
|
-
|
|
633
|
+
if (!rawCfg.extensionConfig)
|
|
634
|
+
rawCfg.extensionConfig = {};
|
|
635
|
+
if (!rawCfg.extensionConfig['cleanup-repo']) {
|
|
636
|
+
rawCfg.extensionConfig['cleanup-repo'] = rawCfg.cleanup || {};
|
|
637
|
+
}
|
|
638
|
+
const extensionCfg = rawCfg.extensionConfig['cleanup-repo'];
|
|
639
|
+
if (!extensionCfg.husky)
|
|
640
|
+
extensionCfg.husky = {};
|
|
641
|
+
extensionCfg.husky.enabled = enabled;
|
|
634
642
|
if (hook)
|
|
635
|
-
|
|
643
|
+
extensionCfg.husky.hook = hook;
|
|
636
644
|
if (mode)
|
|
637
|
-
|
|
645
|
+
extensionCfg.husky.mode = mode;
|
|
638
646
|
await fs.writeJson(fullPath, rawCfg, { spaces: 2 });
|
|
639
647
|
}
|
|
640
648
|
exports.default = extension;
|
|
641
|
-
//# sourceMappingURL=
|
|
649
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -84,7 +84,9 @@ function defaultFilesConfigForProject(project) {
|
|
|
84
84
|
class ConfigManager {
|
|
85
85
|
static loadConfig(rootConfig, project) {
|
|
86
86
|
const canonicalProject = canonicalProjectKey(project);
|
|
87
|
-
|
|
87
|
+
// Try new extensionConfig location first, then fallback to root-level property
|
|
88
|
+
const extensionConfig = rootConfig?.extensionConfig?.['reentry-status'];
|
|
89
|
+
const raw = (extensionConfig ?? (rootConfig && typeof rootConfig === 'object' ? rootConfig.reentryStatus : undefined));
|
|
88
90
|
const basePartial = { ...(raw ?? {}) };
|
|
89
91
|
delete basePartial.projects;
|
|
90
92
|
const projectPartial = canonicalProject && raw && typeof raw === 'object' && raw.projects && typeof raw.projects === 'object'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const REENTRY_EXTENSION_NAME = "reentry-status
|
|
1
|
+
export declare const REENTRY_EXTENSION_NAME = "reentry-status";
|
|
2
2
|
export declare const REENTRY_STATUS_DIRNAME = ".versioning";
|
|
3
3
|
export declare const REENTRY_STATUS_JSON_FILENAME = "reentry.status.json";
|
|
4
4
|
export declare const REENTRY_STATUS_MD_FILENAME = "REENTRY.md";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ROADMAP_MD_FILENAME = exports.REENTRY_STATUS_MD_FILENAME = exports.REENTRY_STATUS_JSON_FILENAME = exports.REENTRY_STATUS_DIRNAME = exports.REENTRY_EXTENSION_NAME = void 0;
|
|
4
|
-
exports.REENTRY_EXTENSION_NAME = 'reentry-status
|
|
4
|
+
exports.REENTRY_EXTENSION_NAME = 'reentry-status';
|
|
5
5
|
exports.REENTRY_STATUS_DIRNAME = '.versioning';
|
|
6
6
|
exports.REENTRY_STATUS_JSON_FILENAME = 'reentry.status.json';
|
|
7
7
|
exports.REENTRY_STATUS_MD_FILENAME = 'REENTRY.md';
|
|
@@ -36,19 +36,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
const commander_1 = require("commander");
|
|
37
37
|
const fs = __importStar(require("fs-extra"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
-
const config_manager_1 = require("./
|
|
40
|
-
const constants_1 = require("./
|
|
41
|
-
const file_manager_1 = require("./
|
|
42
|
-
const dirty_detection_1 = require("./
|
|
43
|
-
const github_rest_client_1 = require("./
|
|
44
|
-
const github_sync_adapter_1 = require("./
|
|
45
|
-
const obsidian_cli_client_1 = require("./
|
|
46
|
-
const obsidian_sync_adapter_1 = require("./
|
|
47
|
-
const roadmap_parser_1 = require("./
|
|
48
|
-
const roadmap_renderer_1 = require("./
|
|
49
|
-
const status_renderer_1 = require("./
|
|
50
|
-
const reentry_status_manager_1 = require("./reentry-status
|
|
51
|
-
const git_context_1 = require("./
|
|
39
|
+
const config_manager_1 = require("./config-manager");
|
|
40
|
+
const constants_1 = require("./constants");
|
|
41
|
+
const file_manager_1 = require("./file-manager");
|
|
42
|
+
const dirty_detection_1 = require("./dirty-detection");
|
|
43
|
+
const github_rest_client_1 = require("./github-rest-client");
|
|
44
|
+
const github_sync_adapter_1 = require("./github-sync-adapter");
|
|
45
|
+
const obsidian_cli_client_1 = require("./obsidian-cli-client");
|
|
46
|
+
const obsidian_sync_adapter_1 = require("./obsidian-sync-adapter");
|
|
47
|
+
const roadmap_parser_1 = require("./roadmap-parser");
|
|
48
|
+
const roadmap_renderer_1 = require("./roadmap-renderer");
|
|
49
|
+
const status_renderer_1 = require("./status-renderer");
|
|
50
|
+
const reentry_status_manager_1 = require("./reentry-status-manager");
|
|
51
|
+
const git_context_1 = require("./git-context");
|
|
52
52
|
const extension = {
|
|
53
53
|
name: constants_1.REENTRY_EXTENSION_NAME,
|
|
54
54
|
description: 'Maintains canonical re-entry status and synchronizes to files, GitHub Issues, and Obsidian notes',
|
|
@@ -672,4 +672,4 @@ const extension = {
|
|
|
672
672
|
}
|
|
673
673
|
};
|
|
674
674
|
exports.default = extension;
|
|
675
|
-
//# sourceMappingURL=
|
|
675
|
+
//# sourceMappingURL=extension.js.map
|
|
@@ -13,6 +13,9 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
13
13
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
16
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
20
|
__exportStar(require("./constants"), exports);
|
|
18
21
|
__exportStar(require("./config-manager"), exports);
|
|
@@ -28,4 +31,6 @@ __exportStar(require("./roadmap-renderer"), exports);
|
|
|
28
31
|
__exportStar(require("./git-context"), exports);
|
|
29
32
|
__exportStar(require("./reentry-status-manager"), exports);
|
|
30
33
|
__exportStar(require("./status-renderer"), exports);
|
|
34
|
+
const extension_1 = __importDefault(require("./extension"));
|
|
35
|
+
exports.default = extension_1.default;
|
|
31
36
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { VersioningExtension } from '../../extensions';
|
|
2
|
+
export interface SecretCheckResult {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
content: string;
|
|
6
|
+
pattern: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SecretsConfig {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
patterns?: string[];
|
|
11
|
+
allowlist?: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function checkContentForSecrets(content: string, patterns: RegExp[], allowlist: string[], filename: string): SecretCheckResult[];
|
|
14
|
+
declare const extension: VersioningExtension;
|
|
15
|
+
export default extension;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkContentForSecrets = checkContentForSecrets;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
// Definition of patterns to search for
|
|
41
|
+
// Each pattern is a regex
|
|
42
|
+
const DEFAULT_PATTERNS = [
|
|
43
|
+
// PEM / JSON private keys
|
|
44
|
+
new RegExp('-----BEGIN ' + 'PRIVATE KEY-----'),
|
|
45
|
+
new RegExp('-----BEGIN ' + 'RSA PRIVATE KEY-----'),
|
|
46
|
+
/"private_key":\s*"/,
|
|
47
|
+
/"private_key_id":\s*"/,
|
|
48
|
+
// Cloud / API tokens
|
|
49
|
+
/AIza[0-9A-Za-z\-_]{35}/,
|
|
50
|
+
/ghp_[0-9A-Za-z]{36}/,
|
|
51
|
+
/npm_[0-9A-Za-z]{36}/,
|
|
52
|
+
// Ethereum / EVM hex private keys
|
|
53
|
+
/PRIVATE_DEPLOYER=[0-9a-fA-F]{40,}/,
|
|
54
|
+
/DEPLOYER_PRIVATE_KEY=0x[0-9a-fA-F]{40,}/,
|
|
55
|
+
/PRIVATE_KEY=[0-9a-fA-F]{64}/,
|
|
56
|
+
/_KEY=0x[0-9a-fA-F]{64}/,
|
|
57
|
+
/cast wallet address 0x[0-9a-fA-F]{64}/,
|
|
58
|
+
// Seed phrases
|
|
59
|
+
/MNEMONIC=.{20,}/
|
|
60
|
+
];
|
|
61
|
+
// Allowlist patterns that are safe
|
|
62
|
+
const DEFAULT_ALLOWLIST = [
|
|
63
|
+
"POOL_MANAGER_ADDRESS=",
|
|
64
|
+
"HOOK_OWNER=",
|
|
65
|
+
"NEXT_PUBLIC_",
|
|
66
|
+
"ETHERSCAN_API_KEY=YOUR_",
|
|
67
|
+
"RPC_URL=",
|
|
68
|
+
"YOUR_DEPLOYER_PRIVATE_KEY",
|
|
69
|
+
"YOUR_LOCAL_PRIVATE_KEY",
|
|
70
|
+
"YOUR_TESTNET_PRIVATE_KEY",
|
|
71
|
+
"your_private_key_here"
|
|
72
|
+
];
|
|
73
|
+
function checkContentForSecrets(content, patterns, allowlist, filename) {
|
|
74
|
+
const results = [];
|
|
75
|
+
const lines = content.split('\n');
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
const line = lines[i];
|
|
78
|
+
for (const pattern of patterns) {
|
|
79
|
+
if (pattern.test(line)) {
|
|
80
|
+
// Check allowance
|
|
81
|
+
let isAllowed = false;
|
|
82
|
+
for (const allow of allowlist) {
|
|
83
|
+
if (line.includes(allow)) {
|
|
84
|
+
isAllowed = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!isAllowed) {
|
|
89
|
+
results.push({
|
|
90
|
+
file: filename,
|
|
91
|
+
line: i + 1,
|
|
92
|
+
content: line.trim(),
|
|
93
|
+
pattern: pattern.toString()
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
const extension = {
|
|
102
|
+
name: 'secrets-check',
|
|
103
|
+
description: 'Checks for hardcoded secrets and private keys in staged files',
|
|
104
|
+
version: '1.1.0',
|
|
105
|
+
register: async (program, config) => {
|
|
106
|
+
// Try to get config from extensionConfig first, fallback to top-level secrets for backcompat
|
|
107
|
+
const extensionConfig = config.extensionConfig?.['secrets-check'];
|
|
108
|
+
const secretsConfig = extensionConfig || config.secrets || {};
|
|
109
|
+
if (secretsConfig.enabled === false) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Merge patterns
|
|
113
|
+
const patterns = [...DEFAULT_PATTERNS];
|
|
114
|
+
if (secretsConfig.patterns) {
|
|
115
|
+
for (const p of secretsConfig.patterns) {
|
|
116
|
+
try {
|
|
117
|
+
patterns.push(new RegExp(p));
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
console.warn(`⚠️ Invalid regex pattern in config: ${p}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Merge allowlist
|
|
125
|
+
const allowlist = [...DEFAULT_ALLOWLIST];
|
|
126
|
+
if (secretsConfig.allowlist) {
|
|
127
|
+
allowlist.push(...secretsConfig.allowlist);
|
|
128
|
+
}
|
|
129
|
+
const secretsCmd = program
|
|
130
|
+
.command('check-secrets')
|
|
131
|
+
.description('Scan staged files for potential secrets')
|
|
132
|
+
.action(async () => {
|
|
133
|
+
await runSecretsCheck();
|
|
134
|
+
});
|
|
135
|
+
secretsCmd
|
|
136
|
+
.command('husky')
|
|
137
|
+
.description('Add secrets check to Husky pre-commit hook')
|
|
138
|
+
.option('--remove', 'Remove secrets check from Husky hook', false)
|
|
139
|
+
.action(async (options) => {
|
|
140
|
+
const rootDir = process.cwd();
|
|
141
|
+
const huskyDir = path.join(rootDir, '.husky');
|
|
142
|
+
const hookPath = path.join(huskyDir, 'pre-commit');
|
|
143
|
+
const MARKER_START = '# === secrets-check: start ===';
|
|
144
|
+
const MARKER_END = '# === secrets-check: end ===';
|
|
145
|
+
if (options.remove) {
|
|
146
|
+
if (!fs.existsSync(hookPath)) {
|
|
147
|
+
console.log('ℹ️ Hook pre-commit does not exist.');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const content = fs.readFileSync(hookPath, 'utf8');
|
|
151
|
+
const startIdx = content.indexOf(MARKER_START);
|
|
152
|
+
const endIdx = content.indexOf(MARKER_END);
|
|
153
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
154
|
+
console.log('ℹ️ No secrets-check block found in pre-commit.');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const before = content.slice(0, startIdx).trimEnd();
|
|
158
|
+
const after = content.slice(endIdx + MARKER_END.length).trimStart();
|
|
159
|
+
const updated = [before, '', after].join('\n').replace(/\n{3,}/g, '\n\n').trim() + '\n';
|
|
160
|
+
fs.writeFileSync(hookPath, updated, 'utf8');
|
|
161
|
+
console.log('✅ Removed secrets check from .husky/pre-commit');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// Add cleanup block to hook
|
|
165
|
+
if (!fs.existsSync(huskyDir)) {
|
|
166
|
+
fs.mkdirSync(huskyDir, { recursive: true });
|
|
167
|
+
}
|
|
168
|
+
const block = [
|
|
169
|
+
'',
|
|
170
|
+
MARKER_START,
|
|
171
|
+
'echo "🔒 Running secrets check…"',
|
|
172
|
+
'npx versioning check-secrets 2>/dev/null',
|
|
173
|
+
MARKER_END,
|
|
174
|
+
''
|
|
175
|
+
].join('\n');
|
|
176
|
+
if (fs.existsSync(hookPath)) {
|
|
177
|
+
const existing = fs.readFileSync(hookPath, 'utf8');
|
|
178
|
+
if (existing.includes(MARKER_START)) {
|
|
179
|
+
console.log('ℹ️ Secrets check is already integrated in .husky/pre-commit.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const updated = existing.trimEnd() + '\n' + block;
|
|
183
|
+
fs.writeFileSync(hookPath, updated, 'utf8');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const content = [
|
|
187
|
+
'#!/bin/sh',
|
|
188
|
+
'. "$(dirname "$0")/_/husky.sh"',
|
|
189
|
+
'',
|
|
190
|
+
block
|
|
191
|
+
].join('\n');
|
|
192
|
+
fs.writeFileSync(hookPath, content, { mode: 0o755 });
|
|
193
|
+
}
|
|
194
|
+
console.log('✅ Secrets check integrated into .husky/pre-commit');
|
|
195
|
+
});
|
|
196
|
+
async function runSecretsCheck() {
|
|
197
|
+
try {
|
|
198
|
+
console.log("🔒 Scanning for secrets...");
|
|
199
|
+
let failed = false;
|
|
200
|
+
// Get list of staged files
|
|
201
|
+
let output = '';
|
|
202
|
+
try {
|
|
203
|
+
output = (0, child_process_1.execSync)('git diff --cached --name-only --diff-filter=ACMR', { encoding: 'utf-8' });
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
// If not in a git repo or no staged files, just return
|
|
207
|
+
console.log("⚠️ Not a git repository or git error.");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const files = output.split('\n').filter(f => f.trim() !== '');
|
|
211
|
+
if (files.length === 0) {
|
|
212
|
+
console.log("✅ No staged files to check.");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const relativePath of files) {
|
|
216
|
+
const filePath = path.resolve(process.cwd(), relativePath);
|
|
217
|
+
// Skip check if file doesn't exist (deleted)
|
|
218
|
+
if (!fs.existsSync(filePath)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// Skip lock files
|
|
222
|
+
if (relativePath.includes('lock.yaml') || relativePath.includes('lock.json')) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
// Read file content
|
|
226
|
+
let content = '';
|
|
227
|
+
try {
|
|
228
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
console.warn(`⚠️ Could not read file ${relativePath}:`, e);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const findings = checkContentForSecrets(content, patterns, allowlist, relativePath);
|
|
235
|
+
if (findings.length > 0) {
|
|
236
|
+
failed = true;
|
|
237
|
+
for (const finding of findings) {
|
|
238
|
+
console.log(`❌ POTENTIAL SECRET FOUND in ${finding.file}:${finding.line}`);
|
|
239
|
+
console.log(` ${finding.content}`);
|
|
240
|
+
console.log(` (matched pattern: ${finding.pattern})`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (failed) {
|
|
245
|
+
console.log("----------------------------------------------------");
|
|
246
|
+
console.log("⛔ COMMIT REJECTED");
|
|
247
|
+
console.log("Sensitive data was found in the staged files.");
|
|
248
|
+
console.log("Please remove the secrets before committing.");
|
|
249
|
+
console.log("----------------------------------------------------");
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
console.log("✅ No secrets found.");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.error('❌ Check failed:', error instanceof Error ? error.message : String(error));
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
exports.default = extension;
|
|
264
|
+
//# sourceMappingURL=index.js.map
|
package/dist/extensions.js
CHANGED
|
@@ -81,24 +81,36 @@ async function loadExtensions(program) {
|
|
|
81
81
|
if (await fs.pathExists(extensionsDir)) {
|
|
82
82
|
const extensionFiles = await fs.readdir(extensionsDir);
|
|
83
83
|
for (const file of extensionFiles) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Register hooks if available
|
|
92
|
-
if (extension.hooks) {
|
|
93
|
-
context.hooks.push(extension.hooks);
|
|
94
|
-
}
|
|
95
|
-
console.log(`✅ Loaded extension: ${extension.name}@${extension.version}`);
|
|
96
|
-
}
|
|
84
|
+
let extensionPath = path.join(extensionsDir, file);
|
|
85
|
+
const stats = await fs.stat(extensionPath);
|
|
86
|
+
if (stats.isDirectory()) {
|
|
87
|
+
// Check for index.js in directory
|
|
88
|
+
const indexPath = path.join(extensionPath, 'index.js');
|
|
89
|
+
if (await fs.pathExists(indexPath)) {
|
|
90
|
+
extensionPath = indexPath;
|
|
97
91
|
}
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
else {
|
|
93
|
+
continue;
|
|
100
94
|
}
|
|
101
95
|
}
|
|
96
|
+
else if (!file.endsWith('.js')) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const extensionModule = require(extensionPath);
|
|
101
|
+
const extension = extensionModule.default || extensionModule;
|
|
102
|
+
if (extension && typeof extension.register === 'function') {
|
|
103
|
+
await extension.register(program, config);
|
|
104
|
+
// Register hooks if available
|
|
105
|
+
if (extension.hooks) {
|
|
106
|
+
context.hooks.push(extension.hooks);
|
|
107
|
+
}
|
|
108
|
+
console.log(`✅ Loaded extension: ${extension.name}@${extension.version}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.warn(`⚠️ Failed to load extension ${file}:`, error instanceof Error ? error.message : String(error));
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
// Note: External extensions from config.extensions are not yet implemented
|
package/dist/versioning.d.ts
CHANGED
package/package.json
CHANGED