fireflyy 4.0.0-alpha.8 → 4.0.0-dev.3098d1f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/firefly.schema.json +10 -6
- package/dist/commit-analysis.service-B8W5aORO.js +503 -0
- package/dist/config.d.ts +130 -0
- package/dist/{index.js → config.js} +0 -11
- package/dist/{dry-run-BfYCtldz.js → dry-run-BPDFHMIs.js} +2 -2
- package/dist/{filesystem.service-B_dgIoTJ.js → filesystem.service-Bsm3j1Bv.js} +8 -10
- package/dist/{git.service-C5zcZ5BB.js → git.service-B3PC7qIe.js} +95 -23
- package/dist/logging-BuIkRrn1.js +20 -0
- package/dist/main.js +4 -26
- package/dist/{package-json.service-QN7SzRTt.js → package-json.service-d4pVcOFd.js} +4 -3
- package/dist/{program-CASpr1JR.js → program-DSsTcwvN.js} +662 -182
- package/dist/{result.constructors-C9M1MP3_.js → result.constructors-D9jmQ0uj.js} +5 -1
- package/dist/{result.utilities-oXWCXEvw.js → result.utilities-DiG1ae54.js} +3 -19
- package/dist/{schema.utilities-BGd9t1wm.js → schema.utilities-Y2MdlRMu.js} +1 -1
- package/dist/version-D6rAEmbf.js +164 -0
- package/dist/version-bumper.service-ZBJ2jcLS.js +171 -0
- package/dist/version-strategy.service-BdpKRWLc.js +257 -0
- package/package.json +6 -6
- package/dist/index.d.ts +0 -78
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { t as RuntimeEnv } from "./main.js";
|
|
2
|
+
import { _ as notFoundError, a as conflictErrAsync, b as wrapErrorMessage, c as invalidErrAsync, d as validationErr, f as validationErrAsync, h as failedError, i as FireflyOkAsync, l as notFoundErrAsync, n as FireflyErrAsync, o as failedErrAsync, p as conflictError, r as FireflyOk, s as invalidErr, t as FireflyErr, u as timeoutErrAsync, y as validationError } from "./result.constructors-D9jmQ0uj.js";
|
|
3
|
+
import { n as wrapPromise, t as ensureNotAsync } from "./result.utilities-DiG1ae54.js";
|
|
4
|
+
import { t as logger } from "./logging-BuIkRrn1.js";
|
|
5
|
+
import { t as Version } from "./version-D6rAEmbf.js";
|
|
6
|
+
import { t as formatZodErrors } from "./schema.utilities-Y2MdlRMu.js";
|
|
7
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
5
8
|
import { LogLevels } from "consola";
|
|
6
9
|
import { colors } from "consola/utils";
|
|
7
|
-
import { Command } from "commander";
|
|
8
10
|
import { loadConfig } from "c12";
|
|
9
11
|
import { Result, ResultAsync, err, ok } from "neverthrow";
|
|
10
12
|
import z$1 from "zod";
|
|
@@ -184,6 +186,223 @@ function camelToKebab(str) {
|
|
|
184
186
|
return result.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/_/g, "-");
|
|
185
187
|
}
|
|
186
188
|
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/cli/options/options.validation.ts
|
|
191
|
+
/**
|
|
192
|
+
* Extracts enum values from a Zod schema (handles wrapped types like optional, default).
|
|
193
|
+
*
|
|
194
|
+
* @param schema - The Zod schema to extract enum values from
|
|
195
|
+
* @returns Array of string values if it's an enum, or null if not
|
|
196
|
+
*/
|
|
197
|
+
function extractEnumValues(schema) {
|
|
198
|
+
const unwrapped = unwrapZodSchema(schema);
|
|
199
|
+
if (unwrapped instanceof z$1.ZodEnum) return getEnumValuesFromZodEnum(unwrapped);
|
|
200
|
+
if (unwrapped instanceof z$1.ZodUnion) {
|
|
201
|
+
const unionOptions = getZodDef(unwrapped).options;
|
|
202
|
+
if (!unionOptions) return null;
|
|
203
|
+
const values = [];
|
|
204
|
+
for (const option of unionOptions) {
|
|
205
|
+
if (typeof option === "string") continue;
|
|
206
|
+
const innerUnwrapped = unwrapZodSchema(option);
|
|
207
|
+
if (innerUnwrapped instanceof z$1.ZodEnum) {
|
|
208
|
+
const enumValues = getEnumValuesFromZodEnum(innerUnwrapped);
|
|
209
|
+
if (enumValues) values.push(...enumValues);
|
|
210
|
+
} else if (innerUnwrapped instanceof z$1.ZodLiteral) {
|
|
211
|
+
const literalValue = getZodDef(innerUnwrapped).value;
|
|
212
|
+
if (typeof literalValue === "string" && literalValue !== "") values.push(literalValue);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return values.length > 0 ? values : null;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Extracts enum values from a ZodEnum type.
|
|
221
|
+
* Handles both Zod v4 (options array or entries object) and legacy (values array) structures.
|
|
222
|
+
*
|
|
223
|
+
* @param enumSchema - The ZodEnum schema
|
|
224
|
+
* @returns Array of string values, or null if not found
|
|
225
|
+
*/
|
|
226
|
+
function getEnumValuesFromZodEnum(enumSchema) {
|
|
227
|
+
const schemaAsAny = enumSchema;
|
|
228
|
+
if (Array.isArray(schemaAsAny.options)) return schemaAsAny.options;
|
|
229
|
+
const def = getZodDef(enumSchema);
|
|
230
|
+
if (def.entries && typeof def.entries === "object") return Object.values(def.entries);
|
|
231
|
+
if (def.values && Array.isArray(def.values)) return def.values;
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Extracts literal values from a Zod union schema (e.g., z.union([z.literal("0"), z.literal("1")])).
|
|
236
|
+
*
|
|
237
|
+
* @param schema - The Zod schema to extract literal values from
|
|
238
|
+
* @returns Array of literal values if it's a union of literals, or null if not
|
|
239
|
+
*/
|
|
240
|
+
function extractLiteralValues(schema) {
|
|
241
|
+
const unwrapped = unwrapZodSchema(schema);
|
|
242
|
+
if (unwrapped instanceof z$1.ZodUnion) {
|
|
243
|
+
const options = getZodDef(unwrapped).options;
|
|
244
|
+
if (!options) return null;
|
|
245
|
+
const values = [];
|
|
246
|
+
for (const option of options) {
|
|
247
|
+
if (typeof option === "string" || typeof option === "number") continue;
|
|
248
|
+
const innerUnwrapped = unwrapZodSchema(option);
|
|
249
|
+
if (innerUnwrapped instanceof z$1.ZodLiteral) {
|
|
250
|
+
const value = getZodDef(innerUnwrapped).value;
|
|
251
|
+
if (typeof value === "string" || typeof value === "number") values.push(value);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return values.length > 0 ? values : null;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Creates a CLI option validator for enum fields.
|
|
260
|
+
*
|
|
261
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
262
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
263
|
+
*
|
|
264
|
+
* @param schema - The Zod schema for validation
|
|
265
|
+
* @param optionName - The CLI option name (for error messages)
|
|
266
|
+
* @param choices - The valid enum choices
|
|
267
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
268
|
+
*/
|
|
269
|
+
function createEnumValidator(schema, optionName, choices) {
|
|
270
|
+
return (input) => {
|
|
271
|
+
const result = schema.safeParse(input);
|
|
272
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". Must be one of: ${choices.join(", ")}`);
|
|
273
|
+
return result.data;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Creates a CLI option validator for number fields.
|
|
278
|
+
*
|
|
279
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
280
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
281
|
+
*
|
|
282
|
+
* @param schema - The Zod schema for validation
|
|
283
|
+
* @param optionName - The CLI option name (for error messages)
|
|
284
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
285
|
+
*/
|
|
286
|
+
function createNumberValidator(schema, optionName) {
|
|
287
|
+
return (input) => {
|
|
288
|
+
const num = Number(input);
|
|
289
|
+
if (Number.isNaN(num)) throw new InvalidArgumentError(`Invalid number for --${optionName}: "${input}". Expected a valid number.`);
|
|
290
|
+
const result = schema.safeParse(num);
|
|
291
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". ${formatZodErrorMessage(result.error)}`);
|
|
292
|
+
return result.data;
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Creates a CLI option validator for string fields.
|
|
297
|
+
*
|
|
298
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
299
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
300
|
+
*
|
|
301
|
+
* @param schema - The Zod schema for validation
|
|
302
|
+
* @param optionName - The CLI option name (for error messages)
|
|
303
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
304
|
+
*/
|
|
305
|
+
function createStringValidator(schema, optionName) {
|
|
306
|
+
return (input) => {
|
|
307
|
+
const result = schema.safeParse(input);
|
|
308
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". ${formatZodErrorMessage(result.error)}`);
|
|
309
|
+
return result.data;
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Creates a CLI option validator for union types with literal values.
|
|
314
|
+
*
|
|
315
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
316
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
317
|
+
*
|
|
318
|
+
* @param schema - The Zod schema for validation
|
|
319
|
+
* @param optionName - The CLI option name (for error messages)
|
|
320
|
+
* @param values - The valid literal values
|
|
321
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
322
|
+
*/
|
|
323
|
+
function createLiteralUnionValidator(schema, optionName, values) {
|
|
324
|
+
return (input) => {
|
|
325
|
+
const numericValues = values.filter((v) => typeof v === "number");
|
|
326
|
+
let parsedInput = input;
|
|
327
|
+
if (numericValues.length > 0 && !Number.isNaN(Number(input))) parsedInput = Number(input);
|
|
328
|
+
const result = schema.safeParse(parsedInput);
|
|
329
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". Must be one of: ${values.map((v) => typeof v === "string" ? `"${v}"` : String(v)).join(", ")}`);
|
|
330
|
+
return result.data;
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Creates a generic CLI option validator.
|
|
335
|
+
*
|
|
336
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
337
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
338
|
+
*
|
|
339
|
+
* @param schema - The Zod schema for validation
|
|
340
|
+
* @param optionName - The CLI option name (for error messages)
|
|
341
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
342
|
+
*/
|
|
343
|
+
function createGenericValidator(schema, optionName) {
|
|
344
|
+
return (input) => {
|
|
345
|
+
const result = schema.safeParse(input);
|
|
346
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". ${formatZodErrorMessage(result.error)}`);
|
|
347
|
+
return result.data;
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Formats a Zod error into a user-friendly message.
|
|
352
|
+
*
|
|
353
|
+
* @param error - The Zod error object
|
|
354
|
+
* @returns A formatted error message
|
|
355
|
+
*/
|
|
356
|
+
function formatZodErrorMessage(error) {
|
|
357
|
+
const firstIssue = error.issues[0];
|
|
358
|
+
if (!firstIssue) return "Validation failed.";
|
|
359
|
+
switch (firstIssue.code) {
|
|
360
|
+
case "invalid_value": {
|
|
361
|
+
const issue = firstIssue;
|
|
362
|
+
if (issue.values) return `Expected one of: ${issue.values.join(", ")}`;
|
|
363
|
+
return firstIssue.message;
|
|
364
|
+
}
|
|
365
|
+
case "invalid_type": return `Expected ${firstIssue.expected}`;
|
|
366
|
+
case "too_small":
|
|
367
|
+
case "too_big": return firstIssue.message;
|
|
368
|
+
default: return firstIssue.message;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Gets the internal definition object from a Zod type.
|
|
373
|
+
* Handles both modern Zod v4 (_zod.def) and legacy (_def) structures.
|
|
374
|
+
*
|
|
375
|
+
* @param field - The Zod type to get the definition from
|
|
376
|
+
* @returns The internal definition object
|
|
377
|
+
*/
|
|
378
|
+
function getZodDef(field) {
|
|
379
|
+
const zodContainer = field._zod;
|
|
380
|
+
if (zodContainer?.def) return zodContainer.def;
|
|
381
|
+
return field._def ?? {};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Unwraps Zod wrapper types (optional, default, etc.) to get the inner type.
|
|
385
|
+
*
|
|
386
|
+
* @param field - The Zod type to unwrap
|
|
387
|
+
* @returns The unwrapped inner type
|
|
388
|
+
*/
|
|
389
|
+
function unwrapZodSchema(field) {
|
|
390
|
+
const def = getZodDef(field);
|
|
391
|
+
if (field instanceof z$1.ZodDefault) {
|
|
392
|
+
const inner = def.innerType;
|
|
393
|
+
return inner ? unwrapZodSchema(inner) : field;
|
|
394
|
+
}
|
|
395
|
+
if (field instanceof z$1.ZodOptional) {
|
|
396
|
+
const inner = def.innerType;
|
|
397
|
+
return inner ? unwrapZodSchema(inner) : field;
|
|
398
|
+
}
|
|
399
|
+
if (field instanceof z$1.ZodNullable) {
|
|
400
|
+
const inner = def.innerType;
|
|
401
|
+
return inner ? unwrapZodSchema(inner) : field;
|
|
402
|
+
}
|
|
403
|
+
return field;
|
|
404
|
+
}
|
|
405
|
+
|
|
187
406
|
//#endregion
|
|
188
407
|
//#region src/cli/options/options.builder.ts
|
|
189
408
|
/**
|
|
@@ -304,15 +523,14 @@ var OptionsBuilder = class {
|
|
|
304
523
|
this.registerGenericOption(ctx);
|
|
305
524
|
}
|
|
306
525
|
/**
|
|
307
|
-
* Registers a number option with numeric parsing.
|
|
526
|
+
* Registers a number option with numeric parsing and validation.
|
|
308
527
|
*
|
|
309
528
|
* @param ctx - The option context
|
|
310
529
|
*/
|
|
311
530
|
registerNumberOption(ctx) {
|
|
312
531
|
const { command, rawField, optionFlag, optionName, description, parsedDefault } = ctx;
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
command.option(`${optionFlag} <${optionName}>`, description, wrappedParser, parsedDefault);
|
|
532
|
+
const validator = createNumberValidator(rawField, optionName);
|
|
533
|
+
command.option(`${optionFlag} <${optionName}>`, description, validator, parsedDefault);
|
|
316
534
|
}
|
|
317
535
|
/**
|
|
318
536
|
* Registers an enum option with choice validation.
|
|
@@ -320,111 +538,46 @@ var OptionsBuilder = class {
|
|
|
320
538
|
* @param ctx - The option context
|
|
321
539
|
*/
|
|
322
540
|
registerEnumOption(ctx) {
|
|
323
|
-
const { command, rawField,
|
|
324
|
-
const choices =
|
|
325
|
-
const
|
|
326
|
-
const wrappedParser = this.wrapParser(parser);
|
|
541
|
+
const { command, rawField, optionFlag, optionName, description, parsedDefault } = ctx;
|
|
542
|
+
const choices = extractEnumValues(rawField) ?? [];
|
|
543
|
+
const validator = createEnumValidator(rawField, optionName, choices);
|
|
327
544
|
const fullDescription = `${description}${choices.length ? ` (choices: ${choices.join(", ")})` : ""}`;
|
|
328
|
-
command.option(`${optionFlag} <${optionName}>`, fullDescription,
|
|
545
|
+
command.option(`${optionFlag} <${optionName}>`, fullDescription, validator, parsedDefault);
|
|
329
546
|
}
|
|
330
547
|
/**
|
|
331
|
-
*
|
|
548
|
+
* Registers a string option with validation.
|
|
332
549
|
*
|
|
333
550
|
* @param ctx - The option context
|
|
334
551
|
*/
|
|
335
552
|
registerStringOption(ctx) {
|
|
336
553
|
const { command, rawField, optionFlag, optionName, description, parsedDefault } = ctx;
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
command.option(`${optionFlag} <${optionName}>`, description, wrappedParser, parsedDefault);
|
|
554
|
+
const validator = createStringValidator(rawField, optionName);
|
|
555
|
+
command.option(`${optionFlag} <${optionName}>`, description, validator, parsedDefault);
|
|
340
556
|
}
|
|
341
557
|
/**
|
|
342
558
|
* Registers a generic option for other Zod types.
|
|
559
|
+
* Handles union types with literal values specially for better error messages.
|
|
343
560
|
*
|
|
344
561
|
* @param ctx - The option context
|
|
345
562
|
*/
|
|
346
563
|
registerGenericOption(ctx) {
|
|
347
564
|
const { command, rawField, optionFlag, optionName, description, parsedDefault } = ctx;
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (result.isErr()) throw new Error(result.error);
|
|
365
|
-
return result.value;
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Creates a parser for number options.
|
|
370
|
-
*
|
|
371
|
-
* @template T - The expected return type
|
|
372
|
-
* @param rawField - The raw Zod field
|
|
373
|
-
* @param optionName - The option name for error messages
|
|
374
|
-
* @returns A parser function that converts strings to numbers with validation
|
|
375
|
-
*/
|
|
376
|
-
createNumberParser(rawField, optionName) {
|
|
377
|
-
return (input) => {
|
|
378
|
-
const num = Number(input);
|
|
379
|
-
if (Number.isNaN(num)) return err(`Invalid number for --${optionName}: ${input}`);
|
|
380
|
-
const result = parseSchema(rawField, num);
|
|
381
|
-
if (result.isErr()) return err(result.error.message);
|
|
382
|
-
return ok(result.value);
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Creates a parser for enum options.
|
|
387
|
-
*
|
|
388
|
-
* @template T - The expected return type
|
|
389
|
-
* @param rawField - The raw Zod field
|
|
390
|
-
* @param optionName - The option name for error messages
|
|
391
|
-
* @param choices - The valid enum choices
|
|
392
|
-
* @returns A parser function that validates input against the enum choices
|
|
393
|
-
*/
|
|
394
|
-
createEnumParser(rawField, optionName, choices) {
|
|
395
|
-
return (input) => {
|
|
396
|
-
const result = parseSchema(rawField, input);
|
|
397
|
-
if (result.isErr()) return err(`Invalid value for --${optionName}: ${input}. Allowed: ${choices.join(", ")}`);
|
|
398
|
-
return ok(result.value);
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Creates a parser for string options.
|
|
403
|
-
*
|
|
404
|
-
* @template T - The expected return type
|
|
405
|
-
* @param rawField - The raw Zod field
|
|
406
|
-
* @returns A parser function that validates input against the schema
|
|
407
|
-
*/
|
|
408
|
-
createStringParser(rawField) {
|
|
409
|
-
return (input) => {
|
|
410
|
-
const result = parseSchema(rawField, input);
|
|
411
|
-
if (result.isErr()) return err(result.error.message);
|
|
412
|
-
return ok(result.value);
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Creates a generic parser using Zod validation.
|
|
417
|
-
*
|
|
418
|
-
* @template T - The expected return type
|
|
419
|
-
* @param rawField - The raw Zod field
|
|
420
|
-
* @returns A parser function that validates input against the schema
|
|
421
|
-
*/
|
|
422
|
-
createGenericParser(rawField) {
|
|
423
|
-
return (input) => {
|
|
424
|
-
const result = parseSchema(rawField, input);
|
|
425
|
-
if (result.isErr()) return err(result.error.message);
|
|
426
|
-
return ok(result.value);
|
|
427
|
-
};
|
|
565
|
+
const literalValues = extractLiteralValues(rawField);
|
|
566
|
+
if (literalValues) {
|
|
567
|
+
const validator$1 = createLiteralUnionValidator(rawField, optionName, literalValues);
|
|
568
|
+
const fullDescription = `${description} (choices: ${literalValues.map((v) => typeof v === "string" ? v : String(v)).join(", ")})`;
|
|
569
|
+
command.option(`${optionFlag} <${optionName}>`, fullDescription, validator$1, parsedDefault);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const enumValues = extractEnumValues(rawField);
|
|
573
|
+
if (enumValues) {
|
|
574
|
+
const validator$1 = createEnumValidator(rawField, optionName, enumValues);
|
|
575
|
+
const fullDescription = `${description} (choices: ${enumValues.join(", ")})`;
|
|
576
|
+
command.option(`${optionFlag} <${optionName}>`, fullDescription, validator$1, parsedDefault);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const validator = createGenericValidator(rawField, optionName);
|
|
580
|
+
command.option(`${optionFlag} <${optionName}>`, description, validator, parsedDefault);
|
|
428
581
|
}
|
|
429
582
|
/**
|
|
430
583
|
* Gets the internal definition object from a Zod type.
|
|
@@ -469,15 +622,6 @@ var OptionsBuilder = class {
|
|
|
469
622
|
const def = this.getInternalDef(rawField);
|
|
470
623
|
return (def.innerType ?? def.schema) instanceof z$1.ZodBoolean;
|
|
471
624
|
}
|
|
472
|
-
/**
|
|
473
|
-
* Extracts enum choices from a ZodEnum field.
|
|
474
|
-
*
|
|
475
|
-
* @param field - The ZodEnum field
|
|
476
|
-
* @returns The array of valid enum choices
|
|
477
|
-
*/
|
|
478
|
-
getEnumChoices(field) {
|
|
479
|
-
return this.getInternalDef(field).values ?? [];
|
|
480
|
-
}
|
|
481
625
|
};
|
|
482
626
|
|
|
483
627
|
//#endregion
|
|
@@ -1003,9 +1147,10 @@ function createBumpExecutionGroup() {
|
|
|
1003
1147
|
*/
|
|
1004
1148
|
function shouldSkipBumpStrategy(ctx) {
|
|
1005
1149
|
const { skipBump, releaseType, bumpStrategy } = ctx.config;
|
|
1150
|
+
const { selectedReleaseType, selectedBumpStrategy } = ctx.data;
|
|
1006
1151
|
if (skipBump) return true;
|
|
1007
|
-
if (releaseType) return true;
|
|
1008
|
-
if (!bumpStrategy) return true;
|
|
1152
|
+
if (releaseType || selectedReleaseType) return true;
|
|
1153
|
+
if (!Boolean(bumpStrategy || selectedBumpStrategy)) return true;
|
|
1009
1154
|
return false;
|
|
1010
1155
|
}
|
|
1011
1156
|
/**
|
|
@@ -1013,9 +1158,11 @@ function shouldSkipBumpStrategy(ctx) {
|
|
|
1013
1158
|
*/
|
|
1014
1159
|
function getSkipReason(ctx) {
|
|
1015
1160
|
const { skipBump, releaseType, bumpStrategy } = ctx.config;
|
|
1161
|
+
const { selectedReleaseType, selectedBumpStrategy } = ctx.data;
|
|
1016
1162
|
if (skipBump) return "skipBump is enabled";
|
|
1017
|
-
if (releaseType) return `releaseType already set to '${releaseType}'`;
|
|
1018
|
-
if (
|
|
1163
|
+
if (releaseType) return `releaseType already set to '${releaseType}' (config)`;
|
|
1164
|
+
if (selectedReleaseType) return `releaseType already set to '${selectedReleaseType}' (data)`;
|
|
1165
|
+
if (!Boolean(bumpStrategy || selectedBumpStrategy)) return "no bumpStrategy configured";
|
|
1019
1166
|
return "unknown reason";
|
|
1020
1167
|
}
|
|
1021
1168
|
function createDelegateBumpStrategyTask() {
|
|
@@ -1034,7 +1181,10 @@ function createDelegateBumpStrategyTask() {
|
|
|
1034
1181
|
//#endregion
|
|
1035
1182
|
//#region src/commands/release/tasks/determine-automatic-bump.task.ts
|
|
1036
1183
|
function createDetermineAutomaticBump() {
|
|
1037
|
-
return TaskBuilder.create("determine-automatic-bump").description("Automatically determines the version bump from commit messages").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) =>
|
|
1184
|
+
return TaskBuilder.create("determine-automatic-bump").description("Automatically determines the version bump from commit messages").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) => {
|
|
1185
|
+
const bumpStrategy = ctx.data.selectedBumpStrategy ?? ctx.config.bumpStrategy;
|
|
1186
|
+
return ctx.config.skipBump || bumpStrategy !== BUMP_STRATEGY_AUTO;
|
|
1187
|
+
}, "Skipped: skipBump enabled or bumpStrategy is not 'auto'").execute((ctx) => {
|
|
1038
1188
|
logger.info("determine-automatic-bump");
|
|
1039
1189
|
return FireflyOkAsync(ctx);
|
|
1040
1190
|
}).build();
|
|
@@ -1042,17 +1192,69 @@ function createDetermineAutomaticBump() {
|
|
|
1042
1192
|
|
|
1043
1193
|
//#endregion
|
|
1044
1194
|
//#region src/commands/release/tasks/prompt-bump-strategy.task.ts
|
|
1195
|
+
const BUMP_STRATEGIES = [{
|
|
1196
|
+
label: "Automatic Bump",
|
|
1197
|
+
value: BUMP_STRATEGY_AUTO,
|
|
1198
|
+
hint: "Determines the next version based on conventional commits history"
|
|
1199
|
+
}, {
|
|
1200
|
+
label: "Manual Bump",
|
|
1201
|
+
value: BUMP_STRATEGY_MANUAL,
|
|
1202
|
+
hint: "Manually specify the next version"
|
|
1203
|
+
}];
|
|
1204
|
+
const VALID_STRATEGY_VALUES = BUMP_STRATEGIES.map((s) => s.value);
|
|
1205
|
+
/**
|
|
1206
|
+
* Validates that the selected strategy is one of the allowed values.
|
|
1207
|
+
*
|
|
1208
|
+
* @param strategy The selected version bump strategy.
|
|
1209
|
+
* @returns A FireflyResult indicating success or failure of validation.
|
|
1210
|
+
*/
|
|
1211
|
+
function validateStrategy(strategy) {
|
|
1212
|
+
if (!VALID_STRATEGY_VALUES.includes(strategy)) return invalidErr({ message: `Invalid version bump strategy: ${strategy}` });
|
|
1213
|
+
return FireflyOk(strategy);
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Prompts the user to select a bump strategy using the logger's prompt API.
|
|
1217
|
+
*/
|
|
1218
|
+
function promptBumpStrategy() {
|
|
1219
|
+
const defaultStrategy = BUMP_STRATEGIES[0];
|
|
1220
|
+
if (!defaultStrategy) return notFoundErrAsync({ message: "No default version bump strategy found" });
|
|
1221
|
+
logger.verbose("PromptBumpStrategyTask: Prompting user for version bump strategy.");
|
|
1222
|
+
return wrapPromise(logger.prompt("Select version bump strategy", {
|
|
1223
|
+
type: "select",
|
|
1224
|
+
options: BUMP_STRATEGIES,
|
|
1225
|
+
initial: defaultStrategy.value,
|
|
1226
|
+
cancel: "undefined"
|
|
1227
|
+
})).andThen((selected) => {
|
|
1228
|
+
if (!selected || selected === "") return failedErrAsync({ message: "Operation cancelled by user" });
|
|
1229
|
+
if (logger.level === LogLevels.verbose) logger.log("");
|
|
1230
|
+
if (logger.level !== LogLevels.verbose) logger.log("");
|
|
1231
|
+
const validationResult = validateStrategy(selected);
|
|
1232
|
+
if (validationResult.isErr()) return FireflyErrAsync(validationResult.error);
|
|
1233
|
+
logger.verbose(`PromptBumpStrategyTask: Selected version bump strategy: '${selected}'`);
|
|
1234
|
+
return FireflyOkAsync(selected);
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Creates the Prompt Bump Strategy Task.
|
|
1239
|
+
*
|
|
1240
|
+
* This task prompts the user to select a version bump strategy (automatic or manual) and
|
|
1241
|
+
* stores it in the release context. It depends on `initialize-release-version` and will be
|
|
1242
|
+
* skipped when `skipBump` is enabled or a `bumpStrategy`/`releaseType` is already provided.
|
|
1243
|
+
*
|
|
1244
|
+
* This task:
|
|
1245
|
+
* 1. Prompts the user to select a bump strategy
|
|
1246
|
+
*/
|
|
1045
1247
|
function createPromptBumpStrategyTask() {
|
|
1046
|
-
return TaskBuilder.create("prompt-bump-strategy").description("Prompts the user for a version bump strategy").dependsOn("initialize-release-version").skipWhenWithReason((ctx) => ctx.config.skipBump || Boolean(ctx.config.bumpStrategy) || Boolean(ctx.config.releaseType), "Skipped: skipBump enabled, or bumpStrategy/releaseType already specified").execute((ctx) =>
|
|
1047
|
-
logger.info("prompt-bump-strategy");
|
|
1048
|
-
return FireflyOkAsync(ctx);
|
|
1049
|
-
}).build();
|
|
1248
|
+
return TaskBuilder.create("prompt-bump-strategy").description("Prompts the user for a version bump strategy").dependsOn("initialize-release-version").skipWhenWithReason((ctx) => ctx.config.skipBump || Boolean(ctx.config.bumpStrategy) || Boolean(ctx.config.releaseType), "Skipped: skipBump enabled, or bumpStrategy/releaseType already specified").execute((ctx) => promptBumpStrategy().andThen((strategy) => FireflyOkAsync(ctx.fork("selectedBumpStrategy", strategy)))).build();
|
|
1050
1249
|
}
|
|
1051
1250
|
|
|
1052
1251
|
//#endregion
|
|
1053
1252
|
//#region src/commands/release/tasks/prompt-manual-bump.task.ts
|
|
1054
1253
|
function createPromptManualVersionTask() {
|
|
1055
|
-
return TaskBuilder.create("prompt-manual-version").description("Prompts the user for a manual version bump selections").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) =>
|
|
1254
|
+
return TaskBuilder.create("prompt-manual-version").description("Prompts the user for a manual version bump selections").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) => {
|
|
1255
|
+
const bumpStrategy = ctx.data.selectedBumpStrategy ?? ctx.config.bumpStrategy;
|
|
1256
|
+
return ctx.config.skipBump || bumpStrategy !== BUMP_STRATEGY_MANUAL;
|
|
1257
|
+
}, "Skipped: skipBump enabled or bumpStrategy is not 'manual'").execute((ctx) => {
|
|
1056
1258
|
logger.info("prompt-manual-version");
|
|
1057
1259
|
return FireflyOkAsync(ctx);
|
|
1058
1260
|
}).build();
|
|
@@ -1060,11 +1262,43 @@ function createPromptManualVersionTask() {
|
|
|
1060
1262
|
|
|
1061
1263
|
//#endregion
|
|
1062
1264
|
//#region src/commands/release/tasks/straight-version-bump.task.ts
|
|
1265
|
+
/**
|
|
1266
|
+
* Parses the current version from a raw string.
|
|
1267
|
+
*
|
|
1268
|
+
* @param currentVersionRaw - The raw string representing the current version
|
|
1269
|
+
* @returns A FireflyResult containing the parsed Version or a validation error
|
|
1270
|
+
*/
|
|
1271
|
+
function parseCurrentVersion(currentVersionRaw) {
|
|
1272
|
+
if (!currentVersionRaw) return validationErr({ message: "Current version is undefined" });
|
|
1273
|
+
return Version.from(currentVersionRaw);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Builds the bump options from the release context.
|
|
1277
|
+
*/
|
|
1278
|
+
function buildBumpOptionsFromContext(ctx) {
|
|
1279
|
+
const currentVersionResult = parseCurrentVersion(ctx.data.currentVersion);
|
|
1280
|
+
if (currentVersionResult.isErr()) return FireflyErrAsync(currentVersionResult.error);
|
|
1281
|
+
const releaseType = ctx.config.releaseType;
|
|
1282
|
+
if (releaseType === void 0) return invalidErrAsync({ message: "Release type is required for straight bump" });
|
|
1283
|
+
return FireflyOkAsync({
|
|
1284
|
+
currentVersion: currentVersionResult.value,
|
|
1285
|
+
releaseType,
|
|
1286
|
+
preReleaseID: ctx.config.preReleaseID,
|
|
1287
|
+
preReleaseBase: ctx.config.preReleaseBase
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Performs the straight bump by delegating to the version bumper service.
|
|
1292
|
+
*/
|
|
1293
|
+
function executeStraightVersionBump(ctx) {
|
|
1294
|
+
return buildBumpOptionsFromContext(ctx).andThen((options) => ctx.services.versionBumper.bump(options)).andThen((newVersion) => {
|
|
1295
|
+
const from = ctx.data.currentVersion || "unknown";
|
|
1296
|
+
logger.info(`Bumped version: ${colors.green(from)} -> ${colors.green(newVersion.raw)}`);
|
|
1297
|
+
return FireflyOkAsync(ctx.fork("nextVersion", newVersion.toString()));
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1063
1300
|
function createStraightVersionBump() {
|
|
1064
|
-
return TaskBuilder.create("straight-version-bump").description("Performs a direct version bump based on the configured release type").dependsOn("initialize-release-version").skipWhenWithReason((ctx) => ctx.config.skipBump || ctx.config.releaseType === void 0, "Skipped: skipBump is enabled or no release type specified").execute((ctx) =>
|
|
1065
|
-
logger.info("straight-version-bump");
|
|
1066
|
-
return FireflyOkAsync(ctx);
|
|
1067
|
-
}).build();
|
|
1301
|
+
return TaskBuilder.create("straight-version-bump").description("Performs a direct version bump based on the configured release type").dependsOn("initialize-release-version").skipWhenWithReason((ctx) => ctx.config.skipBump || ctx.config.releaseType === void 0, "Skipped: skipBump is enabled or no release type specified").execute((ctx) => executeStraightVersionBump(ctx)).build();
|
|
1068
1302
|
}
|
|
1069
1303
|
|
|
1070
1304
|
//#endregion
|
|
@@ -1091,11 +1325,36 @@ function createBumpStrategyGroup() {
|
|
|
1091
1325
|
|
|
1092
1326
|
//#endregion
|
|
1093
1327
|
//#region src/commands/release/tasks/initialize-release-version.task.ts
|
|
1328
|
+
const PACKAGE_JSON_FILE = "package.json";
|
|
1329
|
+
/**
|
|
1330
|
+
* Reads package.json and extracts the version field.
|
|
1331
|
+
*
|
|
1332
|
+
* Behavior:
|
|
1333
|
+
* - Reads the configured package.json file.
|
|
1334
|
+
* - If the version property is missing, returns a validation error.
|
|
1335
|
+
* - Otherwise resolves with the version string.
|
|
1336
|
+
*/
|
|
1337
|
+
function getVersionFromPackageJson(ctx) {
|
|
1338
|
+
return ctx.services.packageJson.read(PACKAGE_JSON_FILE).andThen((pkg) => {
|
|
1339
|
+
const version = pkg.version;
|
|
1340
|
+
if (!version) return validationErrAsync({ message: "The 'version' field is missing in package.json." });
|
|
1341
|
+
logger.verbose(`InitializeReleaseVersionTask: Prepared current version: ${version}`);
|
|
1342
|
+
return FireflyOkAsync(version);
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Creates the Initialize Release Version task.
|
|
1347
|
+
*
|
|
1348
|
+
* This task initializes the current release version by reading it from package.json.
|
|
1349
|
+
*
|
|
1350
|
+
* This task:
|
|
1351
|
+
* 1. Reads the version from package.json
|
|
1352
|
+
*/
|
|
1094
1353
|
function createInitializeReleaseVersion() {
|
|
1095
|
-
return TaskBuilder.create("initialize-release-version").description("
|
|
1096
|
-
logger.info(
|
|
1097
|
-
return FireflyOkAsync(ctx);
|
|
1098
|
-
}).build();
|
|
1354
|
+
return TaskBuilder.create("initialize-release-version").description("Initialize current release version from package.json").dependsOn("prepare-release-config").execute((ctx) => getVersionFromPackageJson(ctx).andThen((currentVersion) => {
|
|
1355
|
+
logger.info(`Current version is ${colors.green(currentVersion)}`);
|
|
1356
|
+
return FireflyOkAsync(ctx.fork("currentVersion", currentVersion));
|
|
1357
|
+
})).build();
|
|
1099
1358
|
}
|
|
1100
1359
|
|
|
1101
1360
|
//#endregion
|
|
@@ -1105,6 +1364,12 @@ const SSH_REMOTE_REGEX = /git@[^:]+:([^/]+)\/([^/.]+)(?:\.git)?/;
|
|
|
1105
1364
|
const SCOPED_PACKAGE_REGEX = /^@([^/]+)\/(.+)$/;
|
|
1106
1365
|
const PRERELEASE_REGEX = /^\d+\.\d+\.\d+-([a-zA-Z]+)/;
|
|
1107
1366
|
/**
|
|
1367
|
+
* Terminologies:
|
|
1368
|
+
*
|
|
1369
|
+
* Prepared: The value has been determined and set in the context.
|
|
1370
|
+
* Using: The value was explicitly provided in the config and is used as-is.
|
|
1371
|
+
*/
|
|
1372
|
+
/**
|
|
1108
1373
|
* Parses a git remote URL to extract owner and repository name.
|
|
1109
1374
|
* Supports both HTTPS and SSH formats.
|
|
1110
1375
|
*
|
|
@@ -1147,38 +1412,17 @@ function parsePackageName(packageName) {
|
|
|
1147
1412
|
* extractPreReleaseId("1.0.0-beta.1") // "beta"
|
|
1148
1413
|
* extractPreReleaseId("1.0.0") // undefined
|
|
1149
1414
|
*/
|
|
1150
|
-
function
|
|
1415
|
+
function extractPreReleaseID(version) {
|
|
1151
1416
|
return version.match(PRERELEASE_REGEX)?.[1];
|
|
1152
1417
|
}
|
|
1153
1418
|
/**
|
|
1154
|
-
* Hydrates the
|
|
1419
|
+
* Hydrates the `name` field from package.json when not provided in config.
|
|
1420
|
+
*
|
|
1421
|
+
* Cases:
|
|
1422
|
+
* 1. If name is undefined and package.json has no name, returns a validation error.
|
|
1423
|
+
* 2. If name is undefined and package.json has a name, extracts the name (stripping scope) and returns it.
|
|
1424
|
+
* 3. Otherwise uses provided name.
|
|
1155
1425
|
*/
|
|
1156
|
-
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
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
function hydrateFromPackageJson(ctx) {
|
|
1167
|
-
return ctx.services.fs.exists("package.json").andThen((exists) => {
|
|
1168
|
-
if (!exists) return FireflyOkAsync({
|
|
1169
|
-
name: void 0,
|
|
1170
|
-
scope: void 0,
|
|
1171
|
-
preReleaseId: void 0
|
|
1172
|
-
});
|
|
1173
|
-
return ctx.services.packageJson.read("package.json").andThen((pkg) => zip3Async(hydrateNameFromPackageJson(ctx, pkg), hydrateScopeFromPackageJson(ctx, pkg), hydratePreReleaseIdFromPackageJson(ctx, pkg)).map(([name, scope, preReleaseId]) => {
|
|
1174
|
-
const result = {};
|
|
1175
|
-
if (name) result.name = name;
|
|
1176
|
-
if (scope) result.scope = scope;
|
|
1177
|
-
if (preReleaseId) result.preReleaseId = preReleaseId;
|
|
1178
|
-
return result;
|
|
1179
|
-
}));
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
1426
|
function hydrateNameFromPackageJson(ctx, packageJson) {
|
|
1183
1427
|
if (ctx.config.name === void 0 && !packageJson.name) return validationErrAsync({ message: "Could not find a valid name in package.json" });
|
|
1184
1428
|
if (ctx.config.name === void 0 && packageJson.name) {
|
|
@@ -1189,6 +1433,14 @@ function hydrateNameFromPackageJson(ctx, packageJson) {
|
|
|
1189
1433
|
logger.verbose(`PrepareReleaseConfigTask: Using provided name: "${ctx.config.name}" as it is explicitly set`);
|
|
1190
1434
|
return FireflyOkAsync(ctx.config.name);
|
|
1191
1435
|
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Hydrates the `scope` field from package.json when not provided in config.
|
|
1438
|
+
*
|
|
1439
|
+
* Cases:
|
|
1440
|
+
* 1. If scope is explicitly provided (key exists and value is not undefined), it is used.
|
|
1441
|
+
* 2. If not provided, but package.json has a scoped `name` (e.g., "@scope/name"), the scope will be extracted and returned.
|
|
1442
|
+
* 3. Otherwise returns undefined.
|
|
1443
|
+
*/
|
|
1192
1444
|
function hydrateScopeFromPackageJson(ctx, packageJson) {
|
|
1193
1445
|
if (Object.hasOwn(ctx.config, "scope") && ctx.config.scope !== void 0) {
|
|
1194
1446
|
logger.verbose(`PrepareReleaseConfigTask: Using provided scope: "${ctx.config.scope}" as it is explicitly set`);
|
|
@@ -1204,24 +1456,181 @@ function hydrateScopeFromPackageJson(ctx, packageJson) {
|
|
|
1204
1456
|
logger.verbose("PrepareReleaseConfigTask: No scope to prepare from package.json");
|
|
1205
1457
|
return FireflyOkAsync(void 0);
|
|
1206
1458
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1459
|
+
/**
|
|
1460
|
+
* Hydrates the `preReleaseID` field from `package.json.version` when not provided.
|
|
1461
|
+
*
|
|
1462
|
+
* Cases:
|
|
1463
|
+
* 1. If preReleaseID is explicitly provided and not an empty string, it is used.
|
|
1464
|
+
* 2. If not provided, and `package.json.version` contains a prerelease segment, the prerelease identifier will be extracted and returned.
|
|
1465
|
+
* 3. Otherwise the function defaults to "alpha".
|
|
1466
|
+
*/
|
|
1467
|
+
function hydratePreReleaseIDFromPackageJson(ctx, packageJson) {
|
|
1468
|
+
if (Object.hasOwn(ctx.config, "preReleaseID") && ctx.config.preReleaseID !== void 0) {
|
|
1469
|
+
logger.verbose(`PrepareReleaseConfigTask: Using provided preReleaseID: "${ctx.config.preReleaseID}" as it is explicitly set`);
|
|
1470
|
+
return FireflyOkAsync(ctx.config.preReleaseID);
|
|
1211
1471
|
}
|
|
1212
1472
|
if (packageJson.version) {
|
|
1213
1473
|
const parsed = parse(packageJson.version);
|
|
1214
1474
|
if (!parsed) return validationErrAsync({ message: `Invalid version format in package.json: ${packageJson.version}` });
|
|
1215
1475
|
if (parsed.prerelease.length > 0 && typeof parsed.prerelease[0] === "string") {
|
|
1216
|
-
const
|
|
1217
|
-
logger.verbose(`PrepareReleaseConfigTask: Prepared
|
|
1218
|
-
return FireflyOkAsync(
|
|
1476
|
+
const preReleaseID = extractPreReleaseID(packageJson.version);
|
|
1477
|
+
logger.verbose(`PrepareReleaseConfigTask: Prepared preReleaseID from package.json: ${preReleaseID}`);
|
|
1478
|
+
return FireflyOkAsync(preReleaseID);
|
|
1219
1479
|
}
|
|
1220
1480
|
}
|
|
1221
|
-
logger.verbose("PrepareReleaseConfigTask: No
|
|
1481
|
+
logger.verbose("PrepareReleaseConfigTask: No preReleaseID to prepare from package.json, defaulting to 'alpha'");
|
|
1222
1482
|
return FireflyOkAsync("alpha");
|
|
1223
1483
|
}
|
|
1224
1484
|
/**
|
|
1485
|
+
* Hydrates the `preReleaseBase` field.
|
|
1486
|
+
*
|
|
1487
|
+
* Behavior:
|
|
1488
|
+
* - If explicitly provided in config (key exists and value is not undefined) use as-is.
|
|
1489
|
+
* - Otherwise default to 0.
|
|
1490
|
+
*
|
|
1491
|
+
* Note: we do NOT infer `preReleaseBase` from package.json anymore.
|
|
1492
|
+
*/
|
|
1493
|
+
function hydratePreReleaseBase(ctx) {
|
|
1494
|
+
const baseMaybe = ctx.config.preReleaseBase;
|
|
1495
|
+
if (Object.hasOwn(ctx.config, "preReleaseBase") && baseMaybe !== void 0) {
|
|
1496
|
+
logger.verbose(`PrepareReleaseConfigTask: Using provided preReleaseBase: "${ctx.config.preReleaseBase}" as it is explicitly set`);
|
|
1497
|
+
return FireflyOkAsync(baseMaybe);
|
|
1498
|
+
}
|
|
1499
|
+
logger.verbose("PrepareReleaseConfigTask: No preReleaseBase explicitly provided, defaulting to 0");
|
|
1500
|
+
return FireflyOkAsync(0);
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Hydrates name, scope, and preReleaseId from package.json.
|
|
1504
|
+
*
|
|
1505
|
+
* Behavior:
|
|
1506
|
+
* - If package.json does not exist, returns all values as undefined.
|
|
1507
|
+
* - If it exists, reads package.json and returns parsed results for name, scope and preReleaseId.
|
|
1508
|
+
* - If preReleaseBase is explicitly provided in config, it is used as-is, if not, it defaults to 0.
|
|
1509
|
+
*/
|
|
1510
|
+
function hydrateFromPackageJson(ctx) {
|
|
1511
|
+
return ctx.services.fs.exists("package.json").andThen((exists) => {
|
|
1512
|
+
if (!exists) return FireflyOkAsync({
|
|
1513
|
+
name: void 0,
|
|
1514
|
+
scope: void 0,
|
|
1515
|
+
preReleaseID: void 0,
|
|
1516
|
+
preReleaseBase: void 0
|
|
1517
|
+
});
|
|
1518
|
+
return ctx.services.packageJson.read("package.json").andThen((pkg) => hydrateNameFromPackageJson(ctx, pkg).andThen((name) => hydrateScopeFromPackageJson(ctx, pkg).andThen((scope) => hydratePreReleaseIDFromPackageJson(ctx, pkg).andThen((preReleaseId) => hydratePreReleaseBase(ctx).map((preReleaseBase) => {
|
|
1519
|
+
const result = {};
|
|
1520
|
+
if (name) result.name = name;
|
|
1521
|
+
if (scope) result.scope = scope;
|
|
1522
|
+
if (preReleaseId) result.preReleaseId = preReleaseId;
|
|
1523
|
+
if (preReleaseBase !== void 0) result.preReleaseBase = preReleaseBase;
|
|
1524
|
+
return result;
|
|
1525
|
+
})))));
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Hydrates the repository field from git remote URL.
|
|
1530
|
+
*
|
|
1531
|
+
* Behavior:
|
|
1532
|
+
* - If not inside a git repository, resolves to undefined.
|
|
1533
|
+
* - If inside a repository, detect the repository URL
|
|
1534
|
+
* using a fall-through strategy (upstream remote → origin → first remote).
|
|
1535
|
+
* - Parses the URL and returns "owner/repo" when possible.
|
|
1536
|
+
*/
|
|
1537
|
+
function hydrateRepository(ctx) {
|
|
1538
|
+
return ctx.services.git.inferRepositoryUrl().andThen((url) => {
|
|
1539
|
+
if (!url) return validationErrAsync({ message: "Could not determine git remote URL to infer repository information" });
|
|
1540
|
+
const parsed = parseGitRemoteUrl(url);
|
|
1541
|
+
if (parsed) return FireflyOkAsync(`${parsed.owner}/${parsed.repo}`);
|
|
1542
|
+
return validationErrAsync({ message: `Could not parse repository information from git remote URL: ${url}` });
|
|
1543
|
+
}).andTee((repository) => logger.verbose(`PrepareReleaseConfigTask: Prepared repository: ${repository}`));
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Hydrates branch setting from git.
|
|
1547
|
+
*
|
|
1548
|
+
* Behavior:
|
|
1549
|
+
* - If not inside a git repository, resolves to undefined.
|
|
1550
|
+
* - If a branch is explicitly provided in the config, validates it against the
|
|
1551
|
+
* current git branch and returns it (otherwise returns a validation error).
|
|
1552
|
+
* - If no branch is provided in the config, uses current git branch.
|
|
1553
|
+
*/
|
|
1554
|
+
function hydrateBranch(ctx) {
|
|
1555
|
+
return ctx.services.git.isInsideRepository().andThen((isRepo) => {
|
|
1556
|
+
if (!isRepo) return FireflyOkAsync(void 0);
|
|
1557
|
+
return ctx.services.git.getCurrentBranch().andThen((currentBranch) => {
|
|
1558
|
+
if (Object.hasOwn(ctx.config, "branch") && ctx.config.branch !== void 0 && ctx.config.branch.trim() !== "") {
|
|
1559
|
+
if (ctx.config.branch !== currentBranch) return validationErrAsync({ message: `Configured branch "${ctx.config.branch}" does not match current git branch "${currentBranch}"` });
|
|
1560
|
+
logger.verbose(`PrepareReleaseConfigTask: Using provided branch: "${ctx.config.branch}" as it is explicitly set`);
|
|
1561
|
+
return FireflyOkAsync(ctx.config.branch);
|
|
1562
|
+
}
|
|
1563
|
+
logger.verbose(`PrepareReleaseConfigTask: Prepared branch from git: ${currentBranch}`);
|
|
1564
|
+
return FireflyOkAsync(currentBranch);
|
|
1565
|
+
});
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Hydrates repository and branch information from git.
|
|
1570
|
+
*
|
|
1571
|
+
* Behavior:
|
|
1572
|
+
* - If not inside a git repository, resolves both values to undefined.
|
|
1573
|
+
* - Otherwise it composes `hydrateRepository` and `hydrateBranch` and returns both values.
|
|
1574
|
+
*/
|
|
1575
|
+
function hydrateFromGit(ctx) {
|
|
1576
|
+
return ctx.services.git.isInsideRepository().andThen((isRepo) => {
|
|
1577
|
+
if (!isRepo) return FireflyOkAsync({
|
|
1578
|
+
repository: void 0,
|
|
1579
|
+
branch: void 0
|
|
1580
|
+
});
|
|
1581
|
+
return hydrateRepository(ctx).andThen((repository) => hydrateBranch(ctx).map((branch) => {
|
|
1582
|
+
const result = {};
|
|
1583
|
+
if (repository) result.repository = repository;
|
|
1584
|
+
if (branch) result.branch = branch;
|
|
1585
|
+
return result;
|
|
1586
|
+
}));
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Hydrates release flags (releaseLatest, releasePreRelease, releaseDraft).
|
|
1591
|
+
*
|
|
1592
|
+
* Behavior:
|
|
1593
|
+
* - If exactly one flag is explicitly set to true, use that and set others to false.
|
|
1594
|
+
* - If no flags are explicitly set, default to releaseLatest = true, others = false.
|
|
1595
|
+
* - Validation of exclusivity is handled by the schema, so we only need to determine defaults.
|
|
1596
|
+
*/
|
|
1597
|
+
function hydrateReleaseFlags(ctx) {
|
|
1598
|
+
const { releaseLatest, releasePreRelease, releaseDraft } = ctx.config;
|
|
1599
|
+
const latestExplicit = releaseLatest === true;
|
|
1600
|
+
const preReleaseExplicit = releasePreRelease === true;
|
|
1601
|
+
const draftExplicit = releaseDraft === true;
|
|
1602
|
+
if (preReleaseExplicit) {
|
|
1603
|
+
logger.verbose(`PrepareReleaseConfigTask: Using "releasePreRelease" as it is explicitly set`);
|
|
1604
|
+
return FireflyOkAsync({
|
|
1605
|
+
releaseLatest: false,
|
|
1606
|
+
releasePreRelease: true,
|
|
1607
|
+
releaseDraft: false
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
if (draftExplicit) {
|
|
1611
|
+
logger.verbose(`PrepareReleaseConfigTask: Using "releaseDraft" as it is explicitly set`);
|
|
1612
|
+
return FireflyOkAsync({
|
|
1613
|
+
releaseLatest: false,
|
|
1614
|
+
releasePreRelease: false,
|
|
1615
|
+
releaseDraft: true
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
if (latestExplicit) {
|
|
1619
|
+
logger.verbose(`PrepareReleaseConfigTask: Using "releaseLatest" as it is explicitly set`);
|
|
1620
|
+
return FireflyOkAsync({
|
|
1621
|
+
releaseLatest: true,
|
|
1622
|
+
releasePreRelease: false,
|
|
1623
|
+
releaseDraft: false
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
logger.verbose("PrepareReleaseConfigTask: Prepared releaseLatest as default since no flag was explicitly set");
|
|
1627
|
+
return FireflyOkAsync({
|
|
1628
|
+
releaseLatest: true,
|
|
1629
|
+
releasePreRelease: false,
|
|
1630
|
+
releaseDraft: false
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1225
1634
|
* Creates the Prepare Release Config Task.
|
|
1226
1635
|
*
|
|
1227
1636
|
* This task determines and hydrates configuration settings, by inferring values from the environment.
|
|
@@ -1230,17 +1639,27 @@ function hydratePreReleaseIdFromPackageJson(ctx, packageJson) {
|
|
|
1230
1639
|
* 1. Detects repository owner/repo from git remote URL
|
|
1231
1640
|
* 2. Extracts name and scope from package.json
|
|
1232
1641
|
* 3. Extracts preReleaseId from package.json version
|
|
1642
|
+
* 4. Detects current git branch if not provided
|
|
1643
|
+
* 5. Determines release flags (latest, preRelease, draft) with proper defaults
|
|
1233
1644
|
*/
|
|
1234
1645
|
function createPrepareReleaseConfigTask() {
|
|
1235
1646
|
return TaskBuilder.create("prepare-release-config").description("Hydrate and prepare the release configuration").execute((ctx) => {
|
|
1236
1647
|
const hydrated = {};
|
|
1237
|
-
return
|
|
1238
|
-
if (repository) hydrated.repository = repository;
|
|
1648
|
+
return hydrateFromGit(ctx).andThen((gitData) => {
|
|
1649
|
+
if (gitData.repository) hydrated.repository = gitData.repository;
|
|
1650
|
+
if (gitData.branch) hydrated.branch = gitData.branch;
|
|
1651
|
+
return hydrateFromPackageJson(ctx);
|
|
1652
|
+
}).andThen((pkgData) => {
|
|
1239
1653
|
if (pkgData.name) hydrated.name = pkgData.name;
|
|
1240
1654
|
if (pkgData.scope) hydrated.scope = pkgData.scope;
|
|
1241
|
-
if (pkgData.
|
|
1242
|
-
|
|
1243
|
-
return ctx
|
|
1655
|
+
if (pkgData.preReleaseID) hydrated.preReleaseID = pkgData.preReleaseID;
|
|
1656
|
+
if (pkgData.preReleaseBase !== void 0) hydrated.preReleaseBase = pkgData.preReleaseBase;
|
|
1657
|
+
return hydrateReleaseFlags(ctx);
|
|
1658
|
+
}).map((releaseFlags) => {
|
|
1659
|
+
hydrated.releaseLatest = releaseFlags.releaseLatest;
|
|
1660
|
+
hydrated.releasePreRelease = releaseFlags.releasePreRelease;
|
|
1661
|
+
hydrated.releaseDraft = releaseFlags.releaseDraft;
|
|
1662
|
+
return ctx.forkConfig(hydrated);
|
|
1244
1663
|
});
|
|
1245
1664
|
}).build();
|
|
1246
1665
|
}
|
|
@@ -1407,7 +1826,7 @@ function validateReleaseFlagExclusivity(ctx) {
|
|
|
1407
1826
|
"releasePreRelease",
|
|
1408
1827
|
"releaseDraft"
|
|
1409
1828
|
];
|
|
1410
|
-
if (flagNames.filter((k) => ctx.value[k]).length > 1) ctx.issues.push({
|
|
1829
|
+
if (flagNames.filter((k) => ctx.value[k] === true).length > 1) ctx.issues.push({
|
|
1411
1830
|
code: "custom",
|
|
1412
1831
|
message: `Only one of ${flagNames.join(", ")} can be set to true.`,
|
|
1413
1832
|
input: ctx.value,
|
|
@@ -1453,13 +1872,15 @@ function validateSkipFlagCombinations(ctx) {
|
|
|
1453
1872
|
const ReleaseConfigSchema = z$1.object({
|
|
1454
1873
|
name: z$1.string().optional().describe("Unscoped project name. Auto-detected from package.json."),
|
|
1455
1874
|
scope: z$1.string().optional().describe("Org/user scope without '@'. Auto-detected from package.json."),
|
|
1456
|
-
|
|
1875
|
+
repository: z$1.string().optional().describe("GitHub repository in 'owner/repo' format."),
|
|
1876
|
+
base: z$1.string().optional().describe("Relative path from repository root to project root."),
|
|
1877
|
+
branch: z$1.string().optional().describe("Git branch to release from."),
|
|
1457
1878
|
changelogPath: z$1.string().default("CHANGELOG.md").describe("Changelog file path, relative to project root."),
|
|
1458
1879
|
bumpStrategy: BumpStrategySchema.describe("\"auto\" (from commits) or \"manual\" (user-specified)."),
|
|
1459
1880
|
releaseType: ReleaseTypeSchema.optional().describe("The release type to bump."),
|
|
1460
|
-
|
|
1881
|
+
preReleaseID: z$1.string().optional().describe("Pre-release ID (e.g., \"alpha\", \"beta\")."),
|
|
1461
1882
|
preReleaseBase: PreReleaseBaseSchema.describe("Starting version for pre-releases."),
|
|
1462
|
-
releaseNotes: z$1.string().
|
|
1883
|
+
releaseNotes: z$1.string().optional().describe("Custom release notes for changelog."),
|
|
1463
1884
|
commitMessage: z$1.string().default(COMMIT_MSG_TEMPLATE).describe("Commit message template with placeholders."),
|
|
1464
1885
|
tagName: z$1.string().default(TAG_NAME_TEMPLATE).describe("Tag name template with placeholders."),
|
|
1465
1886
|
skipBump: z$1.coerce.boolean().default(false).describe("Skip version bump step."),
|
|
@@ -1469,9 +1890,9 @@ const ReleaseConfigSchema = z$1.object({
|
|
|
1469
1890
|
skipGit: z$1.coerce.boolean().default(false).describe("Skip all git-related steps."),
|
|
1470
1891
|
skipPreflightCheck: z$1.coerce.boolean().default(false).describe("Skip preflight checks."),
|
|
1471
1892
|
releaseTitle: z$1.string().default(RELEASE_TITLE_TEMPLATE).describe("GitHub release title with placeholders."),
|
|
1472
|
-
releaseLatest: z$1.coerce.boolean().
|
|
1473
|
-
releasePreRelease: z$1.coerce.boolean().
|
|
1474
|
-
releaseDraft: z$1.coerce.boolean().
|
|
1893
|
+
releaseLatest: z$1.coerce.boolean().optional().describe("Mark as latest release."),
|
|
1894
|
+
releasePreRelease: z$1.coerce.boolean().optional().describe("Mark as pre-release."),
|
|
1895
|
+
releaseDraft: z$1.coerce.boolean().optional().describe("Release as draft version.")
|
|
1475
1896
|
}).check((ctx) => {
|
|
1476
1897
|
validateReleaseFlagExclusivity(ctx);
|
|
1477
1898
|
validateSkipGitRedundancy(ctx);
|
|
@@ -1525,21 +1946,41 @@ function defineService(definition) {
|
|
|
1525
1946
|
*/
|
|
1526
1947
|
const SERVICE_DEFINITIONS = {
|
|
1527
1948
|
fs: defineService({ factory: async ({ basePath }) => {
|
|
1528
|
-
const { createFileSystemService } = await import("./filesystem.service-
|
|
1949
|
+
const { createFileSystemService } = await import("./filesystem.service-Bsm3j1Bv.js");
|
|
1529
1950
|
return createFileSystemService(basePath);
|
|
1530
1951
|
} }),
|
|
1531
1952
|
packageJson: defineService({
|
|
1532
1953
|
dependencies: ["fs"],
|
|
1533
1954
|
factory: async ({ getService }) => {
|
|
1534
1955
|
const fs = await getService("fs");
|
|
1535
|
-
const { createPackageJsonService } = await import("./package-json.service-
|
|
1956
|
+
const { createPackageJsonService } = await import("./package-json.service-d4pVcOFd.js");
|
|
1536
1957
|
return createPackageJsonService(fs);
|
|
1537
1958
|
}
|
|
1538
1959
|
}),
|
|
1539
1960
|
git: defineService({ factory: async ({ basePath }) => {
|
|
1540
|
-
const { createGitService } = await import("./git.service-
|
|
1961
|
+
const { createGitService } = await import("./git.service-B3PC7qIe.js");
|
|
1541
1962
|
return createGitService(basePath);
|
|
1542
|
-
} })
|
|
1963
|
+
} }),
|
|
1964
|
+
versionBumper: defineService({ factory: async () => {
|
|
1965
|
+
const { createVersionBumperService } = await import("./version-bumper.service-ZBJ2jcLS.js");
|
|
1966
|
+
return createVersionBumperService();
|
|
1967
|
+
} }),
|
|
1968
|
+
versionStrategy: defineService({
|
|
1969
|
+
dependencies: ["versionBumper"],
|
|
1970
|
+
factory: async ({ getService }) => {
|
|
1971
|
+
const versionBumper = await getService("versionBumper");
|
|
1972
|
+
const { createVersionStrategyService } = await import("./version-strategy.service-BdpKRWLc.js");
|
|
1973
|
+
return createVersionStrategyService(versionBumper);
|
|
1974
|
+
}
|
|
1975
|
+
}),
|
|
1976
|
+
commitAnalysis: defineService({
|
|
1977
|
+
dependencies: ["git"],
|
|
1978
|
+
factory: async ({ getService }) => {
|
|
1979
|
+
const git = await getService("git");
|
|
1980
|
+
const { createCommitAnalysisService } = await import("./commit-analysis.service-B8W5aORO.js");
|
|
1981
|
+
return createCommitAnalysisService(git);
|
|
1982
|
+
}
|
|
1983
|
+
})
|
|
1543
1984
|
};
|
|
1544
1985
|
/**
|
|
1545
1986
|
* Array of all service keys for iteration
|
|
@@ -1800,7 +2241,7 @@ function logGraphStatistics(stats) {
|
|
|
1800
2241
|
|
|
1801
2242
|
//#endregion
|
|
1802
2243
|
//#region src/commands/release/release.command.ts
|
|
1803
|
-
const RELEASE_SERVICES = defineServiceKeys("fs", "packageJson", "git");
|
|
2244
|
+
const RELEASE_SERVICES = defineServiceKeys("fs", "packageJson", "git", "commitAnalysis", "versionBumper", "versionStrategy");
|
|
1804
2245
|
const releaseCommand = createCommand({
|
|
1805
2246
|
meta: {
|
|
1806
2247
|
name: "release",
|
|
@@ -2120,6 +2561,30 @@ var ImmutableWorkflowContext = class ImmutableWorkflowContext {
|
|
|
2120
2561
|
services: this.services
|
|
2121
2562
|
});
|
|
2122
2563
|
}
|
|
2564
|
+
/**
|
|
2565
|
+
* @example Merging hydrated config values
|
|
2566
|
+
* ```typescript
|
|
2567
|
+
* const updatedCtx = ctx.forkConfig({
|
|
2568
|
+
* repository: "owner/repo",
|
|
2569
|
+
* branch: "main"
|
|
2570
|
+
* });
|
|
2571
|
+
* ```
|
|
2572
|
+
*/
|
|
2573
|
+
forkConfig(updates) {
|
|
2574
|
+
if (Object.keys(updates).length === 0) return this;
|
|
2575
|
+
const mergedConfig = {
|
|
2576
|
+
...this.config,
|
|
2577
|
+
...updates
|
|
2578
|
+
};
|
|
2579
|
+
const frozenConfig = Object.freeze(mergedConfig);
|
|
2580
|
+
return new ImmutableWorkflowContext({
|
|
2581
|
+
startTime: this.startTime,
|
|
2582
|
+
workspace: this.workspace,
|
|
2583
|
+
config: frozenConfig,
|
|
2584
|
+
data: this.#data,
|
|
2585
|
+
services: this.services
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2123
2588
|
has(key) {
|
|
2124
2589
|
return key in this.#data;
|
|
2125
2590
|
}
|
|
@@ -2178,7 +2643,11 @@ var WorkflowExecutor = class {
|
|
|
2178
2643
|
const startTime = /* @__PURE__ */ new Date();
|
|
2179
2644
|
const executedTaskIds = [];
|
|
2180
2645
|
const skippedTaskIds = [];
|
|
2181
|
-
if (this.options.dryRun) logger.warn("
|
|
2646
|
+
if (this.options.dryRun) logger.warn("Running in DRY-RUN mode: No changes will be made.");
|
|
2647
|
+
const version = RuntimeEnv.version;
|
|
2648
|
+
const dashIndex = version.indexOf("-");
|
|
2649
|
+
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.`);
|
|
2650
|
+
else logger.warn(`You are running a PRE-RELEASE version of Firefly (${colors.dim(version)}). Unexpected behavior or bugs may occur.`);
|
|
2182
2651
|
logger.verbose(`WorkflowExecutor: Starting execution of ${tasks.length} tasks`);
|
|
2183
2652
|
return this.executeTasksSequentially(tasks, initialContext, executedTaskIds, skippedTaskIds).andThen(() => this.buildExecutionSuccessResult(startTime, executedTaskIds, skippedTaskIds)).orElse((error) => this.handleExecutionFailure({
|
|
2184
2653
|
error,
|
|
@@ -2206,6 +2675,8 @@ var WorkflowExecutor = class {
|
|
|
2206
2675
|
handleExecutionFailure(args) {
|
|
2207
2676
|
const { error, startTime, executedTaskIds, skippedTaskIds, initialContext } = args;
|
|
2208
2677
|
const endTime = /* @__PURE__ */ new Date();
|
|
2678
|
+
if (DebugFlags.showRawError) logger.error(error);
|
|
2679
|
+
else logger.error(error.message);
|
|
2209
2680
|
if (this.options.enableRollback && this.executedTasks.length > 0) {
|
|
2210
2681
|
logger.verbose(`WorkflowExecutor: Attempting rollback of ${this.executedTasks.length} tasks`);
|
|
2211
2682
|
return this.rollback(initialContext).andThen((rollbackSuccess) => {
|
|
@@ -2281,9 +2752,6 @@ var WorkflowExecutor = class {
|
|
|
2281
2752
|
executionLists.executedTaskIds.push(currentTask.meta.id);
|
|
2282
2753
|
this.executedTasks.push(currentTask);
|
|
2283
2754
|
return this.executeTasksSequentially(remainingTasks, updatedContext, executionLists.executedTaskIds, executionLists.skippedTaskIds);
|
|
2284
|
-
}).mapErr((error) => {
|
|
2285
|
-
logger.error(error.message);
|
|
2286
|
-
return error;
|
|
2287
2755
|
});
|
|
2288
2756
|
}
|
|
2289
2757
|
rollback(context) {
|
|
@@ -2955,6 +3423,18 @@ async function resolveServiceWithContext(key, context) {
|
|
|
2955
3423
|
/**
|
|
2956
3424
|
* Creates a lazy proxy that defers async service instantiation until first access.
|
|
2957
3425
|
*
|
|
3426
|
+
* IMPORTANT:
|
|
3427
|
+
* This proxy always defers instantiation and wraps calls in a ResultAsync chain
|
|
3428
|
+
* in order to safely await the real service instance before invoking the method.
|
|
3429
|
+
*
|
|
3430
|
+
* At runtime the proxy therefore returns ResultAsync even if the concrete service
|
|
3431
|
+
* method returns a synchronous Result.
|
|
3432
|
+
*
|
|
3433
|
+
* Because of this behavior, service interfaces should return FireflyAsyncResult<T>
|
|
3434
|
+
* from their public methods to avoid type mismatches and confusion. Implementations
|
|
3435
|
+
* that have purely synchronous behavior should return FireflyOkAsync / FireflyErrAsync
|
|
3436
|
+
* so they still conform to the async contract expected by the proxy.
|
|
3437
|
+
*
|
|
2958
3438
|
* @template T - The service interface type
|
|
2959
3439
|
* @param factory - Async function that creates the actual service instance
|
|
2960
3440
|
* @returns A proxy that behaves like the service but instantiates lazily
|