@edcalderon/versioning 1.2.0 → 1.3.1

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.
Files changed (31) hide show
  1. package/README.md +121 -14
  2. package/dist/cli.js +76 -4
  3. package/dist/extensions/{cleanup-repo-extension.d.ts → cleanup-repo/index.d.ts} +2 -2
  4. package/dist/extensions/{cleanup-repo-extension.js → cleanup-repo/index.js} +40 -32
  5. package/dist/extensions/lifecycle-hooks/index.d.ts +4 -0
  6. package/dist/extensions/{lifecycle-hooks.js → lifecycle-hooks/index.js} +1 -1
  7. package/dist/extensions/npm-publish/index.d.ts +4 -0
  8. package/dist/extensions/{npm-publish.js → npm-publish/index.js} +1 -1
  9. package/dist/extensions/reentry-status/config-manager.js +3 -1
  10. package/dist/extensions/reentry-status/constants.d.ts +1 -1
  11. package/dist/extensions/reentry-status/constants.js +1 -1
  12. package/dist/extensions/reentry-status/extension.d.ts +4 -0
  13. package/dist/extensions/{reentry-status-extension.js → reentry-status/extension.js} +14 -14
  14. package/dist/extensions/reentry-status/index.d.ts +2 -0
  15. package/dist/extensions/reentry-status/index.js +5 -0
  16. package/dist/extensions/sample-extension/index.d.ts +4 -0
  17. package/dist/extensions/{sample-extension.js → sample-extension/index.js} +1 -1
  18. package/dist/extensions/secrets-check/index.d.ts +16 -0
  19. package/dist/extensions/secrets-check/index.js +264 -0
  20. package/dist/extensions.js +27 -15
  21. package/dist/release.d.ts +16 -12
  22. package/dist/release.js +35 -1
  23. package/dist/versioning.d.ts +49 -0
  24. package/dist/versioning.js +313 -2
  25. package/examples/versioning.config.branch-aware.json +51 -0
  26. package/package.json +2 -2
  27. package/versioning.config.json +44 -1
  28. package/dist/extensions/lifecycle-hooks.d.ts +0 -4
  29. package/dist/extensions/npm-publish.d.ts +0 -4
  30. package/dist/extensions/reentry-status-extension.d.ts +0 -4
  31. package/dist/extensions/sample-extension.d.ts +0 -4
