@versu/core 0.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.
Files changed (117) hide show
  1. package/README.md +241 -0
  2. package/dist/adapters/gradle/constants.d.ts +13 -0
  3. package/dist/adapters/gradle/constants.d.ts.map +1 -0
  4. package/dist/adapters/gradle/constants.js +12 -0
  5. package/dist/adapters/gradle/gradle-project-information.d.ts +19 -0
  6. package/dist/adapters/gradle/gradle-project-information.d.ts.map +1 -0
  7. package/dist/adapters/gradle/gradle-project-information.js +233 -0
  8. package/dist/adapters/gradle/init-project-information.gradle.kts +163 -0
  9. package/dist/adapters/gradle/services/gradle-adapter-identifier.d.ts +21 -0
  10. package/dist/adapters/gradle/services/gradle-adapter-identifier.d.ts.map +1 -0
  11. package/dist/adapters/gradle/services/gradle-adapter-identifier.js +44 -0
  12. package/dist/adapters/gradle/services/gradle-module-detector.d.ts +19 -0
  13. package/dist/adapters/gradle/services/gradle-module-detector.d.ts.map +1 -0
  14. package/dist/adapters/gradle/services/gradle-module-detector.js +28 -0
  15. package/dist/adapters/gradle/services/gradle-module-system-factory.d.ts +26 -0
  16. package/dist/adapters/gradle/services/gradle-module-system-factory.d.ts.map +1 -0
  17. package/dist/adapters/gradle/services/gradle-module-system-factory.js +29 -0
  18. package/dist/adapters/gradle/services/gradle-version-update-strategy.d.ts +23 -0
  19. package/dist/adapters/gradle/services/gradle-version-update-strategy.d.ts.map +1 -0
  20. package/dist/adapters/gradle/services/gradle-version-update-strategy.js +38 -0
  21. package/dist/adapters/project-information.d.ts +62 -0
  22. package/dist/adapters/project-information.d.ts.map +1 -0
  23. package/dist/adapters/project-information.js +1 -0
  24. package/dist/changelog/index.d.ts +14 -0
  25. package/dist/changelog/index.d.ts.map +1 -0
  26. package/dist/changelog/index.js +132 -0
  27. package/dist/config/index.d.ts +122 -0
  28. package/dist/config/index.d.ts.map +1 -0
  29. package/dist/config/index.js +117 -0
  30. package/dist/factories/adapter-identifier-registry.d.ts +12 -0
  31. package/dist/factories/adapter-identifier-registry.d.ts.map +1 -0
  32. package/dist/factories/adapter-identifier-registry.js +24 -0
  33. package/dist/factories/module-system-factory.d.ts +10 -0
  34. package/dist/factories/module-system-factory.d.ts.map +1 -0
  35. package/dist/factories/module-system-factory.js +18 -0
  36. package/dist/git/index.d.ts +355 -0
  37. package/dist/git/index.d.ts.map +1 -0
  38. package/dist/git/index.js +702 -0
  39. package/dist/index.d.ts +23 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +22 -0
  42. package/dist/semver/index.d.ts +86 -0
  43. package/dist/semver/index.d.ts.map +1 -0
  44. package/dist/semver/index.js +186 -0
  45. package/dist/services/adapter-identifier-registry.d.ts +38 -0
  46. package/dist/services/adapter-identifier-registry.d.ts.map +1 -0
  47. package/dist/services/adapter-identifier-registry.js +59 -0
  48. package/dist/services/adapter-identifier.d.ts +31 -0
  49. package/dist/services/adapter-identifier.d.ts.map +1 -0
  50. package/dist/services/adapter-identifier.js +1 -0
  51. package/dist/services/adapter-metadata-provider.d.ts +51 -0
  52. package/dist/services/adapter-metadata-provider.d.ts.map +1 -0
  53. package/dist/services/adapter-metadata-provider.js +66 -0
  54. package/dist/services/changelog-generator.d.ts +16 -0
  55. package/dist/services/changelog-generator.d.ts.map +1 -0
  56. package/dist/services/changelog-generator.js +28 -0
  57. package/dist/services/commit-analyzer.d.ts +47 -0
  58. package/dist/services/commit-analyzer.d.ts.map +1 -0
  59. package/dist/services/commit-analyzer.js +89 -0
  60. package/dist/services/configuration-loader.d.ts +23 -0
  61. package/dist/services/configuration-loader.d.ts.map +1 -0
  62. package/dist/services/configuration-loader.js +79 -0
  63. package/dist/services/configuration-validator.d.ts +16 -0
  64. package/dist/services/configuration-validator.d.ts.map +1 -0
  65. package/dist/services/configuration-validator.js +24 -0
  66. package/dist/services/git-operations.d.ts +16 -0
  67. package/dist/services/git-operations.d.ts.map +1 -0
  68. package/dist/services/git-operations.js +89 -0
  69. package/dist/services/module-detector.d.ts +24 -0
  70. package/dist/services/module-detector.d.ts.map +1 -0
  71. package/dist/services/module-detector.js +1 -0
  72. package/dist/services/module-registry.d.ts +45 -0
  73. package/dist/services/module-registry.d.ts.map +1 -0
  74. package/dist/services/module-registry.js +57 -0
  75. package/dist/services/module-system-factory.d.ts +27 -0
  76. package/dist/services/module-system-factory.d.ts.map +1 -0
  77. package/dist/services/module-system-factory.js +1 -0
  78. package/dist/services/version-applier.d.ts +26 -0
  79. package/dist/services/version-applier.d.ts.map +1 -0
  80. package/dist/services/version-applier.js +63 -0
  81. package/dist/services/version-bumper.d.ts +159 -0
  82. package/dist/services/version-bumper.d.ts.map +1 -0
  83. package/dist/services/version-bumper.js +291 -0
  84. package/dist/services/version-manager.d.ts +68 -0
  85. package/dist/services/version-manager.d.ts.map +1 -0
  86. package/dist/services/version-manager.js +94 -0
  87. package/dist/services/version-update-strategy.d.ts +18 -0
  88. package/dist/services/version-update-strategy.d.ts.map +1 -0
  89. package/dist/services/version-update-strategy.js +1 -0
  90. package/dist/services/versu-runner.d.ts +46 -0
  91. package/dist/services/versu-runner.d.ts.map +1 -0
  92. package/dist/services/versu-runner.js +188 -0
  93. package/dist/utils/banner.d.ts +2 -0
  94. package/dist/utils/banner.d.ts.map +1 -0
  95. package/dist/utils/banner.js +12 -0
  96. package/dist/utils/commits.d.ts +12 -0
  97. package/dist/utils/commits.d.ts.map +1 -0
  98. package/dist/utils/commits.js +24 -0
  99. package/dist/utils/file.d.ts +7 -0
  100. package/dist/utils/file.d.ts.map +1 -0
  101. package/dist/utils/file.js +19 -0
  102. package/dist/utils/index.d.ts +6 -0
  103. package/dist/utils/index.d.ts.map +1 -0
  104. package/dist/utils/index.js +5 -0
  105. package/dist/utils/logger.d.ts +14 -0
  106. package/dist/utils/logger.d.ts.map +1 -0
  107. package/dist/utils/logger.js +22 -0
  108. package/dist/utils/properties.d.ts +24 -0
  109. package/dist/utils/properties.d.ts.map +1 -0
  110. package/dist/utils/properties.js +94 -0
  111. package/dist/utils/version.d.ts +3 -0
  112. package/dist/utils/version.d.ts.map +1 -0
  113. package/dist/utils/version.js +4 -0
  114. package/dist/utils/versioning.d.ts +9 -0
  115. package/dist/utils/versioning.d.ts.map +1 -0
  116. package/dist/utils/versioning.js +20 -0
  117. package/package.json +73 -0
