@muverse/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +22 -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 +18 -0
  6. package/dist/adapters/gradle/gradle-project-information.d.ts.map +1 -0
  7. package/dist/adapters/gradle/gradle-project-information.js +93 -0
  8. package/dist/adapters/gradle/gradle-properties.d.ts +15 -0
  9. package/dist/adapters/gradle/gradle-properties.d.ts.map +1 -0
  10. package/dist/adapters/gradle/gradle-properties.js +46 -0
  11. package/dist/adapters/gradle/init-project-information.gradle.kts +143 -0
  12. package/dist/adapters/gradle/services/gradle-adapter-identifier.d.ts +21 -0
  13. package/dist/adapters/gradle/services/gradle-adapter-identifier.d.ts.map +1 -0
  14. package/dist/adapters/gradle/services/gradle-adapter-identifier.js +44 -0
  15. package/dist/adapters/gradle/services/gradle-module-detector.d.ts +18 -0
  16. package/dist/adapters/gradle/services/gradle-module-detector.d.ts.map +1 -0
  17. package/dist/adapters/gradle/services/gradle-module-detector.js +26 -0
  18. package/dist/adapters/gradle/services/gradle-module-system-factory.d.ts +23 -0
  19. package/dist/adapters/gradle/services/gradle-module-system-factory.d.ts.map +1 -0
  20. package/dist/adapters/gradle/services/gradle-module-system-factory.js +27 -0
  21. package/dist/adapters/gradle/services/gradle-version-update-strategy.d.ts +21 -0
  22. package/dist/adapters/gradle/services/gradle-version-update-strategy.d.ts.map +1 -0
  23. package/dist/adapters/gradle/services/gradle-version-update-strategy.js +36 -0
  24. package/dist/adapters/project-information.d.ts +58 -0
  25. package/dist/adapters/project-information.d.ts.map +1 -0
  26. package/dist/adapters/project-information.js +1 -0
  27. package/dist/changelog/index.d.ts +27 -0
  28. package/dist/changelog/index.d.ts.map +1 -0
  29. package/dist/changelog/index.js +204 -0
  30. package/dist/config/index.d.ts +122 -0
  31. package/dist/config/index.d.ts.map +1 -0
  32. package/dist/config/index.js +115 -0
  33. package/dist/factories/adapter-identifier-registry.d.ts +12 -0
  34. package/dist/factories/adapter-identifier-registry.d.ts.map +1 -0
  35. package/dist/factories/adapter-identifier-registry.js +24 -0
  36. package/dist/factories/module-system-factory.d.ts +10 -0
  37. package/dist/factories/module-system-factory.d.ts.map +1 -0
  38. package/dist/factories/module-system-factory.js +18 -0
  39. package/dist/git/index.d.ts +253 -0
  40. package/dist/git/index.d.ts.map +1 -0
  41. package/dist/git/index.js +581 -0
  42. package/dist/index.d.ts +23 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +22 -0
  45. package/dist/semver/index.d.ts +85 -0
  46. package/dist/semver/index.d.ts.map +1 -0
  47. package/dist/semver/index.js +176 -0
  48. package/dist/services/adapter-identifier-registry.d.ts +38 -0
  49. package/dist/services/adapter-identifier-registry.d.ts.map +1 -0
  50. package/dist/services/adapter-identifier-registry.js +59 -0
  51. package/dist/services/adapter-identifier.d.ts +31 -0
  52. package/dist/services/adapter-identifier.d.ts.map +1 -0
  53. package/dist/services/adapter-identifier.js +1 -0
  54. package/dist/services/adapter-metadata-provider.d.ts +51 -0
  55. package/dist/services/adapter-metadata-provider.d.ts.map +1 -0
  56. package/dist/services/adapter-metadata-provider.js +66 -0
  57. package/dist/services/changelog-generator.d.ts +13 -0
  58. package/dist/services/changelog-generator.d.ts.map +1 -0
  59. package/dist/services/changelog-generator.js +26 -0
  60. package/dist/services/commit-analyzer.d.ts +44 -0
  61. package/dist/services/commit-analyzer.d.ts.map +1 -0
  62. package/dist/services/commit-analyzer.js +86 -0
  63. package/dist/services/configuration-loader.d.ts +23 -0
  64. package/dist/services/configuration-loader.d.ts.map +1 -0
  65. package/dist/services/configuration-loader.js +79 -0
  66. package/dist/services/configuration-validator.d.ts +16 -0
  67. package/dist/services/configuration-validator.d.ts.map +1 -0
  68. package/dist/services/configuration-validator.js +24 -0
  69. package/dist/services/git-operations.d.ts +16 -0
  70. package/dist/services/git-operations.d.ts.map +1 -0
  71. package/dist/services/git-operations.js +89 -0
  72. package/dist/services/module-detector.d.ts +24 -0
  73. package/dist/services/module-detector.d.ts.map +1 -0
  74. package/dist/services/module-detector.js +1 -0
  75. package/dist/services/module-registry.d.ts +45 -0
  76. package/dist/services/module-registry.d.ts.map +1 -0
  77. package/dist/services/module-registry.js +57 -0
  78. package/dist/services/module-system-factory.d.ts +24 -0
  79. package/dist/services/module-system-factory.d.ts.map +1 -0
  80. package/dist/services/module-system-factory.js +1 -0
  81. package/dist/services/verse-runner.d.ts +45 -0
  82. package/dist/services/verse-runner.d.ts.map +1 -0
  83. package/dist/services/verse-runner.js +182 -0
  84. package/dist/services/version-applier.d.ts +26 -0
  85. package/dist/services/version-applier.d.ts.map +1 -0
  86. package/dist/services/version-applier.js +63 -0
  87. package/dist/services/version-bumper.d.ts +156 -0
  88. package/dist/services/version-bumper.d.ts.map +1 -0
  89. package/dist/services/version-bumper.js +291 -0
  90. package/dist/services/version-manager.d.ts +68 -0
  91. package/dist/services/version-manager.d.ts.map +1 -0
  92. package/dist/services/version-manager.js +94 -0
  93. package/dist/services/version-update-strategy.d.ts +18 -0
  94. package/dist/services/version-update-strategy.d.ts.map +1 -0
  95. package/dist/services/version-update-strategy.js +1 -0
  96. package/dist/utils/banner.d.ts +2 -0
  97. package/dist/utils/banner.d.ts.map +1 -0
  98. package/dist/utils/banner.js +8 -0
  99. package/dist/utils/commits.d.ts +12 -0
  100. package/dist/utils/commits.d.ts.map +1 -0
  101. package/dist/utils/commits.js +24 -0
  102. package/dist/utils/file.d.ts +7 -0
  103. package/dist/utils/file.d.ts.map +1 -0
  104. package/dist/utils/file.js +19 -0
  105. package/dist/utils/index.d.ts +6 -0
  106. package/dist/utils/index.d.ts.map +1 -0
  107. package/dist/utils/index.js +5 -0
  108. package/dist/utils/logger.d.ts +14 -0
  109. package/dist/utils/logger.d.ts.map +1 -0
  110. package/dist/utils/logger.js +22 -0
  111. package/dist/utils/properties.d.ts +16 -0
  112. package/dist/utils/properties.d.ts.map +1 -0
  113. package/dist/utils/properties.js +62 -0
  114. package/dist/utils/versioning.d.ts +8 -0
  115. package/dist/utils/versioning.d.ts.map +1 -0
  116. package/dist/utils/versioning.js +17 -0
  117. package/package.json +70 -0
