fireflyy 4.0.0-alpha.8 → 4.0.0-dev.25c80f6

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.
@@ -35,6 +35,10 @@
35
35
  "default": "",
36
36
  "type": "string"
37
37
  },
38
+ "branch": {
39
+ "description": "Git branch to release from.",
40
+ "type": "string"
41
+ },
38
42
  "changelogPath": {
39
43
  "description": "Changelog file path, relative to project root.",
40
44
  "default": "CHANGELOG.md",
@@ -1,5 +1,6 @@
1
+ import { t as logger } from "./main.js";
1
2
  import { c as notFoundErrAsync } from "./result.constructors-C9M1MP3_.js";
2
- import { n as wrapPromise } from "./result.utilities-oXWCXEvw.js";
3
+ import { n as wrapPromise } from "./result.utilities-DC5shlhT.js";
3
4
  import { t as withDryRun } from "./dry-run-BfYCtldz.js";
4
5
 
5
6
  //#region src/services/implementations/filesystem.service.ts
@@ -29,21 +30,18 @@ var DefaultFileSystemService = class {
29
30
  }
30
31
  exists(path) {
31
32
  const resolved = this.resolvePath(path);
32
- return wrapPromise(Bun.file(resolved).exists());
33
+ return wrapPromise(Bun.file(resolved).exists()).andTee(() => logger.verbose(`DefaultFileSystemService: Checked existence of file: ${resolved}`));
33
34
  }
34
35
  read(path) {
35
36
  const resolved = this.resolvePath(path);
36
37
  const file = Bun.file(resolved);
37
38
  return wrapPromise(file.exists()).andThen((fileExists) => {
38
- if (!fileExists) return notFoundErrAsync({
39
- message: `File not found: ${resolved}`,
40
- source: "FileSystemService.read"
41
- });
42
- return wrapPromise(file.text());
39
+ if (!fileExists) return notFoundErrAsync({ message: `File not found: ${resolved}` });
40
+ return wrapPromise(file.text()).andTee(() => logger.verbose(`DefaultFileSystemService: Read file: ${resolved}`));
43
41
  });
44
42
  }
45
43
  write(path, content, options) {
46
- return withDryRun(options, `Writing to ${this.resolvePath(path)}`, () => wrapPromise(Bun.write(this.resolvePath(path), content).then(() => {})));
44
+ return withDryRun(options, `Writing to ${this.resolvePath(path)}`, () => wrapPromise(Bun.write(this.resolvePath(path), content).then(() => {})).andTee(() => logger.verbose(`DefaultFileSystemService: Wrote file: ${this.resolvePath(path)}`)));
47
45
  }
48
46
  };