@@ -0,0 +1,63 @@
1
+ import { logger } from "../utils/logger.js";
2
+ import { formatSemVer } from "../semver/index.js";
3
+ export class VersionApplier {
4
+ versionManager;
5
+ options;
6
+ constructor(versionManager, options) {
7
+ this.versionManager = versionManager;
8
+ this.options = options;
9
+ }
10
+ async applyVersionChanges(processedModuleChanges) {
11
+ if (processedModuleChanges.length === 0) {
12
+ logger.info("✨ No version changes to apply");
13
+ return [];
14
+ }
15
+ logger.info("🔍 Filtering modules with declared versions...");
16
+ const modulesWithDeclaredVersions = processedModuleChanges.filter((change) => change.module.declaredVersion);
17
+ if (modulesWithDeclaredVersions.length === 0) {
18
+ logger.info("✨ No modules with declared versions to update");
19
+ return [];
20
+ }
21
+ this.logPlannedChanges(modulesWithDeclaredVersions);
22
+ if (this.options.dryRun) {
23
+ logger.info("🏃‍♂️ Dry run mode - version changes will not be written to files");
24
+ }
25
+ else {
26
+ await this.stageVersions(modulesWithDeclaredVersions);
27
+ await this.commitVersions();
28
+ }
29
+ // Create and return result objects
30
+ return processedModuleChanges.map((change) => ({
31
+ id: change.module.id,
32
+ name: change.module.name,
33
+ path: change.module.path,
34
+ type: change.module.type,
35
+ from: formatSemVer(change.fromVersion),
36
+ to: change.toVersion,
37
+ bumpType: change.bumpType,
38
+ declaredVersion: change.module.declaredVersion,
39
+ }));
40
+ }
41
+ logPlannedChanges(processedModuleChanges) {
42
+ logger.info(`📈 Planning to update ${processedModuleChanges.length} modules:`);
43
+ for (const change of processedModuleChanges) {
44
+ const from = formatSemVer(change.fromVersion);
45
+ const to = change.toVersion;
46
+ logger.info(` ${change.module.id}: ${from} → ${to} (${change.bumpType}, ${change.reason})`);
47
+ }
48
+ }
49
+ async stageVersions(processedModuleChanges) {
50
+ logger.info("✍️ Staging new versions...");
51
+ for (const change of processedModuleChanges) {
52
+ // Use toVersion directly (now includes all transformations like Gradle snapshots)
53
+ this.versionManager.updateVersion(change.module.id, change.toVersion);
54
+ logger.info(` Staged ${change.module.id} to ${change.toVersion}`);
55
+ }
56
+ }
57
+ async commitVersions() {
58
+ logger.info("💾 Committing version updates to files...");
59
+ const pendingUpdatesCount = this.versionManager.getPendingUpdatesCount();
60
+ await this.versionManager.commit();
61
+ logger.info(`✅ Committed ${pendingUpdatesCount} version updates`);
62
+ }
63
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Version Bumper Service for VERSU.
3
+ * Implements core version calculation logic: analyzes commits, cascades changes through
4
+ * dependencies, and applies versions with support for prereleases, metadata, and snapshots.
5
+ */
6
+ import { Config } from "../config/index.js";
7
+ import { ModuleRegistry } from "./module-registry.js";
8
+ import { BumpType } from "../semver/index.js";
9
+ import { SemVer } from "semver";
10
+ import { AdapterMetadata } from "./adapter-identifier.js";
11
+ import { Module } from "../adapters/project-information.js";
12
+ import { Commit } from "conventional-commits-parser";
13
+ /**
14
+ * Configuration options for the version bumper service.
15
+ */
16
+ export type VersionBumperOptions = {
17
+ /** Whether to generate prerelease versions (e.g., 1.0.0-alpha.1). */
18
+ prereleaseMode: boolean;
19
+ /** Whether to bump versions of unchanged modules in prerelease mode. */
20
+ bumpUnchanged: boolean;
21
+ /** Whether to add build metadata (commit SHA) to versions. */
22
+ addBuildMetadata: boolean;
23
+ /** Whether to append -SNAPSHOT suffix for Gradle projects. */
24
+ appendSnapshot: boolean;
25
+ /** Adapter metadata providing project-specific capabilities. */
26
+ adapter: AdapterMetadata;
27
+ /** Whether to include timestamps in prerelease identifiers. */
28
+ timestampVersions: boolean;
29
+ /** Base prerelease identifier (e.g., 'alpha', 'beta', 'rc'). */
30
+ prereleaseId: string;
31
+ /** Absolute path to the repository root directory. */
32
+ repoRoot: string;
33
+ /** Project configuration including dependency bump rules. */
34
+ config: Config;
35
+ };
36
+ /**
37
+ * Final processed module version change result.
38
+ * Represents a completed version calculation ready for application.
39
+ */
40
+ export type ProcessedModuleChange = {
41
+ /** The module with calculated version change. */
42
+ readonly module: Module;
43
+ /** Original semantic version before changes. */
44
+ readonly fromVersion: SemVer;
45
+ /** New calculated version string (e.g., '1.1.0', '1.1.0-alpha.1', '1.1.0-SNAPSHOT'). */
46
+ readonly toVersion: string;
47
+ /** Final bump type applied ('major', 'minor', 'patch', or 'none'). */
48
+ readonly bumpType: BumpType;
49
+ /** Reason for version change. */
50
+ readonly reason: ChangeReason;
51
+ };
52
+ /**
53
+ * Reason why a module's version is being changed.
54
+ */
55
+ export type ChangeReason = "commits" | "dependency" | "cascade" | "prerelease-unchanged" | "build-metadata" | "gradle-snapshot";
56
+ /**
57
+ * Service for calculating version bumps across modules.
58
+ * Handles commit analysis, dependency cascade effects, and various versioning strategies
59
+ * (regular, prerelease, snapshot).
60
+ */
61
+ export declare class VersionBumper {
62
+ private readonly moduleRegistry;
63
+ private readonly options;
64
+ /**
65
+ * Creates a new VersionBumper instance.
66
+ * @param moduleRegistry - Registry containing all modules and their interdependencies
67
+ * @param options - Configuration options controlling version bump behavior
68
+ */
69
+ constructor(moduleRegistry: ModuleRegistry, options: VersionBumperOptions);
70
+ /**
71
+ * Calculates version bumps for all modules based on their commits.
72
+ * Orchestrates a three-phase process: initial bumps (commit analysis), cascade effects (dependency propagation),
73
+ * and version application (applying strategies like prerelease, timestamps, build metadata).
74
+ * @param moduleCommits - Map of module IDs to their commits since last version
75
+ * @returns Promise resolving to array of processed module changes (only modules that need updates)
76
+ * @throws {Error} If git operations fail
77
+ */
78
+ calculateVersionBumps(moduleCommits: Map<string, {
79
+ commits: Commit[];
80
+ lastTag: string | null;
81
+ }>): Promise<ProcessedModuleChange[]>;
82
+ /**
83
+ * Calculates initial version bump types for all modules based on commits.
84
+ *
85
+ * This is Phase 1 of the version calculation process. It analyzes commits for each
86
+ * module to determine the required version bump type using Conventional Commits
87
+ * specification.
88
+ *
89
+ * The method creates a `ProcessingModuleChange` for **every** module in the registry,
90
+ * not just those with commits. The `needsProcessing` flag determines which modules
91
+ * will ultimately be updated.
92
+ *
93
+ * **Processing Decision Logic**:
94
+ * - Module has commits requiring bump: `needsProcessing = true, reason = 'commits'`
95
+ * - Prerelease mode with bumpUnchanged: `needsProcessing = true, reason = 'prerelease-unchanged'`
96
+ * - Build metadata enabled: `needsProcessing = true, reason = 'build-metadata'`
97
+ * - Otherwise: `needsProcessing = false, reason = 'unchanged'`
98
+ *
99
+ * @param moduleCommits - Map of module IDs to their commits since last version.
100
+ *
101
+ * @returns Array of processing module changes for all modules in the registry
102
+ */
103
+ private calculateInitialBumps;
104
+ /**
105
+ * Calculates cascade effects when modules change.
106
+ *
107
+ * This is Phase 2 of the version calculation process. It propagates version changes
108
+ * through the module dependency graph, ensuring that when a module changes, all
109
+ * modules that depend on it are also bumped appropriately.
110
+ *
111
+ * **Algorithm**: Uses a breadth-first traversal of the dependency graph:
112
+ * 1. Start with all modules marked for processing (needsProcessing = true)
113
+ * 2. For each module being processed, find all modules that depend on it
114
+ * 3. Calculate required bump for dependents using dependency bump rules
115
+ * 4. If dependent needs a higher bump, update it and add to processing queue
116
+ * 5. Continue until no more cascades are needed
117
+ *
118
+ * **Complexity**: O(V + E) where V = number of modules, E = number of dependencies
119
+ *
120
+ * **In-Place Modification**: This method modifies the input array in place for
121
+ * efficiency. The same array reference is returned with cascade effects applied.
122
+ *
123
+ * @param allModuleChanges - Array of all module changes (will be modified in place).
124
+ * Should include all modules from calculateInitialBumps().
125
+ *
126
+ * @returns The same array with cascade effects applied
127
+ */
128
+ private calculateCascadeEffects;
129
+ /**
130
+ * Applies version calculations and transformations to all modules.
131
+ *
132
+ * This is Phase 3 (final) of the version calculation process. It takes modules
133
+ * with calculated bump types and applies version transformations including:
134
+ * - Semantic version bumping (major, minor, patch)
135
+ * - Prerelease version generation
136
+ * - Build metadata appending
137
+ * - Snapshot suffix appending
138
+ *
139
+ * **Version Application Scenarios**:
140
+ * 1. **Commits + Regular Mode**: Bump semantic version normally
141
+ * 2. **Commits + Prerelease Mode**: Bump to prerelease version
142
+ * 3. **No Commits + Prerelease + bumpUnchanged**: Force prerelease bump
143
+ * 4. **Build Metadata Mode**: Append git SHA as metadata
144
+ * 5. **Snapshot Mode**: Append -SNAPSHOT suffix
145
+ *
146
+ * @param processingModuleChanges - All module changes with calculated bump types
147
+ * from cascade effects phase.
148
+ *
149
+ * @param effectivePrereleaseId - Prerelease identifier to use (may include timestamp).
150
+ * Example: 'alpha', 'alpha.20251021143022'
151
+ *
152
+ * @param shortSha - Optional git commit short SHA for build metadata.
153
+ * Example: 'a1b2c3d'
154
+ *
155
+ * @returns Array of processed module changes ready for application (only modules with needsProcessing = true)
156
+ */
157
+ private applyVersionCalculations;
158
+ }
159
+ //# sourceMappingURL=version-bumper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-bumper.d.ts","sourceRoot":"","sources":["../../src/services/version-bumper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,MAAM,EAAyB,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAOL,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,qEAAqE;IACrE,cAAc,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,aAAa,EAAE,OAAO,CAAC;IACvB,8DAA8D;IAC9D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,cAAc,EAAE,OAAO,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,eAAe,CAAC;IACzB,+DAA+D;IAC/D,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAsBF;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,YAAY,GACZ,SAAS,GACT,sBAAsB,GACtB,gBAAgB,GAChB,iBAAiB,CAAC;AAEtB;;;;GAIG;AACH,qBAAa,aAAa;IAOtB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP1B;;;;OAIG;gBAEgB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,oBAAoB;IAGhD;;;;;;;OAOG;IACG,qBAAqB,CACzB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,GACxE,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAuCnC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,qBAAqB;IA6C7B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,uBAAuB;IAgG/B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,OAAO,CAAC,wBAAwB;CAgFjC"}
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Version Bumper Service for VERSU.
3
+ * Implements core version calculation logic: analyzes commits, cascades changes through
4
+ * dependencies, and applies versions with support for prereleases, metadata, and snapshots.
5
+ */
6
+ import { logger } from "../utils/logger.js";
7
+ import { getDependencyBumpType } from "../config/index.js";
8
+ import { calculateBumpFromCommits } from "../utils/commits.js";
9
+ import { bumpSemVer, bumpToPrerelease, formatSemVer, addBuildMetadata, generateTimestampPrereleaseId, maxBumpType, } from "../semver/index.js";
10
+ import { getCurrentCommitShortSha } from "../git/index.js";
11
+ import { applySnapshotSuffix } from "../utils/versioning.js";
12
+ /**
13
+ * Service for calculating version bumps across modules.
14
+ * Handles commit analysis, dependency cascade effects, and various versioning strategies
15
+ * (regular, prerelease, snapshot).
16
+ */
17
+ export class VersionBumper {
18
+ moduleRegistry;
19
+ options;
20
+ /**
21
+ * Creates a new VersionBumper instance.
22
+ * @param moduleRegistry - Registry containing all modules and their interdependencies
23
+ * @param options - Configuration options controlling version bump behavior
24
+ */
25
+ constructor(moduleRegistry, options) {
26
+ this.moduleRegistry = moduleRegistry;
27
+ this.options = options;
28
+ }
29
+ /**
30
+ * Calculates version bumps for all modules based on their commits.
31
+ * Orchestrates a three-phase process: initial bumps (commit analysis), cascade effects (dependency propagation),
32
+ * and version application (applying strategies like prerelease, timestamps, build metadata).
33
+ * @param moduleCommits - Map of module IDs to their commits since last version
34
+ * @returns Promise resolving to array of processed module changes (only modules that need updates)
35
+ * @throws {Error} If git operations fail
36
+ */
37
+ async calculateVersionBumps(moduleCommits) {
38
+ logger.info("🔢 Calculating version bumps from commits...");
39
+ // Generate timestamp-based prerelease ID if timestamp versions are enabled
40
+ let effectivePrereleaseId = this.options.prereleaseId;
41
+ if (this.options.timestampVersions && this.options.prereleaseMode) {
42
+ effectivePrereleaseId = generateTimestampPrereleaseId(this.options.prereleaseId);
43
+ logger.info(`🕐 Generated timestamp prerelease ID: ${effectivePrereleaseId}`);
44
+ }
45
+ // Get current commit short SHA if build metadata is enabled
46
+ let shortSha;
47
+ if (this.options.addBuildMetadata) {
48
+ shortSha = await getCurrentCommitShortSha({ cwd: this.options.repoRoot });
49
+ logger.info(`📋 Build metadata will include short SHA: ${shortSha}`);
50
+ }
51
+ // Step 1: Calculate initial bump types for all modules
52
+ const processingModuleChanges = this.calculateInitialBumps(moduleCommits);
53
+ // Step 2: Calculate cascade effects
54
+ logger.info("🌊 Calculating cascade effects...");
55
+ const cascadedChanges = this.calculateCascadeEffects(processingModuleChanges);
56
+ // Step 3: Apply version calculations and transformations
57
+ logger.info("🔢 Calculating actual versions...");
58
+ return this.applyVersionCalculations(cascadedChanges, effectivePrereleaseId, shortSha);
59
+ }
60
+ /**
61
+ * Calculates initial version bump types for all modules based on commits.
62
+ *
63
+ * This is Phase 1 of the version calculation process. It analyzes commits for each
64
+ * module to determine the required version bump type using Conventional Commits
65
+ * specification.
66
+ *
67
+ * The method creates a `ProcessingModuleChange` for **every** module in the registry,
68
+ * not just those with commits. The `needsProcessing` flag determines which modules
69
+ * will ultimately be updated.
70
+ *
71
+ * **Processing Decision Logic**:
72
+ * - Module has commits requiring bump: `needsProcessing = true, reason = 'commits'`
73
+ * - Prerelease mode with bumpUnchanged: `needsProcessing = true, reason = 'prerelease-unchanged'`
74
+ * - Build metadata enabled: `needsProcessing = true, reason = 'build-metadata'`
75
+ * - Otherwise: `needsProcessing = false, reason = 'unchanged'`
76
+ *
77
+ * @param moduleCommits - Map of module IDs to their commits since last version.
78
+ *
79
+ * @returns Array of processing module changes for all modules in the registry
80
+ */
81
+ calculateInitialBumps(moduleCommits) {
82
+ const processingModuleChanges = [];
83
+ // Iterate through ALL modules in the registry
84
+ for (const [projectId, projectInfo] of this.moduleRegistry.getModules()) {
85
+ const commits = moduleCommits.get(projectId)?.commits || [];
86
+ // Determine bump type from commits only
87
+ // Uses Conventional Commits spec to analyze commit types
88
+ const bumpType = calculateBumpFromCommits(commits, this.options.config);
89
+ // Determine processing requirements and reason
90
+ let reason = "unchanged";
91
+ let needsProcessing = false;
92
+ if (bumpType !== "none") {
93
+ // Module has commits that require version changes
94
+ needsProcessing = true;
95
+ reason = "commits";
96
+ }
97
+ else if (this.options.prereleaseMode && this.options.bumpUnchanged) {
98
+ // Prerelease mode with bumpUnchanged - include modules with no changes
99
+ needsProcessing = true;
100
+ reason = "prerelease-unchanged";
101
+ }
102
+ else if (this.options.addBuildMetadata) {
103
+ // Build metadata mode - all modules need updates for metadata
104
+ needsProcessing = true;
105
+ reason = "build-metadata";
106
+ }
107
+ // Create module change for ALL modules - processing flag determines behavior
108
+ processingModuleChanges.push({
109
+ module: projectInfo,
110
+ fromVersion: projectInfo.version,
111
+ toVersion: "", // Will be calculated later
112
+ bumpType: bumpType,
113
+ reason: reason,
114
+ needsProcessing: needsProcessing,
115
+ });
116
+ }
117
+ return processingModuleChanges;
118
+ }
119
+ /**
120
+ * Calculates cascade effects when modules change.
121
+ *
122
+ * This is Phase 2 of the version calculation process. It propagates version changes
123
+ * through the module dependency graph, ensuring that when a module changes, all
124
+ * modules that depend on it are also bumped appropriately.
125
+ *
126
+ * **Algorithm**: Uses a breadth-first traversal of the dependency graph:
127
+ * 1. Start with all modules marked for processing (needsProcessing = true)
128
+ * 2. For each module being processed, find all modules that depend on it
129
+ * 3. Calculate required bump for dependents using dependency bump rules
130
+ * 4. If dependent needs a higher bump, update it and add to processing queue
131
+ * 5. Continue until no more cascades are needed
132
+ *
133
+ * **Complexity**: O(V + E) where V = number of modules, E = number of dependencies
134
+ *
135
+ * **In-Place Modification**: This method modifies the input array in place for
136
+ * efficiency. The same array reference is returned with cascade effects applied.
137
+ *
138
+ * @param allModuleChanges - Array of all module changes (will be modified in place).
139
+ * Should include all modules from calculateInitialBumps().
140
+ *
141
+ * @returns The same array with cascade effects applied
142
+ */
143
+ calculateCascadeEffects(allModuleChanges) {
144
+ const processed = new Set();
145
+ const moduleMap = new Map();
146
+ // Create module map for O(1) lookups
147
+ for (const change of allModuleChanges) {
148
+ moduleMap.set(change.module.id, change);
149
+ }
150
+ // Start with ALL modules - treat them completely equally
151
+ const queue = [...allModuleChanges];
152
+ while (queue.length > 0) {
153
+ const currentChange = queue.shift();
154
+ // No processing needed, mark as processed
155
+ if (!currentChange.needsProcessing || currentChange.bumpType === "none") {
156
+ logger.debug(`🔄 Skipping module ${currentChange.module.id} - no processing needed`);
157
+ processed.add(currentChange.module.id);
158
+ continue;
159
+ }
160
+ // Skip if already processed
161
+ if (processed.has(currentChange.module.id)) {
162
+ logger.debug(`🔄 Skipping module ${currentChange.module.id} - already processed`);
163
+ continue;
164
+ }
165
+ // Mark as processed to avoid duplicate work
166
+ processed.add(currentChange.module.id);
167
+ const currentModuleInfo = this.moduleRegistry.getModule(currentChange.module.id);
168
+ // Iterate through all modules that depend on the current module
169
+ for (const dependentName of currentModuleInfo.affectedModules) {
170
+ logger.debug(`➡️ Processing dependent module ${dependentName} affected by ${currentChange.module.id} with bump ${currentChange.bumpType}`);
171
+ // Get the dependent module using O(1) lookup
172
+ const existingChange = moduleMap.get(dependentName);
173
+ if (!existingChange) {
174
+ logger.debug(`⚠️ Dependent module ${dependentName} not found in module changes list`);
175
+ continue; // Module not found in our module list
176
+ }
177
+ // Calculate the bump needed for the dependent
178
+ // Uses configuration rules to determine how changes propagate
179
+ const requiredBump = getDependencyBumpType(currentChange.bumpType, this.options.config);
180
+ if (requiredBump === "none") {
181
+ logger.debug(`➡️ No cascade bump needed for module ${dependentName} from ${currentChange.module.id}`);
182
+ continue; // No cascade needed
183
+ }
184
+ // Update the existing change with cascade information
185
+ // Take the maximum bump type if multiple dependencies affect this module
186
+ const mergedBump = maxBumpType([existingChange.bumpType, requiredBump]);
187
+ if (mergedBump !== existingChange.bumpType) {
188
+ logger.debug(`🔄 Cascading bump for module ${dependentName} from ${existingChange.bumpType} to ${mergedBump} due to ${currentChange.module.id}`);
189
+ // Update the module change in place
190
+ existingChange.bumpType = mergedBump;
191
+ existingChange.reason = "cascade";
192
+ existingChange.needsProcessing = true;
193
+ // Add to queue for further processing (transitive cascades)
194
+ processed.delete(dependentName); // Allow re-processing
195
+ queue.push(existingChange);
196
+ }
197
+ else {
198
+ logger.debug(`🔄 No changes needed for module ${dependentName} - already at ${existingChange.bumpType}`);
199
+ }
200
+ }
201
+ }
202
+ // Return the modified array (same reference, but with cascade effects applied)
203
+ return allModuleChanges;
204
+ }
205
+ /**
206
+ * Applies version calculations and transformations to all modules.
207
+ *
208
+ * This is Phase 3 (final) of the version calculation process. It takes modules
209
+ * with calculated bump types and applies version transformations including:
210
+ * - Semantic version bumping (major, minor, patch)
211
+ * - Prerelease version generation
212
+ * - Build metadata appending
213
+ * - Snapshot suffix appending
214
+ *
215
+ * **Version Application Scenarios**:
216
+ * 1. **Commits + Regular Mode**: Bump semantic version normally
217
+ * 2. **Commits + Prerelease Mode**: Bump to prerelease version
218
+ * 3. **No Commits + Prerelease + bumpUnchanged**: Force prerelease bump
219
+ * 4. **Build Metadata Mode**: Append git SHA as metadata
220
+ * 5. **Snapshot Mode**: Append -SNAPSHOT suffix
221
+ *
222
+ * @param processingModuleChanges - All module changes with calculated bump types
223
+ * from cascade effects phase.
224
+ *
225
+ * @param effectivePrereleaseId - Prerelease identifier to use (may include timestamp).
226
+ * Example: 'alpha', 'alpha.20251021143022'
227
+ *
228
+ * @param shortSha - Optional git commit short SHA for build metadata.
229
+ * Example: 'a1b2c3d'
230
+ *
231
+ * @returns Array of processed module changes ready for application (only modules with needsProcessing = true)
232
+ */
233
+ applyVersionCalculations(processingModuleChanges, effectivePrereleaseId, shortSha) {
234
+ const processedModuleChanges = [];
235
+ for (const change of processingModuleChanges) {
236
+ let newVersion = change.fromVersion;
237
+ // Only apply version changes if module needs processing
238
+ if (change.needsProcessing) {
239
+ // Apply version bumps based on module state
240
+ if (change.bumpType !== "none" && this.options.prereleaseMode) {
241
+ // Scenario 1: Commits with changes in prerelease mode
242
+ // Bump semantic version AND add prerelease identifier
243
+ newVersion = bumpToPrerelease(change.fromVersion, change.bumpType, effectivePrereleaseId);
244
+ }
245
+ else if (change.bumpType !== "none" && !this.options.prereleaseMode) {
246
+ // Scenario 2: Commits with changes in normal mode
247
+ // Standard semantic version bump (major.minor.patch)
248
+ newVersion = bumpSemVer(change.fromVersion, change.bumpType);
249
+ }
250
+ else if (change.reason === "prerelease-unchanged") {
251
+ // Scenario 3: No changes but force prerelease bump (bumpUnchanged enabled)
252
+ // Keep semantic version, just add prerelease identifier
253
+ newVersion = bumpToPrerelease(change.fromVersion, "none", effectivePrereleaseId);
254
+ }
255
+ // Scenario 4: reason === 'build-metadata' or 'unchanged' - no version bump, keep fromVersion
256
+ // Add build metadata if enabled (applies to all scenarios)
257
+ // Build metadata doesn't affect version precedence per semver spec
258
+ if (this.options.addBuildMetadata && shortSha) {
259
+ newVersion = addBuildMetadata(newVersion, shortSha);
260
+ }
261
+ }
262
+ // Convert to string version
263
+ change.toVersion = formatSemVer(newVersion);
264
+ // Apply append snapshot suffix if enabled (to all modules in append mode)
265
+ if (this.options.appendSnapshot &&
266
+ this.options.adapter.capabilities.supportsSnapshots) {
267
+ const originalVersion = change.toVersion;
268
+ change.toVersion = applySnapshotSuffix(change.toVersion);
269
+ // If snapshot suffix was actually added and module wasn't already being processed, mark it for processing
270
+ if (!change.needsProcessing && change.toVersion !== originalVersion) {
271
+ change.needsProcessing = true;
272
+ change.reason = "gradle-snapshot";
273
+ }
274
+ }
275
+ // Add to update collection only if module needs processing
276
+ if (change.needsProcessing) {
277
+ // Convert to ProcessedModuleChange since we know needsProcessing is true
278
+ const processedChange = {
279
+ module: change.module,
280
+ fromVersion: change.fromVersion,
281
+ toVersion: change.toVersion,
282
+ bumpType: change.bumpType,
283
+ reason: change.reason, // Safe cast since needsProcessing is true
284
+ };
285
+ processedModuleChanges.push(processedChange);
286
+ }
287
+ }
288
+ logger.info(`📈 Calculated versions for ${processedModuleChanges.length} modules requiring updates`);
289
+ return processedModuleChanges;
290
+ }
291
+ }
@@ -0,0 +1,68 @@
1
+ import { SemVer } from "semver";
2
+ import { ModuleRegistry } from "./module-registry.js";
3
+ import { VersionUpdateStrategy } from "./version-update-strategy.js";
4
+ /**
5
+ * Manages version updates for modules with staged commits and batch persistence.
6
+ *
7
+ * @remarks
8
+ * Implements a two-phase update strategy:
9
+ * 1. Stage updates in memory via `updateVersion()`
10
+ * 2. Persist all updates via `commit()`
11
+ *
12
+ * Uses {@link VersionUpdateStrategy} for build system-specific operations.
13
+ * Validates modules against {@link ModuleRegistry}.
14
+ */
15
+ export declare class VersionManager {
16
+ private readonly moduleRegistry;
17
+ private readonly strategy;
18
+ /** Pending version updates awaiting commit (module ID → version string). */
19
+ private readonly pendingUpdates;
20
+ /**
21
+ * Creates a new VersionManager.
22
+ *
23
+ * @param moduleRegistry - Module registry for validation
24
+ * @param strategy - Build system-specific strategy for writing updates
25
+ */
26
+ constructor(moduleRegistry: ModuleRegistry, strategy: VersionUpdateStrategy);
27
+ /**
28
+ * Stages a version update for a module without persisting to files.
29
+ *
30
+ * @param moduleId - Module identifier (e.g., `':'`, `':core'`)
31
+ * @param newVersion - New version as SemVer object or string
32
+ * @throws {Error} If module ID doesn't exist in registry
33
+ */
34
+ updateVersion(moduleId: string, newVersion: SemVer | string): void;
35
+ /**
36
+ * Commits all pending version updates to build files in a single batch operation.
37
+ *
38
+ * @returns Promise that resolves when all updates are written
39
+ * @throws {Error} If file operations fail (specific errors depend on strategy)
40
+ */
41
+ commit(): Promise<void>;
42
+ /**
43
+ * Returns a copy of all pending updates that haven't been committed.
44
+ *
45
+ * @returns Map of module ID to version string
46
+ */
47
+ getPendingUpdates(): Map<string, string>;
48
+ /**
49
+ * Checks whether there are any pending updates awaiting commit.
50
+ *
51
+ * @returns `true` if updates are staged, `false` otherwise
52
+ */
53
+ hasPendingUpdates(): boolean;
54
+ /**
55
+ * Clears all pending updates without committing them.
56
+ *
57
+ * @remarks
58
+ * Use with caution - this operation cannot be undone.
59
+ */
60
+ clearPendingUpdates(): void;
61
+ /**
62
+ * Gets the number of pending updates in the queue.
63
+ *
64
+ * @returns The count of pending updates that have not been processed yet.
65
+ */
66
+ getPendingUpdatesCount(): number;
67
+ }
68
+ //# sourceMappingURL=version-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-manager.d.ts","sourceRoot":"","sources":["../../src/services/version-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAGrE;;;;;;;;;;GAUG;AACH,qBAAa,cAAc;IAWvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAE5D;;;;;OAKG;gBAEgB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,qBAAqB;IAGlD;;;;;;OAMG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAclE;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAa7B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAIxC;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;OAKG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;;;OAIG;IACH,sBAAsB,IAAI,MAAM;CAGjC"}