fireflyy 3.0.11 → 4.0.0-dev.352994a

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.
@@ -0,0 +1,587 @@
1
+ import { t as logger } from "./main.js";
2
+ import { i as FireflyOkAsync, m as failedError, o as failedErrAsync } from "./result.constructors-C9M1MP3_.js";
3
+ import { t as withDryRun } from "./dry-run-BfYCtldz.js";
4
+ import { ResultAsync } from "neverthrow";
5
+ import { z } from "zod";
6
+
7
+ //#region src/infrastructure/executors/git-command.executor.ts
8
+ /**
9
+ * Git commands that modify repository state.
10
+ * These commands are skipped during dry-run mode.
11
+ */
12
+ const SIDE_EFFECT_COMMANDS = new Set([
13
+ "add",
14
+ "am",
15
+ "apply",
16
+ "branch",
17
+ "checkout",
18
+ "cherry-pick",
19
+ "clean",
20
+ "commit",
21
+ "fetch",
22
+ "merge",
23
+ "mv",
24
+ "pull",
25
+ "push",
26
+ "rebase",
27
+ "reset",
28
+ "restore",
29
+ "revert",
30
+ "rm",
31
+ "stash",
32
+ "switch",
33
+ "tag",
34
+ "worktree"
35
+ ]);
36
+ /**
37
+ * Git commands that benefit from streaming execution.
38
+ * These commands often produce large outputs that should be processed incrementally.
39
+ */
40
+ const STREAMING_COMMANDS = new Set([
41
+ "rev-list",
42
+ "log",
43
+ "show",
44
+ "diff",
45
+ "blame",
46
+ "cat-file"
47
+ ]);
48
+ const gitArgsSchema = z.array(z.string().min(1));
49
+ /**
50
+ * Determines if the given arguments contain commands that modify repository state.
51
+ *
52
+ * @param args - Git command arguments to check
53
+ * @returns `true` if any argument is a side-effect command
54
+ */
55
+ function hasSideEffects(args) {
56
+ if (args.length === 0) return false;
57
+ return args.map((token) => token.toLowerCase()).some((token) => SIDE_EFFECT_COMMANDS.has(token));
58
+ }
59
+ /**
60
+ * Determines if the given arguments should use streaming execution.
61
+ *
62
+ * @param args - Git command arguments to check
63
+ * @returns `true` if streaming execution is recommended
64
+ */
65
+ function shouldUseStreaming(args) {
66
+ if (args.length === 0) return false;
67
+ return args.map((token) => token.toLowerCase()).some((token) => STREAMING_COMMANDS.has(token));
68
+ }
69
+ /**
70
+ * Reads a process stdout stream into a string with abort support.
71
+ *
72
+ * @param args - Git command arguments
73
+ * @param spawnOptions - Spawn configuration for the Git process
74
+ * @param signal - Optional abort signal for cancellation
75
+ * @returns Promise resolving to the complete stdout output
76
+ */
77
+ function streamToString(args, spawnOptions, signal) {
78
+ return new Promise((resolve, reject) => {
79
+ const proc = Bun.spawn(["git", ...args], spawnOptions);
80
+ if (signal) {
81
+ const onAbort = () => {
82
+ proc.kill();
83
+ reject(new Error("Git command aborted", { cause: signal.reason }));
84
+ };
85
+ if (signal.aborted) {
86
+ onAbort();
87
+ return;
88
+ }
89
+ signal.addEventListener("abort", onAbort, { once: true });
90
+ }
91
+ const chunks = [];
92
+ const reader = proc.stdout.getReader();
93
+ const decoder = new TextDecoder();
94
+ const readChunks = () => {
95
+ if (signal?.aborted) {
96
+ reader.cancel();
97
+ return;
98
+ }
99
+ reader.read().then((readResult) => {
100
+ if (readResult.done) {
101
+ const finalChunk = decoder.decode();
102
+ if (finalChunk) chunks.push(finalChunk);
103
+ proc.exited.then(() => {
104
+ if (proc.exitCode !== 0) new Response(proc.stderr).text().then((stderrText) => {
105
+ const errorMessage = `Git process exited with code ${proc.exitCode}: ${stderrText}`;
106
+ reject(new Error(errorMessage));
107
+ });
108
+ else resolve(chunks.join(""));
109
+ });
110
+ } else {
111
+ const decodedChunk = decoder.decode(readResult.value, { stream: true });
112
+ chunks.push(decodedChunk);
113
+ readChunks();
114
+ }
115
+ });
116
+ };
117
+ readChunks();
118
+ });
119
+ }
120
+ /**
121
+ * Executes a Git command using buffered output collection.
122
+ * Waits for the process to complete before returning the full output.
123
+ *
124
+ * @param args - Git command arguments
125
+ * @param spawnOptions - Spawn configuration for the Git process
126
+ * @param commandStr - Full command string for error messages
127
+ * @param signal - Optional abort signal for cancellation
128
+ * @returns FireflyAsyncResult containing stdout output or error
129
+ */
130
+ function executeGitCommandBuffered(args, spawnOptions, commandStr, signal) {
131
+ const proc = Bun.spawn(["git", ...args], spawnOptions);
132
+ const outputPromise = new Response(proc.stdout).text();
133
+ const exitPromise = proc.exited;
134
+ const abortPromise = signal ? new Promise((_, reject) => {
135
+ const onAbort = () => {
136
+ proc.kill();
137
+ reject(new Error(`Git command aborted: ${commandStr}`, { cause: signal.reason }));
138
+ };
139
+ if (signal.aborted) onAbort();
140
+ else signal.addEventListener("abort", onAbort, { once: true });
141
+ }) : null;
142
+ const executionPromise = Promise.all([outputPromise, exitPromise]).then(([output]) => {
143
+ if (proc.exitCode !== 0) return new Response(proc.stderr).text().then((stderrText) => {
144
+ const errorMessage = `Git process exited with code ${proc.exitCode}: ${stderrText}`;
145
+ return Promise.reject(new Error(errorMessage));
146
+ });
147
+ return output;
148
+ });
149
+ const racePromise = abortPromise ? Promise.race([executionPromise, abortPromise]) : executionPromise;
150
+ return ResultAsync.fromPromise(racePromise, (error) => failedError({
151
+ message: `Git command failed: ${commandStr}`,
152
+ details: error
153
+ }));
154
+ }
155
+ /**
156
+ * Executes a Git command using streaming output collection.
157
+ *
158
+ * @param args - Git command arguments
159
+ * @param spawnOptions - Spawn configuration for the Git process
160
+ * @param commandStr - Full command string for error messages
161
+ * @param signal - Optional abort signal for cancellation
162
+ * @returns FireflyAsyncResult containing stdout output or error
163
+ */
164
+ function executeGitCommandStreaming(args, spawnOptions, commandStr, signal) {
165
+ return ResultAsync.fromPromise(streamToString(args, spawnOptions, signal), (error) => failedError({
166
+ message: `Git command failed: ${commandStr}`,
167
+ details: error
168
+ }));
169
+ }
170
+ /**
171
+ * Executes a Git command with comprehensive error handling and cancellation support.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * // Simple command
176
+ * const result = await executeGitCommand(["status", "--porcelain"]);
177
+ *
178
+ * // With timeout
179
+ * const result = await executeGitCommand(["log", "--oneline"], { timeoutMs: 5000 });
180
+ *
181
+ * // With abort signal
182
+ * const controller = new AbortController();
183
+ * const result = await executeGitCommand(["fetch", "--all"], { signal: controller.signal });
184
+ * ```
185
+ *
186
+ * @param args - Git command arguments (e.g., ["status", "--porcelain"])
187
+ * @param options - Execution options for dry-run, timeout, etc.
188
+ * @returns FireflyAsyncResult containing stdout output or FireflyError
189
+ */
190
+ function executeGitCommand(args, options = {}) {
191
+ const resolvedOptions = {
192
+ verbose: true,
193
+ ...options
194
+ };
195
+ const parseResult = gitArgsSchema.safeParse(args);
196
+ if (!parseResult.success) return failedErrAsync({
197
+ message: "Invalid git arguments",
198
+ details: parseResult.error
199
+ });
200
+ const validatedArgs = parseResult.data;
201
+ const commandStr = `git ${validatedArgs.join(" ")}`;
202
+ const signal = resolvedOptions.signal ?? (resolvedOptions.timeoutMs ? AbortSignal.timeout(resolvedOptions.timeoutMs) : void 0);
203
+ if (signal?.aborted) return failedErrAsync({
204
+ message: `Git command aborted before execution: ${commandStr}`,
205
+ details: signal.reason
206
+ });
207
+ const useStreaming = shouldUseStreaming(validatedArgs) && !resolvedOptions.forceBuffered;
208
+ const executionMode = useStreaming ? "streaming" : "buffered";
209
+ if (resolvedOptions.verbose) logger.verbose(`GitCommandExecutor: Executing git command (${executionMode}): ${commandStr}`);
210
+ if (resolvedOptions.dryRun && hasSideEffects(validatedArgs)) {
211
+ const dryRunMessage = `Dry run: Skipping ${commandStr}`;
212
+ return withDryRun(resolvedOptions, dryRunMessage, () => FireflyOkAsync(dryRunMessage), dryRunMessage);
213
+ }
214
+ const spawnOptions = {
215
+ cwd: resolvedOptions.cwd ?? process.cwd(),
216
+ stdout: "pipe",
217
+ stderr: "pipe"
218
+ };
219
+ if (useStreaming) return executeGitCommandStreaming(validatedArgs, spawnOptions, commandStr, signal);
220
+ return executeGitCommandBuffered(validatedArgs, spawnOptions, commandStr, signal);
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/services/implementations/git.service.ts
225
+ const CURRENT_BRANCH_MARKER_REGEX = /^\*\s*/;
226
+ const REMOTES_PREFIX_REGEX = /^remotes\//;
227
+ /**
228
+ * Default implementation of the git service.
229
+ */
230
+ var DefaultGitService = class {
231
+ /**
232
+ * The working directory where git commands are executed.
233
+ */
234
+ cwd;
235
+ /**
236
+ * Creates a new git service.
237
+ * @param cwd - The working directory for git operations
238
+ */
239
+ constructor(cwd) {
240
+ this.cwd = cwd;
241
+ }
242
+ /**
243
+ * Executes a git command with the configured working directory
244
+ *
245
+ * @param args - Git command arguments
246
+ * @param options - Execution options
247
+ * @returns Command output or error
248
+ */
249
+ git(args, options) {
250
+ return executeGitCommand(args, {
251
+ cwd: this.cwd,
252
+ dryRun: options?.dryRun,
253
+ verbose: options?.verbose ?? true
254
+ });
255
+ }
256
+ isInsideRepository() {
257
+ return this.git(["rev-parse", "--is-inside-work-tree"]).map(() => true).orElse(() => FireflyOkAsync(false));
258
+ }
259
+ getRepositoryRoot() {
260
+ return this.git(["rev-parse", "--show-toplevel"]).map((output) => output.trim());
261
+ }
262
+ getRemoteUrl(remote) {
263
+ const remoteName = remote ?? "origin";
264
+ return this.git([
265
+ "remote",
266
+ "get-url",
267
+ remoteName
268
+ ]).map((output) => output.trim());
269
+ }
270
+ getStatus() {
271
+ return this.git(["status", "--porcelain"]).map((output) => {
272
+ const lines = output.split("\n").filter((line) => line.length > 0);
273
+ let hasStaged = false;
274
+ let hasUnstaged = false;
275
+ let hasUntracked = false;
276
+ for (const line of lines) {
277
+ const index = line[0];
278
+ const workTree = line[1];
279
+ if (index === "?") hasUntracked = true;
280
+ else if (index !== " " && index !== "?") hasStaged = true;
281
+ if (workTree !== " " && workTree !== "?") hasUnstaged = true;
282
+ }
283
+ return {
284
+ hasStaged,
285
+ hasUnstaged,
286
+ hasUntracked,
287
+ isClean: lines.length === 0
288
+ };
289
+ });
290
+ }
291
+ isWorkingTreeClean() {
292
+ return this.getStatus().map((status) => status.isClean);
293
+ }
294
+ /**
295
+ * Parses git status porcelain output into structured file status objects.
296
+ *
297
+ * @param output - The raw output from `git status --porcelain`
298
+ * @returns Array of GitFileStatus objects
299
+ */
300
+ parseStatusOutput(output) {
301
+ return output.split("\n").filter((line) => line.length >= 3).map((line) => {
302
+ const indexStatus = line[0] ?? " ";
303
+ const workTreeStatus = line[1] ?? " ";
304
+ return {
305
+ path: line.slice(3).trim(),
306
+ indexStatus,
307
+ workTreeStatus
308
+ };
309
+ });
310
+ }
311
+ getFiles(filter) {
312
+ const includeStaged = filter?.staged ?? true;
313
+ const includeUnstaged = filter?.unstaged ?? true;
314
+ return this.git(["status", "--porcelain"]).map((output) => {
315
+ return this.parseStatusOutput(output).filter((file) => {
316
+ const isStaged = file.indexStatus !== " " && file.indexStatus !== "?";
317
+ const isUnstaged = file.workTreeStatus !== " " && file.workTreeStatus !== "?";
318
+ if (includeStaged && includeUnstaged) return isStaged || isUnstaged;
319
+ if (includeStaged) return isStaged;
320
+ if (includeUnstaged) return isUnstaged;
321
+ return false;
322
+ });
323
+ });
324
+ }
325
+ getFileNames(filter) {
326
+ return this.getFiles(filter).map((files) => files.map((file) => file.path));
327
+ }
328
+ getCurrentBranch() {
329
+ return this.git([
330
+ "rev-parse",
331
+ "--abbrev-ref",
332
+ "HEAD"
333
+ ]).map((output) => output.trim());
334
+ }
335
+ hasBranch(branch) {
336
+ return this.git([
337
+ "rev-parse",
338
+ "--verify",
339
+ branch
340
+ ]).map(() => true).orElse(() => FireflyOkAsync(false));
341
+ }
342
+ parseBranchLine(line) {
343
+ const isCurrent = line.startsWith("*");
344
+ const isRemote = line.includes("remotes/");
345
+ let branchName = line.replace(CURRENT_BRANCH_MARKER_REGEX, "").trim();
346
+ if (isRemote) branchName = branchName.replace(REMOTES_PREFIX_REGEX, "");
347
+ return {
348
+ name: branchName,
349
+ isCurrent,
350
+ isRemote
351
+ };
352
+ }
353
+ listBranches(includeRemote) {
354
+ const args = ["branch"];
355
+ if (includeRemote) args.push("-a");
356
+ return this.git(args).map((output) => {
357
+ return output.split("\n").filter((line) => line.trim().length > 0).map((line) => this.parseBranchLine(line));
358
+ });
359
+ }
360
+ createCommit(message, options) {
361
+ if (options?.dryRun) {
362
+ logger.verbose("GitService: Dry run, skipping commit");
363
+ return FireflyOkAsync({ sha: "dry-run-sha" });
364
+ }
365
+ const args = [
366
+ "commit",
367
+ "-m",
368
+ message
369
+ ];
370
+ if (options?.sign) args.push("-S");
371
+ if (options?.allowEmpty) args.push("--allow-empty");
372
+ if (options?.noVerify) args.push("--no-verify");
373
+ if (options?.paths && options.paths.length > 0) args.push("--", ...options.paths);
374
+ return this.git(args).andThen(() => this.git(["rev-parse", "HEAD"]).map((sha) => ({ sha: sha.trim().substring(0, 7) })));
375
+ }
376
+ getCommitHashesSince(since) {
377
+ const args = since ? ["rev-list", `${since}..HEAD`] : ["rev-list", "HEAD"];
378
+ return this.git(args).map((output) => output.trim().split("\n").map((line) => line.trim()).filter((line) => line.length > 0));
379
+ }
380
+ getCommitDetails(hash) {
381
+ const format = [
382
+ "hash:%H",
383
+ "date:%ci",
384
+ "author:%an <%ae>",
385
+ "subject:%s",
386
+ "body:%b",
387
+ "notes:%N"
388
+ ].join("%n");
389
+ return this.git([
390
+ "show",
391
+ "--no-patch",
392
+ `--format=${format}`,
393
+ hash
394
+ ]);
395
+ }
396
+ getUnpushedCommits() {
397
+ return this.getCurrentBranch().andThen((branch) => {
398
+ const upstream = `origin/${branch}`;
399
+ return this.git([
400
+ "rev-list",
401
+ "--count",
402
+ `${upstream}..HEAD`
403
+ ]).map((output) => {
404
+ const count = Number.parseInt(output.trim(), 10) || 0;
405
+ return {
406
+ hasUnpushed: count > 0,
407
+ count
408
+ };
409
+ }).orElse(() => {
410
+ return this.git([
411
+ "rev-list",
412
+ "--count",
413
+ "HEAD"
414
+ ]).map((output) => {
415
+ const count = Number.parseInt(output.trim(), 10) || 0;
416
+ return {
417
+ hasUnpushed: count > 0,
418
+ count
419
+ };
420
+ }).orElse(() => FireflyOkAsync({
421
+ hasUnpushed: false,
422
+ count: 0
423
+ }));
424
+ });
425
+ });
426
+ }
427
+ createTag(name, options) {
428
+ if (options?.dryRun) {
429
+ logger.verbose(`GitService: Dry run, skipping tag creation: ${name}`);
430
+ return FireflyOkAsync(void 0);
431
+ }
432
+ const args = ["tag"];
433
+ if (options?.message) args.push("-a", name, "-m", options.message);
434
+ else args.push(name);
435
+ if (options?.sign) args.push("-s");
436
+ return this.git(args).map(() => void 0);
437
+ }
438
+ deleteTag(name, options) {
439
+ const scope = options?.scope ?? "local";
440
+ const remote = options?.remote ?? "origin";
441
+ if (options?.dryRun) {
442
+ logger.verbose(`GitService: Dry run, skipping tag deletion (${scope}): ${name}`);
443
+ return FireflyOkAsync(void 0);
444
+ }
445
+ if (scope === "local") return this.git([
446
+ "tag",
447
+ "-d",
448
+ name
449
+ ]).map(() => void 0);
450
+ if (scope === "remote") return this.git([
451
+ "push",
452
+ remote,
453
+ `:refs/tags/${name}`
454
+ ]).map(() => void 0);
455
+ return this.git([
456
+ "tag",
457
+ "-d",
458
+ name
459
+ ]).andThen(() => this.git([
460
+ "push",
461
+ remote,
462
+ `:refs/tags/${name}`
463
+ ])).map(() => void 0);
464
+ }
465
+ hasTag(name) {
466
+ return this.git([
467
+ "tag",
468
+ "--list",
469
+ name
470
+ ]).map((output) => output.trim() === name);
471
+ }
472
+ hasAnyTags() {
473
+ return this.getLatestTag().map((tag) => tag !== null);
474
+ }
475
+ listTags() {
476
+ return this.git(["tag", "--list"]).map((output) => output.split("\n").map((tag) => tag.trim()).filter((tag) => tag.length > 0));
477
+ }
478
+ getLatestTag() {
479
+ return this.git([
480
+ "describe",
481
+ "--tags",
482
+ "--abbrev=0"
483
+ ]).map((output) => {
484
+ return output.trim() || null;
485
+ }).orElse((error) => {
486
+ if (error.message.includes("No names found") || error.message.includes("fatal")) return FireflyOkAsync(null);
487
+ return FireflyOkAsync(null);
488
+ });
489
+ }
490
+ getTagMessage(name) {
491
+ return this.git([
492
+ "tag",
493
+ "-l",
494
+ "--format=%(contents)",
495
+ name
496
+ ]).map((output) => {
497
+ return output.trim() || null;
498
+ }).orElse(() => FireflyOkAsync(null));
499
+ }
500
+ stage(paths) {
501
+ const pathArray = Array.isArray(paths) ? paths : [paths];
502
+ return this.git(["add", ...pathArray]).map(() => void 0);
503
+ }
504
+ unstage(paths) {
505
+ const pathArray = Array.isArray(paths) ? paths : [paths];
506
+ return this.git([
507
+ "reset",
508
+ "HEAD",
509
+ "--",
510
+ ...pathArray
511
+ ]).map(() => void 0);
512
+ }
513
+ push(options) {
514
+ if (options?.dryRun) {
515
+ logger.verbose("GitService: Dry run, skipping push");
516
+ return FireflyOkAsync(void 0);
517
+ }
518
+ const args = ["push"];
519
+ const remote = options?.remote ?? "origin";
520
+ args.push(remote);
521
+ if (options?.branch) args.push(options.branch);
522
+ if (options?.tags) args.push("--tags");
523
+ if (options?.followTags) args.push("--follow-tags");
524
+ return this.git(args).map(() => void 0);
525
+ }
526
+ /**
527
+ * Gets the upstream remote name for the current branch.
528
+ * @returns The remote name or null if no upstream is configured.
529
+ */
530
+ getUpstreamRemote() {
531
+ return this.git([
532
+ "rev-parse",
533
+ "--abbrev-ref",
534
+ "--symbolic-full-name",
535
+ "@{upstream}"
536
+ ]).map((output) => {
537
+ const upstream = output.trim();
538
+ const slashIndex = upstream.indexOf("/");
539
+ if (slashIndex > 0) return upstream.substring(0, slashIndex);
540
+ return null;
541
+ }).orElse(() => FireflyOkAsync(null));
542
+ }
543
+ /**
544
+ * Lists all configured remotes.
545
+ * @returns Array of remote names.
546
+ */
547
+ listRemotes() {
548
+ return this.git(["remote"]).map((output) => output.split("\n").map((remote) => remote.trim()).filter((remote) => remote.length > 0));
549
+ }
550
+ inferRepositoryUrl() {
551
+ return this.getUpstreamRemote().andThen((upstreamRemote) => {
552
+ if (upstreamRemote) {
553
+ logger.verbose(`GitService: Inferring repository URL from upstream remote: ${upstreamRemote}`);
554
+ return this.getRemoteUrl(upstreamRemote).map((url) => url).orElse(() => this.tryOriginOrFirstRemote());
555
+ }
556
+ return this.tryOriginOrFirstRemote();
557
+ });
558
+ }
559
+ /**
560
+ * Tries to get the repository URL from 'origin' or the first available remote.
561
+ */
562
+ tryOriginOrFirstRemote() {
563
+ return this.getRemoteUrl("origin").map((url) => {
564
+ logger.verbose("GitService: Inferring repository URL from origin remote");
565
+ return url;
566
+ }).orElse(() => {
567
+ return this.listRemotes().andThen((remotes) => {
568
+ if (remotes.length === 0) {
569
+ logger.verbose("GitService: No remotes configured, cannot infer repository URL");
570
+ return FireflyOkAsync(null);
571
+ }
572
+ const firstRemote = remotes[0];
573
+ logger.verbose(`GitService: Inferring repository URL from first remote: ${firstRemote}`);
574
+ return this.getRemoteUrl(firstRemote).map((url) => url).orElse(() => FireflyOkAsync(null));
575
+ });
576
+ });
577
+ }
578
+ };
579
+ /**
580
+ * Creates a git service instance.
581
+ */
582
+ function createGitService(cwd) {
583
+ return new DefaultGitService(cwd);
584
+ }
585
+
586
+ //#endregion
587
+ export { createGitService };
package/dist/index.d.ts CHANGED
@@ -1,19 +1,17 @@
1
- import z$1, { z } from "zod";
1
+ import z from "zod";
2
2
 
3
- //#region src/modules/configuration/schema/config-base.schema.d.ts
4
- declare const BaseConfigSchema: z$1.ZodObject<{
5
- repository: z$1.ZodDefault<z$1.ZodUnion<[z$1.ZodString, z$1.ZodLiteral<"">]>>;
6
- verbose: z$1.ZodDefault<z$1.ZodBoolean>;
7
- dryRun: z$1.ZodDefault<z$1.ZodBoolean>;
8
- branch: z$1.ZodOptional<z$1.ZodString>;
9
- }, z$1.core.$strip>;
10
- //#endregion
11
- //#region src/modules/configuration/config-schema.provider.d.ts
12
- declare const schemas: {
13
- readonly release: z.ZodObject<{
3
+ //#region src/cli/config/config.schema.d.ts
4
+
5
+ /**
6
+ * Complete Firefly configuration schema.
7
+ * Combines global options with command-specific configuration sections.
8
+ */
9
+ declare const FireflyConfigSchema: z.ZodObject<{
10
+ release: z.ZodOptional<z.ZodObject<{
14
11
  name: z.ZodOptional<z.ZodString>;
15
12
  scope: z.ZodOptional<z.ZodString>;
16
13
  base: z.ZodDefault<z.ZodString>;
14
+ branch: z.ZodOptional<z.ZodString>;
17
15
  changelogPath: z.ZodDefault<z.ZodString>;
18
16
  bumpStrategy: z.ZodDefault<z.ZodUnion<[z.ZodEnum<{
19
17
  auto: "auto";
@@ -29,7 +27,7 @@ declare const schemas: {
29
27
  prepatch: "prepatch";
30
28
  graduate: "graduate";
31
29
  }>>;
32
- preReleaseId: z.ZodDefault<z.ZodString>;
30
+ preReleaseId: z.ZodOptional<z.ZodString>;
33
31
  preReleaseBase: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"0">, z.ZodLiteral<"1">]>>;
34
32
  releaseNotes: z.ZodDefault<z.ZodString>;
35
33
  commitMessage: z.ZodDefault<z.ZodString>;
@@ -44,16 +42,38 @@ declare const schemas: {
44
42
  releaseLatest: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
45
43
  releasePreRelease: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
46
44
  releaseDraft: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
47
- }, z.core.$strip>;
48
- };
49
- type BaseConfig = z.infer<typeof BaseConfigSchema>;
50
- type CommandName = keyof typeof schemas;
51
- type CommandConfigMap = { [K in keyof typeof schemas]: z.infer<(typeof schemas)[K]> };
52
- type FinalConfigFor<C extends CommandName> = BaseConfig & CommandConfigMap[C];
53
- type _FireflyConfig = FinalConfigFor<CommandName>;
45
+ }, z.core.$strip>>;
46
+ cwd: z.ZodOptional<z.ZodString>;
47
+ dryRun: z.ZodOptional<z.ZodBoolean>;
48
+ verbose: z.ZodOptional<z.ZodBoolean>;
49
+ enableRollback: z.ZodOptional<z.ZodBoolean>;
50
+ }, z.core.$strip>;
51
+ /**
52
+ * TypeScript type for Firefly configuration.
53
+ * Use this type when you need to reference the configuration shape without runtime validation.
54
+ */
55
+ type FireflyConfig = z.infer<typeof FireflyConfigSchema>;
54
56
  //#endregion
55
- //#region src/platform/config/index.d.ts
56
- type FireflyConfig = Partial<_FireflyConfig>;
57
+ //#region src/config/index.d.ts
58
+ /**
59
+ * Helper function to define a type-safe Firefly configuration.
60
+ *
61
+ * Provides IntelliSense autocompletion and type checking for config files.
62
+ *
63
+ * @param options - The configuration options
64
+ * @returns The same options (identity function for type inference)
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * export default defineConfig({
69
+ * verbose: true,
70
+ * release: {
71
+ * bumpStrategy: "conventional",
72
+ * releaseType: "github",
73
+ * },
74
+ * });
75
+ * ```
76
+ */
57
77
  declare function defineConfig<T extends FireflyConfig>(options: T): T;
58
78
  //#endregion
59
- export { FireflyConfig, defineConfig };
79
+ export { defineConfig };
package/dist/index.js CHANGED
@@ -1,4 +1,23 @@
1
- //#region src/platform/config/index.ts
1
+ //#region src/config/index.ts
2
+ /**
3
+ * Helper function to define a type-safe Firefly configuration.
4
+ *
5
+ * Provides IntelliSense autocompletion and type checking for config files.
6
+ *
7
+ * @param options - The configuration options
8
+ * @returns The same options (identity function for type inference)
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * export default defineConfig({
13
+ * verbose: true,
14
+ * release: {
15
+ * bumpStrategy: "conventional",
16
+ * releaseType: "github",
17
+ * },
18
+ * });
19
+ * ```
20
+ */
2
21
  function defineConfig(options) {
3
22
  return options;
4
23
  }