49
47
  /**
@@ -250,14 +250,14 @@ var DefaultGitService = class {
250
250
  return executeGitCommand(args, {
251
251
  cwd: this.cwd,
252
252
  dryRun: options?.dryRun,
253
- verbose: options?.verbose ?? false
253
+ verbose: options?.verbose ?? true
254
254
  });
255
255
  }
256
256
  isInsideRepository() {
257
257
  return this.git(["rev-parse", "--is-inside-work-tree"]).map(() => true).orElse(() => FireflyOkAsync(false));
258
258
  }
259
259
  getRepositoryRoot() {
260
- return this.git(["rev-parse", "--show-toplevel"]).map((output) => output.trim());
260
+ return this.git(["rev-parse", "--show-toplevel"]).andTee(() => logger.verbose("DefaultGitService: Resolving repository root")).map((output) => output.trim()).andTee(() => logger.verbose("DefaultGitService: Repository root resolved"));
261
261
  }
262
262
  getRemoteUrl(remote) {
263
263
  const remoteName = remote ?? "origin";
@@ -280,12 +280,14 @@ var DefaultGitService = class {
280
280
  else if (index !== " " && index !== "?") hasStaged = true;
281
281
  if (workTree !== " " && workTree !== "?") hasUnstaged = true;
282
282
  }
283
- return {
283
+ const status = {
284
284
  hasStaged,
285
285
  hasUnstaged,
286
286
  hasUntracked,
287
287
  isClean: lines.length === 0
288
288
  };
289
+ logger.verbose(`DefaultGitService: Git status: staged=${status.hasStaged},unstaged=${status.hasUnstaged},untracked=${status.hasUntracked},clean=${status.isClean}`);
290
+ return status;
289
291
  });
290
292
  }
291
293
  isWorkingTreeClean() {
@@ -312,7 +314,7 @@ var DefaultGitService = class {
312
314
  const includeStaged = filter?.staged ?? true;
313
315
  const includeUnstaged = filter?.unstaged ?? true;
314
316
  return this.git(["status", "--porcelain"]).map((output) => {
315
- return this.parseStatusOutput(output).filter((file) => {
317
+ const filtered = this.parseStatusOutput(output).filter((file) => {
316
318
  const isStaged = file.indexStatus !== " " && file.indexStatus !== "?";
317
319
  const isUnstaged = file.workTreeStatus !== " " && file.workTreeStatus !== "?";
318
320
  if (includeStaged && includeUnstaged) return isStaged || isUnstaged;
@@ -320,6 +322,8 @@ var DefaultGitService = class {
320
322
  if (includeUnstaged) return isUnstaged;
321
323
  return false;
322
324
  });
325
+ logger.verbose(`DefaultGitService: Found ${filtered.length} file(s) for filter staged=${includeStaged} unstaged=${includeUnstaged}`);
326
+ return filtered;
323
327
  });
324
328
  }
325
329
  getFileNames(filter) {
@@ -344,11 +348,13 @@ var DefaultGitService = class {
344
348
  const isRemote = line.includes("remotes/");
345
349
  let branchName = line.replace(CURRENT_BRANCH_MARKER_REGEX, "").trim();
346
350
  if (isRemote) branchName = branchName.replace(REMOTES_PREFIX_REGEX, "");
347
- return {
351
+ const branch = {
348
352
  name: branchName,
349
353
  isCurrent,
350
354
  isRemote
351
355
  };
356
+ logger.verbose(`DefaultGitService: Parsed branch: ${branch.name} current=${branch.isCurrent} remote=${branch.isRemote}`);
357
+ return branch;
352
358
  }
353
359
  listBranches(includeRemote) {
354
360
  const args = ["branch"];
@@ -359,7 +365,7 @@ var DefaultGitService = class {
359
365
  }
360
366
  createCommit(message, options) {
361
367
  if (options?.dryRun) {
362
- logger.verbose("GitService: Dry run, skipping commit");
368
+ logger.verbose("DefaultGitService: Dry run, skipping commit");
363
369
  return FireflyOkAsync({ sha: "dry-run-sha" });
364
370
  }
365
371
  const args = [
@@ -402,6 +408,7 @@ var DefaultGitService = class {
402
408
  `${upstream}..HEAD`
403
409
  ]).map((output) => {
404
410
  const count = Number.parseInt(output.trim(), 10) || 0;
411
+ logger.verbose(`DefaultGitService: Unpushed commits count for ${upstream}: ${count}`);
405
412
  return {
406
413
  hasUnpushed: count > 0,
407
414
  count
@@ -413,6 +420,7 @@ var DefaultGitService = class {
413
420
  "HEAD"
414
421
  ]).map((output) => {
415
422
  const count = Number.parseInt(output.trim(), 10) || 0;
423
+ logger.verbose(`DefaultGitService: Total commit count: ${count}`);
416
424
  return {
417
425
  hasUnpushed: count > 0,
418
426
  count
@@ -426,7 +434,7 @@ var DefaultGitService = class {
426
434
  }
427
435
  createTag(name, options) {
428
436
  if (options?.dryRun) {
429
- logger.verbose(`GitService: Dry run, skipping tag creation: ${name}`);
437
+ logger.verbose(`DefaultGitService: Dry run, skipping tag creation: ${name}`);
430
438
  return FireflyOkAsync(void 0);
431
439
  }
432
440
  const args = ["tag"];
@@ -439,7 +447,7 @@ var DefaultGitService = class {
439
447
  const scope = options?.scope ?? "local";
440
448
  const remote = options?.remote ?? "origin";
441
449
  if (options?.dryRun) {
442
- logger.verbose(`GitService: Dry run, skipping tag deletion (${scope}): ${name}`);
450
+ logger.verbose(`DefaultGitService: Dry run, skipping tag deletion (${scope}): ${name}`);
443
451
  return FireflyOkAsync(void 0);
444
452
  }
445
453
  if (scope === "local") return this.git([
@@ -447,11 +455,15 @@ var DefaultGitService = class {
447
455
  "-d",
448
456
  name
449
457
  ]).map(() => void 0);
450
- if (scope === "remote") return this.git([
451
- "push",
452
- remote,
453
- `:refs/tags/${name}`
454
- ]).map(() => void 0);
458
+ if (scope === "remote") {
459
+ logger.verbose(`DefaultGitService: Deleting remote tag: ${name} on ${remote}`);
460
+ return this.git([
461
+ "push",
462
+ remote,
463
+ `:refs/tags/${name}`
464
+ ]).andTee(() => logger.verbose(`DefaultGitService: Remote tag deleted: ${name} on ${remote}`)).map(() => void 0);
465
+ }
466
+ logger.verbose(`DefaultGitService: Deleting tag locally and remotely: ${name} on ${remote}`);
455
467
  return this.git([
456
468
  "tag",
457
469
  "-d",
@@ -460,17 +472,21 @@ var DefaultGitService = class {
460
472
  "push",
461
473
  remote,
462
474
  `:refs/tags/${name}`
463
- ])).map(() => void 0);
475
+ ])).andTee(() => logger.verbose(`DefaultGitService: Local and remote tag deleted: ${name} on ${remote}`)).map(() => void 0);
464
476
  }
465
477
  hasTag(name) {
466
478
  return this.git([
467
479
  "tag",
468
480
  "--list",
469
481
  name
470
- ]).map((output) => output.trim() === name);
482
+ ]).map((output) => {
483
+ const exists = output.trim() === name;
484
+ logger.verbose(`DefaultGitService: Tag ${name} exists=${exists}`);
485
+ return exists;
486
+ });
471
487
  }
472
488
  hasAnyTags() {
473
- return this.getLatestTag().map((tag) => tag !== null);
489
+ return this.getLatestTag().andTee(() => logger.verbose("DefaultGitService: Checking if any tags exist")).map((tag) => tag !== null);
474
490
  }
475
491
  listTags() {
476
492
  return this.git(["tag", "--list"]).map((output) => output.split("\n").map((tag) => tag.trim()).filter((tag) => tag.length > 0));
@@ -494,25 +510,28 @@ var DefaultGitService = class {
494
510
  "--format=%(contents)",
495
511
  name
496
512
  ]).map((output) => {
497
- return output.trim() || null;
513
+ const message = output.trim();
514
+ logger.verbose(`DefaultGitService: Tag message for ${name}: ${message?.substring(0, 60) ?? "(none)"}`);
515
+ return message || null;
498
516
  }).orElse(() => FireflyOkAsync(null));
499
517
  }
500
518
  stage(paths) {
501
519
  const pathArray = Array.isArray(paths) ? paths : [paths];
502
- return this.git(["add", ...pathArray]).map(() => void 0);
520
+ return this.git(["add", ...pathArray]).andTee(() => logger.verbose(`DefaultGitService: Staged paths: ${pathArray.join(", ")}`)).map(() => void 0);
503
521
  }
504
522
  unstage(paths) {
505
523
  const pathArray = Array.isArray(paths) ? paths : [paths];
524
+ logger.verbose(`DefaultGitService: Unstaging paths: ${pathArray.join(", ")}`);
506
525
  return this.git([
507
526
  "reset",
508
527
  "HEAD",
509
528
  "--",
510
529
  ...pathArray
511
- ]).map(() => void 0);
530
+ ]).andTee(() => logger.verbose(`DefaultGitService: Unstaged paths: ${pathArray.join(", ")}`)).map(() => void 0);
512
531
  }
513
532
  push(options) {
514
533
  if (options?.dryRun) {
515
- logger.verbose("GitService: Dry run, skipping push");
534
+ logger.verbose("DefaultGitService: Dry run, skipping push");
516
535
  return FireflyOkAsync(void 0);
517
536
  }
518
537
  const args = ["push"];
@@ -523,6 +542,59 @@ var DefaultGitService = class {
523
542
  if (options?.followTags) args.push("--follow-tags");
524
543
  return this.git(args).map(() => void 0);
525
544
  }
545
+ /**
546
+ * Gets the upstream remote name for the current branch.
547
+ * @returns The remote name or null if no upstream is configured.
548
+ */
549
+ getUpstreamRemote() {
550
+ return this.git([
551
+ "rev-parse",
552
+ "--abbrev-ref",
553
+ "--symbolic-full-name",
554
+ "@{upstream}"
555
+ ]).map((output) => {
556
+ const upstream = output.trim();
557
+ const slashIndex = upstream.indexOf("/");
558
+ if (slashIndex > 0) return upstream.substring(0, slashIndex);
559
+ return null;
560
+ }).orElse(() => FireflyOkAsync(null));
561
+ }
562
+ /**
563
+ * Lists all configured remotes.
564
+ * @returns Array of remote names.
565
+ */
566
+ listRemotes() {
567
+ return this.git(["remote"]).map((output) => output.split("\n").map((remote) => remote.trim()).filter((remote) => remote.length > 0));
568
+ }
569
+ inferRepositoryUrl() {
570
+ return this.getUpstreamRemote().andThen((upstreamRemote) => {
571
+ if (upstreamRemote) {
572
+ logger.verbose(`DefaultGitService: Inferring repository URL from upstream remote: ${upstreamRemote}`);
573
+ return this.getRemoteUrl(upstreamRemote).map((url) => url).orElse(() => this.tryOriginOrFirstRemote());
574
+ }
575
+ logger.verbose("DefaultGitService: No upstream remote; falling back to origin or first remote");
576
+ return this.tryOriginOrFirstRemote();
577
+ });
578
+ }
579
+ /**
580
+ * Tries to get the repository URL from 'origin' or the first available remote.
581
+ */
582
+ tryOriginOrFirstRemote() {
583
+ return this.getRemoteUrl("origin").map((url) => {
584
+ logger.verbose("DefaultGitService: Inferring repository URL from origin remote");
585
+ return url;
586
+ }).orElse(() => {
587
+ return this.listRemotes().andThen((remotes) => {
588
+ if (remotes.length === 0) {
589
+ logger.verbose("DefaultGitService: No remotes configured, cannot infer repository URL");
590
+ return FireflyOkAsync(null);
591
+ }
592
+ const firstRemote = remotes[0];
593
+ logger.verbose(`DefaultGitService: Inferring repository URL from first remote: ${firstRemote}`);
594
+ return this.getRemoteUrl(firstRemote).map((url) => url).orElse(() => FireflyOkAsync(null));
595
+ });
596
+ });
597
+ }
526
598
  };
