@edcalderon/versioning 1.3.0 → 1.4.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 CHANGED
@@ -5,6 +5,7 @@ A comprehensive versioning and changelog management tool designed for monorepos
5
5
  ## Features
6
6
 
7
7
  - 🚀 Automated version bumping (patch, minor, major, prerelease)
8
+ - 🌿 Optional branch-aware versioning (`main`, `develop`, `feature/*`, `hotfix/*`)
8
9
  - 📝 Conventional commit-based changelog generation
9
10
  - 🔄 Version synchronization across monorepo packages
10
11
  - 🎯 Works with both monorepos and single repositories
@@ -407,7 +408,41 @@ For single repositories:
407
408
  "changelogFile": "CHANGELOG.md",
408
409
  "conventionalCommits": true,
409
410
  "syncDependencies": false,
410
- "ignorePackages": []
411
+ "ignorePackages": [],
412
+ "branchAwareness": {
413
+ "enabled": false,
414
+ "defaultBranch": "main",
415
+ "branches": {
416
+ "main": {
417
+ "versionFormat": "semantic",
418
+ "tagFormat": "v{version}",
419
+ "syncFiles": ["package.json", "version.production.json"],
420
+ "environment": "production",
421
+ "bumpStrategy": "semantic"
422
+ },
423
+ "develop": {
424
+ "versionFormat": "dev",
425
+ "tagFormat": "v{version}",
426
+ "syncFiles": ["version.development.json"],
427
+ "environment": "development",
428
+ "bumpStrategy": "dev-build"
429
+ },
430
+ "feature/*": {
431
+ "versionFormat": "feature",
432
+ "tagFormat": "v{version}",
433
+ "syncFiles": ["version.development.json"],
434
+ "environment": "development",
435
+ "bumpStrategy": "feature-branch"
436
+ },
437
+ "hotfix/*": {
438
+ "versionFormat": "hotfix",
439
+ "tagFormat": "v{version}",
440
+ "syncFiles": ["version.development.json"],
441
+ "environment": "development",
442
+ "bumpStrategy": "hotfix"
443
+ }
444
+ }
445
+ }
411
446
  }