@@ -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
@@ -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
- if (file.endsWith('.js')) {
85
- try {
86
- const extensionPath = path.join(extensionsDir, file);
87
- const extensionModule = require(extensionPath);
88
- const extension = extensionModule.default || extensionModule;
89
- if (extension && typeof extension.register === 'function') {
90
- await extension.register(program, config);
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
- catch (error) {
99
- console.warn(`⚠️ Failed to load extension ${file}:`, error instanceof Error ? error.message : String(error));
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/release.d.ts CHANGED
@@ -17,17 +17,21 @@ export declare class ReleaseManager {
17
17
  skipSync?: boolean;
18
18
  }): Promise<void>;
19
19
  private publishPackages;
20
- patchRelease(options?: {
21
- packages?: string[];
22
- message?: string;
23
- }): Promise<string>;
24
- minorRelease(options?: {
25
- packages?: string[];
26
- message?: string;
27
- }): Promise<string>;
28
- majorRelease(options?: {
29
- packages?: string[];
30
- message?: string;
31
- }): Promise<string>;
20
+ patchRelease(options?: ReleaseOptions): Promise<string>;
21
+ minorRelease(options?: ReleaseOptions): Promise<string>;
22
+ majorRelease(options?: ReleaseOptions): Promise<string>;
23
+ private shouldUseBranchAwareFlow;
24
+ private releaseBranchAware;
25
+ }
26
+ interface ReleaseOptions {
27
+ message?: string;
28
+ packages?: string[];
29
+ skipSync?: boolean;
30
+ branchAware?: boolean;
31
+ forceBranchAware?: boolean;
32
+ targetBranch?: string;
33
+ format?: string;
34
+ build?: number;
32
35
  }
36
+ export {};
33
37
  //# sourceMappingURL=release.d.ts.map
package/dist/release.js CHANGED
@@ -81,21 +81,55 @@ class ReleaseManager {
81
81
  console.log('Publishing packages:', packages || 'all');
82
82
  }
83
83
  async patchRelease(options = {}) {
84
- const currentVersion = await this.config.versionManager.getCurrentVersion();
84
+ if (this.shouldUseBranchAwareFlow(options)) {
85
+ return await this.releaseBranchAware('patch', options);
86
+ }
85
87
  const newVersion = await this.config.versionManager.bumpVersion('patch');
86
88
  await this.release(newVersion, options);
87
89
  return newVersion;
88
90
  }
89
91
  async minorRelease(options = {}) {
92
+ if (this.shouldUseBranchAwareFlow(options)) {
93
+ return await this.releaseBranchAware('minor', options);
94
+ }
90
95
  const newVersion = await this.config.versionManager.bumpVersion('minor');
91
96
  await this.release(newVersion, options);
92
97
  return newVersion;
93
98
  }
94
99
  async majorRelease(options = {}) {
100
+ if (this.shouldUseBranchAwareFlow(options)) {
101
+ return await this.releaseBranchAware('major', options);
102
+ }
95
103
  const newVersion = await this.config.versionManager.bumpVersion('major');
96
104
  await this.release(newVersion, options);
97
105
  return newVersion;
98
106
  }
107
+ shouldUseBranchAwareFlow(options) {
108
+ return options.branchAware === true
109
+ || options.forceBranchAware === true
110
+ || typeof options.targetBranch === 'string'
111
+ || typeof options.format === 'string'
112
+ || typeof options.build === 'number';
113
+ }
114
+ async releaseBranchAware(releaseType, options) {
115
+ const result = await this.config.versionManager.bumpVersionBranchAware(releaseType, {
116
+ targetBranch: options.targetBranch,
117
+ forceBranchAware: options.forceBranchAware,
118
+ format: options.format,
119
+ build: options.build
120
+ });
121
+ await this.config.changelogManager.generate();
122
+ if (this.config.createCommit) {
123
+ await this.config.versionManager.commitChanges(result.version);
124
+ }
125
+ if (this.config.createTag) {
126
+ await this.config.versionManager.createGitTagWithFormat(result.version, result.tagFormat, options.message);
127
+ }
128
+ if (this.config.publish) {
129
+ await this.publishPackages(options.packages);
130
+ }
131
+ return result.version;
132
+ }
99
133
  }
100
134
  exports.ReleaseManager = ReleaseManager;
101
135
  //# sourceMappingURL=release.js.map
@@ -1,9 +1,37 @@
1
1
  import * as semver from 'semver';
2
+ export interface BranchRuleConfig {
3
+ versionFormat?: 'semantic' | 'dev' | 'feature' | 'hotfix' | string;
4
+ tagFormat?: string;
5
+ syncFiles?: string[];
6
+ environment?: string;
7
+ bumpStrategy?: 'semantic' | 'dev-build' | 'feature-branch' | 'hotfix' | string;
8
+ }
9
+ export interface BranchAwarenessConfig {
10
+ enabled?: boolean;
11
+ defaultBranch?: string;
12
+ branches?: Record<string, BranchRuleConfig>;
13
+ }
14
+ export interface BranchAwareBumpOptions {
15
+ targetBranch?: string;
16
+ forceBranchAware?: boolean;
17
+ format?: string;
18
+ build?: number;
19
+ }
20
+ export interface BranchAwareBumpResult {
21
+ version: string;
22
+ branch: string;
23
+ matchPattern: string;
24
+ versionFormat: string;
25
+ tagFormat: string;
26
+ syncFiles: string[];
27
+ }
2
28
  export interface VersionConfig {
3
29
  rootPackageJson: string;
4
30
  packages: string[];
5
31
  changelogFile?: string;
6
32
  conventionalCommits?: boolean;
33
+ extensionConfig?: Record<string, any>;
34
+ branchAwareness?: BranchAwarenessConfig;
7
35
  }
8
36
  export declare class VersionManager {
9
37
  private config;
@@ -11,9 +39,30 @@ export declare class VersionManager {
11
39
  constructor(config: VersionConfig);
12
40
  getCurrentVersion(): Promise<string>;
13
41
  bumpVersion(releaseType: semver.ReleaseType, preRelease?: string): Promise<string>;
42
+ bumpVersionBranchAware(releaseType: semver.ReleaseType, options?: BranchAwareBumpOptions): Promise<BranchAwareBumpResult>;
14
43
  updateVersion(newVersion: string): Promise<void>;
15
44
  private updatePackageJson;
16
45
  createGitTag(version: string, message?: string): Promise<void>;
46
+ createGitTagWithFormat(version: string, tagFormat?: string, message?: string): Promise<string>;
17
47
  commitChanges(version: string): Promise<void>;
48
+ private getCurrentBranch;
49
+ private resolveBranchConfig;
50
+ private getBranchAwarenessConfig;
51
+ private getDefaultBranchRules;
52
+ private normalizeBranchRule;
53
+ private getDefaultBumpStrategy;
54
+ private buildBranchAwareVersion;
55
+ private shouldBumpSemantic;
56
+ private coerceBaseVersion;
57
+ private incrementSemanticVersion;
58
+ private resolveBuildNumber;
59
+ private getDefaultSyncFiles;
60
+ private applyVersionFormat;
61
+ private extractBuildNumber;
62
+ private readVersionFromFile;
63
+ private updateVersionFile;
64
+ private matchesPattern;
65
+ private escapeRegex;
66
+ private renderTag;
18
67
  }
19
68
  //# sourceMappingURL=versioning.d.ts.map