527
599
  /**
528
600
  * Creates a git service instance.
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ declare const FireflyConfigSchema: z.ZodObject<{
11
11
  name: z.ZodOptional<z.ZodString>;
12
12
  scope: z.ZodOptional<z.ZodString>;
13
13
  base: z.ZodDefault<z.ZodString>;
14
+ branch: z.ZodOptional<z.ZodString>;
14
15
  changelogPath: z.ZodDefault<z.ZodString>;
15
16
  bumpStrategy: z.ZodDefault<z.ZodUnion<[z.ZodEnum<{
16
17
  auto: "auto";
package/dist/main.js CHANGED
@@ -71,7 +71,7 @@ const logger = createConsola({
71
71
 
72
72
  //#endregion
73
73
  //#region package.json
74
- var version = "4.0.0-alpha.8";
74
+ var version = "4.0.0-dev.25c80f6";
75
75
  var description = " CLI orchestrator for automatic semantic versioning, changelog generation, and creating releases. Built for my own use cases.";
76
76
  var dependencies = {
77
77
  "c12": "^3.3.2",
@@ -99,7 +99,7 @@ async function main() {
99
99
  description,
100
100
  gitCliffVersion: dependencies["git-cliff"]?.replace("^", "") || "unknown"
101
101
  });
102
- const { createFireflyCLI } = await import("./program-CASpr1JR.js");
102
+ const { createFireflyCLI } = await import("./program-DcCz3-Sc.js");
103
103
  createFireflyCLI().parseAsync(process.argv).catch((error) => {
104
104
  logger.error("Fatal error:", error);
105
105
  process.exit(1);
@@ -1,3 +1,4 @@
1
+ import { t as logger } from "./main.js";
1
2
  import { d as validationErrAsync, g as toFireflyError, i as FireflyOkAsync, u as validationErr } from "./result.constructors-C9M1MP3_.js";
2
3
  import { n as parseSchema } from "./schema.utilities-BGd9t1wm.js";
3
4
  import { Result } from "neverthrow";
@@ -28,7 +29,7 @@ var DefaultPackageJsonService = class DefaultPackageJsonService {
28
29
  return this.fs.read(path).map((content) => this.replaceVersionInContent(content, newVersion)).andThen((updatedContent) => this.fs.write(path, updatedContent)).andThen(() => this.read(path).andThen((pkg) => {
29
30
  if (pkg.version !== newVersion) return validationErrAsync({ message: `Failed to verify updated version in package.json at path: ${path}` });
30
31
  return FireflyOkAsync(void 0);
31
- }));
32
+ })).andTee(() => logger.verbose(`DefaultPackageJsonService: Updated version in package.json at path: ${path} to ${newVersion}`));
32
33
  }
33
34
  /**
34
35
  * Replaces the version string in the package.json content.
@@ -1,6 +1,6 @@
1
1
  import { n as RuntimeEnv, t as logger } from "./main.js";
2
2
  import { _ as validationError, a as conflictErrAsync, c as notFoundErrAsync, d as validationErrAsync, f as conflictError, h as notFoundError, i as FireflyOkAsync, l as timeoutErrAsync, m as failedError, n as FireflyErrAsync, r as FireflyOk, s as invalidErr, t as FireflyErr, u as validationErr, v as wrapErrorMessage } from "./result.constructors-C9M1MP3_.js";
3
- import { i as zipAsync, n as wrapPromise, r as zip3Async, t as ensureNotAsync } from "./result.utilities-oXWCXEvw.js";
3
+ import { n as wrapPromise, r as zip3Async, t as ensureNotAsync } from "./result.utilities-DC5shlhT.js";
4
4
  import { n as parseSchema, t as formatZodErrors } from "./schema.utilities-BGd9t1wm.js";
5
5
  import { LogLevels } from "consola";
6
6
  import { colors } from "consola/utils";
@@ -1091,11 +1091,36 @@ function createBumpStrategyGroup() {
1091
1091
 
1092
1092
  //#endregion
1093
1093
  //#region src/commands/release/tasks/initialize-release-version.task.ts
1094
+ const PACKAGE_JSON_FILE = "package.json";
1095
+ /**
1096
+ * Reads package.json and extracts the version field.
1097
+ *
1098
+ * Behavior:
1099
+ * - Reads the configured package.json file.
1100
+ * - If the version property is missing, returns a validation error.
1101
+ * - Otherwise resolves with the version string.
1102
+ */
1103
+ function getVersionFromPackageJson(ctx) {
1104
+ return ctx.services.packageJson.read(PACKAGE_JSON_FILE).andThen((pkg) => {
1105
+ const version = pkg.version;
1106
+ if (!version) return validationErrAsync({ message: "The 'version' field is missing in package.json." });
1107
+ logger.verbose(`InitializeReleaseVersionTask: Prepared current version: ${version}`);
1108
+ return FireflyOkAsync(version);
1109
+ });
1110
+ }
1111
+ /**
1112
+ * Creates the Initialize Release Version task.
1113
+ *
1114
+ * This task initializes the current release version by reading it from package.json.
1115
+ *
1116
+ * This task:
1117
+ * 1. Reads the version from package.json
1118
+ */
1094
1119
  function createInitializeReleaseVersion() {
1095
- return TaskBuilder.create("initialize-release-version").description("Hydrate and prepare the release configuration").dependsOn("prepare-release-config").execute((ctx) => {
1096
- logger.info("initialize-release-version");
1097
- return FireflyOkAsync(ctx);
1098
- }).build();
1120
+ return TaskBuilder.create("initialize-release-version").description("Initialize current release version from package.json").dependsOn("prepare-release-config").execute((ctx) => getVersionFromPackageJson(ctx).andThen((currentVersion) => {
1121
+ logger.info(`Current version is ${currentVersion}`);
1122
+ return FireflyOkAsync(ctx.fork("currentVersion", currentVersion));
1123
+ })).build();
1099
1124
  }