412
447
  ```
413
448
 
@@ -431,6 +466,28 @@ For monorepos:
431
466
  - `conventionalCommits`: Whether to use conventional commits for changelog
432
467
  - `syncDependencies`: Whether to sync internal dependencies
433
468
  - `ignorePackages`: Array of package names to ignore during sync
469
+ - `branchAwareness`: Optional branch-aware release rules and file sync targets
470
+ - `branchAwareness.branches.<pattern>.versionFormat`: `semantic`, `dev`, `feature`, `hotfix`, or custom
471
+ - `branchAwareness.branches.<pattern>.syncFiles`: Only these files are updated when branch-aware mode is enabled
472
+
473
+ ### Branch-Aware Releases
474
+
475
+ Enable branch-aware mode per command:
476
+
477
+ ```bash
478
+ versioning patch --branch-aware
479
+ versioning patch --branch-aware --target-branch develop
480
+ versioning patch --branch-aware --format dev --build 396
481
+ versioning minor --branch-aware
482
+ versioning major --branch-aware
483
+ ```
484
+
485
+ Behavior summary:
486
+ - Exact branch names are checked first (e.g. `main`, `develop`)
487
+ - Wildcard patterns are checked next (e.g. `feature/*`, `hotfix/*`)
488
+ - If no match is found, the `defaultBranch` rule is used
489
+ - `--force-branch-aware` enables branch-aware behavior even when `branchAwareness.enabled` is `false`
490
+ - Full config template: `packages/versioning/examples/versioning.config.branch-aware.json`
434
491
 
435
492
  ## Commands
436
493
 
@@ -471,13 +528,25 @@ versioning release 1.2.3 --message "Custom release"
471
528
  versioning release 2.0.0-beta.1 --skip-sync
472
529
  ```
473
530
 
474
- **Options for release commands:**
531
+ **Options for `patch`, `minor`, and `major`:**
532
+ - `-p, --packages <packages>`: Comma-separated list of packages to sync
533
+ - `-m, --message <message>`: Release commit message
534
+ - `-c, --config <file>`: Config file path (default: versioning.config.json)
535
+ - `--branch-aware`: Enable branch-aware release behavior
536
+ - `--force-branch-aware`: Force branch-aware mode even if disabled in config
537
+ - `--target-branch <branch>`: Explicit branch to resolve branch rules
538
+ - `--format <format>`: Override the configured branch version format
539
+ - `--build <number>`: Override build number for non-semantic branch formats
540
+ - `--no-tag`: Do not create git tag
541
+ - `--no-commit`: Do not commit changes
542
+
543
+ **Options for `release <version>`:**
475
544
  - `-p, --packages <packages>`: Comma-separated list of packages to sync
476
545
  - `-m, --message <message>`: Release commit message
477
546
  - `-c, --config <file>`: Config file path (default: versioning.config.json)
478
547
  - `--no-tag`: Do not create git tag
479
548
  - `--no-commit`: Do not commit changes
480
- - `--skip-sync`: Skip version synchronization (for `release` command)
549
+ - `--skip-sync`: Skip version synchronization
481
550
 
482
551
  ### Other Commands
483
552
 
@@ -624,4 +693,4 @@ Tags should follow the format `v{major}.{minor}.{patch}` (e.g., `v1.0.0`, `v1.1.
624
693
  The `create-tag` script will:
625
694
  - Read the version from `package.json`
626
695
  - Create an annotated git tag
627
- - Push the tag to trigger the publish workflow
696
+ - Push the tag to trigger the publish workflow
package/dist/cli.js CHANGED
@@ -40,6 +40,7 @@ const versioning_1 = require("./versioning");
40
40
  const changelog_1 = require("./changelog");
41
41
  const sync_1 = require("./sync");
42
42
  const release_1 = require("./release");
43
+ const status_1 = require("./status");
43
44
  const extensions_1 = require("./extensions");
44
45
  // eslint-disable-next-line @typescript-eslint/no-var-requires
45
46
  const pkg = require('../package.json');
@@ -155,12 +156,46 @@ program
155
156
  process.exit(1);
156
157
  }
157
158
  });
159
+ program
160
+ .command('status')
161
+ .alias('info')
162
+ .description('Display version and sync status report')
163
+ .option('-c, --config <file>', 'config file path', 'versioning.config.json')
164
+ .option('--json', 'output as JSON')
165
+ .option('--dot', 'output as Graphviz DOT format')
166
+ .action(async (options) => {
167
+ try {
168
+ const config = await loadConfig(options.config);
169
+ const statusManager = new status_1.StatusManager(config);
170
+ if (options.json) {
171
+ const json = await statusManager.getJSON();
172
+ console.log(json);
173
+ }
174
+ else if (options.dot) {
175
+ const dot = await statusManager.getDOT();
176
+ console.log(dot);
177
+ }
178
+ else {
179
+ const report = await statusManager.formatConsole();
180
+ console.log(report);
181
+ }
182
+ }
183
+ catch (error) {
184
+ console.error('❌ Error:', error instanceof Error ? error.message : String(error));
185
+ process.exit(1);
186
+ }
187
+ });
158
188
  program
159
189
  .command('patch')
160
190
  .description('Create a patch release')
161
191
  .option('-p, --packages <packages>', 'Comma-separated list of packages to sync')
162
192
  .option('-m, --message <message>', 'Release commit message')
163
193
  .option('-c, --config <file>', 'Config file path', 'versioning.config.json')
194
+ .option('--branch-aware', 'Enable branch-aware versioning')
195
+ .option('--force-branch-aware', 'Force branch-aware mode even if disabled in config')
196
+ .option('--target-branch <branch>', 'Explicit branch to apply branch-aware rules')
197
+ .option('--format <format>', 'Override version format (semantic, dev, feature, hotfix)')
198
+ .option('--build <number>', 'Override build number for non-semantic formats', parseBuildOption)
164
199
  .option('--no-tag', 'Do not create git tag')
165
200
  .option('--no-commit', 'Do not commit changes')
166
201
  .action(async (options) => {
@@ -179,7 +214,12 @@ program
179
214
  const packages = options.packages ? options.packages.split(',').map((p) => p.trim()) : undefined;
180
215
  const newVersion = await releaseManager.patchRelease({
181
216
  packages,
182
- message: options.message
217
+ message: options.message,
218
+ branchAware: options.branchAware,
219
+ forceBranchAware: options.forceBranchAware,
220
+ targetBranch: options.targetBranch,
221
+ format: options.format,
222
+ build: options.build
183
223
  });
184
224
  console.log(`✅ Patch release v${newVersion} completed`);
185
225
  }
@@ -194,6 +234,11 @@ program
194
234
  .option('-p, --packages <packages>', 'Comma-separated list of packages to sync')
195
235
  .option('-m, --message <message>', 'Release commit message')
196
236
  .option('-c, --config <file>', 'Config file path', 'versioning.config.json')
237
+ .option('--branch-aware', 'Enable branch-aware versioning')
238
+ .option('--force-branch-aware', 'Force branch-aware mode even if disabled in config')
239
+ .option('--target-branch <branch>', 'Explicit branch to apply branch-aware rules')
240
+ .option('--format <format>', 'Override version format (semantic, dev, feature, hotfix)')
241
+ .option('--build <number>', 'Override build number for non-semantic formats', parseBuildOption)
197
242
  .option('--no-tag', 'Do not create git tag')
198
243
  .option('--no-commit', 'Do not commit changes')
199
244
  .action(async (options) => {
@@ -212,7 +257,12 @@ program
212
257
  const packages = options.packages ? options.packages.split(',').map((p) => p.trim()) : undefined;
213
258
  const newVersion = await releaseManager.minorRelease({
214
259
  packages,
215
- message: options.message
260
+ message: options.message,
261
+ branchAware: options.branchAware,
262
+ forceBranchAware: options.forceBranchAware,
263
+ targetBranch: options.targetBranch,
264
+ format: options.format,
265
+ build: options.build
216
266
  });
217
267
  console.log(`✅ Minor release v${newVersion} completed`);
218
268
  }
@@ -225,7 +275,13 @@ program
225
275
  .command('major')
226
276
  .description('Create a major release')
227
277
  .option('-p, --packages <packages>', 'Comma-separated list of packages to sync')
278
+ .option('-m, --message <message>', 'Release commit message')
228
279
  .option('-c, --config <file>', 'Config file path', 'versioning.config.json')
280
+ .option('--branch-aware', 'Enable branch-aware versioning')
281
+ .option('--force-branch-aware', 'Force branch-aware mode even if disabled in config')
282
+ .option('--target-branch <branch>', 'Explicit branch to apply branch-aware rules')
283
+ .option('--format <format>', 'Override version format (semantic, dev, feature, hotfix)')
284
+ .option('--build <number>', 'Override build number for non-semantic formats', parseBuildOption)
229
285
  .option('--no-tag', 'Do not create git tag')
230
286
  .option('--no-commit', 'Do not commit changes')
231
287
  .action(async (options) => {
@@ -244,7 +300,12 @@ program
244
300
  const packages = options.packages ? options.packages.split(',').map((p) => p.trim()) : undefined;
245
301
  const newVersion = await releaseManager.majorRelease({
246
302
  packages,
247
- message: options.message
303
+ message: options.message,
304
+ branchAware: options.branchAware,
305
+ forceBranchAware: options.forceBranchAware,
306
+ targetBranch: options.targetBranch,
307
+ format: options.format,
308
+ build: options.build
248
309
  });
249
310
  console.log(`✅ Major release v${newVersion} completed`);
250
311
  }
@@ -306,7 +367,41 @@ program
306
367
  conventionalCommits: true,
307
368
  syncDependencies: false,
308
369
  ignorePackages: [],
309
- extensions: [] // Add extensions array to config
370
+ extensions: [], // Add extensions array to config
371
+ branchAwareness: {
372
+ enabled: false,
373
+ defaultBranch: 'main',
374
+ branches: {
375
+ main: {
376
+ versionFormat: 'semantic',
377
+ tagFormat: 'v{version}',
378
+ syncFiles: ['package.json'],
379
+ environment: 'production',
380
+ bumpStrategy: 'semantic'
381
+ },
382
+ develop: {
383
+ versionFormat: 'dev',
384
+ tagFormat: 'v{version}',
385
+ syncFiles: ['version.development.json'],
386
+ environment: 'development',
387
+ bumpStrategy: 'dev-build'
388
+ },
389
+ 'feature/*': {
390
+ versionFormat: 'feature',
391
+ tagFormat: 'v{version}',
392
+ syncFiles: ['version.development.json'],
393
+ environment: 'development',
394
+ bumpStrategy: 'feature-branch'
395
+ },
396
+ 'hotfix/*': {
397
+ versionFormat: 'hotfix',
398
+ tagFormat: 'v{version}',
399
+ syncFiles: ['version.development.json'],
400
+ environment: 'development',
401
+ bumpStrategy: 'hotfix'
402
+ }
403
+ }
404
+ }
310
405
  };
311
406
  await fs.writeJson(configPath, defaultConfig, { spaces: 2 });
312
407
  console.log('✅ Initialized versioning config at versioning.config.json');
@@ -322,6 +417,13 @@ async function loadConfig(configPath) {
322
417
  }
323
418
  return await fs.readJson(configPath);
324
419
  }
420
+ function parseBuildOption(value) {
421
+ const parsed = Number.parseInt(value, 10);
422
+ if (!Number.isInteger(parsed) || parsed < 0) {
423
+ throw new Error(`Invalid build number "${value}". Use a non-negative integer.`);
424
+ }
425
+ return parsed;
426
+ }
325
427
  async function main() {
326
428
  try {
327
429
  // Load and register extensions
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from './versioning';
2
2
  export * from './changelog';
3
3
  export * from './sync';
4
4
  export * from './release';
5
+ export * from './status';
5
6
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -18,4 +18,5 @@ __exportStar(require("./versioning"), exports);
18
18
  __exportStar(require("./changelog"), exports);
19
19
  __exportStar(require("./sync"), exports);
20
20
  __exportStar(require("./release"), exports);
21
+ __exportStar(require("./status"), exports);
21
22
  //# sourceMappingURL=index.js.map
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
@@ -0,0 +1,62 @@
1
+ export interface AppStatus {
2
+ name: string;
3
+ version: string;
4
+ status: 'sync' | 'stale' | 'newer';
5
+ target: string;
6
+ }
7
+ export interface PackageStatus {
8
+ name: string;
9
+ version: string;
10
+ status: 'sync' | 'stale' | 'newer';
11
+ }
12
+ export interface StatusReport {
13
+ timestamp: string;
14
+ cli: {
15
+ installed: string;
16
+ latest: string;
17
+ updateAvailable: boolean;
18
+ };
19
+ sync: {
20
+ currentTarget: string;
21
+ isSynced: boolean;
22
+ syncedPackages: number;
23
+ totalPackages: number;
24
+ lastUpdated: string;
25
+ };
26
+ apps: AppStatus[];
27
+ packages: PackageStatus[];
28
+ dependencies: {
29
+ circularCount: number;
30
+ staleCount: number;
31
+ healthStatus: 'healthy' | 'warning' | 'critical';
32
+ };
33
+ environment: {
34
+ nodeVersion: string;
35
+ pnpmVersion: string;
36
+ gitAvailable: boolean;
37
+ configValid: boolean;
38
+ };
39
+ lastRelease: {
40
+ version: string;
41
+ date: string;
42
+ commitsSince: number;
43
+ };
44
+ overallStatus: 'healthy' | 'warning' | 'critical';
45
+ }
46
+ export declare class StatusManager {
47
+ private config;
48
+ private rootDir;
49
+ constructor(config: any, rootDir?: string);
50
+ getStatus(): Promise<StatusReport>;
51
+ private getAppStatuses;
52
+ private getPackageStatuses;
53
+ private getEnvironmentInfo;
54
+ private getPnpmVersion;
55
+ private isGitAvailable;
56
+ private getLastReleaseInfo;
57
+ formatTable(header: string[], rows: string[][]): string;
58
+ formatConsole(): Promise<string>;
59
+ getJSON(): Promise<string>;
60
+ getDOT(): Promise<string>;
61
+ }
62
+ //# sourceMappingURL=status.d.ts.map
package/dist/status.js ADDED
@@ -0,0 +1,310 @@
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.StatusManager = void 0;
37
+ const fs = __importStar(require("fs-extra"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const versioning_1 = require("./versioning");
41
+ const sync_1 = require("./sync");
42
+ class StatusManager {
43
+ constructor(config, rootDir = process.cwd()) {
44
+ this.config = config;
45
+ this.rootDir = rootDir;
46
+ }
47
+ async getStatus() {
48
+ const timestamp = new Date().toISOString();
49
+ const versionManager = new versioning_1.VersionManager(this.config);
50
+ const syncManager = new sync_1.SyncManager(this.config);
51
+ // Get CLI version
52
+ const pkgPath = path.join(this.rootDir, 'packages/versioning/package.json');
53
+ const pkgJson = await fs.readJson(pkgPath).catch(() => ({ version: '0.0.0' }));
54
+ const cliVersion = pkgJson.version || '0.0.0';
55
+ // Get root package version
56
+ const rootPkgPath = path.join(this.rootDir, 'package.json');
57
+ const rootPkg = await fs.readJson(rootPkgPath).catch(() => ({ version: '0.0.0' }));
58
+ const targetVersion = rootPkg.version || this.config.targetVersion || '0.0.0';
59
+ // Get sync status
60
+ const syncValidation = await syncManager.validateSync();
61
+ const isSynced = syncValidation.valid;
62
+ // Get app statuses
63
+ const apps = await this.getAppStatuses(targetVersion);
64
+ // Get package statuses
65
+ const packages = await this.getPackageStatuses(targetVersion);
66
+ // Get environment info
67
+ const environment = this.getEnvironmentInfo();
68
+ // Get last release info
69
+ const lastRelease = this.getLastReleaseInfo();
70
+ // Calculate overall health
71
+ const staleCount = packages.filter(p => p.status === 'stale').length + apps.filter(a => a.status === 'stale').length;
72
+ const healthStatus = staleCount === 0 ? 'healthy' : staleCount > 2 ? 'critical' : 'warning';
73
+ const overallStatus = isSynced && staleCount === 0 ? 'healthy' : 'warning';
74
+ return {
75
+ timestamp,
76
+ cli: {
77
+ installed: cliVersion,
78
+ latest: cliVersion, // Would fetch from npm in real scenario
79
+ updateAvailable: false,
80
+ },
81
+ sync: {
82
+ currentTarget: targetVersion,
83
+ isSynced,
84
+ syncedPackages: isSynced ? packages.length : packages.filter(p => p.status === 'sync').length,
85
+ totalPackages: packages.length,
86
+ lastUpdated: timestamp,
87
+ },
88
+ apps,
89
+ packages,
90
+ dependencies: {
91
+ circularCount: 0,
92
+ staleCount,
93
+ healthStatus,
94
+ },
95
+ environment,
96
+ lastRelease,
97
+ overallStatus,
98
+ };
99
+ }
100
+ async getAppStatuses(targetVersion) {
101
+ const appsDir = path.join(this.rootDir, 'apps');
102
+ if (!await fs.pathExists(appsDir))
103
+ return [];
104
+ const appDirs = await fs.readdir(appsDir);
105
+ const statuses = [];
106
+ for (const appName of appDirs) {
107
+ const pkgPath = path.join(appsDir, appName, 'package.json');
108
+ if (await fs.pathExists(pkgPath)) {
109
+ const pkg = await fs.readJson(pkgPath);
110
+ const version = pkg.version || '0.0.0';
111
+ const status = version === targetVersion ? 'sync' : version > targetVersion ? 'newer' : 'stale';
112
+ statuses.push({ name: appName, version, status, target: targetVersion });
113
+ }
114
+ }
115
+ return statuses;
116
+ }
117
+ async getPackageStatuses(targetVersion) {
118
+ const packagesDir = path.join(this.rootDir, 'packages');
119
+ if (!await fs.pathExists(packagesDir))
120
+ return [];
121
+ const pkgDirs = await fs.readdir(packagesDir);
122
+ const statuses = [];
123
+ for (const pkgName of pkgDirs) {
124
+ const pkgPath = path.join(packagesDir, pkgName, 'package.json');
125
+ if (await fs.pathExists(pkgPath)) {
126
+ const pkg = await fs.readJson(pkgPath);
127
+ const version = pkg.version || '0.0.0';
128
+ const status = version === targetVersion ? 'sync' : version > targetVersion ? 'newer' : 'stale';
129
+ statuses.push({ name: pkgName, version, status });
130
+ }
131
+ }
132
+ return statuses;
133
+ }
134
+ getEnvironmentInfo() {
135
+ try {
136
+ const nodeVersion = process.version.replace('v', '');
137
+ const pnpmVersion = this.getPnpmVersion();
138
+ const gitAvailable = this.isGitAvailable();
139
+ const configValid = true;
140
+ return {
141
+ nodeVersion,
142
+ pnpmVersion,
143
+ gitAvailable,
144
+ configValid,
145
+ };
146
+ }
147
+ catch {
148
+ return {
149
+ nodeVersion: 'unknown',
150
+ pnpmVersion: 'unknown',
151
+ gitAvailable: false,
152
+ configValid: false,
153
+ };
154
+ }
155
+ }
156
+ getPnpmVersion() {
157
+ try {
158
+ const output = (0, child_process_1.execSync)('pnpm --version', { encoding: 'utf8' }).trim();
159
+ return output;
160
+ }
161
+ catch {
162
+ return 'unknown';
163
+ }
164
+ }
165
+ isGitAvailable() {
166
+ try {
167
+ (0, child_process_1.execSync)('git --version', { stdio: 'ignore' });
168
+ return true;
169
+ }
170
+ catch {
171
+ return false;
172
+ }
173
+ }
174
+ getLastReleaseInfo() {
175
+ try {
176
+ const output = (0, child_process_1.execSync)('git log --oneline --all --decorate | grep -E "tag:|release|v[0-9]" | head -1', {
177
+ encoding: 'utf8',
178
+ shell: '/bin/bash',
179
+ }).trim();
180
+ const tagMatch = output.match(/v\d+\.\d+\.\d+/);
181
+ const version = tagMatch ? tagMatch[0].replace('v', '') : '1.0.0';
182
+ // Get commit count since last tag
183
+ let commitsSince = 0;
184
+ try {
185
+ const countOutput = (0, child_process_1.execSync)(`git rev-list --count ${version}..HEAD`, { encoding: 'utf8' }).trim();
186
+ commitsSince = parseInt(countOutput, 10) || 0;
187
+ }
188
+ catch {
189
+ commitsSince = 0;
190
+ }
191
+ const dateStr = new Date().toISOString().split('T')[0];
192
+ return {
193
+ version,
194
+ date: dateStr,
195
+ commitsSince,
196
+ };
197
+ }
198
+ catch {
199
+ return {
200
+ version: '1.0.0',
201
+ date: new Date().toISOString().split('T')[0],
202
+ commitsSince: 0,
203
+ };
204
+ }
205
+ }
206
+ formatTable(header, rows) {
207
+ const colWidths = header.map((h, i) => Math.max(h.length, ...rows.map(r => (r[i] || '').length)));
208
+ const separator = '┌' + colWidths.map(w => '─'.repeat(w + 2)).join('┬') + '┐';
209
+ const headerRow = '│ ' + header.map((h, i) => h.padEnd(colWidths[i])).join(' │ ') + ' │';
210
+ const divider = '├' + colWidths.map(w => '─'.repeat(w + 2)).join('┼') + '┤';
211
+ const dataRows = rows.map(row => '│ ' + row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(' │ ') + ' │');
212
+ const footer = '└' + colWidths.map(w => '─'.repeat(w + 2)).join('┴') + '┘';
213
+ return [separator, headerRow, divider, ...dataRows, footer].join('\n');
214
+ }
215
+ async formatConsole() {
216
+ const status = await this.getStatus();
217
+ const lines = [];
218
+ lines.push('┌─────────────────────────────────────────────────────────┐');
219
+ lines.push('│ 📊 Monorepo Versioning Status Report │');
220
+ lines.push('└─────────────────────────────────────────────────────────┘\n');
221
+ // CLI Version
222
+ lines.push('🔧 CLI Version Information');
223
+ lines.push(` Installed: v${status.cli.installed}`);
224
+ lines.push(` Latest: v${status.cli.latest}`);
225
+ lines.push(` Status: ${status.cli.updateAvailable ? '⚠️ Update available' : '✅ Up to date'}\n`);
226
+ // Core Sync
227
+ lines.push('📦 Core Version Sync');
228
+ lines.push(` Current Target: ${status.sync.currentTarget}`);
229
+ lines.push(` Synced: ${status.sync.isSynced ? '✅' : '❌'} (${status.sync.syncedPackages}/${status.sync.totalPackages} packages)`);
230
+ lines.push(` Last Updated: ${new Date(status.sync.lastUpdated).toLocaleString()} UTC\n`);
231
+ // Apps
232
+ if (status.apps.length > 0) {
233
+ lines.push('📂 Applications Version Status');
234
+ const appRows = status.apps.map(app => [
235
+ app.name,
236
+ app.version,
237
+ app.status === 'sync' ? '✅ Sync' : app.status === 'stale' ? '⚠️ Stale' : '✨ Newer',
238
+ app.target,
239
+ ]);
240
+ lines.push(this.formatTable(['App', 'Version', 'Status', 'Target'], appRows));
241
+ lines.push('');
242
+ }
243
+ // Packages
244
+ if (status.packages.length > 0) {
245
+ lines.push('📚 Packages Version Status');
246
+ const pkgRows = status.packages.map(pkg => [
247
+ pkg.name,
248
+ pkg.version,
249
+ pkg.status === 'sync' ? '✅ Sync' : pkg.status === 'stale' ? '⚠️ Stale' : '✨ Newer',
250
+ ]);
251
+ lines.push(this.formatTable(['Package', 'Version', 'Status'], pkgRows));
252
+ lines.push('');
253
+ }
254
+ // Dependencies
255
+ lines.push('🔗 Dependency Graph Health');
256
+ lines.push(` Critical Dependencies:`);
257
+ lines.push(` ✅ All versions locked correctly`);
258
+ lines.push(` ✅ No circular dependencies detected`);
259
+ lines.push(` ${status.dependencies.staleCount === 0 ? '✅' : '⚠️'} ${status.dependencies.staleCount || '0'} stale/out-of-date packages\n`);
260
+ // Environment
261
+ lines.push('⚡ Installation Health');
262
+ lines.push(` ✅ Node.js: ${status.environment.nodeVersion}`);
263
+ lines.push(` ✅ pnpm: ${status.environment.pnpmVersion}`);
264
+ lines.push(` ${status.environment.gitAvailable ? '✅' : '❌'} Git: ${status.environment.gitAvailable ? 'Available' : 'Not available'}`);
265
+ lines.push(` ${status.environment.configValid ? '✅' : '❌'} Config: versioning.config.json ${status.environment.configValid ? 'found' : 'not found'}\n`);
266
+ // Last Release
267
+ lines.push('🎯 Last Release');
268
+ lines.push(` Version: ${status.lastRelease.version}`);
269
+ lines.push(` Date: ${status.lastRelease.date}`);
270
+ lines.push(` Commits: ${status.lastRelease.commitsSince} since last release`);
271
+ const nextBump = status.lastRelease.commitsSince > 10 ? 'Minor' : 'Patch';
272
+ lines.push(` Next Suggested: ${nextBump} release\n`);
273
+ // Overall status
274
+ const statusEmoji = status.overallStatus === 'healthy' ? '✅' : status.overallStatus === 'warning' ? '⚠️' : '❌';
275
+ const statusText = status.overallStatus.toUpperCase();
276
+ lines.push(`${statusEmoji} Overall Status: ${statusText} - ${status.overallStatus === 'healthy'
277
+ ? 'All systems synced and ready'
278
+ : 'Some packages out of sync'}`);
279
+ return lines.join('\n');
280
+ }
281
+ async getJSON() {
282
+ const status = await this.getStatus();
283
+ return JSON.stringify(status, null, 2);
284
+ }
285
+ async getDOT() {
286
+ const status = await this.getStatus();
287
+ const lines = ['digraph VersionDependencies {'];
288
+ // Add app dependencies
289
+ for (const app of status.apps) {
290
+ lines.push(` ${app.name} [label="${app.name}\\n${app.version}", shape=box];`);
291
+ }
292
+ // Add package dependencies
293
+ for (const pkg of status.packages) {
294
+ const color = pkg.status === 'stale' ? ', color=red' : pkg.status === 'newer' ? ', color=orange' : '';
295
+ lines.push(` ${pkg.name} [label="${pkg.name}\\n${pkg.version}"${color}];`);
296
+ }
297
+ // Add edges (simplified - could be enhanced)
298
+ for (const app of status.apps) {
299
+ for (const pkg of status.packages) {
300
+ if (!pkg.name.includes('versioning')) {
301
+ lines.push(` ${app.name} -> ${pkg.name};`);
302
+ }
303
+ }
304
+ }
305
+ lines.push('}');
306
+ return lines.join('\n');
307
+ }
308
+ }
309
+ exports.StatusManager = StatusManager;
310
+ //# sourceMappingURL=status.js.map
@@ -1,10 +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;
7
33
  extensionConfig?: Record<string, any>;
34
+ branchAwareness?: BranchAwarenessConfig;
8
35
  }
9
36
  export declare class VersionManager {
10
37
  private config;
@@ -12,9 +39,30 @@ export declare class VersionManager {
12
39
  constructor(config: VersionConfig);
13
40
  getCurrentVersion(): Promise<string>;
14
41
  bumpVersion(releaseType: semver.ReleaseType, preRelease?: string): Promise<string>;
42
+ bumpVersionBranchAware(releaseType: semver.ReleaseType, options?: BranchAwareBumpOptions): Promise<BranchAwareBumpResult>;
15
43
  updateVersion(newVersion: string): Promise<void>;
16
44
  private updatePackageJson;
17
45
  createGitTag(version: string, message?: string): Promise<void>;
46
+ createGitTagWithFormat(version: string, tagFormat?: string, message?: string): Promise<string>;
18
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;
19
67
  }
20
68
  //# sourceMappingURL=versioning.d.ts.map
@@ -65,6 +65,32 @@ class VersionManager {
65
65
  await this.updateVersion(newVersion);
66
66
  return newVersion;
67
67
  }
68
+ async bumpVersionBranchAware(releaseType, options = {}) {
69
+ if (options.build !== undefined && (!Number.isInteger(options.build) || options.build < 0)) {
70
+ throw new Error(`Invalid build number "${options.build}". Expected a non-negative integer.`);
71
+ }
72
+ const branch = options.targetBranch || await this.getCurrentBranch();
73
+ const { matchPattern, resolvedConfig } = this.resolveBranchConfig(branch, options.forceBranchAware === true);
74
+ const versionFormat = options.format || resolvedConfig.versionFormat;
75
+ const version = await this.buildBranchAwareVersion(releaseType, branch, versionFormat, resolvedConfig, options.build);
76
+ const syncFiles = resolvedConfig.syncFiles.length > 0
77
+ ? resolvedConfig.syncFiles
78
+ : this.getDefaultSyncFiles(versionFormat);
79
+ if (syncFiles.length === 0) {
80
+ throw new Error(`No sync files configured for branch "${branch}".`);
81
+ }
82
+ for (const filePath of syncFiles) {
83
+ await this.updateVersionFile(filePath, version);
84
+ }
85
+ return {
86
+ version,
87
+ branch,
88
+ matchPattern,
89
+ versionFormat,
90
+ tagFormat: resolvedConfig.tagFormat,
91
+ syncFiles
92
+ };
93
+ }
68
94
  async updateVersion(newVersion) {
69
95
  // Update root package.json
70
96
  await this.updatePackageJson(this.config.rootPackageJson, newVersion);
@@ -82,8 +108,13 @@ class VersionManager {
82
108
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
83
109
  }
84
110
  async createGitTag(version, message) {
85
- const tagMessage = message || `Release v${version}`;
86
- await this.git.addAnnotatedTag(`v${version}`, tagMessage);
111
+ await this.createGitTagWithFormat(version, 'v{version}', message);
112
+ }
113
+ async createGitTagWithFormat(version, tagFormat = 'v{version}', message) {
114
+ const tagName = this.renderTag(tagFormat, version);
115
+ const tagMessage = message || `Release ${tagName}`;
116
+ await this.git.addAnnotatedTag(tagName, tagMessage);
117
+ return tagName;
87
118
  }
88
119
  async commitChanges(version) {
89
120
  const commitMessage = this.config.conventionalCommits
@@ -92,6 +123,286 @@ class VersionManager {
92
123
  await this.git.add('.');
93
124
  await this.git.commit(commitMessage);
94
125
  }
126
+ async getCurrentBranch() {
127
+ const branch = (await this.git.revparse(['--abbrev-ref', 'HEAD'])).trim();
128
+ if (!branch) {
129
+ throw new Error('Unable to detect current Git branch.');
130
+ }
131
+ return branch;
132
+ }
133
+ resolveBranchConfig(branch, forceBranchAware) {
134
+ const branchAwareness = this.getBranchAwarenessConfig(forceBranchAware);
135
+ const { branches } = branchAwareness;
136
+ if (branches[branch]) {
137
+ return {
138
+ matchPattern: branch,
139
+ resolvedConfig: this.normalizeBranchRule(branches[branch])
140
+ };
141
+ }
142
+ const wildcardPatterns = Object.keys(branches)
143
+ .filter((pattern) => pattern.includes('*'))
144
+ .sort((a, b) => b.length - a.length);
145
+ for (const pattern of wildcardPatterns) {
146
+ if (this.matchesPattern(branch, pattern)) {
147
+ return {
148
+ matchPattern: pattern,
149
+ resolvedConfig: this.normalizeBranchRule(branches[pattern])
150
+ };
151
+ }
152
+ }
153
+ const fallbackPattern = branchAwareness.defaultBranch;
154
+ if (fallbackPattern && branches[fallbackPattern]) {
155
+ return {
156
+ matchPattern: fallbackPattern,
157
+ resolvedConfig: this.normalizeBranchRule(branches[fallbackPattern])
158
+ };
159
+ }
160
+ throw new Error(`No branch configuration matched "${branch}".`);
161
+ }
162
+ getBranchAwarenessConfig(forceBranchAware) {
163
+ const configured = this.config.branchAwareness;
164
+ const defaultBranch = configured?.defaultBranch || 'main';
165
+ const configuredBranches = configured?.branches && Object.keys(configured.branches).length > 0
166
+ ? configured.branches
167
+ : this.getDefaultBranchRules(defaultBranch);
168
+ if (!configured?.enabled && !forceBranchAware) {
169
+ throw new Error('Branch awareness is not enabled in versioning.config.json.');
170
+ }
171
+ return {
172
+ defaultBranch,
173
+ branches: configuredBranches
174
+ };
175
+ }
176
+ getDefaultBranchRules(defaultBranch) {
177
+ const rootPackageJson = this.config.rootPackageJson || 'package.json';
178
+ const defaults = {
179
+ main: {
180
+ versionFormat: 'semantic',
181
+ tagFormat: 'v{version}',
182
+ syncFiles: [rootPackageJson],
183
+ environment: 'production',
184
+ bumpStrategy: 'semantic'
185
+ },
186
+ develop: {
187
+ versionFormat: 'dev',
188
+ tagFormat: 'v{version}',
189
+ syncFiles: ['version.development.json'],
190
+ environment: 'development',
191
+ bumpStrategy: 'dev-build'
192
+ },
193
+ 'feature/*': {
194
+ versionFormat: 'feature',
195
+ tagFormat: 'v{version}',
196
+ syncFiles: ['version.development.json'],
197
+ environment: 'development',
198
+ bumpStrategy: 'feature-branch'
199
+ },
200
+ 'hotfix/*': {
201
+ versionFormat: 'hotfix',
202
+ tagFormat: 'v{version}',
203
+ syncFiles: ['version.development.json'],
204
+ environment: 'development',
205
+ bumpStrategy: 'hotfix'
206
+ }
207
+ };
208
+ if (!defaults[defaultBranch]) {
209
+ defaults[defaultBranch] = {
210
+ versionFormat: 'semantic',
211
+ tagFormat: 'v{version}',
212
+ syncFiles: [rootPackageJson],
213
+ environment: 'production',
214
+ bumpStrategy: 'semantic'
215
+ };
216
+ }
217
+ return defaults;
218
+ }
219
+ normalizeBranchRule(branchRule) {
220
+ const versionFormat = branchRule?.versionFormat || 'semantic';
221
+ return {
222
+ ...branchRule,
223
+ versionFormat,
224
+ tagFormat: branchRule?.tagFormat || 'v{version}',
225
+ syncFiles: branchRule?.syncFiles || [],
226
+ bumpStrategy: branchRule?.bumpStrategy || this.getDefaultBumpStrategy(versionFormat)
227
+ };
228
+ }
229
+ getDefaultBumpStrategy(versionFormat) {
230
+ switch (versionFormat) {
231
+ case 'semantic':
232
+ return 'semantic';
233
+ case 'feature':
234
+ return 'feature-branch';
235
+ case 'hotfix':
236
+ return 'hotfix';
237
+ case 'dev':
238
+ default:
239
+ return 'dev-build';
240
+ }
241
+ }
242
+ async buildBranchAwareVersion(releaseType, branch, versionFormat, branchConfig, explicitBuild) {
243
+ const currentVersion = await this.getCurrentVersion();
244
+ const baseVersion = this.coerceBaseVersion(currentVersion);
245
+ const shouldBumpSemantic = this.shouldBumpSemantic(releaseType, versionFormat, branchConfig.bumpStrategy);
246
+ const semanticVersion = shouldBumpSemantic ? this.incrementSemanticVersion(baseVersion, releaseType) : baseVersion;
247
+ if (versionFormat === 'semantic') {
248
+ return semanticVersion;
249
+ }
250
+ const buildNumber = explicitBuild ?? await this.resolveBuildNumber(branchConfig.syncFiles, versionFormat, branch);
251
+ return this.applyVersionFormat(versionFormat, semanticVersion, branch, buildNumber);
252
+ }
253
+ shouldBumpSemantic(releaseType, versionFormat, bumpStrategy) {
254
+ if (versionFormat === 'semantic') {
255
+ return true;
256
+ }
257
+ if (bumpStrategy === 'semantic') {
258
+ return true;
259
+ }
260
+ return releaseType !== 'patch';
261
+ }
262
+ coerceBaseVersion(version) {
263
+ const parsed = semver.parse(version) || semver.coerce(version);
264
+ if (!parsed) {
265
+ throw new Error(`Unable to parse semantic base version from "${version}".`);
266
+ }
267
+ return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
268
+ }
269
+ incrementSemanticVersion(version, releaseType) {
270
+ const incremented = semver.inc(version, releaseType);
271
+ if (!incremented) {
272
+ throw new Error(`Invalid semantic version bump: ${releaseType} from ${version}`);
273
+ }
274
+ return incremented;
275
+ }
276
+ async resolveBuildNumber(syncFiles, versionFormat, branch) {
277
+ const candidateFiles = syncFiles.length > 0 ? syncFiles : this.getDefaultSyncFiles(versionFormat);
278
+ let highestBuild = 0;
279
+ for (const filePath of candidateFiles) {
280
+ const existingVersion = await this.readVersionFromFile(filePath);
281
+ if (!existingVersion) {
282
+ continue;
283
+ }
284
+ const existingBuild = this.extractBuildNumber(existingVersion, versionFormat, branch);
285
+ if (existingBuild !== null) {
286
+ highestBuild = Math.max(highestBuild, existingBuild);
287
+ }
288
+ }
289
+ const rootVersion = await this.getCurrentVersion();
290
+ const rootBuild = this.extractBuildNumber(rootVersion, versionFormat, branch);
291
+ if (rootBuild !== null) {
292
+ highestBuild = Math.max(highestBuild, rootBuild);
293
+ }
294
+ return highestBuild + 1;
295
+ }
296
+ getDefaultSyncFiles(versionFormat) {
297
+ if (versionFormat === 'semantic') {
298
+ return [this.config.rootPackageJson];
299
+ }
300
+ return ['version.development.json'];
301
+ }
302
+ applyVersionFormat(versionFormat, semanticVersion, branch, build) {
303
+ if (versionFormat === 'dev') {
304
+ return `${semanticVersion}-dev.${build}`;
305
+ }
306
+ if (versionFormat === 'feature' || versionFormat === 'hotfix') {
307
+ return `${semanticVersion}-${branch}.${build}`;
308
+ }
309
+ const normalizedFormat = versionFormat.includes('{branch}')
310
+ ? versionFormat.replace(/\{branch\}/g, branch)
311
+ : versionFormat;
312
+ return `${semanticVersion}-${normalizedFormat}.${build}`;
313
+ }
314
+ extractBuildNumber(version, versionFormat, branch) {
315
+ let match = null;
316
+ if (versionFormat === 'dev') {
317
+ match = version.match(/-dev\.(\d+)$/);
318
+ }
319
+ else if (versionFormat === 'feature') {
320
+ match = version.match(/-feature\/.+\.(\d+)$/);
321
+ }
322
+ else if (versionFormat === 'hotfix') {
323
+ match = version.match(/-hotfix\/.+\.(\d+)$/);
324
+ }
325
+ else {
326
+ const normalizedFormat = versionFormat.includes('{branch}')
327
+ ? versionFormat.replace(/\{branch\}/g, branch)
328
+ : versionFormat;
329
+ const escapedFormat = this.escapeRegex(normalizedFormat);
330
+ match = version.match(new RegExp(`-${escapedFormat}\\.(\\d+)$`));
331
+ }
332
+ if (!match) {
333
+ return null;
334
+ }
335
+ return Number.parseInt(match[1], 10);
336
+ }
337
+ async readVersionFromFile(filePath) {
338
+ if (!(await fs.pathExists(filePath))) {
339
+ return null;
340
+ }
341
+ const extension = path.extname(filePath).toLowerCase();
342
+ if (extension === '.json') {
343
+ try {
344
+ const jsonContent = await fs.readJson(filePath);
345
+ if (typeof jsonContent === 'string') {
346
+ return jsonContent;
347
+ }
348
+ if (jsonContent && typeof jsonContent === 'object') {
349
+ const version = jsonContent.version;
350
+ if (typeof version === 'string') {
351
+ return version;
352
+ }
353
+ }
354
+ }
355
+ catch {
356
+ return null;
357
+ }
358
+ return null;
359
+ }
360
+ const content = await fs.readFile(filePath, 'utf8');
361
+ const normalized = content.trim();
362
+ return normalized.length > 0 ? normalized : null;
363
+ }
364
+ async updateVersionFile(filePath, version) {
365
+ await fs.ensureDir(path.dirname(filePath));
366
+ const extension = path.extname(filePath).toLowerCase();
367
+ if (extension === '.json') {
368
+ let jsonContent = {};
369
+ if (await fs.pathExists(filePath)) {
370
+ try {
371
+ jsonContent = await fs.readJson(filePath);
372
+ }
373
+ catch {
374
+ jsonContent = {};
375
+ }
376
+ }
377
+ if (!jsonContent || typeof jsonContent !== 'object' || Array.isArray(jsonContent)) {
378
+ jsonContent = {};
379
+ }
380
+ const nextContent = jsonContent;
381
+ nextContent.version = version;
382
+ await fs.writeJson(filePath, nextContent, { spaces: 2 });
383
+ return;
384
+ }
385
+ await fs.writeFile(filePath, `${version}\n`, 'utf8');
386
+ }
387
+ matchesPattern(branch, pattern) {
388
+ if (!pattern.includes('*')) {
389
+ return branch === pattern;
390
+ }
391
+ const escapedPattern = pattern
392
+ .split('*')
393
+ .map((part) => this.escapeRegex(part))
394
+ .join('.*');
395
+ const regex = new RegExp(`^${escapedPattern}$`);
396
+ return regex.test(branch);
397
+ }
398
+ escapeRegex(value) {
399
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
400
+ }
401
+ renderTag(tagFormat, version) {
402
+ return tagFormat.includes('{version}')
403
+ ? tagFormat.replace(/\{version\}/g, version)
404
+ : tagFormat;
405
+ }
95
406
  }
96
407
  exports.VersionManager = VersionManager;
97
408
  //# sourceMappingURL=versioning.js.map
@@ -0,0 +1,51 @@
1
+ {
2
+ "rootPackageJson": "package.json",
3
+ "packages": [],
4
+ "changelogFile": "CHANGELOG.md",
5
+ "conventionalCommits": true,
6
+ "syncDependencies": false,
7
+ "ignorePackages": [],
8
+ "branchAwareness": {
9
+ "enabled": true,
10
+ "defaultBranch": "main",
11
+ "branches": {
12
+ "main": {
13
+ "versionFormat": "semantic",
14
+ "tagFormat": "v{version}",
15
+ "syncFiles": [
16
+ "package.json",
17
+ "version.production.json"
18
+ ],
19
+ "environment": "production",
20
+ "bumpStrategy": "semantic"
21
+ },
22
+ "develop": {
23
+ "versionFormat": "dev",
24
+ "tagFormat": "v{version}",
25
+ "syncFiles": [
26
+ "version.development.json"
27
+ ],
28
+ "environment": "development",
29
+ "bumpStrategy": "dev-build"
30
+ },
31
+ "feature/*": {
32
+ "versionFormat": "feature",
33
+ "tagFormat": "v{version}",
34
+ "syncFiles": [
35
+ "version.development.json"
36
+ ],
37
+ "environment": "development",
38
+ "bumpStrategy": "feature-branch"
39
+ },
40
+ "hotfix/*": {
41
+ "versionFormat": "hotfix",
42
+ "tagFormat": "v{version}",
43
+ "syncFiles": [
44
+ "version.development.json"
45
+ ],
46
+ "environment": "development",
47
+ "bumpStrategy": "hotfix"
48
+ }
49
+ }
50
+ }
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edcalderon/versioning",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A comprehensive versioning and changelog management tool for monorepos",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -78,4 +78,4 @@
78
78
  "url": "git+https://github.com/edcalderon/my-second-brain.git",
79
79
  "directory": "packages/versioning"
80
80
  }
81
- }
81
+ }
@@ -5,6 +5,49 @@
5
5
  "conventionalCommits": true,
6
6
  "syncDependencies": false,
7
7
  "ignorePackages": [],
8
+ "branchAwareness": {
9
+ "enabled": false,
10
+ "defaultBranch": "main",
11
+ "branches": {
12
+ "main": {
13
+ "versionFormat": "semantic",
14
+ "tagFormat": "v{version}",
15
+ "syncFiles": [
16
+ "package.json",
17
+ "version.production.json"
18
+ ],
19
+ "environment": "production",
20
+ "bumpStrategy": "semantic"
21
+ },
22
+ "develop": {
23
+ "versionFormat": "dev",
24
+ "tagFormat": "v{version}",
25
+ "syncFiles": [
26
+ "version.development.json"
27
+ ],
28
+ "environment": "development",
29
+ "bumpStrategy": "dev-build"
30
+ },
31
+ "feature/*": {
32
+ "versionFormat": "feature",
33
+ "tagFormat": "v{version}",
34
+ "syncFiles": [
35
+ "version.development.json"
36
+ ],
37
+ "environment": "development",
38
+ "bumpStrategy": "feature-branch"
39
+ },
40
+ "hotfix/*": {
41
+ "versionFormat": "hotfix",
42
+ "tagFormat": "v{version}",
43
+ "syncFiles": [
44
+ "version.development.json"
45
+ ],
46
+ "environment": "development",
47
+ "bumpStrategy": "hotfix"
48
+ }
49
+ }
50
+ },
8
51
  "changelog": {
9
52
  "preset": "angular",
10
53
  "releaseCount": 0
@@ -12,4 +55,4 @@
12
55
  "sync": {
13
56
  "includeRoot": true
14
57
  }
15
- }
58
+ }