fireflyy 4.0.0-dev.352994a → 4.0.0-dev.5022ada
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 +5 -5
- package/dist/commit-analysis.service-B2Z128t8.js +503 -0
- package/dist/config.d.ts +130 -0
- package/dist/{index.js → config.js} +0 -11
- package/dist/debug-flags-K3yK5B6O.js +74 -0
- package/dist/{dry-run-BfYCtldz.js → dry-run-C7RaPEq8.js} +2 -2
- package/dist/{filesystem.service-DdVwnqoa.js → filesystem.service-DS2AQGNq.js} +8 -10
- package/dist/{git.service-DarjfyXF.js → git.service-B6RdTilO.js} +48 -27
- package/dist/logging-Bpk2RzGc.js +20 -0
- package/dist/main.js +4 -26
- package/dist/{package-json.service-QN7SzRTt.js → package-json.service-BlMNgPGQ.js} +4 -3
- package/dist/{program-DSPj4l5A.js → program-BBUBg7D4.js} +747 -263
- package/dist/{result.constructors-C9M1MP3_.js → result.constructors-DoAoYdfF.js} +5 -1
- package/dist/{result.utilities-B03Jkhlx.js → result.utilities-DXSJU70_.js} +3 -13
- package/dist/{schema.utilities-BGd9t1wm.js → schema.utilities-C1yimTtB.js} +1 -1
- package/dist/version-DJuocyXy.js +164 -0
- package/dist/version-bumper.service-glxzf9Qm.js +171 -0
- package/dist/version-strategy.service-Dln42gxC.js +257 -0
- package/package.json +4 -4
- package/dist/index.d.ts +0 -79
|
@@ -1,80 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import { n as
|
|
1
|
+
import { t as RuntimeEnv } from "./main.js";
|
|
2
|
+
import { t as DebugFlags } from "./debug-flags-K3yK5B6O.js";
|
|
3
|
+
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-DoAoYdfF.js";
|
|
4
|
+
import { n as wrapPromise, t as ensureNotAsync } from "./result.utilities-DXSJU70_.js";
|
|
5
|
+
import { t as logger } from "./logging-Bpk2RzGc.js";
|
|
6
|
+
import { t as Version } from "./version-DJuocyXy.js";
|
|
7
|
+
import { t as formatZodErrors } from "./schema.utilities-C1yimTtB.js";
|
|
8
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
5
9
|
import { LogLevels } from "consola";
|
|
6
10
|
import { colors } from "consola/utils";
|
|
7
|
-
import { Command } from "commander";
|
|
8
11
|
import { loadConfig } from "c12";
|
|
9
12
|
import { Result, ResultAsync, err, ok } from "neverthrow";
|
|
10
13
|
import z$1 from "zod";
|
|
11
14
|
import { parse } from "semver";
|
|
12
15
|
import * as path from "path";
|
|
13
16
|
|
|
14
|
-
//#region src/core/environment/debug-flags.ts
|
|
15
|
-
/**
|
|
16
|
-
* Debug flags are environment variables prefixed with `FIREFLY_DEBUG_` that
|
|
17
|
-
* enable diagnostic features during development and troubleshooting.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* if (DebugFlags.showRawError) {
|
|
22
|
-
* logger.error(parseResult.error);
|
|
23
|
-
* }
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
var DebugFlags = class {
|
|
27
|
-
constructor() {}
|
|
28
|
-
/**
|
|
29
|
-
* When enabled, displays raw Zod validation errors for configuration parsing.
|
|
30
|
-
*
|
|
31
|
-
* Useful for debugging configuration schema issues and understanding
|
|
32
|
-
* why validation failed at a granular level.
|
|
33
|
-
*/
|
|
34
|
-
static get showRawError() {
|
|
35
|
-
return Boolean(process.env.FIREFLY_DEBUG_SHOW_RAW_ERROR);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* When enabled, logs the loaded configuration file contents.
|
|
39
|
-
*
|
|
40
|
-
* Useful for debugging configuration loading and understanding
|
|
41
|
-
* what values are being read from config files.
|
|
42
|
-
*/
|
|
43
|
-
static get showFileConfig() {
|
|
44
|
-
return Boolean(process.env.FIREFLY_DEBUG_SHOW_FILE_CONFIG);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* When enabled, displays task graph statistics during release execution.
|
|
48
|
-
*
|
|
49
|
-
* Shows information about task dependencies, execution order,
|
|
50
|
-
* and graph structure for debugging workflow issues.
|
|
51
|
-
*/
|
|
52
|
-
static get showTaskGraphStats() {
|
|
53
|
-
return Boolean(process.env.FIREFLY_DEBUG_SHOW_TASK_GRAPH_STATS);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* When enabled, prevents truncation of release notes in GitHub CLI logs.
|
|
57
|
-
*
|
|
58
|
-
* By default, release notes are truncated in logs to avoid pollution.
|
|
59
|
-
* Enable this flag to see full release notes content during debugging.
|
|
60
|
-
*/
|
|
61
|
-
static get dontTruncateReleaseNotes() {
|
|
62
|
-
return Boolean(process.env.FIREFLY_DEBUG_DONT_TRUNCATE_RELEASE_NOTES?.trim());
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* When enabled, prevents redaction of sensitive GitHub CLI arguments in logs.
|
|
66
|
-
*
|
|
67
|
-
* By default, sensitive values (tokens, passwords, etc.) are redacted.
|
|
68
|
-
* Enable this flag to see full argument values during debugging.
|
|
69
|
-
*
|
|
70
|
-
* WARNING: Use with caution as this may expose sensitive information.
|
|
71
|
-
*/
|
|
72
|
-
static get dontRedactGithubCliArgs() {
|
|
73
|
-
return Boolean(process.env.FIREFLY_DEBUG_DONT_REDACT_GITHUB_CLI_ARGS?.trim());
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
//#endregion
|
|
78
17
|
//#region src/cli/config/config.loader.ts
|
|
79
18
|
/**
|
|
80
19
|
* Loads and resolves Firefly configuration from files.
|
|
@@ -184,6 +123,223 @@ function camelToKebab(str) {
|
|
|
184
123
|
return result.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/_/g, "-");
|
|
185
124
|
}
|
|
186
125
|
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/cli/options/options.validation.ts
|
|
128
|
+
/**
|
|
129
|
+
* Extracts enum values from a Zod schema (handles wrapped types like optional, default).
|
|
130
|
+
*
|
|
131
|
+
* @param schema - The Zod schema to extract enum values from
|
|
132
|
+
* @returns Array of string values if it's an enum, or null if not
|
|
133
|
+
*/
|
|
134
|
+
function extractEnumValues(schema) {
|
|
135
|
+
const unwrapped = unwrapZodSchema(schema);
|
|
136
|
+
if (unwrapped instanceof z$1.ZodEnum) return getEnumValuesFromZodEnum(unwrapped);
|
|
137
|
+
if (unwrapped instanceof z$1.ZodUnion) {
|
|
138
|
+
const unionOptions = getZodDef(unwrapped).options;
|
|
139
|
+
if (!unionOptions) return null;
|
|
140
|
+
const values = [];
|
|
141
|
+
for (const option of unionOptions) {
|
|
142
|
+
if (typeof option === "string") continue;
|
|
143
|
+
const innerUnwrapped = unwrapZodSchema(option);
|
|
144
|
+
if (innerUnwrapped instanceof z$1.ZodEnum) {
|
|
145
|
+
const enumValues = getEnumValuesFromZodEnum(innerUnwrapped);
|
|
146
|
+
if (enumValues) values.push(...enumValues);
|
|
147
|
+
} else if (innerUnwrapped instanceof z$1.ZodLiteral) {
|
|
148
|
+
const literalValue = getZodDef(innerUnwrapped).value;
|
|
149
|
+
if (typeof literalValue === "string" && literalValue !== "") values.push(literalValue);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return values.length > 0 ? values : null;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Extracts enum values from a ZodEnum type.
|
|
158
|
+
* Handles both Zod v4 (options array or entries object) and legacy (values array) structures.
|
|
159
|
+
*
|
|
160
|
+
* @param enumSchema - The ZodEnum schema
|
|
161
|
+
* @returns Array of string values, or null if not found
|
|
162
|
+
*/
|
|
163
|
+
function getEnumValuesFromZodEnum(enumSchema) {
|
|
164
|
+
const schemaAsAny = enumSchema;
|
|
165
|
+
if (Array.isArray(schemaAsAny.options)) return schemaAsAny.options;
|
|
166
|
+
const def = getZodDef(enumSchema);
|
|
167
|
+
if (def.entries && typeof def.entries === "object") return Object.values(def.entries);
|
|
168
|
+
if (def.values && Array.isArray(def.values)) return def.values;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extracts literal values from a Zod union schema (e.g., z.union([z.literal("0"), z.literal("1")])).
|
|
173
|
+
*
|
|
174
|
+
* @param schema - The Zod schema to extract literal values from
|
|
175
|
+
* @returns Array of literal values if it's a union of literals, or null if not
|
|
176
|
+
*/
|
|
177
|
+
function extractLiteralValues(schema) {
|
|
178
|
+
const unwrapped = unwrapZodSchema(schema);
|
|
179
|
+
if (unwrapped instanceof z$1.ZodUnion) {
|
|
180
|
+
const options = getZodDef(unwrapped).options;
|
|
181
|
+
if (!options) return null;
|
|
182
|
+
const values = [];
|
|
183
|
+
for (const option of options) {
|
|
184
|
+
if (typeof option === "string" || typeof option === "number") continue;
|
|
185
|
+
const innerUnwrapped = unwrapZodSchema(option);
|
|
186
|
+
if (innerUnwrapped instanceof z$1.ZodLiteral) {
|
|
187
|
+
const value = getZodDef(innerUnwrapped).value;
|
|
188
|
+
if (typeof value === "string" || typeof value === "number") values.push(value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return values.length > 0 ? values : null;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Creates a CLI option validator for enum fields.
|
|
197
|
+
*
|
|
198
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
199
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
200
|
+
*
|
|
201
|
+
* @param schema - The Zod schema for validation
|
|
202
|
+
* @param optionName - The CLI option name (for error messages)
|
|
203
|
+
* @param choices - The valid enum choices
|
|
204
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
205
|
+
*/
|
|
206
|
+
function createEnumValidator(schema, optionName, choices) {
|
|
207
|
+
return (input) => {
|
|
208
|
+
const result = schema.safeParse(input);
|
|
209
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". Must be one of: ${choices.join(", ")}`);
|
|
210
|
+
return result.data;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Creates a CLI option validator for number fields.
|
|
215
|
+
*
|
|
216
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
217
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
218
|
+
*
|
|
219
|
+
* @param schema - The Zod schema for validation
|
|
220
|
+
* @param optionName - The CLI option name (for error messages)
|
|
221
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
222
|
+
*/
|
|
223
|
+
function createNumberValidator(schema, optionName) {
|
|
224
|
+
return (input) => {
|
|
225
|
+
const num = Number(input);
|
|
226
|
+
if (Number.isNaN(num)) throw new InvalidArgumentError(`Invalid number for --${optionName}: "${input}". Expected a valid number.`);
|
|
227
|
+
const result = schema.safeParse(num);
|
|
228
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". ${formatZodErrorMessage(result.error)}`);
|
|
229
|
+
return result.data;
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Creates a CLI option validator for string fields.
|
|
234
|
+
*
|
|
235
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
236
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
237
|
+
*
|
|
238
|
+
* @param schema - The Zod schema for validation
|
|
239
|
+
* @param optionName - The CLI option name (for error messages)
|
|
240
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
241
|
+
*/
|
|
242
|
+
function createStringValidator(schema, optionName) {
|
|
243
|
+
return (input) => {
|
|
244
|
+
const result = schema.safeParse(input);
|
|
245
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". ${formatZodErrorMessage(result.error)}`);
|
|
246
|
+
return result.data;
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Creates a CLI option validator for union types with literal values.
|
|
251
|
+
*
|
|
252
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
253
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
254
|
+
*
|
|
255
|
+
* @param schema - The Zod schema for validation
|
|
256
|
+
* @param optionName - The CLI option name (for error messages)
|
|
257
|
+
* @param values - The valid literal values
|
|
258
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
259
|
+
*/
|
|
260
|
+
function createLiteralUnionValidator(schema, optionName, values) {
|
|
261
|
+
return (input) => {
|
|
262
|
+
const numericValues = values.filter((v) => typeof v === "number");
|
|
263
|
+
let parsedInput = input;
|
|
264
|
+
if (numericValues.length > 0 && !Number.isNaN(Number(input))) parsedInput = Number(input);
|
|
265
|
+
const result = schema.safeParse(parsedInput);
|
|
266
|
+
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(", ")}`);
|
|
267
|
+
return result.data;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Creates a generic CLI option validator.
|
|
272
|
+
*
|
|
273
|
+
* Commander.js requires throwing InvalidArgumentError for validation errors.
|
|
274
|
+
* This is a boundary where exceptions are appropriate for the CLI framework.
|
|
275
|
+
*
|
|
276
|
+
* @param schema - The Zod schema for validation
|
|
277
|
+
* @param optionName - The CLI option name (for error messages)
|
|
278
|
+
* @returns A parser function that throws InvalidArgumentError on failure
|
|
279
|
+
*/
|
|
280
|
+
function createGenericValidator(schema, optionName) {
|
|
281
|
+
return (input) => {
|
|
282
|
+
const result = schema.safeParse(input);
|
|
283
|
+
if (!result.success) throw new InvalidArgumentError(`Invalid value for --${optionName}: "${input}". ${formatZodErrorMessage(result.error)}`);
|
|
284
|
+
return result.data;
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Formats a Zod error into a user-friendly message.
|
|
289
|
+
*
|
|
290
|
+
* @param error - The Zod error object
|
|
291
|
+
* @returns A formatted error message
|
|
292
|
+
*/
|
|
293
|
+
function formatZodErrorMessage(error) {
|
|
294
|
+
const firstIssue = error.issues[0];
|
|
295
|
+
if (!firstIssue) return "Validation failed.";
|
|
296
|
+
switch (firstIssue.code) {
|
|
297
|
+
case "invalid_value": {
|
|
298
|
+
const issue = firstIssue;
|
|
299
|
+
if (issue.values) return `Expected one of: ${issue.values.join(", ")}`;
|
|
300
|
+
return firstIssue.message;
|
|
301
|
+
}
|
|
302
|
+
case "invalid_type": return `Expected ${firstIssue.expected}`;
|
|
303
|
+
case "too_small":
|
|
304
|
+
case "too_big": return firstIssue.message;
|
|
305
|
+
default: return firstIssue.message;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Gets the internal definition object from a Zod type.
|
|
310
|
+
* Handles both modern Zod v4 (_zod.def) and legacy (_def) structures.
|
|
311
|
+
*
|
|
312
|
+
* @param field - The Zod type to get the definition from
|
|
313
|
+
* @returns The internal definition object
|
|
314
|
+
*/
|
|
315
|
+
function getZodDef(field) {
|
|
316
|
+
const zodContainer = field._zod;
|
|
317
|
+
if (zodContainer?.def) return zodContainer.def;
|
|
318
|
+
return field._def ?? {};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Unwraps Zod wrapper types (optional, default, etc.) to get the inner type.
|
|
322
|
+
*
|
|
323
|
+
* @param field - The Zod type to unwrap
|
|
324
|
+
* @returns The unwrapped inner type
|
|
325
|
+
*/
|
|
326
|
+
function unwrapZodSchema(field) {
|
|
327
|
+
const def = getZodDef(field);
|
|
328
|
+
if (field instanceof z$1.ZodDefault) {
|
|
329
|
+
const inner = def.innerType;
|
|
330
|
+
return inner ? unwrapZodSchema(inner) : field;
|
|
331
|
+
}
|
|
332
|
+
if (field instanceof z$1.ZodOptional) {
|
|
333
|
+
const inner = def.innerType;
|
|
334
|
+
return inner ? unwrapZodSchema(inner) : field;
|
|
335
|
+
}
|
|
336
|
+
if (field instanceof z$1.ZodNullable) {
|
|
337
|
+
const inner = def.innerType;
|
|
338
|
+
return inner ? unwrapZodSchema(inner) : field;
|
|
339
|
+
}
|
|
340
|
+
return field;
|
|
341
|
+
}
|
|
342
|
+
|
|
187
343
|
//#endregion
|
|
188
344
|
//#region src/cli/options/options.builder.ts
|
|
189
345
|
/**
|
|
@@ -304,15 +460,14 @@ var OptionsBuilder = class {
|
|
|
304
460
|
this.registerGenericOption(ctx);
|
|
305
461
|
}
|
|
306
462
|
/**
|
|
307
|
-
* Registers a number option with numeric parsing.
|
|
463
|
+
* Registers a number option with numeric parsing and validation.
|
|
308
464
|
*
|
|
309
465
|
* @param ctx - The option context
|
|
310
466
|
*/
|
|
311
467
|
registerNumberOption(ctx) {
|
|
312
468
|
const { command, rawField, optionFlag, optionName, description, parsedDefault } = ctx;
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
command.option(`${optionFlag} <${optionName}>`, description, wrappedParser, parsedDefault);
|
|
469
|
+
const validator = createNumberValidator(rawField, optionName);
|
|
470
|
+
command.option(`${optionFlag} <${optionName}>`, description, validator, parsedDefault);
|
|
316
471
|
}
|
|
317
472
|
/**
|
|
318
473
|
* Registers an enum option with choice validation.
|
|
@@ -320,111 +475,46 @@ var OptionsBuilder = class {
|
|
|
320
475
|
* @param ctx - The option context
|
|
321
476
|
*/
|
|
322
477
|
registerEnumOption(ctx) {
|
|
323
|
-
const { command, rawField,
|
|
324
|
-
const choices =
|
|
325
|
-
const
|
|
326
|
-
const wrappedParser = this.wrapParser(parser);
|
|
478
|
+
const { command, rawField, optionFlag, optionName, description, parsedDefault } = ctx;
|
|
479
|
+
const choices = extractEnumValues(rawField) ?? [];
|
|
480
|
+
const validator = createEnumValidator(rawField, optionName, choices);
|
|
327
481
|
const fullDescription = `${description}${choices.length ? ` (choices: ${choices.join(", ")})` : ""}`;
|
|
328
|
-
command.option(`${optionFlag} <${optionName}>`, fullDescription,
|
|
482
|
+
command.option(`${optionFlag} <${optionName}>`, fullDescription, validator, parsedDefault);
|
|
329
483
|
}
|
|
330
484
|
/**
|
|
331
|
-
*
|
|
485
|
+
* Registers a string option with validation.
|
|
332
486
|
*
|
|
333
487
|
* @param ctx - The option context
|
|
334
488
|
*/
|
|
335
489
|
registerStringOption(ctx) {
|
|
336
490
|
const { command, rawField, optionFlag, optionName, description, parsedDefault } = ctx;
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
command.option(`${optionFlag} <${optionName}>`, description, wrappedParser, parsedDefault);
|
|
491
|
+
const validator = createStringValidator(rawField, optionName);
|
|
492
|
+
command.option(`${optionFlag} <${optionName}>`, description, validator, parsedDefault);
|
|
340
493
|
}
|
|
341
494
|
/**
|
|
342
495
|
* Registers a generic option for other Zod types.
|
|
496
|
+
* Handles union types with literal values specially for better error messages.
|
|
343
497
|
*
|
|
344
498
|
* @param ctx - The option context
|
|
345
499
|
*/
|
|
346
500
|
registerGenericOption(ctx) {
|
|
347
501
|
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
|
-
};
|
|
502
|
+
const literalValues = extractLiteralValues(rawField);
|
|
503
|
+
if (literalValues) {
|
|
504
|
+
const validator$1 = createLiteralUnionValidator(rawField, optionName, literalValues);
|
|
505
|
+
const fullDescription = `${description} (choices: ${literalValues.map((v) => typeof v === "string" ? v : String(v)).join(", ")})`;
|
|
506
|
+
command.option(`${optionFlag} <${optionName}>`, fullDescription, validator$1, parsedDefault);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const enumValues = extractEnumValues(rawField);
|
|
510
|
+
if (enumValues) {
|
|
511
|
+
const validator$1 = createEnumValidator(rawField, optionName, enumValues);
|
|
512
|
+
const fullDescription = `${description} (choices: ${enumValues.join(", ")})`;
|
|
513
|
+
command.option(`${optionFlag} <${optionName}>`, fullDescription, validator$1, parsedDefault);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const validator = createGenericValidator(rawField, optionName);
|
|
517
|
+
command.option(`${optionFlag} <${optionName}>`, description, validator, parsedDefault);
|
|
428
518
|
}
|
|
429
519
|
/**
|
|
430
520
|
* Gets the internal definition object from a Zod type.
|
|
@@ -469,15 +559,6 @@ var OptionsBuilder = class {
|
|
|
469
559
|
const def = this.getInternalDef(rawField);
|
|
470
560
|
return (def.innerType ?? def.schema) instanceof z$1.ZodBoolean;
|
|
471
561
|
}
|
|
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
562
|
};
|
|
482
563
|
|
|
483
564
|
//#endregion
|
|
@@ -804,10 +885,63 @@ var TaskBuilder = class TaskBuilder {
|
|
|
804
885
|
|
|
805
886
|
//#endregion
|
|
806
887
|
//#region src/commands/release/tasks/bump-release-version.task.ts
|
|
888
|
+
const PACKAGE_JSON_FILE$1 = "package.json";
|
|
889
|
+
/**
|
|
890
|
+
* Updates the configured package.json with the next version.
|
|
891
|
+
*
|
|
892
|
+
* @param ctx - The current release context
|
|
893
|
+
* @returns A FireflyAsyncResult resolving to the release context after update
|
|
894
|
+
*/
|
|
895
|
+
function updatePackageJsonVersion(ctx) {
|
|
896
|
+
return ctx.services.packageJson.updateVersion(PACKAGE_JSON_FILE$1, ctx.data.nextVersion).map(() => ctx);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Restores a previous version value in the configured package.json.
|
|
900
|
+
*
|
|
901
|
+
* @param ctx - The current release context
|
|
902
|
+
* @param previousVersion - The previous version string to restore
|
|
903
|
+
* @returns A FireflyAsyncResult resolving to the release context after restore
|
|
904
|
+
*/
|
|
905
|
+
function restorePackageJsonVersion(ctx, previousVersion) {
|
|
906
|
+
return ctx.services.packageJson.updateVersion(PACKAGE_JSON_FILE$1, previousVersion).map(() => ctx);
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Validates that the release context contains a next version value.
|
|
910
|
+
*
|
|
911
|
+
* @param ctx - The current release context
|
|
912
|
+
* @returns A FireflyResult with the next version or a validation error
|
|
913
|
+
*/
|
|
914
|
+
function parseNextVersion(ctx) {
|
|
915
|
+
const nextVersion = ctx.data.nextVersion;
|
|
916
|
+
if (!nextVersion) return validationErr({ message: "Next version is undefined" });
|
|
917
|
+
return FireflyOk(nextVersion);
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Creates the Bump Release Version task.
|
|
921
|
+
*
|
|
922
|
+
* This task updates the version of the project to the next version determined
|
|
923
|
+
* by earlier steps. Specifically, it:
|
|
924
|
+
* 1. Validates that a `nextVersion` exists in the release context
|
|
925
|
+
* 2. Writes the `nextVersion` into the configured package.json file
|
|
926
|
+
* 3. Provides an undo operation that restores the original version
|
|
927
|
+
*/
|
|
807
928
|
function createBumpReleaseVersion() {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
929
|
+
/**
|
|
930
|
+
* Holds the previous version before bumping, for undo purposes if needed.
|
|
931
|
+
*/
|
|
932
|
+
let previousVersion;
|
|
933
|
+
return TaskBuilder.create("bump-release-version").description("Applies the new version bump to relevant files").dependsOnAll("straight-version-bump", "determine-automatic-bump", "prompt-manual-version", "prompt-bump-strategy").skipWhenWithReason((ctx) => ctx.config.skipBump || !ctx.data.nextVersion, "Skipped: skipBump is enabled or next version is not set").execute((ctx) => {
|
|
934
|
+
previousVersion = ctx.data.currentVersion;
|
|
935
|
+
const nextVersionRes = parseNextVersion(ctx);
|
|
936
|
+
if (nextVersionRes.isErr()) return FireflyErrAsync(nextVersionRes.error);
|
|
937
|
+
logger.info(`Next version to be released: ${colors.green(nextVersionRes.value)}`);
|
|
938
|
+
return updatePackageJsonVersion(ctx).andTee(() => logger.success("package.json version updated successfully."));
|
|
939
|
+
}).withUndo((ctx) => {
|
|
940
|
+
if (!previousVersion) {
|
|
941
|
+
logger.verbose("BumpReleaseVersionTask: Previous version is undefined, skipping undo operation.");
|
|
942
|
+
return FireflyOkAsync(void 0);
|
|
943
|
+
}
|
|
944
|
+
return restorePackageJsonVersion(ctx, previousVersion).map(() => void 0);
|
|
811
945
|
}).build();
|
|
812
946
|
}
|
|
813
947
|
|
|
@@ -1003,9 +1137,10 @@ function createBumpExecutionGroup() {
|
|
|
1003
1137
|
*/
|
|
1004
1138
|
function shouldSkipBumpStrategy(ctx) {
|
|
1005
1139
|
const { skipBump, releaseType, bumpStrategy } = ctx.config;
|
|
1140
|
+
const { selectedReleaseType, selectedBumpStrategy } = ctx.data;
|
|
1006
1141
|
if (skipBump) return true;
|
|
1007
|
-
if (releaseType) return true;
|
|
1008
|
-
if (!bumpStrategy) return true;
|
|
1142
|
+
if (releaseType || selectedReleaseType) return true;
|
|
1143
|
+
if (!Boolean(bumpStrategy || selectedBumpStrategy)) return true;
|
|
1009
1144
|
return false;
|
|
1010
1145
|
}
|
|
1011
1146
|
/**
|
|
@@ -1013,9 +1148,11 @@ function shouldSkipBumpStrategy(ctx) {
|
|
|
1013
1148
|
*/
|
|
1014
1149
|
function getSkipReason(ctx) {
|
|
1015
1150
|
const { skipBump, releaseType, bumpStrategy } = ctx.config;
|
|
1151
|
+
const { selectedReleaseType, selectedBumpStrategy } = ctx.data;
|
|
1016
1152
|
if (skipBump) return "skipBump is enabled";
|
|
1017
|
-
if (releaseType) return `releaseType already set to '${releaseType}'`;
|
|
1018
|
-
if (
|
|
1153
|
+
if (releaseType) return `releaseType already set to '${releaseType}' (config)`;
|
|
1154
|
+
if (selectedReleaseType) return `releaseType already set to '${selectedReleaseType}' (data)`;
|
|
1155
|
+
if (!Boolean(bumpStrategy || selectedBumpStrategy)) return "no bumpStrategy configured";
|
|
1019
1156
|
return "unknown reason";
|
|
1020
1157
|
}
|
|
1021
1158
|
function createDelegateBumpStrategyTask() {
|
|
@@ -1025,46 +1162,208 @@ function createDelegateBumpStrategyTask() {
|
|
|
1025
1162
|
shouldSkip,
|
|
1026
1163
|
reason: shouldSkip ? getSkipReason(ctx) : void 0
|
|
1027
1164
|
});
|
|
1028
|
-
}).execute((ctx) =>
|
|
1029
|
-
logger.info("delegate-bump-strategy");
|
|
1030
|
-
return FireflyOkAsync(ctx);
|
|
1031
|
-
}).build();
|
|
1165
|
+
}).execute((ctx) => FireflyOkAsync(ctx)).build();
|
|
1032
1166
|
}
|
|
1033
1167
|
|
|
1034
1168
|
//#endregion
|
|
1035
1169
|
//#region src/commands/release/tasks/determine-automatic-bump.task.ts
|
|
1170
|
+
/**
|
|
1171
|
+
* Parses the current version from a raw string.
|
|
1172
|
+
*
|
|
1173
|
+
* @param currentVersionRaw - The raw string representing the current version
|
|
1174
|
+
* @returns A FireflyResult containing the parsed Version or a validation error
|
|
1175
|
+
*/
|
|
1176
|
+
function parseCurrentVersion$2(currentVersionRaw) {
|
|
1177
|
+
if (!currentVersionRaw) return validationErr({ message: "Current version is undefined" });
|
|
1178
|
+
return Version.from(currentVersionRaw);
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Logs the analysis recommendation with formatting.
|
|
1182
|
+
*
|
|
1183
|
+
* @param reason - The reason string from the recommendation
|
|
1184
|
+
*/
|
|
1185
|
+
function logRecommendation(reason) {
|
|
1186
|
+
if (!reason) return;
|
|
1187
|
+
if (reason.startsWith("Analysis found:")) {
|
|
1188
|
+
const prefix = "Analysis found:";
|
|
1189
|
+
const details = reason.slice(15).trim();
|
|
1190
|
+
logger.info(`${prefix} ${colors.green(details)}`);
|
|
1191
|
+
} else logger.info(reason);
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Performs the automatic bump by analyzing commits and resolving version strategy.
|
|
1195
|
+
*/
|
|
1196
|
+
function executeAutomaticBump(ctx) {
|
|
1197
|
+
const currentVersionResult = parseCurrentVersion$2(ctx.data.currentVersion);
|
|
1198
|
+
if (currentVersionResult.isErr()) return FireflyErrAsync(currentVersionResult.error);
|
|
1199
|
+
const currentVersion = currentVersionResult.value;
|
|
1200
|
+
return ctx.services.commitAnalysis.analyzeForVersion().andThen((recommendation) => {
|
|
1201
|
+
logRecommendation(recommendation.reason);
|
|
1202
|
+
const options = {
|
|
1203
|
+
currentVersion,
|
|
1204
|
+
preReleaseId: ctx.config.preReleaseId,
|
|
1205
|
+
preReleaseBase: ctx.config.preReleaseBase
|
|
1206
|
+
};
|
|
1207
|
+
return ctx.services.versionStrategy.resolveVersion(options, recommendation);
|
|
1208
|
+
}).andThen((newVersion) => {
|
|
1209
|
+
const from = ctx.data.currentVersion || "unknown";
|
|
1210
|
+
logger.info(`Determined version: ${colors.green(from)} -> ${colors.green(newVersion.raw)}`);
|
|
1211
|
+
return FireflyOkAsync(ctx.fork("nextVersion", newVersion.toString()));
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1036
1214
|
function createDetermineAutomaticBump() {
|
|
1037
|
-
return TaskBuilder.create("determine-automatic-bump").description("Automatically determines the version bump from commit messages").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) =>
|
|
1038
|
-
|
|
1039
|
-
return
|
|
1040
|
-
}).build();
|
|
1215
|
+
return TaskBuilder.create("determine-automatic-bump").description("Automatically determines the version bump from commit messages").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) => {
|
|
1216
|
+
const bumpStrategy = ctx.data.selectedBumpStrategy ?? ctx.config.bumpStrategy;
|
|
1217
|
+
return ctx.config.skipBump || bumpStrategy !== BUMP_STRATEGY_AUTO;
|
|
1218
|
+
}, "Skipped: skipBump enabled or bumpStrategy is not 'auto'").execute((ctx) => executeAutomaticBump(ctx)).build();
|
|
1041
1219
|
}
|
|
1042
1220
|
|
|
1043
1221
|
//#endregion
|
|
1044
1222
|
//#region src/commands/release/tasks/prompt-bump-strategy.task.ts
|
|
1223
|
+
const BUMP_STRATEGIES = [{
|
|
1224
|
+
label: "Automatic Bump",
|
|
1225
|
+
value: BUMP_STRATEGY_AUTO,
|
|
1226
|
+
hint: "Determines the next version based on conventional commits history"
|
|
1227
|
+
}, {
|
|
1228
|
+
label: "Manual Bump",
|
|
1229
|
+
value: BUMP_STRATEGY_MANUAL,
|
|
1230
|
+
hint: "Manually specify the next version"
|
|
1231
|
+
}];
|
|
1232
|
+
const VALID_STRATEGY_VALUES = BUMP_STRATEGIES.map((s) => s.value);
|
|
1233
|
+
/**
|
|
1234
|
+
* Validates that the selected strategy is one of the allowed values.
|
|
1235
|
+
*
|
|
1236
|
+
* @param strategy The selected version bump strategy.
|
|
1237
|
+
* @returns A FireflyResult indicating success or failure of validation.
|
|
1238
|
+
*/
|
|
1239
|
+
function validateStrategy(strategy) {
|
|
1240
|
+
if (!VALID_STRATEGY_VALUES.includes(strategy)) return invalidErr({ message: `Invalid version bump strategy: ${strategy}` });
|
|
1241
|
+
return FireflyOk(strategy);
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Prompts the user to select a bump strategy using the logger's prompt API.
|
|
1245
|
+
*/
|
|
1246
|
+
function promptBumpStrategy() {
|
|
1247
|
+
const defaultStrategy = BUMP_STRATEGIES[0];
|
|
1248
|
+
if (!defaultStrategy) return notFoundErrAsync({ message: "No default version bump strategy found" });
|
|
1249
|
+
logger.verbose("PromptBumpStrategyTask: Prompting user for version bump strategy.");
|
|
1250
|
+
return wrapPromise(logger.prompt("Select version bump strategy", {
|
|
1251
|
+
type: "select",
|
|
1252
|
+
options: BUMP_STRATEGIES,
|
|
1253
|
+
initial: defaultStrategy.value,
|
|
1254
|
+
cancel: "undefined"
|
|
1255
|
+
})).andThen((selected) => {
|
|
1256
|
+
if (!selected || selected === "") return failedErrAsync({ message: "Operation cancelled by user" });
|
|
1257
|
+
if (logger.level === LogLevels.verbose) logger.log("");
|
|
1258
|
+
if (logger.level !== LogLevels.verbose) logger.log("");
|
|
1259
|
+
const validationResult = validateStrategy(selected);
|
|
1260
|
+
if (validationResult.isErr()) return FireflyErrAsync(validationResult.error);
|
|
1261
|
+
logger.verbose(`PromptBumpStrategyTask: Selected version bump strategy: '${selected}'`);
|
|
1262
|
+
return FireflyOkAsync(selected);
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Creates the Prompt Bump Strategy Task.
|
|
1267
|
+
*
|
|
1268
|
+
* This task prompts the user to select a version bump strategy (automatic or manual) and
|
|
1269
|
+
* stores it in the release context. It depends on `initialize-release-version` and will be
|
|
1270
|
+
* skipped when `skipBump` is enabled or a `bumpStrategy`/`releaseType` is already provided.
|
|
1271
|
+
*
|
|
1272
|
+
* This task:
|
|
1273
|
+
* 1. Prompts the user to select a bump strategy
|
|
1274
|
+
*/
|
|
1045
1275
|
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();
|
|
1276
|
+
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
1277
|
}
|
|
1051
1278
|
|
|
1052
1279
|
//#endregion
|
|
1053
1280
|
//#region src/commands/release/tasks/prompt-manual-bump.task.ts
|
|
1281
|
+
/**
|
|
1282
|
+
* Parses the current version from a raw string.
|
|
1283
|
+
*
|
|
1284
|
+
* @param currentVersionRaw - The raw string representing the current version
|
|
1285
|
+
* @returns A FireflyResult containing the parsed Version or a validation error
|
|
1286
|
+
*/
|
|
1287
|
+
function parseCurrentVersion$1(currentVersionRaw) {
|
|
1288
|
+
if (!currentVersionRaw) return validationErr({ message: "Current version is undefined" });
|
|
1289
|
+
return Version.from(currentVersionRaw);
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Generates version choices and prompts the user to select one.
|
|
1293
|
+
*/
|
|
1294
|
+
function promptForManualVersion(ctx) {
|
|
1295
|
+
const currentVersionResult = parseCurrentVersion$1(ctx.data.currentVersion);
|
|
1296
|
+
if (currentVersionResult.isErr()) return FireflyErrAsync(currentVersionResult.error);
|
|
1297
|
+
const currentVersion = currentVersionResult.value;
|
|
1298
|
+
logger.verbose("PromptManualVersionTask: Generating version choices...");
|
|
1299
|
+
return ctx.services.versionStrategy.generateChoices({
|
|
1300
|
+
currentVersion,
|
|
1301
|
+
preReleaseId: ctx.config.preReleaseId,
|
|
1302
|
+
preReleaseBase: ctx.config.preReleaseBase
|
|
1303
|
+
}).andThen((choices) => {
|
|
1304
|
+
if (!choices || choices.length === 0) return validationErrAsync({ message: "No version choices available" });
|
|
1305
|
+
logger.verbose(`PromptManualVersionTask: Generated ${choices.length} version choices.`);
|
|
1306
|
+
const defaultChoice = choices[0];
|
|
1307
|
+
return wrapPromise(logger.prompt("Select version to release", {
|
|
1308
|
+
type: "select",
|
|
1309
|
+
options: choices,
|
|
1310
|
+
initial: defaultChoice?.value,
|
|
1311
|
+
cancel: "undefined"
|
|
1312
|
+
})).andThen((selected) => {
|
|
1313
|
+
if (!selected || selected === "") return failedErrAsync({ message: "Operation cancelled by user" });
|
|
1314
|
+
if (logger.level === LogLevels.verbose) logger.log("");
|
|
1315
|
+
if (logger.level !== LogLevels.verbose) logger.log("");
|
|
1316
|
+
logger.verbose(`PromptManualVersionTask: Selected version: '${selected}'`);
|
|
1317
|
+
return FireflyOkAsync(selected);
|
|
1318
|
+
});
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1054
1321
|
function createPromptManualVersionTask() {
|
|
1055
|
-
return TaskBuilder.create("prompt-manual-version").description("Prompts the user
|
|
1056
|
-
|
|
1057
|
-
return
|
|
1058
|
-
}).build();
|
|
1322
|
+
return TaskBuilder.create("prompt-manual-version").description("Prompts the user to manually select the next version").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) => {
|
|
1323
|
+
const bumpStrategy = ctx.data.selectedBumpStrategy ?? ctx.config.bumpStrategy;
|
|
1324
|
+
return ctx.config.skipBump || bumpStrategy !== BUMP_STRATEGY_MANUAL;
|
|
1325
|
+
}, "Skipped: skipBump enabled or bumpStrategy is not 'manual'").execute((ctx) => promptForManualVersion(ctx).andThen((selectedVersion) => FireflyOkAsync(ctx.fork("nextVersion", selectedVersion)))).build();
|
|
1059
1326
|
}
|
|
1060
1327
|
|
|
1061
1328
|
//#endregion
|
|
1062
1329
|
//#region src/commands/release/tasks/straight-version-bump.task.ts
|
|
1330
|
+
/**
|
|
1331
|
+
* Parses the current version from a raw string.
|
|
1332
|
+
*
|
|
1333
|
+
* @param currentVersionRaw - The raw string representing the current version
|
|
1334
|
+
* @returns A FireflyResult containing the parsed Version or a validation error
|
|
1335
|
+
*/
|
|
1336
|
+
function parseCurrentVersion(currentVersionRaw) {
|
|
1337
|
+
if (!currentVersionRaw) return validationErr({ message: "Current version is undefined" });
|
|
1338
|
+
return Version.from(currentVersionRaw);
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Builds the bump options from the release context.
|
|
1342
|
+
*/
|
|
1343
|
+
function buildBumpOptionsFromContext(ctx) {
|
|
1344
|
+
const currentVersionResult = parseCurrentVersion(ctx.data.currentVersion);
|
|
1345
|
+
if (currentVersionResult.isErr()) return FireflyErrAsync(currentVersionResult.error);
|
|
1346
|
+
const releaseType = ctx.config.releaseType;
|
|
1347
|
+
if (releaseType === void 0) return invalidErrAsync({ message: "Release type is required for straight bump" });
|
|
1348
|
+
return FireflyOkAsync({
|
|
1349
|
+
currentVersion: currentVersionResult.value,
|
|
1350
|
+
releaseType,
|
|
1351
|
+
preReleaseId: ctx.config.preReleaseId,
|
|
1352
|
+
preReleaseBase: ctx.config.preReleaseBase
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Performs the straight bump by delegating to the version bumper service.
|
|
1357
|
+
*/
|
|
1358
|
+
function executeStraightVersionBump(ctx) {
|
|
1359
|
+
return buildBumpOptionsFromContext(ctx).andThen((options) => ctx.services.versionBumper.bump(options)).andThen((newVersion) => {
|
|
1360
|
+
const from = ctx.data.currentVersion || "unknown";
|
|
1361
|
+
logger.info(`Bumped version: ${colors.green(from)} -> ${colors.green(newVersion.raw)}`);
|
|
1362
|
+
return FireflyOkAsync(ctx.fork("nextVersion", newVersion.toString()));
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1063
1365
|
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();
|
|
1366
|
+
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
1367
|
}
|
|
1069
1368
|
|
|
1070
1369
|
//#endregion
|
|
@@ -1091,11 +1390,36 @@ function createBumpStrategyGroup() {
|
|
|
1091
1390
|
|
|
1092
1391
|
//#endregion
|
|
1093
1392
|
//#region src/commands/release/tasks/initialize-release-version.task.ts
|
|
1393
|
+
const PACKAGE_JSON_FILE = "package.json";
|
|
1394
|
+
/**
|
|
1395
|
+
* Reads package.json and extracts the version field.
|
|
1396
|
+
*
|
|
1397
|
+
* Behavior:
|
|
1398
|
+
* - Reads the configured package.json file.
|
|
1399
|
+
* - If the version property is missing, returns a validation error.
|
|
1400
|
+
* - Otherwise resolves with the version string.
|
|
1401
|
+
*/
|
|
1402
|
+
function getVersionFromPackageJson(ctx) {
|
|
1403
|
+
return ctx.services.packageJson.read(PACKAGE_JSON_FILE).andThen((pkg) => {
|
|
1404
|
+
const version = pkg.version;
|
|
1405
|
+
if (!version) return validationErrAsync({ message: "The 'version' field is missing in package.json." });
|
|
1406
|
+
logger.verbose(`InitializeReleaseVersionTask: Prepared current version: ${version}`);
|
|
1407
|
+
return FireflyOkAsync(version);
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Creates the Initialize Release Version task.
|
|
1412
|
+
*
|
|
1413
|
+
* This task initializes the current release version by reading it from package.json.
|
|
1414
|
+
*
|
|
1415
|
+
* This task:
|
|
1416
|
+
* 1. Reads the version from package.json
|
|
1417
|
+
*/
|
|
1094
1418
|
function createInitializeReleaseVersion() {
|
|
1095
|
-
return TaskBuilder.create("initialize-release-version").description("
|
|
1096
|
-
logger.info(
|
|
1097
|
-
return FireflyOkAsync(ctx);
|
|
1098
|
-
}).build();
|
|
1419
|
+
return TaskBuilder.create("initialize-release-version").description("Initialize current release version from package.json").dependsOn("prepare-release-config").execute((ctx) => getVersionFromPackageJson(ctx).andThen((currentVersion) => {
|
|
1420
|
+
logger.info(`Current version is ${colors.green(currentVersion)}`);
|
|
1421
|
+
return FireflyOkAsync(ctx.fork("currentVersion", currentVersion));
|
|
1422
|
+
})).build();
|
|
1099
1423
|
}
|
|
1100
1424
|
|
|
1101
1425
|
//#endregion
|
|
@@ -1105,6 +1429,12 @@ const SSH_REMOTE_REGEX = /git@[^:]+:([^/]+)\/([^/.]+)(?:\.git)?/;
|
|
|
1105
1429
|
const SCOPED_PACKAGE_REGEX = /^@([^/]+)\/(.+)$/;
|
|
1106
1430
|
const PRERELEASE_REGEX = /^\d+\.\d+\.\d+-([a-zA-Z]+)/;
|
|
1107
1431
|
/**
|
|
1432
|
+
* Terminologies:
|
|
1433
|
+
*
|
|
1434
|
+
* Prepared: The value has been determined and set in the context.
|
|
1435
|
+
* Using: The value was explicitly provided in the config and is used as-is.
|
|
1436
|
+
*/
|
|
1437
|
+
/**
|
|
1108
1438
|
* Parses a git remote URL to extract owner and repository name.
|
|
1109
1439
|
* Supports both HTTPS and SSH formats.
|
|
1110
1440
|
*
|
|
@@ -1151,46 +1481,6 @@ function extractPreReleaseId(version) {
|
|
|
1151
1481
|
return version.match(PRERELEASE_REGEX)?.[1];
|
|
1152
1482
|
}
|
|
1153
1483
|
/**
|
|
1154
|
-
* Hydrates the repository field from git remote URL.
|
|
1155
|
-
*
|
|
1156
|
-
* Behavior:
|
|
1157
|
-
* - If not inside a git repository, resolves to undefined.
|
|
1158
|
-
* - If inside a repository, detect the repository URL
|
|
1159
|
-
* using a fall-through strategy (upstream remote → origin → first remote).
|
|
1160
|
-
* - Parses the URL and returns "owner/repo" when possible.
|
|
1161
|
-
*/
|
|
1162
|
-
function hydrateRepository(ctx) {
|
|
1163
|
-
return ctx.services.git.inferRepositoryUrl().map((url) => {
|
|
1164
|
-
if (!url) return null;
|
|
1165
|
-
const parsed = parseGitRemoteUrl(url);
|
|
1166
|
-
if (parsed) return `${parsed.owner}/${parsed.repo}`;
|
|
1167
|
-
return null;
|
|
1168
|
-
}).map((val) => val ?? void 0).andTee((repository) => logger.verbose(`PrepareReleaseConfigTask: Prepared repository: ${repository}`));
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Hydrates name, scope, and preReleaseId from package.json.
|
|
1172
|
-
*
|
|
1173
|
-
* Behavior:
|
|
1174
|
-
* - If package.json does not exist, returns all values as undefined.
|
|
1175
|
-
* - If it exists, reads package.json and returns parsed results for name, scope and preReleaseId.
|
|
1176
|
-
*/
|
|
1177
|
-
function hydrateFromPackageJson(ctx) {
|
|
1178
|
-
return ctx.services.fs.exists("package.json").andThen((exists) => {
|
|
1179
|
-
if (!exists) return FireflyOkAsync({
|
|
1180
|
-
name: void 0,
|
|
1181
|
-
scope: void 0,
|
|
1182
|
-
preReleaseId: void 0
|
|
1183
|
-
});
|
|
1184
|
-
return ctx.services.packageJson.read("package.json").andThen((pkg) => zip3Async(hydrateNameFromPackageJson(ctx, pkg), hydrateScopeFromPackageJson(ctx, pkg), hydratePreReleaseIdFromPackageJson(ctx, pkg)).map(([name, scope, preReleaseId]) => {
|
|
1185
|
-
const result = {};
|
|
1186
|
-
if (name) result.name = name;
|
|
1187
|
-
if (scope) result.scope = scope;
|
|
1188
|
-
if (preReleaseId) result.preReleaseId = preReleaseId;
|
|
1189
|
-
return result;
|
|
1190
|
-
}));
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
/**
|
|
1194
1484
|
* Hydrates the `name` field from package.json when not provided in config.
|
|
1195
1485
|
*
|
|
1196
1486
|
* Cases:
|
|
@@ -1240,7 +1530,7 @@ function hydrateScopeFromPackageJson(ctx, packageJson) {
|
|
|
1240
1530
|
* 3. Otherwise the function defaults to "alpha".
|
|
1241
1531
|
*/
|
|
1242
1532
|
function hydratePreReleaseIdFromPackageJson(ctx, packageJson) {
|
|
1243
|
-
if (ctx.config
|
|
1533
|
+
if (Object.hasOwn(ctx.config, "preReleaseId") && ctx.config.preReleaseId !== void 0) {
|
|
1244
1534
|
logger.verbose(`PrepareReleaseConfigTask: Using provided preReleaseId: "${ctx.config.preReleaseId}" as it is explicitly set`);
|
|
1245
1535
|
return FireflyOkAsync(ctx.config.preReleaseId);
|
|
1246
1536
|
}
|
|
@@ -1257,6 +1547,67 @@ function hydratePreReleaseIdFromPackageJson(ctx, packageJson) {
|
|
|
1257
1547
|
return FireflyOkAsync("alpha");
|
|
1258
1548
|
}
|
|
1259
1549
|
/**
|
|
1550
|
+
* Hydrates the `preReleaseBase` field.
|
|
1551
|
+
*
|
|
1552
|
+
* Behavior:
|
|
1553
|
+
* - If explicitly provided in config (key exists and value is not undefined) use as-is.
|
|
1554
|
+
* - Otherwise default to 0.
|
|
1555
|
+
*
|
|
1556
|
+
* Note: we do NOT infer `preReleaseBase` from package.json anymore.
|
|
1557
|
+
*/
|
|
1558
|
+
function hydratePreReleaseBase(ctx) {
|
|
1559
|
+
const baseMaybe = ctx.config.preReleaseBase;
|
|
1560
|
+
if (Object.hasOwn(ctx.config, "preReleaseBase") && baseMaybe !== void 0) {
|
|
1561
|
+
logger.verbose(`PrepareReleaseConfigTask: Using provided preReleaseBase: "${ctx.config.preReleaseBase}" as it is explicitly set`);
|
|
1562
|
+
return FireflyOkAsync(baseMaybe);
|
|
1563
|
+
}
|
|
1564
|
+
logger.verbose("PrepareReleaseConfigTask: No preReleaseBase explicitly provided, defaulting to 0");
|
|
1565
|
+
return FireflyOkAsync(0);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Hydrates name, scope, and preReleaseId from package.json.
|
|
1569
|
+
*
|
|
1570
|
+
* Behavior:
|
|
1571
|
+
* - If package.json does not exist, returns all values as undefined.
|
|
1572
|
+
* - If it exists, reads package.json and returns parsed results for name, scope and preReleaseId.
|
|
1573
|
+
* - If preReleaseBase is explicitly provided in config, it is used as-is, if not, it defaults to 0.
|
|
1574
|
+
*/
|
|
1575
|
+
function hydrateFromPackageJson(ctx) {
|
|
1576
|
+
return ctx.services.fs.exists("package.json").andThen((exists) => {
|
|
1577
|
+
if (!exists) return FireflyOkAsync({
|
|
1578
|
+
name: void 0,
|
|
1579
|
+
scope: void 0,
|
|
1580
|
+
preReleaseId: void 0,
|
|
1581
|
+
preReleaseBase: void 0
|
|
1582
|
+
});
|
|
1583
|
+
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) => {
|
|
1584
|
+
const result = {};
|
|
1585
|
+
if (name) result.name = name;
|
|
1586
|
+
if (scope) result.scope = scope;
|
|
1587
|
+
if (preReleaseId) result.preReleaseId = preReleaseId;
|
|
1588
|
+
if (preReleaseBase !== void 0) result.preReleaseBase = preReleaseBase;
|
|
1589
|
+
return result;
|
|
1590
|
+
})))));
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Hydrates the repository field from git remote URL.
|
|
1595
|
+
*
|
|
1596
|
+
* Behavior:
|
|
1597
|
+
* - If not inside a git repository, resolves to undefined.
|
|
1598
|
+
* - If inside a repository, detect the repository URL
|
|
1599
|
+
* using a fall-through strategy (upstream remote → origin → first remote).
|
|
1600
|
+
* - Parses the URL and returns "owner/repo" when possible.
|
|
1601
|
+
*/
|
|
1602
|
+
function hydrateRepository(ctx) {
|
|
1603
|
+
return ctx.services.git.inferRepositoryUrl().andThen((url) => {
|
|
1604
|
+
if (!url) return validationErrAsync({ message: "Could not determine git remote URL to infer repository information" });
|
|
1605
|
+
const parsed = parseGitRemoteUrl(url);
|
|
1606
|
+
if (parsed) return FireflyOkAsync(`${parsed.owner}/${parsed.repo}`);
|
|
1607
|
+
return validationErrAsync({ message: `Could not parse repository information from git remote URL: ${url}` });
|
|
1608
|
+
}).andTee((repository) => logger.verbose(`PrepareReleaseConfigTask: Prepared repository: ${repository}`));
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1260
1611
|
* Hydrates branch setting from git.
|
|
1261
1612
|
*
|
|
1262
1613
|
* Behavior:
|
|
@@ -1280,6 +1631,71 @@ function hydrateBranch(ctx) {
|
|
|
1280
1631
|
});
|
|
1281
1632
|
}
|
|
1282
1633
|
/**
|
|
1634
|
+
* Hydrates repository and branch information from git.
|
|
1635
|
+
*
|
|
1636
|
+
* Behavior:
|
|
1637
|
+
* - If not inside a git repository, resolves both values to undefined.
|
|
1638
|
+
* - Otherwise it composes `hydrateRepository` and `hydrateBranch` and returns both values.
|
|
1639
|
+
*/
|
|
1640
|
+
function hydrateFromGit(ctx) {
|
|
1641
|
+
return ctx.services.git.isInsideRepository().andThen((isRepo) => {
|
|
1642
|
+
if (!isRepo) return FireflyOkAsync({
|
|
1643
|
+
repository: void 0,
|
|
1644
|
+
branch: void 0
|
|
1645
|
+
});
|
|
1646
|
+
return hydrateRepository(ctx).andThen((repository) => hydrateBranch(ctx).map((branch) => {
|
|
1647
|
+
const result = {};
|
|
1648
|
+
if (repository) result.repository = repository;
|
|
1649
|
+
if (branch) result.branch = branch;
|
|
1650
|
+
return result;
|
|
1651
|
+
}));
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Hydrates release flags (releaseLatest, releasePreRelease, releaseDraft).
|
|
1656
|
+
*
|
|
1657
|
+
* Behavior:
|
|
1658
|
+
* - If exactly one flag is explicitly set to true, use that and set others to false.
|
|
1659
|
+
* - If no flags are explicitly set, default to releaseLatest = true, others = false.
|
|
1660
|
+
* - Validation of exclusivity is handled by the schema, so we only need to determine defaults.
|
|
1661
|
+
*/
|
|
1662
|
+
function hydrateReleaseFlags(ctx) {
|
|
1663
|
+
const { releaseLatest, releasePreRelease, releaseDraft } = ctx.config;
|
|
1664
|
+
const latestExplicit = releaseLatest === true;
|
|
1665
|
+
const preReleaseExplicit = releasePreRelease === true;
|
|
1666
|
+
const draftExplicit = releaseDraft === true;
|
|
1667
|
+
if (preReleaseExplicit) {
|
|
1668
|
+
logger.verbose(`PrepareReleaseConfigTask: Using "releasePreRelease" as it is explicitly set`);
|
|
1669
|
+
return FireflyOkAsync({
|
|
1670
|
+
releaseLatest: false,
|
|
1671
|
+
releasePreRelease: true,
|
|
1672
|
+
releaseDraft: false
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
if (draftExplicit) {
|
|
1676
|
+
logger.verbose(`PrepareReleaseConfigTask: Using "releaseDraft" as it is explicitly set`);
|
|
1677
|
+
return FireflyOkAsync({
|
|
1678
|
+
releaseLatest: false,
|
|
1679
|
+
releasePreRelease: false,
|
|
1680
|
+
releaseDraft: true
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
if (latestExplicit) {
|
|
1684
|
+
logger.verbose(`PrepareReleaseConfigTask: Using "releaseLatest" as it is explicitly set`);
|
|
1685
|
+
return FireflyOkAsync({
|
|
1686
|
+
releaseLatest: true,
|
|
1687
|
+
releasePreRelease: false,
|
|
1688
|
+
releaseDraft: false
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
logger.verbose("PrepareReleaseConfigTask: Prepared releaseLatest as default since no flag was explicitly set");
|
|
1692
|
+
return FireflyOkAsync({
|
|
1693
|
+
releaseLatest: true,
|
|
1694
|
+
releasePreRelease: false,
|
|
1695
|
+
releaseDraft: false
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1283
1699
|
* Creates the Prepare Release Config Task.
|
|
1284
1700
|
*
|
|
1285
1701
|
* This task determines and hydrates configuration settings, by inferring values from the environment.
|
|
@@ -1289,18 +1705,26 @@ function hydrateBranch(ctx) {
|
|
|
1289
1705
|
* 2. Extracts name and scope from package.json
|
|
1290
1706
|
* 3. Extracts preReleaseId from package.json version
|
|
1291
1707
|
* 4. Detects current git branch if not provided
|
|
1708
|
+
* 5. Determines release flags (latest, preRelease, draft) with proper defaults
|
|
1292
1709
|
*/
|
|
1293
1710
|
function createPrepareReleaseConfigTask() {
|
|
1294
1711
|
return TaskBuilder.create("prepare-release-config").description("Hydrate and prepare the release configuration").execute((ctx) => {
|
|
1295
1712
|
const hydrated = {};
|
|
1296
|
-
return
|
|
1297
|
-
if (repository) hydrated.repository = repository;
|
|
1713
|
+
return hydrateFromGit(ctx).andThen((gitData) => {
|
|
1714
|
+
if (gitData.repository) hydrated.repository = gitData.repository;
|
|
1715
|
+
if (gitData.branch) hydrated.branch = gitData.branch;
|
|
1716
|
+
return hydrateFromPackageJson(ctx);
|
|
1717
|
+
}).andThen((pkgData) => {
|
|
1298
1718
|
if (pkgData.name) hydrated.name = pkgData.name;
|
|
1299
1719
|
if (pkgData.scope) hydrated.scope = pkgData.scope;
|
|
1300
1720
|
if (pkgData.preReleaseId) hydrated.preReleaseId = pkgData.preReleaseId;
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
1303
|
-
|
|
1721
|
+
if (pkgData.preReleaseBase !== void 0) hydrated.preReleaseBase = pkgData.preReleaseBase;
|
|
1722
|
+
return hydrateReleaseFlags(ctx);
|
|
1723
|
+
}).map((releaseFlags) => {
|
|
1724
|
+
hydrated.releaseLatest = releaseFlags.releaseLatest;
|
|
1725
|
+
hydrated.releasePreRelease = releaseFlags.releasePreRelease;
|
|
1726
|
+
hydrated.releaseDraft = releaseFlags.releaseDraft;
|
|
1727
|
+
return ctx.forkConfig(hydrated);
|
|
1304
1728
|
});
|
|
1305
1729
|
}).build();
|
|
1306
1730
|
}
|
|
@@ -1467,7 +1891,7 @@ function validateReleaseFlagExclusivity(ctx) {
|
|
|
1467
1891
|
"releasePreRelease",
|
|
1468
1892
|
"releaseDraft"
|
|
1469
1893
|
];
|
|
1470
|
-
if (flagNames.filter((k) => ctx.value[k]).length > 1) ctx.issues.push({
|
|
1894
|
+
if (flagNames.filter((k) => ctx.value[k] === true).length > 1) ctx.issues.push({
|
|
1471
1895
|
code: "custom",
|
|
1472
1896
|
message: `Only one of ${flagNames.join(", ")} can be set to true.`,
|
|
1473
1897
|
input: ctx.value,
|
|
@@ -1513,14 +1937,15 @@ function validateSkipFlagCombinations(ctx) {
|
|
|
1513
1937
|
const ReleaseConfigSchema = z$1.object({
|
|
1514
1938
|
name: z$1.string().optional().describe("Unscoped project name. Auto-detected from package.json."),
|
|
1515
1939
|
scope: z$1.string().optional().describe("Org/user scope without '@'. Auto-detected from package.json."),
|
|
1516
|
-
|
|
1940
|
+
repository: z$1.string().optional().describe("GitHub repository in 'owner/repo' format."),
|
|
1941
|
+
base: z$1.string().optional().describe("Relative path from repository root to project root."),
|
|
1517
1942
|
branch: z$1.string().optional().describe("Git branch to release from."),
|
|
1518
1943
|
changelogPath: z$1.string().default("CHANGELOG.md").describe("Changelog file path, relative to project root."),
|
|
1519
1944
|
bumpStrategy: BumpStrategySchema.describe("\"auto\" (from commits) or \"manual\" (user-specified)."),
|
|
1520
1945
|
releaseType: ReleaseTypeSchema.optional().describe("The release type to bump."),
|
|
1521
1946
|
preReleaseId: z$1.string().optional().describe("Pre-release ID (e.g., \"alpha\", \"beta\")."),
|
|
1522
1947
|
preReleaseBase: PreReleaseBaseSchema.describe("Starting version for pre-releases."),
|
|
1523
|
-
releaseNotes: z$1.string().
|
|
1948
|
+
releaseNotes: z$1.string().optional().describe("Custom release notes for changelog."),
|
|
1524
1949
|
commitMessage: z$1.string().default(COMMIT_MSG_TEMPLATE).describe("Commit message template with placeholders."),
|
|
1525
1950
|
tagName: z$1.string().default(TAG_NAME_TEMPLATE).describe("Tag name template with placeholders."),
|
|
1526
1951
|
skipBump: z$1.coerce.boolean().default(false).describe("Skip version bump step."),
|
|
@@ -1530,9 +1955,9 @@ const ReleaseConfigSchema = z$1.object({
|
|
|
1530
1955
|
skipGit: z$1.coerce.boolean().default(false).describe("Skip all git-related steps."),
|
|
1531
1956
|
skipPreflightCheck: z$1.coerce.boolean().default(false).describe("Skip preflight checks."),
|
|
1532
1957
|
releaseTitle: z$1.string().default(RELEASE_TITLE_TEMPLATE).describe("GitHub release title with placeholders."),
|
|
1533
|
-
releaseLatest: z$1.coerce.boolean().
|
|
1534
|
-
releasePreRelease: z$1.coerce.boolean().
|
|
1535
|
-
releaseDraft: z$1.coerce.boolean().
|
|
1958
|
+
releaseLatest: z$1.coerce.boolean().optional().describe("Mark as latest release."),
|
|
1959
|
+
releasePreRelease: z$1.coerce.boolean().optional().describe("Mark as pre-release."),
|
|
1960
|
+
releaseDraft: z$1.coerce.boolean().optional().describe("Release as draft version.")
|
|
1536
1961
|
}).check((ctx) => {
|
|
1537
1962
|
validateReleaseFlagExclusivity(ctx);
|
|
1538
1963
|
validateSkipGitRedundancy(ctx);
|
|
@@ -1586,21 +2011,41 @@ function defineService(definition) {
|
|
|
1586
2011
|
*/
|
|
1587
2012
|
const SERVICE_DEFINITIONS = {
|
|
1588
2013
|
fs: defineService({ factory: async ({ basePath }) => {
|
|
1589
|
-
const { createFileSystemService } = await import("./filesystem.service-
|
|
2014
|
+
const { createFileSystemService } = await import("./filesystem.service-DS2AQGNq.js");
|
|
1590
2015
|
return createFileSystemService(basePath);
|
|
1591
2016
|
} }),
|
|
1592
2017
|
packageJson: defineService({
|
|
1593
2018
|
dependencies: ["fs"],
|
|
1594
2019
|
factory: async ({ getService }) => {
|
|
1595
2020
|
const fs = await getService("fs");
|
|
1596
|
-
const { createPackageJsonService } = await import("./package-json.service-
|
|
2021
|
+
const { createPackageJsonService } = await import("./package-json.service-BlMNgPGQ.js");
|
|
1597
2022
|
return createPackageJsonService(fs);
|
|
1598
2023
|
}
|
|
1599
2024
|
}),
|
|
1600
2025
|
git: defineService({ factory: async ({ basePath }) => {
|
|
1601
|
-
const { createGitService } = await import("./git.service-
|
|
2026
|
+
const { createGitService } = await import("./git.service-B6RdTilO.js");
|
|
1602
2027
|
return createGitService(basePath);
|
|
1603
|
-
} })
|
|
2028
|
+
} }),
|
|
2029
|
+
versionBumper: defineService({ factory: async () => {
|
|
2030
|
+
const { createVersionBumperService } = await import("./version-bumper.service-glxzf9Qm.js");
|
|
2031
|
+
return createVersionBumperService();
|
|
2032
|
+
} }),
|
|
2033
|
+
versionStrategy: defineService({
|
|
2034
|
+
dependencies: ["versionBumper"],
|
|
2035
|
+
factory: async ({ getService }) => {
|
|
2036
|
+
const versionBumper = await getService("versionBumper");
|
|
2037
|
+
const { createVersionStrategyService } = await import("./version-strategy.service-Dln42gxC.js");
|
|
2038
|
+
return createVersionStrategyService(versionBumper);
|
|
2039
|
+
}
|
|
2040
|
+
}),
|
|
2041
|
+
commitAnalysis: defineService({
|
|
2042
|
+
dependencies: ["git"],
|
|
2043
|
+
factory: async ({ getService }) => {
|
|
2044
|
+
const git = await getService("git");
|
|
2045
|
+
const { createCommitAnalysisService } = await import("./commit-analysis.service-B2Z128t8.js");
|
|
2046
|
+
return createCommitAnalysisService(git);
|
|
2047
|
+
}
|
|
2048
|
+
})
|
|
1604
2049
|
};
|
|
1605
2050
|
/**
|
|
1606
2051
|
* Array of all service keys for iteration
|
|
@@ -1861,7 +2306,7 @@ function logGraphStatistics(stats) {
|
|
|
1861
2306
|
|
|
1862
2307
|
//#endregion
|
|
1863
2308
|
//#region src/commands/release/release.command.ts
|
|
1864
|
-
const RELEASE_SERVICES = defineServiceKeys("fs", "packageJson", "git");
|
|
2309
|
+
const RELEASE_SERVICES = defineServiceKeys("fs", "packageJson", "git", "commitAnalysis", "versionBumper", "versionStrategy");
|
|
1865
2310
|
const releaseCommand = createCommand({
|
|
1866
2311
|
meta: {
|
|
1867
2312
|
name: "release",
|
|
@@ -2181,6 +2626,30 @@ var ImmutableWorkflowContext = class ImmutableWorkflowContext {
|
|
|
2181
2626
|
services: this.services
|
|
2182
2627
|
});
|
|
2183
2628
|
}
|
|
2629
|
+
/**
|
|
2630
|
+
* @example Merging hydrated config values
|
|
2631
|
+
* ```typescript
|
|
2632
|
+
* const updatedCtx = ctx.forkConfig({
|
|
2633
|
+
* repository: "owner/repo",
|
|
2634
|
+
* branch: "main"
|
|
2635
|
+
* });
|
|
2636
|
+
* ```
|
|
2637
|
+
*/
|
|
2638
|
+
forkConfig(updates) {
|
|
2639
|
+
if (Object.keys(updates).length === 0) return this;
|
|
2640
|
+
const mergedConfig = {
|
|
2641
|
+
...this.config,
|
|
2642
|
+
...updates
|
|
2643
|
+
};
|
|
2644
|
+
const frozenConfig = Object.freeze(mergedConfig);
|
|
2645
|
+
return new ImmutableWorkflowContext({
|
|
2646
|
+
startTime: this.startTime,
|
|
2647
|
+
workspace: this.workspace,
|
|
2648
|
+
config: frozenConfig,
|
|
2649
|
+
data: this.#data,
|
|
2650
|
+
services: this.services
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2184
2653
|
has(key) {
|
|
2185
2654
|
return key in this.#data;
|
|
2186
2655
|
}
|
|
@@ -2239,7 +2708,11 @@ var WorkflowExecutor = class {
|
|
|
2239
2708
|
const startTime = /* @__PURE__ */ new Date();
|
|
2240
2709
|
const executedTaskIds = [];
|
|
2241
2710
|
const skippedTaskIds = [];
|
|
2242
|
-
if (this.options.dryRun) logger.warn("
|
|
2711
|
+
if (this.options.dryRun) logger.warn("Running in DRY-RUN mode: No changes will be made.");
|
|
2712
|
+
const version = RuntimeEnv.version;
|
|
2713
|
+
const dashIndex = version.indexOf("-");
|
|
2714
|
+
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.`);
|
|
2715
|
+
else logger.warn(`You are running a PRE-RELEASE version of Firefly (${colors.dim(version)}). Unexpected behavior or bugs may occur.`);
|
|
2243
2716
|
logger.verbose(`WorkflowExecutor: Starting execution of ${tasks.length} tasks`);
|
|
2244
2717
|
return this.executeTasksSequentially(tasks, initialContext, executedTaskIds, skippedTaskIds).andThen(() => this.buildExecutionSuccessResult(startTime, executedTaskIds, skippedTaskIds)).orElse((error) => this.handleExecutionFailure({
|
|
2245
2718
|
error,
|
|
@@ -2267,6 +2740,8 @@ var WorkflowExecutor = class {
|
|
|
2267
2740
|
handleExecutionFailure(args) {
|
|
2268
2741
|
const { error, startTime, executedTaskIds, skippedTaskIds, initialContext } = args;
|
|
2269
2742
|
const endTime = /* @__PURE__ */ new Date();
|
|
2743
|
+
if (DebugFlags.showRawError) logger.error(error);
|
|
2744
|
+
else logger.error(error.message);
|
|
2270
2745
|
if (this.options.enableRollback && this.executedTasks.length > 0) {
|
|
2271
2746
|
logger.verbose(`WorkflowExecutor: Attempting rollback of ${this.executedTasks.length} tasks`);
|
|
2272
2747
|
return this.rollback(initialContext).andThen((rollbackSuccess) => {
|
|
@@ -2342,9 +2817,6 @@ var WorkflowExecutor = class {
|
|
|
2342
2817
|
executionLists.executedTaskIds.push(currentTask.meta.id);
|
|
2343
2818
|
this.executedTasks.push(currentTask);
|
|
2344
2819
|
return this.executeTasksSequentially(remainingTasks, updatedContext, executionLists.executedTaskIds, executionLists.skippedTaskIds);
|
|
2345
|
-
}).mapErr((error) => {
|
|
2346
|
-
logger.error(error.message);
|
|
2347
|
-
return error;
|
|
2348
2820
|
});
|
|
2349
2821
|
}
|
|
2350
2822
|
rollback(context) {
|
|
@@ -3016,6 +3488,18 @@ async function resolveServiceWithContext(key, context) {
|
|
|
3016
3488
|
/**
|
|
3017
3489
|
* Creates a lazy proxy that defers async service instantiation until first access.
|
|
3018
3490
|
*
|
|
3491
|
+
* IMPORTANT:
|
|
3492
|
+
* This proxy always defers instantiation and wraps calls in a ResultAsync chain
|
|
3493
|
+
* in order to safely await the real service instance before invoking the method.
|
|
3494
|
+
*
|
|
3495
|
+
* At runtime the proxy therefore returns ResultAsync even if the concrete service
|
|
3496
|
+
* method returns a synchronous Result.
|
|
3497
|
+
*
|
|
3498
|
+
* Because of this behavior, service interfaces should return FireflyAsyncResult<T>
|
|
3499
|
+
* from their public methods to avoid type mismatches and confusion. Implementations
|
|
3500
|
+
* that have purely synchronous behavior should return FireflyOkAsync / FireflyErrAsync
|
|
3501
|
+
* so they still conform to the async contract expected by the proxy.
|
|
3502
|
+
*
|
|
3019
3503
|
* @template T - The service interface type
|
|
3020
3504
|
* @param factory - Async function that creates the actual service instance
|
|
3021
3505
|
* @returns A proxy that behaves like the service but instantiates lazily
|