1100
1125
 
1101
1126
  //#endregion
@@ -1152,17 +1177,28 @@ function extractPreReleaseId(version) {
1152
1177
  }
1153
1178
  /**
1154
1179
  * Hydrates the repository field from git remote URL.
1180
+ *
1181
+ * Behavior:
1182
+ * - If not inside a git repository, resolves to undefined.
1183
+ * - If inside a repository, detect the repository URL
1184
+ * using a fall-through strategy (upstream remote → origin → first remote).
1185
+ * - Parses the URL and returns "owner/repo" when possible.
1155
1186
  */
1156
1187
  function hydrateRepository(ctx) {
1157
- return ctx.services.git.isInsideRepository().andThen((isRepo) => {
1158
- if (!isRepo) return FireflyOkAsync(void 0);
1159
- return ctx.services.git.getRemoteUrl().map((url) => {
1160
- const parsed = parseGitRemoteUrl(url);
1161
- if (parsed) return `${parsed.owner}/${parsed.repo}`;
1162
- return null;
1163
- }).orElse(() => FireflyOkAsync(null)).map((val) => val ?? void 0).andTee((repository) => logger.verbose(`PrepareReleaseConfigTask: Prepared repository: ${repository}`));
1164
- });
1188
+ return ctx.services.git.inferRepositoryUrl().map((url) => {
1189
+ if (!url) return null;
1190
+ const parsed = parseGitRemoteUrl(url);
1191
+ if (parsed) return `${parsed.owner}/${parsed.repo}`;
1192
+ return null;
1193
+ }).map((val) => val ?? void 0).andTee((repository) => logger.verbose(`PrepareReleaseConfigTask: Prepared repository: ${repository}`));
1165
1194
  }