@@ -0,0 +1,581 @@
1
+ /**
2
+ * Git operations module for μVERSE version management.
3
+ * Provides interfaces for commit analysis, tagging, and conventional commit parsing.
4
+ * Supports monorepo and multi-module projects with module-specific tag management.
5
+ */
6
+ import { CommitParser } from "conventional-commits-parser";
7
+ import { logger } from "../utils/logger.js";
8
+ import { execa } from "execa";
9
+ /**
10
+ * Shared CommitParser instance for parsing conventional commits.
11
+ * Reused across all commit parsing operations to avoid repeated instantiation.
12
+ */
13
+ const commitParser = new CommitParser({
14
+ breakingHeaderPattern: /^(\w*)(?:\(([\w$@.\-*/ ]*)\))?!: (.*)$/,
15
+ });
16
+ /**
17
+ * Retrieves all commits for a module since its last release tag.
18
+ * Handles monorepo and single-repo scenarios with path filtering.
19
+ * @param modulePath - Relative path to module from repository root (use '.' for root)
20
+ * @param moduleName - Module name used for tag searching
21
+ * @param moduleType - 'root' for general tags, 'module' for module-specific tags
22
+ * @param options - Git operation options
23
+ * @param excludePaths - Paths to exclude using git pathspec syntax
24
+ * @returns Promise resolving to array of parsed commits (oldest to newest)
25
+ */
26
+ export async function getCommitsSinceLastTag(modulePath, moduleName, moduleType, options = {}, excludePaths = []) {
27
+ // Resolve the working directory, defaulting to current process directory
28
+ const cwd = options.cwd || process.cwd();
29
+ try {
30
+ // Find the most recent tag for this module
31
+ // For root modules, this finds general tags (v1.0.0)
32
+ // For submodules, this finds module-specific tags (module@1.0.0)
33
+ const lastTag = await getLastTagForModule(moduleName, moduleType, { cwd });
34
+ // Build the git revision range
35
+ // If tag exists: 'tag..HEAD' means commits after tag up to HEAD
36
+ // If no tag: empty string means all commits in history
37
+ const range = lastTag ? `${lastTag}..HEAD` : "";
38
+ return getCommitsInRange(range, modulePath, { cwd }, excludePaths);
39
+ }
40
+ catch (error) {
41
+ // If tag lookup fails for any reason, fall back to all commits
42
+ // This ensures we always have commit history for version determination
43
+ return getCommitsInRange("", modulePath, { cwd }, excludePaths);
44
+ }
45
+ }
46
+ /**
47
+ * Retrieves commits within a specific git revision range with path filtering.
48
+ * Uses git's native pathspec syntax for efficient filtering in monorepos.
49
+ * @param range - Git revision range (e.g., 'tag1..tag2', 'tag..HEAD', or '' for all)
50
+ * @param pathFilter - Optional path to filter commits (use '.' for root)
51
+ * @param options - Git operation options
52
+ * @param excludePaths - Paths to exclude using ':(exclude)path' syntax
53
+ * @returns Promise resolving to array of parsed commits (oldest to newest)
54
+ */
55
+ export async function getCommitsInRange(range, pathFilter, options = {}, excludePaths = []) {
56
+ // Resolve working directory, defaulting to current directory
57
+ const cwd = options.cwd || process.cwd();
58
+ try {
59
+ // Build git log command with custom format for easy parsing
60
+ // Format: hash, subject, body, delimiter
61
+ const args = ["log", "--format=%H%n%s%n%b%n---COMMIT-END---"];
62
+ // Only add range if it's not empty
63
+ // Empty range means "all commits" which is valid
64
+ if (range.trim()) {
65
+ args.push(range);
66
+ }
67
+ // Add pathspec separator ('--') if we have paths or excludes
68
+ // This separates revision arguments from path arguments
69
+ // Add path filter if provided and not root
70
+ if (pathFilter && pathFilter !== ".") {
71
+ args.push("--", pathFilter);
72
+ }
73
+ else if (excludePaths.length > 0) {
74
+ // For root path, we still need to add the pathspec separator
75
+ // when we have exclude patterns
76
+ args.push("--");
77
+ }
78
+ // Add each exclude pattern using git's pathspec syntax
79
+ // :(exclude)path tells git to ignore commits touching that path
80
+ for (const excludePath of excludePaths) {
81
+ if (excludePath && excludePath !== ".") {
82
+ args.push(`:(exclude)${excludePath}`);
83
+ }
84
+ }
85
+ // Execute git log command
86
+ // Silent mode prevents output pollution in GitHub Actions
87
+ const { stdout } = await execa("git", args, { cwd });
88
+ // Parse the formatted output into CommitInfo objects
89
+ return parseGitLog(stdout);
90
+ }
91
+ catch (error) {
92
+ // Non-throwing error handling: log warning and return empty array
93
+ // This allows the system to continue even if git operations fail
94
+ logger.warning(`Warning: Failed to get git commits: ${error}`);
95
+ return [];
96
+ }
97
+ }
98
+ /**
99
+ * Parses raw git log output into structured CommitInfo objects with Conventional Commits analysis.
100
+ * Resilient to parsing failures - classifies non-conventional commits as 'unknown' type.
101
+ * @param output - Raw git log output using custom format
102
+ * @returns Array of parsed CommitInfo objects (empty if no valid commits)
103
+ * @internal
104
+ */
105
+ function parseGitLog(output) {
106
+ // Early return for empty output - no commits to parse
107
+ if (!output.trim()) {
108
+ return [];
109
+ }
110
+ logger.debug(`Raw git log output:\n${output}`);
111
+ const commits = [];
112
+ // Split output into individual commit blocks using custom delimiter
113
+ // Filter removes empty blocks (trailing delimiters, etc.)
114
+ const commitBlocks = output
115
+ .split("---COMMIT-END---")
116
+ .filter((block) => block.trim());
117
+ for (const block of commitBlocks) {
118
+ // Split block into lines: [hash, subject, body...]
119
+ const lines = block.trim().split("\n");
120
+ // Skip malformed blocks (need at least hash and subject)
121
+ if (lines.length < 2) {
122
+ logger.debug(`Skipping malformed commit block:\n${block}`);
123
+ continue;
124
+ }
125
+ // Extract structured data from the block
126
+ const hash = lines[0]; // Line 1: commit SHA
127
+ const subject = lines[1]; // Line 2: commit message subject
128
+ const body = lines.slice(2).join("\n").trim(); // Remaining: commit body
129
+ logger.debug(`Processing commit ${hash} with subject: ${subject}`);
130
+ logger.debug(`Commit body:\n${body}`);
131
+ try {
132
+ // Parse using Conventional Commits specification
133
+ // Combines subject and body for full context (breaking changes may be in body)
134
+ const parsed = commitParser.parse(subject + "\n\n" + body);
135
+ logger.debug(`Parsed commit ${hash}: ${JSON.stringify(parsed)}`);
136
+ // Build CommitInfo from parsed data
137
+ commits.push({
138
+ hash,
139
+ type: parsed.type || "unknown", // Default to 'unknown' if type missing
140
+ scope: parsed.scope || undefined,
141
+ subject: parsed.subject || subject, // Fallback to raw subject if parsing fails
142
+ body: body || undefined,
143
+ // Check if any note has title 'BREAKING CHANGE'
144
+ breaking: parsed.notes?.some((note) => note.title === "BREAKING CHANGE") ||
145
+ false,
146
+ });
147
+ }
148
+ catch (error) {
149
+ // If conventional commits parsing fails, treat as unknown type
150
+ // This ensures non-conventional commits don't break the system
151
+ commits.push({
152
+ hash,
153
+ type: "unknown",
154
+ subject,
155
+ body: body || undefined,
156
+ breaking: false,
157
+ });
158
+ }
159
+ }
160
+ return commits;
161
+ }
162
+ /**
163
+ * Finds the most recent git tag for a specific module with fallback to general tags.
164
+ * Searches module-specific tags first (moduleName@*), then falls back to general tags.
165
+ * @param moduleName - Module name for tag pattern construction
166
+ * @param moduleType - 'root' skips module tags, 'module' tries module tags first
167
+ * @param options - Git operation options
168
+ * @returns Most recent tag name or null if no tags exist
169
+ */
170
+ export async function getLastTagForModule(moduleName, moduleType, options = {}) {
171
+ // Resolve working directory, defaulting to current directory
172
+ const cwd = options.cwd || process.cwd();
173
+ try {
174
+ // Generate glob pattern for module-specific tags (e.g., 'api@*')
175
+ const moduleTagPattern = getModuleTagPattern(moduleName);
176
+ // Only search for module-specific tags if it's not root
177
+ // Root projects use general tags (v1.0.0) rather than module tags (root@1.0.0)
178
+ if (moduleType !== "root") {
179
+ // Search for module-specific tags with version sorting
180
+ // --sort=-version:refname: Sort by version in descending order (newest first)
181
+ const { stdout } = await execa("git", ["tag", "-l", moduleTagPattern, "--sort=-version:refname"], {
182
+ cwd,
183
+ });
184
+ // If we found module-specific tags, return the first (most recent)
185
+ if (stdout.trim()) {
186
+ return stdout.trim().split("\n")[0];
187
+ }
188
+ }
189
+ // Fallback to general tags when:
190
+ // 1. Module type is 'root', or
191
+ // 2. No module-specific tags were found
192
+ try {
193
+ // git describe finds the most recent tag reachable from HEAD
194
+ // --tags: Consider all tags (not just annotated)
195
+ // --abbrev=0: Don't show commit hash suffix
196
+ const { stdout: fallbackOutput } = await execa("git", ["describe", "--tags", "--abbrev=0", "HEAD"], {
197
+ cwd,
198
+ });
199
+ return fallbackOutput.trim();
200
+ }
201
+ catch {
202
+ // If no tags at all, return null
203
+ // This typically means it's a new repository or no releases yet
204
+ return null;
205
+ }
206
+ }
207
+ catch (error) {
208
+ // Catch-all error handler: return null if any unexpected error occurs
209
+ // This makes the function non-throwing, which is safer for version calculations
210
+ return null;
211
+ }
212
+ }
213
+ /**
214
+ * Retrieves all git tags in the repository with parsed metadata.
215
+ * Returns array with tag name, commit hash, and parsed module/version information.
216
+ * @param options - Git operation options
217
+ * @returns Promise resolving to array of GitTag objects (empty array if no tags exist)
218
+ */
219
+ export async function getAllTags(options = {}) {
220
+ // Resolve working directory
221
+ const cwd = options.cwd || process.cwd();
222
+ try {
223
+ // List all tags with custom format to get name and commit hash
224
+ // %(refname:short): Tag name without refs/tags/ prefix
225
+ // %(objectname): Full commit SHA that the tag points to
226
+ const { stdout } = await execa("git", ["tag", "-l", "--format=%(refname:short) %(objectname)"], {
227
+ cwd,
228
+ });
229
+ // Parse each line into a GitTag object
230
+ return stdout
231
+ .trim()
232
+ .split("\n")
233
+ .filter((line) => line.trim()) // Remove empty lines
234
+ .map((line) => {
235
+ // Each line format: "tagname commithash"
236
+ const [name, hash] = line.split(" ");
237
+ // Parse tag name to extract module and version (if present)
238
+ const { module, version } = parseTagName(name);
239
+ // Return structured tag object
240
+ return {
241
+ name,
242
+ hash,
243
+ module,
244
+ version,
245
+ };
246
+ });
247
+ }
248
+ catch (error) {
249
+ // Non-throwing: return empty array if git command fails
250
+ // This could happen if not in a git repository or no tags exist
251
+ return [];
252
+ }
253
+ }
254
+ /**
255
+ * Creates an annotated git tag at the current HEAD commit.
256
+ * Annotated tags include tagger metadata, date, message, and can be GPG signed.
257
+ * @param tagName - The tag name (e.g., 'core@1.0.0' or 'v1.0.0'). Must not already exist
258
+ * @param message - The annotation message for the tag
259
+ * @param options - Git operation options
260
+ * @returns Promise that resolves when the tag is successfully created
261
+ * @throws {Error} If tag creation fails (tag exists, invalid name, etc.)
262
+ */
263
+ export async function createTag(tagName, message, options = {}) {
264
+ // Resolve working directory
265
+ const cwd = options.cwd || process.cwd();
266
+ try {
267
+ // Create annotated tag with message
268
+ // -a: Create an annotated tag (full git object)
269
+ // -m: Provide tag message inline
270
+ await execa("git", ["tag", "-a", tagName, "-m", message], { cwd });
271
+ }
272
+ catch (error) {
273
+ // Wrap error with more context for debugging
274
+ // Common failures: tag exists, no git repo, no user config
275
+ throw new Error(`Failed to create tag ${tagName}: ${error}`);
276
+ }
277
+ }
278
+ /**
279
+ * Pushes all local tags to the configured remote repository.
280
+ * Only pushes tags that don't exist on remote. Does NOT push commits.
281
+ * @param options - Git operation options
282
+ * @returns Promise that resolves when all tags are successfully pushed
283
+ * @throws {Error} If push fails (no remote, authentication, network, conflicts, etc.)
284
+ */
285
+ export async function pushTags(options = {}) {
286
+ // Resolve working directory
287
+ const cwd = options.cwd || process.cwd();
288
+ try {
289
+ // Push all tags to the remote repository
290
+ // --tags: Push all tags (annotated and lightweight)
291
+ // This does NOT push commits, only tags
292
+ await execa("git", ["push", "--tags"], { cwd });
293
+ }
294
+ catch (error) {
295
+ // Wrap error with context
296
+ // Common failures: no remote, auth, network, conflicts
297
+ throw new Error(`Failed to push tags: ${error}`);
298
+ }
299
+ }
300
+ /**
301
+ * Generates a glob pattern for searching module-specific git tags (moduleName@*).
302
+ * @param moduleName - The name of the module
303
+ * @returns A glob pattern string matching all tags for the module
304
+ * @internal
305
+ */
306
+ function getModuleTagPattern(moduleName) {
307
+ // Create glob pattern for module-specific tags
308
+ // Format: moduleName@* where * matches any version
309
+ return `${moduleName}@*`;
310
+ }
311
+ /**
312
+ * Parses a git tag name to extract module and version components.
313
+ *
314
+ * This internal utility function handles multiple tag naming conventions used in
315
+ * μVERSE and returns a structured object with extracted metadata. It supports:
316
+ * - **Module tags**: `moduleName@version` (monorepo convention)
317
+ * - **Version tags**: `v{version}` or `{version}` (single repo convention)
318
+ * - **Custom tags**: Returns empty object for unrecognized formats
319
+ *
320
+ * @param tagName - The full git tag name to parse.
321
+ * Can be any string, but structured formats are recognized.
322
+ *
323
+ * @returns Object with optional `module` and `version` fields:
324
+ * - Both present: Module tag (e.g., `core@1.0.0`)
325
+ * - Only version: General tag (e.g., `v1.0.0`)
326
+ * - Empty object: Unrecognized format
327
+ * @internal
328
+ */
329
+ function parseTagName(tagName) {
330
+ // Try to match module-specific tag pattern: moduleName@version
331
+ // Regex: ^(.+)@(.+)$
332
+ // ^(.+) - Start of string, capture group 1 (module name, greedy)
333
+ // @ - Literal @ separator
334
+ // (.+)$ - Capture group 2 (version, greedy), end of string
335
+ const match = tagName.match(/^(.+)@(.+)$/);
336
+ if (match) {
337
+ // Module tag matched - return both components
338
+ return {
339
+ module: match[1],
340
+ version: match[2],
341
+ };
342
+ }
343
+ // Try to match version-only tag pattern: v?MAJOR.MINOR.PATCH...
344
+ // Regex: ^v?(\d+\.\d+\.\d+.*)$
345
+ // ^v? - Start, optional 'v' prefix
346
+ // (\d+\.\d+\.\d+ - Capture group: MAJOR.MINOR.PATCH (digits)
347
+ // .*)$ - Any remaining characters (pre-release, metadata), end
348
+ const versionMatch = tagName.match(/^v?(\d+\.\d+\.\d+.*)$/);
349
+ if (versionMatch) {
350
+ // Version tag matched - return only version (no module)
351
+ return {
352
+ version: versionMatch[1],
353
+ };
354
+ }
355
+ // Unrecognized format - return empty object
356
+ return {};
357
+ }
358
+ /**
359
+ * Checks if the git working directory is clean (no uncommitted changes).
360
+ * Uses `git status --porcelain` to detect modified, staged, deleted, or untracked files.
361
+ * @param options - Git operation options
362
+ * @returns Promise resolving to true if clean, false if there are changes or on error
363
+ */
364
+ export async function isWorkingDirectoryClean(options = {}) {
365
+ // Resolve working directory
366
+ const cwd = options.cwd || process.cwd();
367
+ try {
368
+ // Get machine-readable status output
369
+ // --porcelain: Stable, easy-to-parse format
370
+ const { stdout } = await execa("git", ["status", "--porcelain"], {
371
+ cwd,
372
+ });
373
+ // Empty output means clean working directory
374
+ // Any output indicates changes (modified, untracked, staged, etc.)
375
+ return stdout.trim() === "";
376
+ }
377
+ catch (error) {
378
+ // On error, assume directory is not clean (safe default)
379
+ // This could happen if not a git repo, or permissions issue
380
+ return false;
381
+ }
382
+ }
383
+ /**
384
+ * Retrieves the name of the currently checked out git branch.
385
+ *
386
+ * This function returns the active branch name, which is useful for:
387
+ * - Conditional logic based on branch (e.g., only release from 'main')
388
+ * - CI/CD branch-specific workflows
389
+ * - Logging and debugging
390
+ * - Branch validation before operations
391
+ *
392
+ * Returns empty string if in detached HEAD state.
393
+ * @param options - Git operation options
394
+ * @returns Promise resolving to the current branch name (empty string if detached HEAD)
395
+ * @throws {Error} If git command fails
396
+ */
397
+ export async function getCurrentBranch(options = {}) {
398
+ // Resolve working directory
399
+ const cwd = options.cwd || process.cwd();
400
+ try {
401
+ // Get the current branch name
402
+ // --show-current: Returns active branch name or empty string if detached
403
+ const { stdout } = await execa("git", ["branch", "--show-current"], {
404
+ cwd,
405
+ });
406
+ // Return branch name (or empty string for detached HEAD)
407
+ return stdout.trim();
408
+ }
409
+ catch (error) {
410
+ // Wrap error with context
411
+ throw new Error(`Failed to get current branch: ${error}`);
412
+ }
413
+ }
414
+ /**
415
+ * Retrieves the abbreviated (short) SHA-1 hash of the current HEAD commit.
416
+ *
417
+ * This function returns a shortened version of the commit hash (typically 7 characters),
418
+ * which is:
419
+ * - Human-readable and easier to reference
420
+ * - Suitable for build metadata in semantic versions
421
+ * - Commonly used in CI/CD for build identification
422
+ * - Still unique enough for most repositories
423
+ *
424
+ * The short SHA is git's default abbreviated format and balances uniqueness with brevity.
425
+ *
426
+ * @param options - Git operation options, primarily for specifying working directory.
427
+ *
428
+ * @returns Promise resolving to the abbreviated commit SHA.
429
+ * Typically 7 characters (e.g., 'abc1234').
430
+ * Length may vary based on repository size to ensure uniqueness.
431
+ *
432
+ * @throws {Error} If git command fails:
433
+ * - Not in a git repository
434
+ * - No commits exist (empty repository)
435
+ * - Permissions issues
436
+ */
437
+ export async function getCurrentCommitShortSha(options = {}) {
438
+ // Resolve working directory
439
+ const cwd = options.cwd || process.cwd();
440
+ try {
441
+ // Get abbreviated commit SHA
442
+ // rev-parse: Resolve git revision to commit hash
443
+ // --short: Return abbreviated version (typically 7 chars)
444
+ // HEAD: The current commit
445
+ const { stdout } = await execa("git", ["rev-parse", "--short", "HEAD"], {
446
+ cwd,
447
+ });
448
+ // Return the short SHA
449
+ return stdout.trim();
450
+ }
451
+ catch (error) {
452
+ // Wrap error with context
453
+ throw new Error(`Failed to get current commit SHA: ${error}`);
454
+ }
455
+ }
456
+ /**
457
+ * Stages all changed files in the working directory for the next commit.
458
+ *
459
+ * This function executes `git add .` which stages:
460
+ * - All modified tracked files
461
+ * - All new untracked files
462
+ * - All deleted files
463
+ *
464
+ * **Warning**: This stages **everything** in the working directory. Use with caution
465
+ * in interactive environments. For selective staging, use git commands directly.
466
+ *
467
+ * @param options - Git operation options, primarily for specifying working directory.
468
+ *
469
+ * @returns Promise that resolves when all files are successfully staged.
470
+ *
471
+ * @throws {Error} If git add command fails:
472
+ * - Not in a git repository
473
+ * - Permissions issues
474
+ * - Invalid .gitignore patterns
475
+ */
476
+ export async function addChangedFiles(options = {}) {
477
+ // Resolve working directory
478
+ const cwd = options.cwd || process.cwd();
479
+ try {
480
+ // Stage all changes in the working directory
481
+ // '.': Current directory and all subdirectories
482
+ await execa("git", ["add", "."], { cwd });
483
+ }
484
+ catch (error) {
485
+ // Wrap error with context
486
+ throw new Error(`Failed to add changed files: ${error}`);
487
+ }
488
+ }
489
+ /**
490
+ * Creates a git commit with the specified message using currently staged changes.
491
+ * Files must be staged first (via `git add`). Follows Conventional Commits format.
492
+ * @param message - The commit message (e.g., 'feat: description', 'fix: description')
493
+ * @param options - Git operation options
494
+ * @returns Promise that resolves when commit is created
495
+ * @throws {Error} If commit fails (no staged changes, no git user, empty message, etc.)
496
+ */
497
+ export async function commitChanges(message, options = {}) {
498
+ // Resolve working directory
499
+ const cwd = options.cwd || process.cwd();
500
+ try {
501
+ // Create commit with staged changes
502
+ // -m: Specify commit message inline
503
+ await execa("git", ["commit", "-m", message], { cwd });
504
+ }
505
+ catch (error) {
506
+ // Wrap error with context
507
+ throw new Error(`Failed to commit changes: ${error}`);
508
+ }
509
+ }
510
+ /**
511
+ * Pushes local commits to the remote repository.
512
+ *
513
+ * This function uploads all commits from the current branch that don't exist
514
+ * on the remote. It uses `git push` without arguments, which:
515
+ * - Pushes the current branch to its configured upstream
516
+ * - Only pushes commits (use `pushTags()` for tags)
517
+ * - Requires network access and authentication
518
+ *
519
+ * @param options - Git operation options, primarily for specifying working directory.
520
+ *
521
+ * @returns Promise that resolves when commits are successfully pushed.
522
+ *
523
+ * @throws {Error} If push fails:
524
+ * - No remote configured
525
+ * - No upstream branch set
526
+ * - Authentication failure
527
+ * - Network issues
528
+ * - Remote rejects (e.g., force push needed, protected branch)
529
+ */
530
+ export async function pushCommits(options = {}) {
531
+ // Resolve working directory
532
+ const cwd = options.cwd || process.cwd();
533
+ try {
534
+ // Push commits to remote
535
+ // No arguments: Push current branch to configured upstream
536
+ await execa("git", ["push"], { cwd });
537
+ }
538
+ catch (error) {
539
+ // Wrap error with context
540
+ throw new Error(`Failed to push commits: ${error}`);
541
+ }
542
+ }
543
+ /**
544
+ * Checks if there are any changes in the working directory or staging area.
545
+ *
546
+ * This function is similar to `isWorkingDirectoryClean()` but returns the opposite
547
+ * boolean value. It's useful when you want to check if there's work to commit.
548
+ *
549
+ * Uses `git status --porcelain` to detect:
550
+ * - Modified tracked files
551
+ * - New untracked files
552
+ * - Deleted files
553
+ * - Staged changes
554
+ *
555
+ * @param options - Git operation options, primarily for specifying working directory.
556
+ *
557
+ * @returns Promise resolving to:
558
+ * - `true`: There are changes (modified, staged, untracked files)
559
+ * - `false`: Working directory is clean OR git command failed
560
+ *
561
+ * @throws {Error} If git status command fails.
562
+ * Unlike `isWorkingDirectoryClean()`, this function throws on errors.
563
+ */
564
+ export async function hasChangesToCommit(options = {}) {
565
+ // Resolve working directory
566
+ const cwd = options.cwd || process.cwd();
567
+ try {
568
+ // Get machine-readable status output
569
+ // --porcelain: Stable, easy-to-parse format
570
+ const { stdout } = await execa("git", ["status", "--porcelain"], {
571
+ cwd,
572
+ });
573
+ // If output is not empty, there are changes
574
+ // Returns true if changes exist, false if clean
575
+ return stdout.trim().length > 0;
576
+ }
577
+ catch (error) {
578
+ // Throw on error (unlike isWorkingDirectoryClean which returns false)
579
+ throw new Error(`Failed to check git status: ${error}`);
580
+ }
581
+ }
@@ -0,0 +1,23 @@
1
+ export * from './config/index.js';
2
+ export { VerseRunner } from './services/verse-runner.js';
3
+ export type { RunnerOptions, RunnerResult } from './services/verse-runner.js';
4
+ export { VersionManager } from './services/version-manager.js';
5
+ export { ModuleRegistry } from './services/module-registry.js';
6
+ export { VersionBumper } from './services/version-bumper.js';
7
+ export type { VersionBumperOptions } from './services/version-bumper.js';
8
+ export { VersionApplier } from './services/version-applier.js';
9
+ export type { VersionApplierOptions, ModuleChangeResult } from './services/version-applier.js';
10
+ export { ChangelogGenerator } from './services/changelog-generator.js';
11
+ export { GitOperations } from './services/git-operations.js';
12
+ export type { GitOperationsOptions } from './services/git-operations.js';
13
+ export { CommitAnalyzer } from './services/commit-analyzer.js';
14
+ export { ConfigurationLoader } from './services/configuration-loader.js';
15
+ export type { AdapterIdentifier, AdapterMetadata } from './services/adapter-identifier.js';
16
+ export { AdapterIdentifierRegistry } from './services/adapter-identifier-registry.js';
17
+ export type { ProjectInformation, Module } from './adapters/project-information.js';
18
+ export { createModuleSystemFactory } from './factories/module-system-factory.js';
19
+ export type { ModuleSystemFactory } from './services/module-system-factory.js';
20
+ export * from './git/index.js';
21
+ export * from './semver/index.js';
22
+ export * from './utils/index.js';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAC/F,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC3F,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAGtF,YAAY,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAGpF,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAG/E,cAAc,gBAAgB,CAAC;AAG/B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ // Core μVERSE exports - business logic without GitHub Actions dependency
2
+ // Configuration
3
+ export * from './config/index.js';
4
+ // Services
5
+ export { VerseRunner } from './services/verse-runner.js';
6
+ export { VersionManager } from './services/version-manager.js';
7
+ export { ModuleRegistry } from './services/module-registry.js';
8
+ export { VersionBumper } from './services/version-bumper.js';
9
+ export { VersionApplier } from './services/version-applier.js';
10
+ export { ChangelogGenerator } from './services/changelog-generator.js';
11
+ export { GitOperations } from './services/git-operations.js';
12
+ export { CommitAnalyzer } from './services/commit-analyzer.js';
13
+ export { ConfigurationLoader } from './services/configuration-loader.js';
14
+ export { AdapterIdentifierRegistry } from './services/adapter-identifier-registry.js';
15
+ // Factories
16
+ export { createModuleSystemFactory } from './factories/module-system-factory.js';
17
+ // Git utilities
18
+ export * from './git/index.js';
19
+ // Semver utilities
20
+ export * from './semver/index.js';
21
+ // Utilities
22
+ export * from './utils/index.js';