@kaiord/cli 1.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bin/kaiord.js +839 -612
- package/package.json +21 -21
package/dist/bin/kaiord.js
CHANGED
|
@@ -79,7 +79,7 @@ var pretty_logger_exports = {};
|
|
|
79
79
|
__export(pretty_logger_exports, {
|
|
80
80
|
createPrettyLogger: () => createPrettyLogger
|
|
81
81
|
});
|
|
82
|
-
import
|
|
82
|
+
import chalk6 from "chalk";
|
|
83
83
|
var createPrettyLogger;
|
|
84
84
|
var init_pretty_logger = __esm({
|
|
85
85
|
"src/adapters/logger/pretty-logger.ts"() {
|
|
@@ -105,31 +105,31 @@ var init_pretty_logger = __esm({
|
|
|
105
105
|
return "";
|
|
106
106
|
}
|
|
107
107
|
const contextStr = JSON.stringify(context);
|
|
108
|
-
return " " + (useColors ?
|
|
108
|
+
return " " + (useColors ? chalk6.gray(contextStr) : contextStr);
|
|
109
109
|
};
|
|
110
110
|
return {
|
|
111
111
|
debug: (message, context) => {
|
|
112
112
|
if (shouldLog("debug")) {
|
|
113
113
|
const formatted = `\u{1F41B} ${message}${formatContext(context)}`;
|
|
114
|
-
console.log(useColors ?
|
|
114
|
+
console.log(useColors ? chalk6.gray(formatted) : formatted);
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
117
|
info: (message, context) => {
|
|
118
118
|
if (shouldLog("info")) {
|
|
119
119
|
const formatted = `\u2139 ${message}${formatContext(context)}`;
|
|
120
|
-
console.log(useColors ?
|
|
120
|
+
console.log(useColors ? chalk6.blue(formatted) : formatted);
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
warn: (message, context) => {
|
|
124
124
|
if (shouldLog("warn")) {
|
|
125
125
|
const formatted = `\u26A0 ${message}${formatContext(context)}`;
|
|
126
|
-
console.warn(useColors ?
|
|
126
|
+
console.warn(useColors ? chalk6.yellow(formatted) : formatted);
|
|
127
127
|
}
|
|
128
128
|
},
|
|
129
129
|
error: (message, context) => {
|
|
130
130
|
if (shouldLog("error")) {
|
|
131
131
|
const formatted = `\u2716 ${message}${formatContext(context)}`;
|
|
132
|
-
console.error(useColors ?
|
|
132
|
+
console.error(useColors ? chalk6.red(formatted) : formatted);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
};
|
|
@@ -139,7 +139,6 @@ var init_pretty_logger = __esm({
|
|
|
139
139
|
|
|
140
140
|
// src/bin/kaiord.ts
|
|
141
141
|
init_esm_shims();
|
|
142
|
-
import chalk5 from "chalk";
|
|
143
142
|
import { readFileSync } from "fs";
|
|
144
143
|
import { dirname as dirname2, join as join3 } from "path";
|
|
145
144
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -148,16 +147,15 @@ import { hideBin } from "yargs/helpers";
|
|
|
148
147
|
|
|
149
148
|
// src/commands/convert.ts
|
|
150
149
|
init_esm_shims();
|
|
150
|
+
|
|
151
|
+
// src/commands/convert/index.ts
|
|
152
|
+
init_esm_shims();
|
|
151
153
|
import {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
ToleranceExceededError as ToleranceExceededError2
|
|
154
|
+
FitParsingError as FitParsingError3,
|
|
155
|
+
KrdValidationError as KrdValidationError3,
|
|
156
|
+
ToleranceExceededError as ToleranceExceededError3
|
|
156
157
|
} from "@kaiord/core";
|
|
157
|
-
import
|
|
158
|
-
import ora from "ora";
|
|
159
|
-
import { basename, join as join2 } from "path";
|
|
160
|
-
import { z as z3 } from "zod";
|
|
158
|
+
import { createAllProviders } from "@kaiord/all";
|
|
161
159
|
|
|
162
160
|
// src/utils/config-loader.ts
|
|
163
161
|
init_esm_shims();
|
|
@@ -197,7 +195,7 @@ var configSchema = z2.object({
|
|
|
197
195
|
json: z2.boolean().optional(),
|
|
198
196
|
logFormat: z2.enum(["pretty", "structured"]).optional()
|
|
199
197
|
});
|
|
200
|
-
var
|
|
198
|
+
var loadConfigWithMetadata = async () => {
|
|
201
199
|
const configPaths = [
|
|
202
200
|
join(process.cwd(), ".kaiordrc.json"),
|
|
203
201
|
join(homedir(), ".kaiordrc.json")
|
|
@@ -207,12 +205,12 @@ var loadConfig = async () => {
|
|
|
207
205
|
const configContent = await readFile(configPath, "utf-8");
|
|
208
206
|
const configJson = JSON.parse(configContent);
|
|
209
207
|
const config = configSchema.parse(configJson);
|
|
210
|
-
return config;
|
|
211
|
-
} catch
|
|
208
|
+
return { config, loadedFrom: configPath, searchedPaths: configPaths };
|
|
209
|
+
} catch {
|
|
212
210
|
continue;
|
|
213
211
|
}
|
|
214
212
|
}
|
|
215
|
-
return {};
|
|
213
|
+
return { config: {}, loadedFrom: null, searchedPaths: configPaths };
|
|
216
214
|
};
|
|
217
215
|
var mergeWithConfig = (cliOptions, config) => {
|
|
218
216
|
const merged = { ...config };
|
|
@@ -226,33 +224,219 @@ var mergeWithConfig = (cliOptions, config) => {
|
|
|
226
224
|
|
|
227
225
|
// src/utils/error-formatter.ts
|
|
228
226
|
init_esm_shims();
|
|
229
|
-
|
|
227
|
+
|
|
228
|
+
// src/utils/error-formatter-json.ts
|
|
229
|
+
init_esm_shims();
|
|
230
230
|
import {
|
|
231
231
|
FitParsingError,
|
|
232
232
|
KrdValidationError,
|
|
233
233
|
ToleranceExceededError
|
|
234
234
|
} from "@kaiord/core";
|
|
235
|
+
|
|
236
|
+
// src/utils/error-suggestions.ts
|
|
237
|
+
init_esm_shims();
|
|
238
|
+
var ERROR_PATTERNS = [
|
|
239
|
+
{
|
|
240
|
+
pattern: "file not found",
|
|
241
|
+
title: "File not found",
|
|
242
|
+
suggestions: [
|
|
243
|
+
"Check that the file path is correct.",
|
|
244
|
+
"Use 'ls' or 'dir' to verify the file exists.",
|
|
245
|
+
"Ensure the path is relative to current directory or absolute."
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
pattern: "permission denied",
|
|
250
|
+
title: "Permission denied",
|
|
251
|
+
suggestions: [
|
|
252
|
+
"Check file/directory permissions with 'ls -la'.",
|
|
253
|
+
"Ensure you have read/write access to the path.",
|
|
254
|
+
"Try running with appropriate permissions."
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
pattern: "failed to create directory",
|
|
259
|
+
title: "Directory creation failed",
|
|
260
|
+
suggestions: [
|
|
261
|
+
"Check that the parent directory exists.",
|
|
262
|
+
"Verify you have write permissions to the parent directory.",
|
|
263
|
+
"Ensure the path does not conflict with an existing file."
|
|
264
|
+
]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
pattern: "cannot create directory",
|
|
268
|
+
title: "Directory creation failed",
|
|
269
|
+
suggestions: [
|
|
270
|
+
"Check that the parent directory exists.",
|
|
271
|
+
"Verify you have write permissions to the parent directory.",
|
|
272
|
+
"Ensure the path does not conflict with an existing file."
|
|
273
|
+
]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
pattern: "cannot use both",
|
|
277
|
+
title: "Invalid argument combination",
|
|
278
|
+
suggestions: [
|
|
279
|
+
"Use --output (-o) for single file conversion.",
|
|
280
|
+
"Use --output-dir for batch conversion with glob patterns."
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
pattern: "no files found matching",
|
|
285
|
+
title: "No files matched",
|
|
286
|
+
suggestions: [
|
|
287
|
+
"Verify the glob pattern matches your files.",
|
|
288
|
+
'Use quotes around patterns with wildcards: "*.fit"',
|
|
289
|
+
"Check that files exist in the specified directory."
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
pattern: "batch mode requires",
|
|
294
|
+
title: "Invalid argument",
|
|
295
|
+
suggestions: [
|
|
296
|
+
"Add --output-dir to specify where converted files should go.",
|
|
297
|
+
"Add --output-format to specify the target format."
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
pattern: "unable to detect format",
|
|
302
|
+
title: "Invalid argument",
|
|
303
|
+
suggestions: [
|
|
304
|
+
"Use --input-format to explicitly specify the format.",
|
|
305
|
+
"Supported formats: fit, krd, tcx, zwo"
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
];
|
|
309
|
+
var findMatchingPattern = (msg) => {
|
|
310
|
+
return ERROR_PATTERNS.find((p) => msg.includes(p.pattern));
|
|
311
|
+
};
|
|
312
|
+
var getErrorTitle = (error) => {
|
|
313
|
+
const msg = error.message.toLowerCase();
|
|
314
|
+
const match = findMatchingPattern(msg);
|
|
315
|
+
if (match) return match.title;
|
|
316
|
+
if (error.name === "InvalidArgumentError") return "Invalid argument";
|
|
317
|
+
return "An unexpected error occurred";
|
|
318
|
+
};
|
|
319
|
+
var getSuggestionForError = (error) => {
|
|
320
|
+
const msg = error.message.toLowerCase();
|
|
321
|
+
const match = findMatchingPattern(msg);
|
|
322
|
+
return match?.suggestions ?? null;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// src/utils/error-formatter-json.ts
|
|
326
|
+
var toJson = (result) => {
|
|
327
|
+
return JSON.stringify(result, null, 2);
|
|
328
|
+
};
|
|
329
|
+
var formatFitError = (error) => toJson({
|
|
330
|
+
success: false,
|
|
331
|
+
error: {
|
|
332
|
+
type: "FitParsingError",
|
|
333
|
+
message: error.message,
|
|
334
|
+
cause: error.cause ? String(error.cause) : void 0,
|
|
335
|
+
suggestion: "Verify the file is a valid FIT workout file."
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
var formatKrdError = (error) => toJson({
|
|
339
|
+
success: false,
|
|
340
|
+
error: {
|
|
341
|
+
type: "KrdValidationError",
|
|
342
|
+
message: error.message,
|
|
343
|
+
errors: error.errors,
|
|
344
|
+
suggestion: "Check the KRD file against the schema."
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
var formatToleranceError = (error) => toJson({
|
|
348
|
+
success: false,
|
|
349
|
+
error: {
|
|
350
|
+
type: "ToleranceExceededError",
|
|
351
|
+
message: error.message,
|
|
352
|
+
violations: error.violations,
|
|
353
|
+
suggestion: "Consider adjusting tolerance values if acceptable."
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
var formatGenericError = (error) => {
|
|
357
|
+
const suggestion = getSuggestionForError(error);
|
|
358
|
+
return toJson({
|
|
359
|
+
success: false,
|
|
360
|
+
error: {
|
|
361
|
+
type: error.name || "Error",
|
|
362
|
+
message: error.message,
|
|
363
|
+
suggestion: suggestion ? suggestion.join(" ") : void 0
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
var formatUnknownError = (error) => toJson({
|
|
368
|
+
success: false,
|
|
369
|
+
error: {
|
|
370
|
+
type: "UnknownError",
|
|
371
|
+
message: String(error)
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
var formatErrorAsJson = (error) => {
|
|
375
|
+
if (error instanceof FitParsingError) return formatFitError(error);
|
|
376
|
+
if (error instanceof KrdValidationError) return formatKrdError(error);
|
|
377
|
+
if (error instanceof ToleranceExceededError)
|
|
378
|
+
return formatToleranceError(error);
|
|
379
|
+
if (error instanceof Error) return formatGenericError(error);
|
|
380
|
+
return formatUnknownError(error);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/utils/error-formatter-pretty.ts
|
|
384
|
+
init_esm_shims();
|
|
385
|
+
init_is_tty();
|
|
386
|
+
import {
|
|
387
|
+
FitParsingError as FitParsingError2,
|
|
388
|
+
KrdValidationError as KrdValidationError2,
|
|
389
|
+
ToleranceExceededError as ToleranceExceededError2
|
|
390
|
+
} from "@kaiord/core";
|
|
391
|
+
|
|
392
|
+
// src/utils/pretty-formatters/index.ts
|
|
393
|
+
init_esm_shims();
|
|
394
|
+
|
|
395
|
+
// src/utils/pretty-formatters/fit-error.ts
|
|
396
|
+
init_esm_shims();
|
|
235
397
|
import chalk from "chalk";
|
|
398
|
+
var formatFitParsingError = (error, useColors) => {
|
|
399
|
+
const lines = [
|
|
400
|
+
useColors ? chalk.red("\u2716 Error: Failed to parse FIT file") : "\u2716 Error: Failed to parse FIT file",
|
|
401
|
+
"",
|
|
402
|
+
useColors ? chalk.gray("Details:") : "Details:",
|
|
403
|
+
` ${error.message}`
|
|
404
|
+
];
|
|
405
|
+
if (error.cause) {
|
|
406
|
+
lines.push("", useColors ? chalk.gray("Cause:") : "Cause:");
|
|
407
|
+
lines.push(` ${String(error.cause)}`);
|
|
408
|
+
}
|
|
409
|
+
lines.push(
|
|
410
|
+
"",
|
|
411
|
+
useColors ? chalk.cyan("Suggestion:") : "Suggestion:",
|
|
412
|
+
" Verify the file is a valid FIT workout file.",
|
|
413
|
+
" Try opening it in Garmin Connect to confirm."
|
|
414
|
+
);
|
|
415
|
+
return lines.join("\n");
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// src/utils/pretty-formatters/krd-error.ts
|
|
419
|
+
init_esm_shims();
|
|
420
|
+
import chalk3 from "chalk";
|
|
421
|
+
|
|
422
|
+
// src/utils/format-violations.ts
|
|
423
|
+
init_esm_shims();
|
|
424
|
+
init_is_tty();
|
|
425
|
+
import chalk2 from "chalk";
|
|
236
426
|
var shouldUseColors = () => {
|
|
237
427
|
return isTTY() || process.env.FORCE_COLOR === "1";
|
|
238
428
|
};
|
|
239
|
-
var formatError = (error, options = {}) => {
|
|
240
|
-
if (options.json) {
|
|
241
|
-
return formatErrorAsJson(error);
|
|
242
|
-
}
|
|
243
|
-
return formatErrorAsPretty(error);
|
|
244
|
-
};
|
|
245
429
|
var formatValidationErrors = (errors) => {
|
|
246
430
|
if (errors.length === 0) {
|
|
247
431
|
return "";
|
|
248
432
|
}
|
|
249
433
|
const useColors = shouldUseColors();
|
|
250
434
|
const lines = [
|
|
251
|
-
useColors ?
|
|
435
|
+
useColors ? chalk2.red("Validation errors:") : "Validation errors:"
|
|
252
436
|
];
|
|
253
437
|
for (const error of errors) {
|
|
254
|
-
const fieldPath = useColors ?
|
|
255
|
-
const bullet = useColors ?
|
|
438
|
+
const fieldPath = useColors ? chalk2.yellow(error.field) : error.field;
|
|
439
|
+
const bullet = useColors ? chalk2.red("\u2022") : "\u2022";
|
|
256
440
|
lines.push(` ${bullet} ${fieldPath}: ${error.message}`);
|
|
257
441
|
}
|
|
258
442
|
return lines.join("\n");
|
|
@@ -263,173 +447,209 @@ var formatToleranceViolations = (violations) => {
|
|
|
263
447
|
}
|
|
264
448
|
const useColors = shouldUseColors();
|
|
265
449
|
const lines = [
|
|
266
|
-
useColors ?
|
|
450
|
+
useColors ? chalk2.red("Tolerance violations:") : "Tolerance violations:"
|
|
267
451
|
];
|
|
268
452
|
for (const violation of violations) {
|
|
269
|
-
const fieldPath = useColors ?
|
|
270
|
-
const bullet = useColors ?
|
|
271
|
-
const expected = violation
|
|
272
|
-
const actual = violation.actual;
|
|
453
|
+
const fieldPath = useColors ? chalk2.yellow(violation.field) : violation.field;
|
|
454
|
+
const bullet = useColors ? chalk2.red("\u2022") : "\u2022";
|
|
455
|
+
const { expected, actual, tolerance } = violation;
|
|
273
456
|
const deviation = Math.abs(violation.deviation);
|
|
274
|
-
const tolerance = violation.tolerance;
|
|
275
457
|
lines.push(
|
|
276
458
|
` ${bullet} ${fieldPath}: expected ${expected}, got ${actual} (deviation: ${deviation}, tolerance: \xB1${tolerance})`
|
|
277
459
|
);
|
|
278
460
|
}
|
|
279
461
|
return lines.join("\n");
|
|
280
462
|
};
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
463
|
+
|
|
464
|
+
// src/utils/pretty-formatters/krd-error.ts
|
|
465
|
+
var formatKrdValidationError = (error, useColors) => {
|
|
466
|
+
const lines = [
|
|
467
|
+
useColors ? chalk3.red("\u2716 Error: Invalid KRD format") : "\u2716 Error: Invalid KRD format",
|
|
468
|
+
"",
|
|
469
|
+
formatValidationErrors(error.errors),
|
|
470
|
+
"",
|
|
471
|
+
useColors ? chalk3.cyan("Suggestion:") : "Suggestion:",
|
|
472
|
+
" Check the KRD file against the schema.",
|
|
473
|
+
" Ensure all required fields are present and have valid values."
|
|
474
|
+
];
|
|
475
|
+
return lines.join("\n");
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/utils/pretty-formatters/tolerance-error.ts
|
|
479
|
+
init_esm_shims();
|
|
480
|
+
import chalk4 from "chalk";
|
|
481
|
+
var formatToleranceError2 = (error, useColors) => {
|
|
482
|
+
const lines = [
|
|
483
|
+
useColors ? chalk4.red("\u2716 Error: Round-trip conversion failed") : "\u2716 Error: Round-trip conversion failed",
|
|
484
|
+
"",
|
|
485
|
+
formatToleranceViolations(error.violations),
|
|
486
|
+
"",
|
|
487
|
+
useColors ? chalk4.cyan("Suggestion:") : "Suggestion:",
|
|
488
|
+
" The conversion may have lost precision.",
|
|
489
|
+
" Consider adjusting tolerance values if the deviations are acceptable."
|
|
490
|
+
];
|
|
491
|
+
return lines.join("\n");
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/utils/pretty-formatters/generic-error.ts
|
|
495
|
+
init_esm_shims();
|
|
496
|
+
import chalk5 from "chalk";
|
|
497
|
+
var formatGenericError2 = (error, useColors) => {
|
|
498
|
+
const suggestion = getSuggestionForError(error);
|
|
499
|
+
const lines = [
|
|
500
|
+
useColors ? chalk5.red(`\u2716 Error: ${getErrorTitle(error)}`) : `\u2716 Error: ${getErrorTitle(error)}`,
|
|
501
|
+
"",
|
|
502
|
+
useColors ? chalk5.gray("Details:") : "Details:",
|
|
503
|
+
` ${error.message}`
|
|
504
|
+
];
|
|
505
|
+
if (suggestion) {
|
|
322
506
|
lines.push(
|
|
323
507
|
"",
|
|
324
|
-
useColors ?
|
|
325
|
-
|
|
326
|
-
" Consider adjusting tolerance values if the deviations are acceptable."
|
|
508
|
+
useColors ? chalk5.cyan("Suggestion:") : "Suggestion:",
|
|
509
|
+
...suggestion.map((s) => ` ${s}`)
|
|
327
510
|
);
|
|
328
|
-
return lines.join("\n");
|
|
329
|
-
}
|
|
330
|
-
if (error instanceof Error) {
|
|
331
|
-
return [
|
|
332
|
-
useColors ? chalk.red("\u2716 Error: An unexpected error occurred") : "\u2716 Error: An unexpected error occurred",
|
|
333
|
-
"",
|
|
334
|
-
useColors ? chalk.gray("Details:") : "Details:",
|
|
335
|
-
` ${error.message}`,
|
|
336
|
-
"",
|
|
337
|
-
...error.stack ? [
|
|
338
|
-
useColors ? chalk.gray("Stack trace:") : "Stack trace:",
|
|
339
|
-
` ${error.stack}`
|
|
340
|
-
] : []
|
|
341
|
-
].join("\n");
|
|
342
511
|
}
|
|
512
|
+
return lines.join("\n");
|
|
513
|
+
};
|
|
514
|
+
var formatUnknownError2 = (error, useColors) => {
|
|
343
515
|
return [
|
|
344
|
-
useColors ?
|
|
516
|
+
useColors ? chalk5.red("\u2716 Error: An unexpected error occurred") : "\u2716 Error: An unexpected error occurred",
|
|
345
517
|
"",
|
|
346
|
-
useColors ?
|
|
518
|
+
useColors ? chalk5.gray("Details:") : "Details:",
|
|
347
519
|
` ${String(error)}`
|
|
348
520
|
].join("\n");
|
|
349
521
|
};
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
suggestion: "Verify the file is a valid FIT workout file."
|
|
360
|
-
}
|
|
361
|
-
},
|
|
362
|
-
null,
|
|
363
|
-
2
|
|
364
|
-
);
|
|
522
|
+
|
|
523
|
+
// src/utils/error-formatter-pretty.ts
|
|
524
|
+
var shouldUseColors2 = () => {
|
|
525
|
+
return isTTY() || process.env.FORCE_COLOR === "1";
|
|
526
|
+
};
|
|
527
|
+
var formatErrorAsPretty = (error) => {
|
|
528
|
+
const useColors = shouldUseColors2();
|
|
529
|
+
if (error instanceof FitParsingError2) {
|
|
530
|
+
return formatFitParsingError(error, useColors);
|
|
365
531
|
}
|
|
366
|
-
if (error instanceof
|
|
367
|
-
return
|
|
368
|
-
{
|
|
369
|
-
success: false,
|
|
370
|
-
error: {
|
|
371
|
-
type: "KrdValidationError",
|
|
372
|
-
message: error.message,
|
|
373
|
-
errors: error.errors,
|
|
374
|
-
suggestion: "Check the KRD file against the schema."
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
null,
|
|
378
|
-
2
|
|
379
|
-
);
|
|
532
|
+
if (error instanceof KrdValidationError2) {
|
|
533
|
+
return formatKrdValidationError(error, useColors);
|
|
380
534
|
}
|
|
381
|
-
if (error instanceof
|
|
382
|
-
return
|
|
383
|
-
{
|
|
384
|
-
success: false,
|
|
385
|
-
error: {
|
|
386
|
-
type: "ToleranceExceededError",
|
|
387
|
-
message: error.message,
|
|
388
|
-
violations: error.violations,
|
|
389
|
-
suggestion: "Consider adjusting tolerance values if acceptable."
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
null,
|
|
393
|
-
2
|
|
394
|
-
);
|
|
535
|
+
if (error instanceof ToleranceExceededError2) {
|
|
536
|
+
return formatToleranceError2(error, useColors);
|
|
395
537
|
}
|
|
396
538
|
if (error instanceof Error) {
|
|
397
|
-
return
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
539
|
+
return formatGenericError2(error, useColors);
|
|
540
|
+
}
|
|
541
|
+
return formatUnknownError2(error, useColors);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/utils/error-formatter.ts
|
|
545
|
+
var formatError = (error, options = {}) => {
|
|
546
|
+
if (options.json) {
|
|
547
|
+
return formatErrorAsJson(error);
|
|
548
|
+
}
|
|
549
|
+
return formatErrorAsPretty(error);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/utils/exit-codes.ts
|
|
553
|
+
init_esm_shims();
|
|
554
|
+
var ExitCode = {
|
|
555
|
+
/** Successful execution */
|
|
556
|
+
SUCCESS: 0,
|
|
557
|
+
/** Invalid arguments or usage error */
|
|
558
|
+
INVALID_ARGUMENT: 1,
|
|
559
|
+
/** File not found */
|
|
560
|
+
FILE_NOT_FOUND: 2,
|
|
561
|
+
/** Permission denied */
|
|
562
|
+
PERMISSION_DENIED: 3,
|
|
563
|
+
/** Parsing error (corrupted or invalid file format) */
|
|
564
|
+
PARSING_ERROR: 4,
|
|
565
|
+
/** Validation error (schema validation failed) */
|
|
566
|
+
VALIDATION_ERROR: 5,
|
|
567
|
+
/** Tolerance exceeded (round-trip validation failed) */
|
|
568
|
+
TOLERANCE_EXCEEDED: 6,
|
|
569
|
+
/** Differences found (diff command - files are different, not an error) */
|
|
570
|
+
DIFFERENCES_FOUND: 10,
|
|
571
|
+
/** Partial success (batch operations with some failures) */
|
|
572
|
+
PARTIAL_SUCCESS: 11,
|
|
573
|
+
/** Directory creation failed */
|
|
574
|
+
DIRECTORY_CREATE_ERROR: 12,
|
|
575
|
+
/** Unknown or unhandled error */
|
|
576
|
+
UNKNOWN_ERROR: 99
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// src/utils/logger-factory.ts
|
|
580
|
+
init_esm_shims();
|
|
581
|
+
var isCI = () => {
|
|
582
|
+
return process.env.CI === "true" || process.env.NODE_ENV === "production" || !process.stdout.isTTY;
|
|
583
|
+
};
|
|
584
|
+
var createLogger = async (options = {}) => {
|
|
585
|
+
const loggerType = options.type || (isCI() ? "structured" : "pretty");
|
|
586
|
+
if (loggerType === "structured") {
|
|
587
|
+
const { createStructuredLogger: createStructuredLogger2 } = await Promise.resolve().then(() => (init_structured_logger(), structured_logger_exports));
|
|
588
|
+
return createStructuredLogger2(options);
|
|
589
|
+
} else {
|
|
590
|
+
const { createPrettyLogger: createPrettyLogger2 } = await Promise.resolve().then(() => (init_pretty_logger(), pretty_logger_exports));
|
|
591
|
+
return createPrettyLogger2(options);
|
|
409
592
|
}
|
|
410
|
-
return JSON.stringify(
|
|
411
|
-
{
|
|
412
|
-
success: false,
|
|
413
|
-
error: {
|
|
414
|
-
type: "UnknownError",
|
|
415
|
-
message: String(error)
|
|
416
|
-
}
|
|
417
|
-
},
|
|
418
|
-
null,
|
|
419
|
-
2
|
|
420
|
-
);
|
|
421
593
|
};
|
|
422
594
|
|
|
595
|
+
// src/commands/convert/batch.ts
|
|
596
|
+
init_esm_shims();
|
|
597
|
+
import chalk7 from "chalk";
|
|
598
|
+
import ora2 from "ora";
|
|
599
|
+
import { basename, join as join2 } from "path";
|
|
600
|
+
|
|
423
601
|
// src/utils/file-handler.ts
|
|
424
602
|
init_esm_shims();
|
|
425
|
-
import {
|
|
426
|
-
readFile as fsReadFile,
|
|
427
|
-
writeFile as fsWriteFile,
|
|
428
|
-
mkdir
|
|
429
|
-
} from "fs/promises";
|
|
603
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
|
430
604
|
import { glob } from "glob";
|
|
605
|
+
|
|
606
|
+
// src/utils/directory-handler.ts
|
|
607
|
+
init_esm_shims();
|
|
608
|
+
import { mkdir } from "fs/promises";
|
|
431
609
|
import { dirname } from "path";
|
|
610
|
+
|
|
611
|
+
// src/utils/fs-errors.ts
|
|
612
|
+
init_esm_shims();
|
|
613
|
+
var isNodeSystemError = (error) => {
|
|
614
|
+
return error instanceof Error && "code" in error;
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// src/utils/directory-handler.ts
|
|
618
|
+
var createOutputDirectory = async (path2) => {
|
|
619
|
+
const dir = dirname(path2);
|
|
620
|
+
try {
|
|
621
|
+
await mkdir(dir, { recursive: true });
|
|
622
|
+
} catch (error) {
|
|
623
|
+
if (isNodeSystemError(error)) {
|
|
624
|
+
if (error.code === "EACCES") {
|
|
625
|
+
throw new Error(`Permission denied creating directory: ${dir}`);
|
|
626
|
+
}
|
|
627
|
+
if (error.code === "ENOTDIR" || error.code === "EEXIST") {
|
|
628
|
+
throw new Error(
|
|
629
|
+
`Cannot create directory (path exists as file): ${dir}`
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
throw new Error(`Failed to create directory: ${dir}`);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// src/utils/path-security.ts
|
|
638
|
+
init_esm_shims();
|
|
639
|
+
var validatePathSecurity = (inputPath) => {
|
|
640
|
+
if (inputPath.includes("\0")) {
|
|
641
|
+
throw new Error(`Invalid path: null byte detected in ${inputPath}`);
|
|
642
|
+
}
|
|
643
|
+
if (inputPath.includes("|") || inputPath.includes(";")) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Invalid path: shell metacharacters detected in ${inputPath}`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// src/utils/file-handler.ts
|
|
432
651
|
var readFile2 = async (path2, format) => {
|
|
652
|
+
validatePathSecurity(path2);
|
|
433
653
|
try {
|
|
434
654
|
if (format === "fit") {
|
|
435
655
|
const buffer = await fsReadFile(path2);
|
|
@@ -438,7 +658,7 @@ var readFile2 = async (path2, format) => {
|
|
|
438
658
|
return await fsReadFile(path2, "utf-8");
|
|
439
659
|
}
|
|
440
660
|
} catch (error) {
|
|
441
|
-
if (error
|
|
661
|
+
if (isNodeSystemError(error)) {
|
|
442
662
|
if (error.code === "ENOENT") {
|
|
443
663
|
throw new Error(`File not found: ${path2}`);
|
|
444
664
|
}
|
|
@@ -450,28 +670,28 @@ var readFile2 = async (path2, format) => {
|
|
|
450
670
|
}
|
|
451
671
|
};
|
|
452
672
|
var writeFile = async (path2, data, format) => {
|
|
673
|
+
validatePathSecurity(path2);
|
|
674
|
+
if (format === "fit" && !(data instanceof Uint8Array)) {
|
|
675
|
+
throw new Error("FIT files require Uint8Array data");
|
|
676
|
+
}
|
|
677
|
+
if (format !== "fit" && typeof data !== "string") {
|
|
678
|
+
throw new Error("Text files require string data");
|
|
679
|
+
}
|
|
680
|
+
await createOutputDirectory(path2);
|
|
453
681
|
try {
|
|
454
|
-
const dir = dirname(path2);
|
|
455
|
-
await mkdir(dir, { recursive: true });
|
|
456
682
|
if (format === "fit") {
|
|
457
|
-
if (!(data instanceof Uint8Array)) {
|
|
458
|
-
throw new Error("FIT files require Uint8Array data");
|
|
459
|
-
}
|
|
460
683
|
await fsWriteFile(path2, data);
|
|
461
684
|
} else {
|
|
462
|
-
if (typeof data !== "string") {
|
|
463
|
-
throw new Error("Text files require string data");
|
|
464
|
-
}
|
|
465
685
|
await fsWriteFile(path2, data, "utf-8");
|
|
466
686
|
}
|
|
467
687
|
} catch (error) {
|
|
468
|
-
if (error
|
|
688
|
+
if (isNodeSystemError(error)) {
|
|
469
689
|
if (error.code === "EACCES") {
|
|
470
|
-
throw new Error(`Permission denied: ${path2}`);
|
|
690
|
+
throw new Error(`Permission denied writing file: ${path2}`);
|
|
691
|
+
}
|
|
692
|
+
if (error.code === "EISDIR") {
|
|
693
|
+
throw new Error(`Cannot write file (path is a directory): ${path2}`);
|
|
471
694
|
}
|
|
472
|
-
}
|
|
473
|
-
if (error instanceof Error && error.message.includes("require")) {
|
|
474
|
-
throw error;
|
|
475
695
|
}
|
|
476
696
|
throw new Error(`Failed to write file: ${path2}`);
|
|
477
697
|
}
|
|
@@ -484,23 +704,258 @@ var findFiles = async (pattern) => {
|
|
|
484
704
|
return files.sort();
|
|
485
705
|
};
|
|
486
706
|
|
|
487
|
-
// src/
|
|
707
|
+
// src/commands/convert/single-file.ts
|
|
488
708
|
init_esm_shims();
|
|
489
|
-
|
|
490
|
-
|
|
709
|
+
import ora from "ora";
|
|
710
|
+
|
|
711
|
+
// src/utils/krd-file-loader.ts
|
|
712
|
+
init_esm_shims();
|
|
713
|
+
var loadFileAsKrd = async (filePath, format, providers) => {
|
|
714
|
+
const detectedFormat = format || detectFormat(filePath);
|
|
715
|
+
if (!detectedFormat) {
|
|
716
|
+
throw new Error(
|
|
717
|
+
`Unable to detect format for file: ${filePath}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
const fileData = await readFile2(filePath, detectedFormat);
|
|
721
|
+
return convertToKrd(fileData, detectedFormat, providers);
|
|
491
722
|
};
|
|
492
|
-
var
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
723
|
+
var convertToKrd = async (data, format, providers) => {
|
|
724
|
+
if (format === "fit") {
|
|
725
|
+
if (!(data instanceof Uint8Array)) {
|
|
726
|
+
throw new Error("FIT input must be Uint8Array");
|
|
727
|
+
}
|
|
728
|
+
return providers.convertFitToKrd({ fitBuffer: data });
|
|
729
|
+
}
|
|
730
|
+
if (format === "tcx") {
|
|
731
|
+
if (typeof data !== "string") {
|
|
732
|
+
throw new Error("TCX input must be string");
|
|
733
|
+
}
|
|
734
|
+
return providers.convertTcxToKrd({ tcxString: data });
|
|
735
|
+
}
|
|
736
|
+
if (format === "zwo") {
|
|
737
|
+
if (typeof data !== "string") {
|
|
738
|
+
throw new Error("ZWO input must be string");
|
|
739
|
+
}
|
|
740
|
+
return providers.convertZwiftToKrd({ zwiftString: data });
|
|
741
|
+
}
|
|
742
|
+
if (format === "krd") {
|
|
743
|
+
if (typeof data !== "string") {
|
|
744
|
+
throw new Error("KRD input must be string");
|
|
745
|
+
}
|
|
746
|
+
return JSON.parse(data);
|
|
747
|
+
}
|
|
748
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
749
|
+
};
|
|
750
|
+
var convertFromKrd = async (krd, format, providers) => {
|
|
751
|
+
if (format === "fit") {
|
|
752
|
+
return providers.convertKrdToFit({ krd });
|
|
753
|
+
}
|
|
754
|
+
if (format === "tcx") {
|
|
755
|
+
return providers.convertKrdToTcx({ krd });
|
|
756
|
+
}
|
|
757
|
+
if (format === "zwo") {
|
|
758
|
+
return providers.convertKrdToZwift({ krd });
|
|
759
|
+
}
|
|
760
|
+
if (format === "krd") {
|
|
761
|
+
return JSON.stringify(krd, null, 2);
|
|
762
|
+
}
|
|
763
|
+
throw new Error(`Unsupported output format: ${format}`);
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
// src/commands/convert/single-file.ts
|
|
767
|
+
var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat, providers) => {
|
|
768
|
+
const krd = await loadFileAsKrd(inputFile, inputFormat, providers);
|
|
769
|
+
const outputData = await convertFromKrd(krd, outputFormat, providers);
|
|
770
|
+
await writeFile(outputFile, outputData, outputFormat);
|
|
771
|
+
};
|
|
772
|
+
var executeSingleFileConversion = async (options, providers, logger) => {
|
|
773
|
+
const inputFormat = options.inputFormat || detectFormat(options.input);
|
|
774
|
+
if (!inputFormat) {
|
|
775
|
+
const error = new Error(
|
|
776
|
+
`Unable to detect input format from file: ${options.input}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
777
|
+
);
|
|
778
|
+
error.name = "InvalidArgumentError";
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
781
|
+
if (!options.output) {
|
|
782
|
+
const error = new Error("Output file is required");
|
|
783
|
+
error.name = "InvalidArgumentError";
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
786
|
+
const outputFormat = options.outputFormat || detectFormat(options.output);
|
|
787
|
+
if (!outputFormat) {
|
|
788
|
+
const error = new Error(
|
|
789
|
+
`Unable to detect output format from file: ${options.output}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
790
|
+
);
|
|
791
|
+
error.name = "InvalidArgumentError";
|
|
792
|
+
throw error;
|
|
793
|
+
}
|
|
794
|
+
logger.debug("Convert command initialized", {
|
|
795
|
+
input: options.input,
|
|
796
|
+
output: options.output,
|
|
797
|
+
inputFormat,
|
|
798
|
+
outputFormat
|
|
799
|
+
});
|
|
800
|
+
const isTTY2 = process.stdout.isTTY && !options.quiet && !options.json;
|
|
801
|
+
const spinner = isTTY2 ? ora("Converting...").start() : null;
|
|
802
|
+
try {
|
|
803
|
+
await convertSingleFile(
|
|
804
|
+
options.input,
|
|
805
|
+
options.output,
|
|
806
|
+
inputFormat,
|
|
807
|
+
outputFormat,
|
|
808
|
+
providers
|
|
809
|
+
);
|
|
810
|
+
if (options.json) {
|
|
811
|
+
console.log(
|
|
812
|
+
JSON.stringify(
|
|
813
|
+
{
|
|
814
|
+
success: true,
|
|
815
|
+
inputFile: options.input,
|
|
816
|
+
outputFile: options.output,
|
|
817
|
+
inputFormat,
|
|
818
|
+
outputFormat
|
|
819
|
+
},
|
|
820
|
+
null,
|
|
821
|
+
2
|
|
822
|
+
)
|
|
823
|
+
);
|
|
824
|
+
} else if (spinner) {
|
|
825
|
+
spinner.succeed(
|
|
826
|
+
`Conversion complete: ${options.input} -> ${options.output}`
|
|
827
|
+
);
|
|
828
|
+
} else {
|
|
829
|
+
logger.info("Conversion complete", {
|
|
830
|
+
input: options.input,
|
|
831
|
+
output: options.output
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
} catch (error) {
|
|
835
|
+
if (spinner) {
|
|
836
|
+
spinner.fail("Conversion failed");
|
|
837
|
+
}
|
|
838
|
+
throw error;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
// src/commands/convert/batch.ts
|
|
843
|
+
var executeBatchConversion = async (options, providers, logger) => {
|
|
844
|
+
if (!options.outputDir) {
|
|
845
|
+
const error = new Error("Batch mode requires --output-dir flag");
|
|
846
|
+
error.name = "InvalidArgumentError";
|
|
847
|
+
throw error;
|
|
848
|
+
}
|
|
849
|
+
const outputFormat = options.outputFormat;
|
|
850
|
+
if (!outputFormat) {
|
|
851
|
+
const error = new Error(
|
|
852
|
+
"Batch mode requires --output-format flag to specify target format"
|
|
853
|
+
);
|
|
854
|
+
error.name = "InvalidArgumentError";
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
857
|
+
const startTime = Date.now();
|
|
858
|
+
const files = await findFiles(options.input);
|
|
859
|
+
if (files.length === 0) {
|
|
860
|
+
const error = new Error(
|
|
861
|
+
`No files found matching pattern: ${options.input}`
|
|
862
|
+
);
|
|
863
|
+
error.name = "InvalidArgumentError";
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
logger.debug("Batch conversion started", {
|
|
867
|
+
pattern: options.input,
|
|
868
|
+
fileCount: files.length,
|
|
869
|
+
outputDir: options.outputDir,
|
|
870
|
+
outputFormat
|
|
871
|
+
});
|
|
872
|
+
const isTTY2 = process.stdout.isTTY && !options.quiet && !options.json;
|
|
873
|
+
const spinner = isTTY2 ? ora2("Processing batch conversion...").start() : null;
|
|
874
|
+
const results = [];
|
|
875
|
+
for (const [index, file] of files.entries()) {
|
|
876
|
+
const fileNum = index + 1;
|
|
877
|
+
const fileName = basename(file);
|
|
878
|
+
if (spinner) {
|
|
879
|
+
spinner.text = `Converting ${fileNum}/${files.length}: ${fileName}`;
|
|
880
|
+
} else {
|
|
881
|
+
logger.info(`Converting ${fileNum}/${files.length}: ${fileName}`);
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
const inputFormat = options.inputFormat || detectFormat(file);
|
|
885
|
+
if (!inputFormat) {
|
|
886
|
+
throw new Error(`Unable to detect format for file: ${file}`);
|
|
887
|
+
}
|
|
888
|
+
const outputFileName = fileName.replace(
|
|
889
|
+
/\.(fit|krd|tcx|zwo)$/i,
|
|
890
|
+
`.${outputFormat}`
|
|
891
|
+
);
|
|
892
|
+
const outputFile = join2(options.outputDir, outputFileName);
|
|
893
|
+
await convertSingleFile(
|
|
894
|
+
file,
|
|
895
|
+
outputFile,
|
|
896
|
+
inputFormat,
|
|
897
|
+
outputFormat,
|
|
898
|
+
providers
|
|
899
|
+
);
|
|
900
|
+
results.push({ success: true, inputFile: file, outputFile });
|
|
901
|
+
} catch (error) {
|
|
902
|
+
results.push({
|
|
903
|
+
success: false,
|
|
904
|
+
inputFile: file,
|
|
905
|
+
error: error instanceof Error ? error.message : String(error)
|
|
906
|
+
});
|
|
907
|
+
logger.error(`Failed to convert ${file}`, { error });
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const totalTime = Date.now() - startTime;
|
|
911
|
+
const successful = results.filter((r) => r.success);
|
|
912
|
+
const failed = results.filter((r) => !r.success);
|
|
913
|
+
if (spinner) {
|
|
914
|
+
spinner.stop();
|
|
915
|
+
}
|
|
916
|
+
if (!options.json) {
|
|
917
|
+
console.log("\nBatch conversion complete:");
|
|
918
|
+
console.log(
|
|
919
|
+
chalk7.green(` Successful: ${successful.length}/${files.length}`)
|
|
920
|
+
);
|
|
921
|
+
if (failed.length > 0) {
|
|
922
|
+
console.log(chalk7.red(` Failed: ${failed.length}/${files.length}`));
|
|
923
|
+
}
|
|
924
|
+
console.log(` Total time: ${(totalTime / 1e3).toFixed(2)}s`);
|
|
925
|
+
if (failed.length > 0) {
|
|
926
|
+
console.log(chalk7.red("\nFailed conversions:"));
|
|
927
|
+
for (const result of failed) {
|
|
928
|
+
console.log(chalk7.red(` ${result.inputFile}: ${result.error}`));
|
|
929
|
+
}
|
|
930
|
+
}
|
|
497
931
|
} else {
|
|
498
|
-
|
|
499
|
-
|
|
932
|
+
console.log(
|
|
933
|
+
JSON.stringify(
|
|
934
|
+
{
|
|
935
|
+
success: failed.length === 0,
|
|
936
|
+
total: files.length,
|
|
937
|
+
successful: successful.length,
|
|
938
|
+
failed: failed.length,
|
|
939
|
+
totalTime,
|
|
940
|
+
results
|
|
941
|
+
},
|
|
942
|
+
null,
|
|
943
|
+
2
|
|
944
|
+
)
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
if (failed.length === 0) {
|
|
948
|
+
return ExitCode.SUCCESS;
|
|
949
|
+
}
|
|
950
|
+
if (successful.length === 0) {
|
|
951
|
+
return ExitCode.INVALID_ARGUMENT;
|
|
500
952
|
}
|
|
953
|
+
return ExitCode.PARTIAL_SUCCESS;
|
|
501
954
|
};
|
|
502
955
|
|
|
503
|
-
// src/commands/convert.ts
|
|
956
|
+
// src/commands/convert/types.ts
|
|
957
|
+
init_esm_shims();
|
|
958
|
+
import { z as z3 } from "zod";
|
|
504
959
|
var convertOptionsSchema = z3.object({
|
|
505
960
|
input: z3.string(),
|
|
506
961
|
output: z3.string().optional(),
|
|
@@ -512,51 +967,14 @@ var convertOptionsSchema = z3.object({
|
|
|
512
967
|
json: z3.boolean().optional(),
|
|
513
968
|
logFormat: z3.enum(["pretty", "structured"]).optional()
|
|
514
969
|
});
|
|
970
|
+
|
|
971
|
+
// src/commands/convert/index.ts
|
|
515
972
|
var isBatchMode = (input) => {
|
|
516
973
|
return input.includes("*") || input.includes("?");
|
|
517
974
|
};
|
|
518
|
-
var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat, providers) => {
|
|
519
|
-
const inputData = await readFile2(inputFile, inputFormat);
|
|
520
|
-
let krd;
|
|
521
|
-
if (inputFormat === "fit") {
|
|
522
|
-
if (!(inputData instanceof Uint8Array)) {
|
|
523
|
-
throw new Error("FIT input must be Uint8Array");
|
|
524
|
-
}
|
|
525
|
-
krd = await providers.convertFitToKrd({ fitBuffer: inputData });
|
|
526
|
-
} else if (inputFormat === "tcx") {
|
|
527
|
-
if (typeof inputData !== "string") {
|
|
528
|
-
throw new Error("TCX input must be string");
|
|
529
|
-
}
|
|
530
|
-
krd = await providers.convertTcxToKrd({ tcxString: inputData });
|
|
531
|
-
} else if (inputFormat === "zwo") {
|
|
532
|
-
if (typeof inputData !== "string") {
|
|
533
|
-
throw new Error("ZWO input must be string");
|
|
534
|
-
}
|
|
535
|
-
krd = await providers.convertZwiftToKrd({ zwiftString: inputData });
|
|
536
|
-
} else if (inputFormat === "krd") {
|
|
537
|
-
if (typeof inputData !== "string") {
|
|
538
|
-
throw new Error("KRD input must be string");
|
|
539
|
-
}
|
|
540
|
-
krd = JSON.parse(inputData);
|
|
541
|
-
} else {
|
|
542
|
-
throw new Error(`Unsupported input format: ${inputFormat}`);
|
|
543
|
-
}
|
|
544
|
-
let outputData;
|
|
545
|
-
if (outputFormat === "fit") {
|
|
546
|
-
outputData = await providers.convertKrdToFit({ krd });
|
|
547
|
-
} else if (outputFormat === "tcx") {
|
|
548
|
-
outputData = await providers.convertKrdToTcx({ krd });
|
|
549
|
-
} else if (outputFormat === "zwo") {
|
|
550
|
-
outputData = await providers.convertKrdToZwift({ krd });
|
|
551
|
-
} else if (outputFormat === "krd") {
|
|
552
|
-
outputData = JSON.stringify(krd, null, 2);
|
|
553
|
-
} else {
|
|
554
|
-
throw new Error(`Unsupported output format: ${outputFormat}`);
|
|
555
|
-
}
|
|
556
|
-
await writeFile(outputFile, outputData, outputFormat);
|
|
557
|
-
};
|
|
558
975
|
var convertCommand = async (options) => {
|
|
559
|
-
const
|
|
976
|
+
const configResult = await loadConfigWithMetadata();
|
|
977
|
+
const { config } = configResult;
|
|
560
978
|
const mergedOptions = mergeWithConfig(options, config);
|
|
561
979
|
const optionsWithDefaults = {
|
|
562
980
|
...mergedOptions,
|
|
@@ -569,196 +987,32 @@ var convertCommand = async (options) => {
|
|
|
569
987
|
logFormat: mergedOptions.logFormat || config.logFormat
|
|
570
988
|
};
|
|
571
989
|
const validatedOptions = convertOptionsSchema.parse(optionsWithDefaults);
|
|
990
|
+
if (validatedOptions.output && validatedOptions.outputDir) {
|
|
991
|
+
const error = new Error(
|
|
992
|
+
"Cannot use both --output and --output-dir. Use --output for single file, --output-dir for batch conversion."
|
|
993
|
+
);
|
|
994
|
+
error.name = "InvalidArgumentError";
|
|
995
|
+
throw error;
|
|
996
|
+
}
|
|
572
997
|
const logger = await createLogger({
|
|
573
998
|
type: validatedOptions.logFormat,
|
|
574
999
|
level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
|
|
575
1000
|
quiet: validatedOptions.quiet
|
|
576
1001
|
});
|
|
1002
|
+
if (configResult.loadedFrom) {
|
|
1003
|
+
logger.debug("Configuration loaded", { path: configResult.loadedFrom });
|
|
1004
|
+
} else {
|
|
1005
|
+
logger.debug("No configuration file found", {
|
|
1006
|
+
searchedPaths: configResult.searchedPaths
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
577
1009
|
try {
|
|
578
|
-
const
|
|
579
|
-
if (
|
|
580
|
-
|
|
581
|
-
const error = new Error("Batch mode requires --output-dir flag");
|
|
582
|
-
error.name = "InvalidArgumentError";
|
|
583
|
-
throw error;
|
|
584
|
-
}
|
|
585
|
-
const outputFormat = validatedOptions.outputFormat;
|
|
586
|
-
if (!outputFormat) {
|
|
587
|
-
const error = new Error(
|
|
588
|
-
"Batch mode requires --output-format flag to specify target format"
|
|
589
|
-
);
|
|
590
|
-
error.name = "InvalidArgumentError";
|
|
591
|
-
throw error;
|
|
592
|
-
}
|
|
593
|
-
const providers = createDefaultProviders(logger);
|
|
594
|
-
const startTime = Date.now();
|
|
595
|
-
const files = await findFiles(validatedOptions.input);
|
|
596
|
-
if (files.length === 0) {
|
|
597
|
-
const error = new Error(
|
|
598
|
-
`No files found matching pattern: ${validatedOptions.input}`
|
|
599
|
-
);
|
|
600
|
-
error.name = "InvalidArgumentError";
|
|
601
|
-
throw error;
|
|
602
|
-
}
|
|
603
|
-
logger.debug("Batch conversion started", {
|
|
604
|
-
pattern: validatedOptions.input,
|
|
605
|
-
fileCount: files.length,
|
|
606
|
-
outputDir: validatedOptions.outputDir,
|
|
607
|
-
outputFormat
|
|
608
|
-
});
|
|
609
|
-
const isTTY2 = process.stdout.isTTY && !validatedOptions.quiet && !validatedOptions.json;
|
|
610
|
-
const spinner = isTTY2 ? ora("Processing batch conversion...").start() : null;
|
|
611
|
-
const results = [];
|
|
612
|
-
for (const [index, file] of files.entries()) {
|
|
613
|
-
const fileNum = index + 1;
|
|
614
|
-
const fileName = basename(file);
|
|
615
|
-
if (spinner) {
|
|
616
|
-
spinner.text = `Converting ${fileNum}/${files.length}: ${fileName}`;
|
|
617
|
-
} else {
|
|
618
|
-
logger.info(`Converting ${fileNum}/${files.length}: ${fileName}`);
|
|
619
|
-
}
|
|
620
|
-
try {
|
|
621
|
-
const inputFormat = validatedOptions.inputFormat || detectFormat(file);
|
|
622
|
-
if (!inputFormat) {
|
|
623
|
-
throw new Error(`Unable to detect format for file: ${file}`);
|
|
624
|
-
}
|
|
625
|
-
const outputFileName = fileName.replace(
|
|
626
|
-
/\.(fit|krd|tcx|zwo)$/i,
|
|
627
|
-
`.${outputFormat}`
|
|
628
|
-
);
|
|
629
|
-
const outputFile = join2(validatedOptions.outputDir, outputFileName);
|
|
630
|
-
await convertSingleFile(
|
|
631
|
-
file,
|
|
632
|
-
outputFile,
|
|
633
|
-
inputFormat,
|
|
634
|
-
outputFormat,
|
|
635
|
-
providers
|
|
636
|
-
);
|
|
637
|
-
results.push({
|
|
638
|
-
success: true,
|
|
639
|
-
inputFile: file,
|
|
640
|
-
outputFile
|
|
641
|
-
});
|
|
642
|
-
} catch (error) {
|
|
643
|
-
results.push({
|
|
644
|
-
success: false,
|
|
645
|
-
inputFile: file,
|
|
646
|
-
error: error instanceof Error ? error.message : String(error)
|
|
647
|
-
});
|
|
648
|
-
logger.error(`Failed to convert ${file}`, { error });
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
const totalTime = Date.now() - startTime;
|
|
652
|
-
const successful = results.filter((r) => r.success);
|
|
653
|
-
const failed = results.filter((r) => !r.success);
|
|
654
|
-
if (spinner) {
|
|
655
|
-
spinner.stop();
|
|
656
|
-
}
|
|
657
|
-
if (!validatedOptions.json) {
|
|
658
|
-
console.log("\nBatch conversion complete:");
|
|
659
|
-
console.log(
|
|
660
|
-
chalk3.green(` \u2713 Successful: ${successful.length}/${files.length}`)
|
|
661
|
-
);
|
|
662
|
-
if (failed.length > 0) {
|
|
663
|
-
console.log(
|
|
664
|
-
chalk3.red(` \u2717 Failed: ${failed.length}/${files.length}`)
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
console.log(` Total time: ${(totalTime / 1e3).toFixed(2)}s`);
|
|
668
|
-
if (failed.length > 0) {
|
|
669
|
-
console.log(chalk3.red("\nFailed conversions:"));
|
|
670
|
-
for (const result of failed) {
|
|
671
|
-
console.log(chalk3.red(` ${result.inputFile}: ${result.error}`));
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
} else {
|
|
675
|
-
console.log(
|
|
676
|
-
JSON.stringify(
|
|
677
|
-
{
|
|
678
|
-
success: failed.length === 0,
|
|
679
|
-
total: files.length,
|
|
680
|
-
successful: successful.length,
|
|
681
|
-
failed: failed.length,
|
|
682
|
-
totalTime,
|
|
683
|
-
results
|
|
684
|
-
},
|
|
685
|
-
null,
|
|
686
|
-
2
|
|
687
|
-
)
|
|
688
|
-
);
|
|
689
|
-
}
|
|
690
|
-
if (failed.length > 0) {
|
|
691
|
-
process.exit(1);
|
|
692
|
-
}
|
|
1010
|
+
const providers = createAllProviders(logger);
|
|
1011
|
+
if (isBatchMode(validatedOptions.input)) {
|
|
1012
|
+
return await executeBatchConversion(validatedOptions, providers, logger);
|
|
693
1013
|
} else {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const error = new Error(
|
|
697
|
-
`Unable to detect input format from file: ${validatedOptions.input}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
698
|
-
);
|
|
699
|
-
error.name = "InvalidArgumentError";
|
|
700
|
-
throw error;
|
|
701
|
-
}
|
|
702
|
-
if (!validatedOptions.output) {
|
|
703
|
-
const error = new Error("Output file is required");
|
|
704
|
-
error.name = "InvalidArgumentError";
|
|
705
|
-
throw error;
|
|
706
|
-
}
|
|
707
|
-
const outputFormat = validatedOptions.outputFormat || detectFormat(validatedOptions.output);
|
|
708
|
-
if (!outputFormat) {
|
|
709
|
-
const error = new Error(
|
|
710
|
-
`Unable to detect output format from file: ${validatedOptions.output}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
711
|
-
);
|
|
712
|
-
error.name = "InvalidArgumentError";
|
|
713
|
-
throw error;
|
|
714
|
-
}
|
|
715
|
-
const providers = createDefaultProviders(logger);
|
|
716
|
-
logger.debug("Convert command initialized", {
|
|
717
|
-
input: validatedOptions.input,
|
|
718
|
-
output: validatedOptions.output,
|
|
719
|
-
inputFormat,
|
|
720
|
-
outputFormat
|
|
721
|
-
});
|
|
722
|
-
const isTTY2 = process.stdout.isTTY && !validatedOptions.quiet && !validatedOptions.json;
|
|
723
|
-
const spinner = isTTY2 ? ora("Converting...").start() : null;
|
|
724
|
-
try {
|
|
725
|
-
await convertSingleFile(
|
|
726
|
-
validatedOptions.input,
|
|
727
|
-
validatedOptions.output,
|
|
728
|
-
inputFormat,
|
|
729
|
-
outputFormat,
|
|
730
|
-
providers
|
|
731
|
-
);
|
|
732
|
-
if (validatedOptions.json) {
|
|
733
|
-
console.log(
|
|
734
|
-
JSON.stringify(
|
|
735
|
-
{
|
|
736
|
-
success: true,
|
|
737
|
-
inputFile: validatedOptions.input,
|
|
738
|
-
outputFile: validatedOptions.output,
|
|
739
|
-
inputFormat,
|
|
740
|
-
outputFormat
|
|
741
|
-
},
|
|
742
|
-
null,
|
|
743
|
-
2
|
|
744
|
-
)
|
|
745
|
-
);
|
|
746
|
-
} else if (spinner) {
|
|
747
|
-
spinner.succeed(
|
|
748
|
-
`Conversion complete: ${validatedOptions.input} \u2192 ${validatedOptions.output}`
|
|
749
|
-
);
|
|
750
|
-
} else {
|
|
751
|
-
logger.info("Conversion complete", {
|
|
752
|
-
input: validatedOptions.input,
|
|
753
|
-
output: validatedOptions.output
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
} catch (error) {
|
|
757
|
-
if (spinner) {
|
|
758
|
-
spinner.fail("Conversion failed");
|
|
759
|
-
}
|
|
760
|
-
throw error;
|
|
761
|
-
}
|
|
1014
|
+
await executeSingleFileConversion(validatedOptions, providers, logger);
|
|
1015
|
+
return ExitCode.SUCCESS;
|
|
762
1016
|
}
|
|
763
1017
|
} catch (error) {
|
|
764
1018
|
logger.error("Conversion failed", { error });
|
|
@@ -770,108 +1024,38 @@ var convertCommand = async (options) => {
|
|
|
770
1024
|
} else {
|
|
771
1025
|
console.error(formattedError);
|
|
772
1026
|
}
|
|
773
|
-
let exitCode =
|
|
1027
|
+
let exitCode = ExitCode.UNKNOWN_ERROR;
|
|
774
1028
|
if (error instanceof Error) {
|
|
775
1029
|
if (error.message.includes("File not found")) {
|
|
776
|
-
exitCode =
|
|
1030
|
+
exitCode = ExitCode.FILE_NOT_FOUND;
|
|
777
1031
|
} else if (error.message.includes("Permission denied")) {
|
|
778
|
-
exitCode =
|
|
779
|
-
} else if (error instanceof
|
|
780
|
-
exitCode =
|
|
781
|
-
} else if (error instanceof
|
|
782
|
-
exitCode =
|
|
783
|
-
} else if (error instanceof
|
|
784
|
-
exitCode =
|
|
1032
|
+
exitCode = ExitCode.PERMISSION_DENIED;
|
|
1033
|
+
} else if (error instanceof FitParsingError3) {
|
|
1034
|
+
exitCode = ExitCode.PARSING_ERROR;
|
|
1035
|
+
} else if (error instanceof KrdValidationError3) {
|
|
1036
|
+
exitCode = ExitCode.VALIDATION_ERROR;
|
|
1037
|
+
} else if (error instanceof ToleranceExceededError3) {
|
|
1038
|
+
exitCode = ExitCode.TOLERANCE_EXCEEDED;
|
|
785
1039
|
} else if (error.name === "InvalidArgumentError") {
|
|
786
|
-
exitCode =
|
|
1040
|
+
exitCode = ExitCode.INVALID_ARGUMENT;
|
|
787
1041
|
}
|
|
788
1042
|
}
|
|
789
1043
|
return exitCode;
|
|
790
1044
|
}
|
|
791
|
-
return 0;
|
|
792
1045
|
};
|
|
793
1046
|
|
|
794
1047
|
// src/commands/diff.ts
|
|
795
1048
|
init_esm_shims();
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
json: z4.boolean().optional(),
|
|
807
|
-
logFormat: z4.enum(["pretty", "structured"]).optional()
|
|
808
|
-
});
|
|
809
|
-
var loadFileAsKrd = async (filePath, format, providers) => {
|
|
810
|
-
const detectedFormat = format || detectFormat(filePath);
|
|
811
|
-
if (!detectedFormat) {
|
|
812
|
-
throw new Error(
|
|
813
|
-
`Unable to detect format for file: ${filePath}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
const fileData = await readFile2(filePath, detectedFormat);
|
|
817
|
-
let krd;
|
|
818
|
-
if (detectedFormat === "fit") {
|
|
819
|
-
if (!(fileData instanceof Uint8Array)) {
|
|
820
|
-
throw new Error("FIT input must be Uint8Array");
|
|
821
|
-
}
|
|
822
|
-
krd = await providers.convertFitToKrd({ fitBuffer: fileData });
|
|
823
|
-
} else if (detectedFormat === "tcx") {
|
|
824
|
-
if (typeof fileData !== "string") {
|
|
825
|
-
throw new Error("TCX input must be string");
|
|
826
|
-
}
|
|
827
|
-
krd = await providers.convertTcxToKrd({ tcxString: fileData });
|
|
828
|
-
} else if (detectedFormat === "zwo") {
|
|
829
|
-
if (typeof fileData !== "string") {
|
|
830
|
-
throw new Error("ZWO input must be string");
|
|
831
|
-
}
|
|
832
|
-
krd = await providers.convertZwiftToKrd({ zwiftString: fileData });
|
|
833
|
-
} else if (detectedFormat === "krd") {
|
|
834
|
-
if (typeof fileData !== "string") {
|
|
835
|
-
throw new Error("KRD input must be string");
|
|
836
|
-
}
|
|
837
|
-
krd = JSON.parse(fileData);
|
|
838
|
-
} else {
|
|
839
|
-
throw new Error(`Unsupported format: ${detectedFormat}`);
|
|
840
|
-
}
|
|
841
|
-
return krd;
|
|
842
|
-
};
|
|
843
|
-
var isDifferent = (value1, value2) => {
|
|
844
|
-
if (value1 === value2) return false;
|
|
845
|
-
if (value1 === null || value1 === void 0) return true;
|
|
846
|
-
if (value2 === null || value2 === void 0) return true;
|
|
847
|
-
if (typeof value1 === "object" && typeof value2 === "object") {
|
|
848
|
-
return JSON.stringify(value1) !== JSON.stringify(value2);
|
|
849
|
-
}
|
|
850
|
-
return value1 !== value2;
|
|
851
|
-
};
|
|
852
|
-
var compareMetadata = (krd1, krd2) => {
|
|
853
|
-
const differences = [];
|
|
854
|
-
const metadataFields = [
|
|
855
|
-
"created",
|
|
856
|
-
"manufacturer",
|
|
857
|
-
"product",
|
|
858
|
-
"serialNumber",
|
|
859
|
-
"sport",
|
|
860
|
-
"subSport"
|
|
861
|
-
];
|
|
862
|
-
for (const field of metadataFields) {
|
|
863
|
-
const value1 = krd1.metadata[field];
|
|
864
|
-
const value2 = krd2.metadata[field];
|
|
865
|
-
if (isDifferent(value1, value2)) {
|
|
866
|
-
differences.push({
|
|
867
|
-
field,
|
|
868
|
-
file1Value: value1,
|
|
869
|
-
file2Value: value2
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
return differences;
|
|
874
|
-
};
|
|
1049
|
+
|
|
1050
|
+
// src/commands/diff/index.ts
|
|
1051
|
+
init_esm_shims();
|
|
1052
|
+
import { createAllProviders as createAllProviders2 } from "@kaiord/all";
|
|
1053
|
+
|
|
1054
|
+
// src/commands/diff/comparators.ts
|
|
1055
|
+
init_esm_shims();
|
|
1056
|
+
|
|
1057
|
+
// src/commands/diff/compare-steps.ts
|
|
1058
|
+
init_esm_shims();
|
|
875
1059
|
var compareSteps = (krd1, krd2) => {
|
|
876
1060
|
const workout1 = krd1.extensions?.workout;
|
|
877
1061
|
const workout2 = krd2.extensions?.workout;
|
|
@@ -901,35 +1085,62 @@ var compareSteps = (krd1, krd2) => {
|
|
|
901
1085
|
continue;
|
|
902
1086
|
}
|
|
903
1087
|
if (!step1 || !step2) continue;
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1088
|
+
compareStepFields(i, step1, step2, differences);
|
|
1089
|
+
}
|
|
1090
|
+
return { file1Count: steps1.length, file2Count: steps2.length, differences };
|
|
1091
|
+
};
|
|
1092
|
+
var STEP_FIELDS = [
|
|
1093
|
+
"stepIndex",
|
|
1094
|
+
"name",
|
|
1095
|
+
"durationType",
|
|
1096
|
+
"duration",
|
|
1097
|
+
"targetType",
|
|
1098
|
+
"target",
|
|
1099
|
+
"intensity",
|
|
1100
|
+
"notes",
|
|
1101
|
+
"equipment"
|
|
1102
|
+
];
|
|
1103
|
+
function compareStepFields(stepIndex, step1, step2, differences) {
|
|
1104
|
+
for (const field of STEP_FIELDS) {
|
|
1105
|
+
if (isDifferent(step1[field], step2[field])) {
|
|
1106
|
+
differences.push({
|
|
1107
|
+
stepIndex,
|
|
1108
|
+
field,
|
|
1109
|
+
file1Value: step1[field],
|
|
1110
|
+
file2Value: step2[field]
|
|
1111
|
+
});
|
|
926
1112
|
}
|
|
927
1113
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// src/commands/diff/comparators.ts
|
|
1117
|
+
var isDifferent = (value1, value2) => {
|
|
1118
|
+
if (value1 === value2) return false;
|
|
1119
|
+
if (value1 === null || value1 === void 0) return true;
|
|
1120
|
+
if (value2 === null || value2 === void 0) return true;
|
|
1121
|
+
if (typeof value1 === "object" && typeof value2 === "object") {
|
|
1122
|
+
return JSON.stringify(value1) !== JSON.stringify(value2);
|
|
1123
|
+
}
|
|
1124
|
+
return value1 !== value2;
|
|
1125
|
+
};
|
|
1126
|
+
var compareMetadata = (krd1, krd2) => {
|
|
1127
|
+
const differences = [];
|
|
1128
|
+
const metadataFields = [
|
|
1129
|
+
"created",
|
|
1130
|
+
"manufacturer",
|
|
1131
|
+
"product",
|
|
1132
|
+
"serialNumber",
|
|
1133
|
+
"sport",
|
|
1134
|
+
"subSport"
|
|
1135
|
+
];
|
|
1136
|
+
for (const field of metadataFields) {
|
|
1137
|
+
const value1 = krd1.metadata[field];
|
|
1138
|
+
const value2 = krd2.metadata[field];
|
|
1139
|
+
if (isDifferent(value1, value2)) {
|
|
1140
|
+
differences.push({ field, file1Value: value1, file2Value: value2 });
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return differences;
|
|
933
1144
|
};
|
|
934
1145
|
var compareExtensions = (krd1, krd2) => {
|
|
935
1146
|
const ext1 = krd1.extensions || {};
|
|
@@ -939,72 +1150,82 @@ var compareExtensions = (krd1, krd2) => {
|
|
|
939
1150
|
const allKeys = /* @__PURE__ */ new Set([...keys1, ...keys2]);
|
|
940
1151
|
const differences = [];
|
|
941
1152
|
for (const key of allKeys) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
if (isDifferent(value1, value2)) {
|
|
945
|
-
differences.push({
|
|
946
|
-
key,
|
|
947
|
-
file1Value: value1,
|
|
948
|
-
file2Value: value2
|
|
949
|
-
});
|
|
1153
|
+
if (isDifferent(ext1[key], ext2[key])) {
|
|
1154
|
+
differences.push({ key, file1Value: ext1[key], file2Value: ext2[key] });
|
|
950
1155
|
}
|
|
951
1156
|
}
|
|
952
|
-
return {
|
|
953
|
-
file1Keys: keys1,
|
|
954
|
-
file2Keys: keys2,
|
|
955
|
-
differences
|
|
956
|
-
};
|
|
1157
|
+
return { file1Keys: keys1, file2Keys: keys2, differences };
|
|
957
1158
|
};
|
|
1159
|
+
|
|
1160
|
+
// src/commands/diff/formatter.ts
|
|
1161
|
+
init_esm_shims();
|
|
1162
|
+
import chalk8 from "chalk";
|
|
958
1163
|
var formatDiffPretty = (result, file1, file2) => {
|
|
959
1164
|
if (result.identical) {
|
|
960
|
-
return
|
|
1165
|
+
return chalk8.green("Files are identical");
|
|
961
1166
|
}
|
|
962
1167
|
const lines = [];
|
|
963
|
-
lines.push(
|
|
1168
|
+
lines.push(chalk8.yellow(`
|
|
964
1169
|
Comparing: ${file1} vs ${file2}
|
|
965
1170
|
`));
|
|
966
1171
|
if (result.metadataDiff && result.metadataDiff.length > 0) {
|
|
967
|
-
lines.push(
|
|
1172
|
+
lines.push(chalk8.bold("Metadata Differences:"));
|
|
968
1173
|
for (const diff of result.metadataDiff) {
|
|
969
1174
|
lines.push(
|
|
970
|
-
` ${
|
|
971
|
-
` ${
|
|
972
|
-
` ${
|
|
1175
|
+
` ${chalk8.cyan(diff.field)}:`,
|
|
1176
|
+
` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
1177
|
+
` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
973
1178
|
);
|
|
974
1179
|
}
|
|
975
1180
|
lines.push("");
|
|
976
1181
|
}
|
|
977
1182
|
if (result.stepsDiff && result.stepsDiff.differences.length > 0) {
|
|
978
|
-
lines.push(
|
|
1183
|
+
lines.push(chalk8.bold("Workout Steps Differences:"));
|
|
979
1184
|
lines.push(
|
|
980
1185
|
` Step count: ${result.stepsDiff.file1Count} vs ${result.stepsDiff.file2Count}`
|
|
981
1186
|
);
|
|
982
1187
|
for (const diff of result.stepsDiff.differences) {
|
|
983
1188
|
lines.push(
|
|
984
|
-
` Step ${diff.stepIndex} - ${
|
|
985
|
-
` ${
|
|
986
|
-
` ${
|
|
1189
|
+
` Step ${diff.stepIndex} - ${chalk8.cyan(diff.field)}:`,
|
|
1190
|
+
` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
1191
|
+
` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
987
1192
|
);
|
|
988
1193
|
}
|
|
989
1194
|
lines.push("");
|
|
990
1195
|
}
|
|
991
1196
|
if (result.extensionsDiff && result.extensionsDiff.differences.length > 0) {
|
|
992
|
-
lines.push(
|
|
1197
|
+
lines.push(chalk8.bold("Extensions Differences:"));
|
|
993
1198
|
lines.push(
|
|
994
1199
|
` Keys in file1: ${result.extensionsDiff.file1Keys.join(", ")}`,
|
|
995
1200
|
` Keys in file2: ${result.extensionsDiff.file2Keys.join(", ")}`
|
|
996
1201
|
);
|
|
997
1202
|
for (const diff of result.extensionsDiff.differences) {
|
|
998
1203
|
lines.push(
|
|
999
|
-
` ${
|
|
1000
|
-
` ${
|
|
1001
|
-
` ${
|
|
1204
|
+
` ${chalk8.cyan(diff.key)}:`,
|
|
1205
|
+
` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
1206
|
+
` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
1002
1207
|
);
|
|
1003
1208
|
}
|
|
1004
1209
|
lines.push("");
|
|
1005
1210
|
}
|
|
1006
1211
|
return lines.join("\n");
|
|
1007
1212
|
};
|
|
1213
|
+
|
|
1214
|
+
// src/commands/diff/types.ts
|
|
1215
|
+
init_esm_shims();
|
|
1216
|
+
import { z as z4 } from "zod";
|
|
1217
|
+
var diffOptionsSchema = z4.object({
|
|
1218
|
+
file1: z4.string(),
|
|
1219
|
+
file2: z4.string(),
|
|
1220
|
+
format1: fileFormatSchema.optional(),
|
|
1221
|
+
format2: fileFormatSchema.optional(),
|
|
1222
|
+
verbose: z4.boolean().optional(),
|
|
1223
|
+
quiet: z4.boolean().optional(),
|
|
1224
|
+
json: z4.boolean().optional(),
|
|
1225
|
+
logFormat: z4.enum(["pretty", "structured"]).optional()
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
// src/commands/diff/index.ts
|
|
1008
1229
|
var diffCommand = async (options) => {
|
|
1009
1230
|
const validatedOptions = diffOptionsSchema.parse(options);
|
|
1010
1231
|
const logger = await createLogger({
|
|
@@ -1012,8 +1233,16 @@ var diffCommand = async (options) => {
|
|
|
1012
1233
|
level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
|
|
1013
1234
|
quiet: validatedOptions.quiet
|
|
1014
1235
|
});
|
|
1236
|
+
const configResult = await loadConfigWithMetadata();
|
|
1237
|
+
if (configResult.loadedFrom) {
|
|
1238
|
+
logger.debug("Configuration loaded", { path: configResult.loadedFrom });
|
|
1239
|
+
} else {
|
|
1240
|
+
logger.debug("No configuration file found", {
|
|
1241
|
+
searchedPaths: configResult.searchedPaths
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1015
1244
|
try {
|
|
1016
|
-
const providers =
|
|
1245
|
+
const providers = createAllProviders2(logger);
|
|
1017
1246
|
logger.debug("Loading files for comparison", {
|
|
1018
1247
|
file1: validatedOptions.file1,
|
|
1019
1248
|
file2: validatedOptions.file2
|
|
@@ -1053,14 +1282,11 @@ var diffCommand = async (options) => {
|
|
|
1053
1282
|
)
|
|
1054
1283
|
);
|
|
1055
1284
|
} else {
|
|
1056
|
-
|
|
1057
|
-
result,
|
|
1058
|
-
validatedOptions.file1,
|
|
1059
|
-
validatedOptions.file2
|
|
1285
|
+
console.log(
|
|
1286
|
+
formatDiffPretty(result, validatedOptions.file1, validatedOptions.file2)
|
|
1060
1287
|
);
|
|
1061
|
-
console.log(output);
|
|
1062
1288
|
}
|
|
1063
|
-
return identical ?
|
|
1289
|
+
return identical ? ExitCode.SUCCESS : ExitCode.DIFFERENCES_FOUND;
|
|
1064
1290
|
} catch (error) {
|
|
1065
1291
|
logger.error("Diff command failed", { error });
|
|
1066
1292
|
const formattedError = formatError(error, {
|
|
@@ -1071,20 +1297,20 @@ var diffCommand = async (options) => {
|
|
|
1071
1297
|
} else {
|
|
1072
1298
|
console.error(formattedError);
|
|
1073
1299
|
}
|
|
1074
|
-
return
|
|
1300
|
+
return ExitCode.UNKNOWN_ERROR;
|
|
1075
1301
|
}
|
|
1076
1302
|
};
|
|
1077
1303
|
|
|
1078
1304
|
// src/commands/validate.ts
|
|
1079
1305
|
init_esm_shims();
|
|
1080
1306
|
import {
|
|
1081
|
-
createDefaultProviders as createDefaultProviders3,
|
|
1082
1307
|
createToleranceChecker,
|
|
1083
1308
|
toleranceConfigSchema,
|
|
1084
1309
|
validateRoundTrip
|
|
1085
1310
|
} from "@kaiord/core";
|
|
1311
|
+
import { createAllProviders as createAllProviders3 } from "@kaiord/all";
|
|
1086
1312
|
import { readFile as fsReadFile2 } from "fs/promises";
|
|
1087
|
-
import
|
|
1313
|
+
import ora3 from "ora";
|
|
1088
1314
|
import { z as z5 } from "zod";
|
|
1089
1315
|
var validateOptionsSchema = z5.object({
|
|
1090
1316
|
input: z5.string(),
|
|
@@ -1098,7 +1324,8 @@ var validateCommand = async (options) => {
|
|
|
1098
1324
|
let logger;
|
|
1099
1325
|
let spinner = null;
|
|
1100
1326
|
try {
|
|
1101
|
-
const
|
|
1327
|
+
const configResult = await loadConfigWithMetadata();
|
|
1328
|
+
const { config } = configResult;
|
|
1102
1329
|
const mergedOptions = mergeWithConfig(
|
|
1103
1330
|
options,
|
|
1104
1331
|
config
|
|
@@ -1118,6 +1345,13 @@ var validateCommand = async (options) => {
|
|
|
1118
1345
|
level: opts.verbose ? "debug" : opts.quiet ? "error" : "info",
|
|
1119
1346
|
quiet: opts.quiet
|
|
1120
1347
|
});
|
|
1348
|
+
if (configResult.loadedFrom) {
|
|
1349
|
+
logger.debug("Configuration loaded", { path: configResult.loadedFrom });
|
|
1350
|
+
} else {
|
|
1351
|
+
logger.debug("No configuration file found", {
|
|
1352
|
+
searchedPaths: configResult.searchedPaths
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1121
1355
|
const format = detectFormat(opts.input);
|
|
1122
1356
|
if (!format) {
|
|
1123
1357
|
throw new Error(`Unable to detect format from file: ${opts.input}`);
|
|
@@ -1144,7 +1378,7 @@ var validateCommand = async (options) => {
|
|
|
1144
1378
|
config: toleranceConfig
|
|
1145
1379
|
});
|
|
1146
1380
|
}
|
|
1147
|
-
const providers =
|
|
1381
|
+
const providers = createAllProviders3(logger);
|
|
1148
1382
|
const toleranceChecker = toleranceConfig ? createToleranceChecker(toleranceConfig) : providers.toleranceChecker;
|
|
1149
1383
|
const roundTripValidator = validateRoundTrip(
|
|
1150
1384
|
providers.fitReader,
|
|
@@ -1152,7 +1386,7 @@ var validateCommand = async (options) => {
|
|
|
1152
1386
|
toleranceChecker,
|
|
1153
1387
|
logger
|
|
1154
1388
|
);
|
|
1155
|
-
spinner = opts.quiet || opts.json ? null :
|
|
1389
|
+
spinner = opts.quiet || opts.json ? null : ora3("Validating round-trip conversion...").start();
|
|
1156
1390
|
logger?.info("Starting round-trip validation", { file: opts.input });
|
|
1157
1391
|
const violations = await roundTripValidator.validateFitToKrdToFit({
|
|
1158
1392
|
originalFit: inputData
|
|
@@ -1182,9 +1416,9 @@ var validateCommand = async (options) => {
|
|
|
1182
1416
|
)
|
|
1183
1417
|
);
|
|
1184
1418
|
} else if (!opts.quiet) {
|
|
1185
|
-
console.log("
|
|
1419
|
+
console.log("Round-trip validation passed");
|
|
1186
1420
|
}
|
|
1187
|
-
|
|
1421
|
+
return ExitCode.SUCCESS;
|
|
1188
1422
|
} else {
|
|
1189
1423
|
logger?.warn("Round-trip validation failed", {
|
|
1190
1424
|
violationCount: violations.length
|
|
@@ -1203,10 +1437,10 @@ var validateCommand = async (options) => {
|
|
|
1203
1437
|
)
|
|
1204
1438
|
);
|
|
1205
1439
|
} else {
|
|
1206
|
-
console.error("
|
|
1440
|
+
console.error("Round-trip validation failed\n");
|
|
1207
1441
|
console.error(formatToleranceViolations(violations));
|
|
1208
1442
|
}
|
|
1209
|
-
|
|
1443
|
+
return ExitCode.TOLERANCE_EXCEEDED;
|
|
1210
1444
|
}
|
|
1211
1445
|
} catch (error) {
|
|
1212
1446
|
logger?.error("Validation failed", { error });
|
|
@@ -1232,7 +1466,15 @@ var validateCommand = async (options) => {
|
|
|
1232
1466
|
} else {
|
|
1233
1467
|
console.error(formatError(error, { json: false }));
|
|
1234
1468
|
}
|
|
1235
|
-
|
|
1469
|
+
if (error instanceof Error) {
|
|
1470
|
+
if (error.message.includes("File not found")) {
|
|
1471
|
+
return ExitCode.FILE_NOT_FOUND;
|
|
1472
|
+
}
|
|
1473
|
+
if (error.message.includes("only supports") || error.message.includes("Unable to detect")) {
|
|
1474
|
+
return ExitCode.INVALID_ARGUMENT;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
return ExitCode.UNKNOWN_ERROR;
|
|
1236
1478
|
} finally {
|
|
1237
1479
|
spinner?.stop();
|
|
1238
1480
|
}
|
|
@@ -1244,33 +1486,8 @@ var __dirname2 = dirname2(__filename2);
|
|
|
1244
1486
|
var packageJsonPath = __dirname2.includes("/dist") ? join3(__dirname2, "../../package.json") : join3(__dirname2, "../../package.json");
|
|
1245
1487
|
var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1246
1488
|
var version = packageJson.version;
|
|
1247
|
-
var showKiroEasterEgg = () => {
|
|
1248
|
-
console.log(
|
|
1249
|
-
chalk5.cyan(`
|
|
1250
|
-
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
1251
|
-
\u2551 \u2551
|
|
1252
|
-
\u2551 \u{1F47B} Built with Kiro AI during Kiroween Hackathon \u{1F47B} \u2551
|
|
1253
|
-
\u2551 \u2551
|
|
1254
|
-
\u2551 Kiro helped design, architect, and implement this \u2551
|
|
1255
|
-
\u2551 entire CLI tool through spec-driven development. \u2551
|
|
1256
|
-
\u2551 \u2551
|
|
1257
|
-
\u2551 Learn more about Kiroween: \u2551
|
|
1258
|
-
\u2551 \u{1F449} http://kiroween.devpost.com/ \u2551
|
|
1259
|
-
\u2551 \u2551
|
|
1260
|
-
\u2551 Kiro: Your AI pair programmer for building better \u2551
|
|
1261
|
-
\u2551 software, faster. \u{1F680} \u2551
|
|
1262
|
-
\u2551 \u2551
|
|
1263
|
-
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1264
|
-
`)
|
|
1265
|
-
);
|
|
1266
|
-
process.exit(0);
|
|
1267
|
-
};
|
|
1268
1489
|
var main = async () => {
|
|
1269
1490
|
try {
|
|
1270
|
-
const args = process.argv.slice(2);
|
|
1271
|
-
if (args.includes("--kiro") || args.includes("--kiroween")) {
|
|
1272
|
-
showKiroEasterEgg();
|
|
1273
|
-
}
|
|
1274
1491
|
await yargs(hideBin(process.argv)).scriptName("kaiord").usage("$0 <command> [options]").command(
|
|
1275
1492
|
"convert",
|
|
1276
1493
|
"Convert workout files between formats",
|
|
@@ -1324,7 +1541,7 @@ var main = async () => {
|
|
|
1324
1541
|
}
|
|
1325
1542
|
).command(
|
|
1326
1543
|
"validate",
|
|
1327
|
-
"Validate round-trip conversion of
|
|
1544
|
+
"Validate round-trip conversion of FIT files (FIT only)",
|
|
1328
1545
|
(yargs2) => {
|
|
1329
1546
|
return yargs2.option("input", {
|
|
1330
1547
|
alias: "i",
|
|
@@ -1343,7 +1560,7 @@ var main = async () => {
|
|
|
1343
1560
|
);
|
|
1344
1561
|
},
|
|
1345
1562
|
async (argv) => {
|
|
1346
|
-
await validateCommand({
|
|
1563
|
+
const exitCode = await validateCommand({
|
|
1347
1564
|
input: argv.input,
|
|
1348
1565
|
toleranceConfig: argv.toleranceConfig,
|
|
1349
1566
|
verbose: argv.verbose,
|
|
@@ -1351,16 +1568,21 @@ var main = async () => {
|
|
|
1351
1568
|
json: argv.json,
|
|
1352
1569
|
logFormat: argv.logFormat
|
|
1353
1570
|
});
|
|
1571
|
+
if (exitCode !== ExitCode.SUCCESS) {
|
|
1572
|
+
process.exit(exitCode);
|
|
1573
|
+
}
|
|
1354
1574
|
}
|
|
1355
1575
|
).command(
|
|
1356
1576
|
"diff",
|
|
1357
1577
|
"Compare two workout files and show differences",
|
|
1358
1578
|
(yargs2) => {
|
|
1359
1579
|
return yargs2.option("file1", {
|
|
1580
|
+
alias: "1",
|
|
1360
1581
|
type: "string",
|
|
1361
1582
|
description: "First file to compare",
|
|
1362
1583
|
demandOption: true
|
|
1363
1584
|
}).option("file2", {
|
|
1585
|
+
alias: "2",
|
|
1364
1586
|
type: "string",
|
|
1365
1587
|
description: "Second file to compare",
|
|
1366
1588
|
demandOption: true
|
|
@@ -1394,7 +1616,10 @@ var main = async () => {
|
|
|
1394
1616
|
json: argv.json,
|
|
1395
1617
|
logFormat: argv.logFormat
|
|
1396
1618
|
});
|
|
1397
|
-
if (exitCode !==
|
|
1619
|
+
if (exitCode !== ExitCode.SUCCESS && exitCode !== ExitCode.DIFFERENCES_FOUND) {
|
|
1620
|
+
process.exit(exitCode);
|
|
1621
|
+
}
|
|
1622
|
+
if (exitCode === ExitCode.DIFFERENCES_FOUND) {
|
|
1398
1623
|
process.exit(exitCode);
|
|
1399
1624
|
}
|
|
1400
1625
|
}
|
|
@@ -1422,25 +1647,27 @@ var main = async () => {
|
|
|
1422
1647
|
if (error && typeof error === "object" && "name" in error) {
|
|
1423
1648
|
const errorName = error.name;
|
|
1424
1649
|
if (errorName === "FitParsingError") {
|
|
1425
|
-
process.exit(
|
|
1650
|
+
process.exit(ExitCode.PARSING_ERROR);
|
|
1426
1651
|
} else if (errorName === "KrdValidationError") {
|
|
1427
|
-
process.exit(
|
|
1652
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
1428
1653
|
} else if (errorName === "ToleranceExceededError") {
|
|
1429
|
-
process.exit(
|
|
1654
|
+
process.exit(ExitCode.TOLERANCE_EXCEEDED);
|
|
1655
|
+
} else if (errorName === "InvalidArgumentError") {
|
|
1656
|
+
process.exit(ExitCode.INVALID_ARGUMENT);
|
|
1430
1657
|
}
|
|
1431
1658
|
}
|
|
1432
|
-
process.exit(
|
|
1659
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
1433
1660
|
}
|
|
1434
1661
|
};
|
|
1435
1662
|
process.on("unhandledRejection", (reason) => {
|
|
1436
1663
|
console.error("Unhandled rejection:", reason);
|
|
1437
|
-
process.exit(
|
|
1664
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
1438
1665
|
});
|
|
1439
1666
|
process.on("uncaughtException", (error) => {
|
|
1440
1667
|
console.error("Uncaught exception:", error);
|
|
1441
|
-
process.exit(
|
|
1668
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
1442
1669
|
});
|
|
1443
1670
|
main().catch((error) => {
|
|
1444
1671
|
console.error("Fatal error:", error);
|
|
1445
|
-
process.exit(
|
|
1672
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
1446
1673
|
});
|