1195
+ /**
1196
+ * Hydrates name, scope, and preReleaseId from package.json.
1197
+ *
1198
+ * Behavior:
1199
+ * - If package.json does not exist, returns all values as undefined.
1200
+ * - If it exists, reads package.json and returns parsed results for name, scope and preReleaseId.
1201
+ */
1166
1202
  function hydrateFromPackageJson(ctx) {
1167
1203
  return ctx.services.fs.exists("package.json").andThen((exists) => {
1168
1204
  if (!exists) return FireflyOkAsync({
@@ -1179,6 +1215,14 @@ function hydrateFromPackageJson(ctx) {
1179
1215
  }));
1180
1216
  });
1181
1217
  }
1218
+ /**
1219
+ * Hydrates the `name` field from package.json when not provided in config.
1220
+ *
1221
+ * Cases:
1222
+ * 1. If name is undefined and package.json has no name, returns a validation error.
1223
+ * 2. If name is undefined and package.json has a name, extracts the name (stripping scope) and returns it.
1224
+ * 3. Otherwise uses provided name.
1225
+ */
1182
1226
  function hydrateNameFromPackageJson(ctx, packageJson) {
1183
1227
  if (ctx.config.name === void 0 && !packageJson.name) return validationErrAsync({ message: "Could not find a valid name in package.json" });
1184
1228
  if (ctx.config.name === void 0 && packageJson.name) {
@@ -1189,6 +1233,14 @@ function hydrateNameFromPackageJson(ctx, packageJson) {
1189
1233
  logger.verbose(`PrepareReleaseConfigTask: Using provided name: "${ctx.config.name}" as it is explicitly set`);
1190
1234
  return FireflyOkAsync(ctx.config.name);
1191
1235
  }
1236
+ /**
1237
+ * Hydrates the `scope` field from package.json when not provided in config.
1238
+ *
1239
+ * Cases:
1240
+ * 1. If scope is explicitly provided (key exists and value is not undefined), it is used.
1241
+ * 2. If not provided, but package.json has a scoped `name` (e.g., "@scope/name"), the scope will be extracted and returned.
1242
+ * 3. Otherwise returns undefined.
1243
+ */
1192
1244
  function hydrateScopeFromPackageJson(ctx, packageJson) {
1193
1245
  if (Object.hasOwn(ctx.config, "scope") && ctx.config.scope !== void 0) {
1194
1246
  logger.verbose(`PrepareReleaseConfigTask: Using provided scope: "${ctx.config.scope}" as it is explicitly set`);
@@ -1204,6 +1256,14 @@ function hydrateScopeFromPackageJson(ctx, packageJson) {
1204
1256
  logger.verbose("PrepareReleaseConfigTask: No scope to prepare from package.json");
1205
1257
  return FireflyOkAsync(void 0);
1206
1258
  }
1259
+ /**
1260
+ * Hydrates the `preReleaseId` field from `package.json.version` when not provided.
1261
+ *
1262
+ * Cases:
1263
+ * 1. If preReleaseId is explicitly provided and not an empty string, it is used.
1264
+ * 2. If not provided, and `package.json.version` contains a prerelease segment, the prerelease identifier will be extracted and returned.
1265
+ * 3. Otherwise the function defaults to "alpha".
1266
+ */
1207
1267
  function hydratePreReleaseIdFromPackageJson(ctx, packageJson) {
1208
1268
  if (ctx.config.preReleaseId !== void 0 && ctx.config.preReleaseId.trim() !== "") {
1209
1269
  logger.verbose(`PrepareReleaseConfigTask: Using provided preReleaseId: "${ctx.config.preReleaseId}" as it is explicitly set`);
@@ -1222,6 +1282,29 @@ function hydratePreReleaseIdFromPackageJson(ctx, packageJson) {
1222
1282
  return FireflyOkAsync("alpha");
1223
1283
  }
1224
1284
  /**
1285
+ * Hydrates branch setting from git.
1286
+ *
1287
+ * Behavior:
1288
+ * - If not inside a git repository, resolves to undefined.
1289
+ * - If a branch is explicitly provided in the config, validates it against the
1290
+ * current git branch and returns it (otherwise returns a validation error).
1291
+ * - If no branch is provided in the config, uses current git branch.
1292
+ */
1293
+ function hydrateBranch(ctx) {
1294
+ return ctx.services.git.isInsideRepository().andThen((isRepo) => {
1295
+ if (!isRepo) return FireflyOkAsync(void 0);
1296
+ return ctx.services.git.getCurrentBranch().andThen((currentBranch) => {
1297
+ if (Object.hasOwn(ctx.config, "branch") && ctx.config.branch !== void 0 && ctx.config.branch.trim() !== "") {
1298
+ if (ctx.config.branch !== currentBranch) return validationErrAsync({ message: `Configured branch "${ctx.config.branch}" does not match current git branch "${currentBranch}"` });
1299
+ logger.verbose(`PrepareReleaseConfigTask: Using provided branch: "${ctx.config.branch}" as it is explicitly set`);
1300
+ return FireflyOkAsync(ctx.config.branch);
1301
+ }
1302
+ logger.verbose(`PrepareReleaseConfigTask: Prepared branch from git: ${currentBranch}`);
1303
+ return FireflyOkAsync(currentBranch);
1304
+ });
1305
+ });
1306
+ }
1307
+ /**
1225
1308
  * Creates the Prepare Release Config Task.
1226
1309
  *
1227
1310
  * This task determines and hydrates configuration settings, by inferring values from the environment.
@@ -1230,15 +1313,17 @@ function hydratePreReleaseIdFromPackageJson(ctx, packageJson) {
1230
1313
  * 1. Detects repository owner/repo from git remote URL
1231
1314
  * 2. Extracts name and scope from package.json
1232
1315
  * 3. Extracts preReleaseId from package.json version
1316
+ * 4. Detects current git branch if not provided
1233
1317
  */
1234
1318
  function createPrepareReleaseConfigTask() {
1235
1319
  return TaskBuilder.create("prepare-release-config").description("Hydrate and prepare the release configuration").execute((ctx) => {
1236
1320
  const hydrated = {};
1237
- return zipAsync(hydrateRepository(ctx), hydrateFromPackageJson(ctx)).map(([repository, pkgData]) => {
1321
+ return zip3Async(hydrateRepository(ctx), hydrateFromPackageJson(ctx), hydrateBranch(ctx)).map(([repository, pkgData, branch]) => {
1238
1322
  if (repository) hydrated.repository = repository;
1239
1323
  if (pkgData.name) hydrated.name = pkgData.name;
1240
1324
  if (pkgData.scope) hydrated.scope = pkgData.scope;
1241
1325
  if (pkgData.preReleaseId) hydrated.preReleaseId = pkgData.preReleaseId;
1326
+ if (branch) hydrated.branch = branch;
1242
1327
  logger.verbose(`PrepareReleaseConfigTask: Hydrated config: ${JSON.stringify(hydrated)}`);
1243
1328
  return ctx.fork("hydratedConfig", hydrated);
1244
1329
  });
@@ -1454,6 +1539,7 @@ const ReleaseConfigSchema = z$1.object({
1454
1539
  name: z$1.string().optional().describe("Unscoped project name. Auto-detected from package.json."),
1455
1540
  scope: z$1.string().optional().describe("Org/user scope without '@'. Auto-detected from package.json."),
1456
1541
  base: z$1.string().default("").describe("Relative path from repository root to project root."),
1542
+ branch: z$1.string().optional().describe("Git branch to release from."),
1457
1543
  changelogPath: z$1.string().default("CHANGELOG.md").describe("Changelog file path, relative to project root."),
1458
1544
  bumpStrategy: BumpStrategySchema.describe("\"auto\" (from commits) or \"manual\" (user-specified)."),
1459
1545
  releaseType: ReleaseTypeSchema.optional().describe("The release type to bump."),
@@ -1525,19 +1611,19 @@ function defineService(definition) {
1525
1611
  */
1526
1612
  const SERVICE_DEFINITIONS = {
1527
1613
  fs: defineService({ factory: async ({ basePath }) => {
1528
- const { createFileSystemService } = await import("./filesystem.service-B_dgIoTJ.js");
1614
+ const { createFileSystemService } = await import("./filesystem.service-9VHML130.js");
1529
1615
  return createFileSystemService(basePath);
1530
1616
  } }),
1531
1617
  packageJson: defineService({
1532
1618
  dependencies: ["fs"],
1533
1619
  factory: async ({ getService }) => {
1534
1620
  const fs = await getService("fs");
1535
- const { createPackageJsonService } = await import("./package-json.service-QN7SzRTt.js");
1621
+ const { createPackageJsonService } = await import("./package-json.service-DACeZzRg.js");
1536
1622
  return createPackageJsonService(fs);
1537
1623
  }
1538
1624
  }),
1539
1625
  git: defineService({ factory: async ({ basePath }) => {
1540
- const { createGitService } = await import("./git.service-C5zcZ5BB.js");
1626
+ const { createGitService } = await import("./git.service-CACrfCW8.js");
1541
1627
  return createGitService(basePath);
1542
1628
  } })
1543
1629
  };
@@ -2178,7 +2264,11 @@ var WorkflowExecutor = class {
2178
2264
  const startTime = /* @__PURE__ */ new Date();
2179
2265
  const executedTaskIds = [];
2180
2266
  const skippedTaskIds = [];
2181
- if (this.options.dryRun) logger.warn("Workflow executing in DRY RUN mode - no actual changes will be made");
2267
+ if (this.options.dryRun) logger.warn("Running in DRY-RUN mode: No changes will be made.");
2268
+ const version = RuntimeEnv.version;
2269
+ const dashIndex = version.indexOf("-");
2270
+ if (dashIndex !== -1) if (dashIndex === version.length - 1 || version[dashIndex + 1] === "n") logger.warn(`You are running a DEVELOPMENT build of Firefly (${colors.dim(version)}). This is a 'next' build and may be unstable.`);
2271
+ else logger.warn(`You are running a PRE-RELEASE version of Firefly (${colors.dim(version)}). Unexpected behavior or bugs may occur.`);
2182
2272
  logger.verbose(`WorkflowExecutor: Starting execution of ${tasks.length} tasks`);
2183
2273
  return this.executeTasksSequentially(tasks, initialContext, executedTaskIds, skippedTaskIds).andThen(() => this.buildExecutionSuccessResult(startTime, executedTaskIds, skippedTaskIds)).orElse((error) => this.handleExecutionFailure({
2184
2274
  error,
@@ -1,5 +1,5 @@
1
1
  import { d as validationErrAsync, g as toFireflyError, i as FireflyOkAsync, p as createFireflyError } from "./result.constructors-C9M1MP3_.js";
2
- import { ResultAsync, okAsync } from "neverthrow";
2
+ import { ResultAsync } from "neverthrow";
3
3
 
4
4
  //#region src/core/result/result.utilities.ts
5
5
  /**
@@ -18,12 +18,6 @@ function ensureNotAsync(condition, errorOpts) {
18
18
  return condition ? validationErrAsync(errorOpts) : FireflyOkAsync(void 0);
19
19
  }
20
20
  /**
21
- * Async version of zip.
22
- */
23
- function zipAsync(resultA, resultB) {
24
- return ResultAsync.combine([resultA, resultB]);
25
- }
26
- /**
27
21
  * Async version of zip3.
28
22
  */
29
23
  function zip3Async(resultA, resultB, resultC) {
@@ -35,4 +29,4 @@ function zip3Async(resultA, resultB, resultC) {
35
29
  }
36
30
 
37
31
  //#endregion
38
- export { zipAsync as i, wrapPromise as n, zip3Async as r, ensureNotAsync as t };
32
+ export { wrapPromise as n, zip3Async as r, ensureNotAsync as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fireflyy",
3
- "version": "4.0.0-alpha.8",
3
+ "version": "4.0.0-dev.25c80f6",
4
4
  "description": " CLI orchestrator for automatic semantic versioning, changelog generation, and creating releases. Built for my own use cases.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,8 +31,8 @@
31
31
  }
32
32
  },
33
33
  "bin": {
34
- "firefly": "./dist/main.js",
35
- "ff": "./dist/main.js"
34
+ "firefly": "dist/main.js",
35
+ "ff": "dist/main.js"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public",
@@ -62,12 +62,12 @@
62
62
  "zod": "^4.1.13"
63
63
  },
64
64
  "devDependencies": {
65
- "@biomejs/biome": "2.3.7",
65
+ "@biomejs/biome": "2.3.8",
66
66
  "@types/bun": "^1.3.3",
67
67
  "@types/semver": "^7.7.1",
68
- "tsdown": "^0.17.0-beta.1",
68
+ "tsdown": "^0.17.0-beta.5",
69
69
  "typescript": "^5.9.3",
70
- "ultracite": "6.3.7"
70
+ "ultracite": "6.3.8"
71
71
  },
72
72
  "keywords": [
73
73
  "cli",