@kaiord/cli 0.1.1 → 2.0.1
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/README.md +96 -63
- package/dist/bin/kaiord.js +1338 -549
- package/dist/package.json +3 -0
- package/package.json +24 -23
- package/dist/chunk-TI3WVGXE.js +0 -10
- package/dist/pretty-logger-P2OWMOGW.js +0 -58
- package/dist/structured-logger-HMEQGEES.js +0 -37
package/dist/bin/kaiord.js
CHANGED
|
@@ -1,55 +1,442 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
var init_esm_shims = __esm({
|
|
16
|
+
"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/utils/is-tty.ts
|
|
22
|
+
var isTTY;
|
|
23
|
+
var init_is_tty = __esm({
|
|
24
|
+
"src/utils/is-tty.ts"() {
|
|
25
|
+
"use strict";
|
|
26
|
+
init_esm_shims();
|
|
27
|
+
isTTY = () => {
|
|
28
|
+
return process.stdout.isTTY === true;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// src/adapters/logger/structured-logger.ts
|
|
34
|
+
var structured_logger_exports = {};
|
|
35
|
+
__export(structured_logger_exports, {
|
|
36
|
+
createStructuredLogger: () => createStructuredLogger
|
|
37
|
+
});
|
|
38
|
+
import winston from "winston";
|
|
39
|
+
var createStructuredLogger;
|
|
40
|
+
var init_structured_logger = __esm({
|
|
41
|
+
"src/adapters/logger/structured-logger.ts"() {
|
|
42
|
+
"use strict";
|
|
43
|
+
init_esm_shims();
|
|
44
|
+
createStructuredLogger = (options = {}) => {
|
|
45
|
+
const level = options.level || "info";
|
|
46
|
+
const quiet = options.quiet || false;
|
|
47
|
+
const winstonLogger = winston.createLogger({
|
|
48
|
+
level: quiet ? "error" : level,
|
|
49
|
+
format: winston.format.combine(
|
|
50
|
+
winston.format.timestamp(),
|
|
51
|
+
winston.format.json()
|
|
52
|
+
),
|
|
53
|
+
transports: [
|
|
54
|
+
new winston.transports.Console({
|
|
55
|
+
stderrLevels: ["error", "warn", "info", "debug"]
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
debug: (message, context) => {
|
|
61
|
+
winstonLogger.debug(message, context);
|
|
62
|
+
},
|
|
63
|
+
info: (message, context) => {
|
|
64
|
+
winstonLogger.info(message, context);
|
|
65
|
+
},
|
|
66
|
+
warn: (message, context) => {
|
|
67
|
+
winstonLogger.warn(message, context);
|
|
68
|
+
},
|
|
69
|
+
error: (message, context) => {
|
|
70
|
+
winstonLogger.error(message, context);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// src/adapters/logger/pretty-logger.ts
|
|
78
|
+
var pretty_logger_exports = {};
|
|
79
|
+
__export(pretty_logger_exports, {
|
|
80
|
+
createPrettyLogger: () => createPrettyLogger
|
|
81
|
+
});
|
|
82
|
+
import chalk6 from "chalk";
|
|
83
|
+
var createPrettyLogger;
|
|
84
|
+
var init_pretty_logger = __esm({
|
|
85
|
+
"src/adapters/logger/pretty-logger.ts"() {
|
|
86
|
+
"use strict";
|
|
87
|
+
init_esm_shims();
|
|
88
|
+
init_is_tty();
|
|
89
|
+
createPrettyLogger = (options = {}) => {
|
|
90
|
+
const level = options.level || "info";
|
|
91
|
+
const quiet = options.quiet || false;
|
|
92
|
+
const forceColor = process.env.FORCE_COLOR === "1";
|
|
93
|
+
const useColors = isTTY() || forceColor;
|
|
94
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
95
|
+
const minLevelIndex = levels.indexOf(level);
|
|
96
|
+
const shouldLog = (messageLevel) => {
|
|
97
|
+
if (quiet && messageLevel !== "error") {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const messageLevelIndex = levels.indexOf(messageLevel);
|
|
101
|
+
return messageLevelIndex >= minLevelIndex;
|
|
102
|
+
};
|
|
103
|
+
const formatContext = (context) => {
|
|
104
|
+
if (!context || Object.keys(context).length === 0) {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
const contextStr = JSON.stringify(context);
|
|
108
|
+
return " " + (useColors ? chalk6.gray(contextStr) : contextStr);
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
debug: (message, context) => {
|
|
112
|
+
if (shouldLog("debug")) {
|
|
113
|
+
const formatted = `\u{1F41B} ${message}${formatContext(context)}`;
|
|
114
|
+
console.log(useColors ? chalk6.gray(formatted) : formatted);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
info: (message, context) => {
|
|
118
|
+
if (shouldLog("info")) {
|
|
119
|
+
const formatted = `\u2139 ${message}${formatContext(context)}`;
|
|
120
|
+
console.log(useColors ? chalk6.blue(formatted) : formatted);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
warn: (message, context) => {
|
|
124
|
+
if (shouldLog("warn")) {
|
|
125
|
+
const formatted = `\u26A0 ${message}${formatContext(context)}`;
|
|
126
|
+
console.warn(useColors ? chalk6.yellow(formatted) : formatted);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
error: (message, context) => {
|
|
130
|
+
if (shouldLog("error")) {
|
|
131
|
+
const formatted = `\u2716 ${message}${formatContext(context)}`;
|
|
132
|
+
console.error(useColors ? chalk6.red(formatted) : formatted);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
});
|
|
5
139
|
|
|
6
140
|
// src/bin/kaiord.ts
|
|
7
|
-
|
|
141
|
+
init_esm_shims();
|
|
8
142
|
import { readFileSync } from "fs";
|
|
9
|
-
import { dirname as dirname2, join as
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
143
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
144
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11
145
|
import yargs from "yargs";
|
|
12
146
|
import { hideBin } from "yargs/helpers";
|
|
13
147
|
|
|
14
148
|
// src/commands/convert.ts
|
|
149
|
+
init_esm_shims();
|
|
150
|
+
|
|
151
|
+
// src/commands/convert/index.ts
|
|
152
|
+
init_esm_shims();
|
|
15
153
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
ToleranceExceededError as ToleranceExceededError2
|
|
154
|
+
FitParsingError as FitParsingError3,
|
|
155
|
+
KrdValidationError as KrdValidationError3,
|
|
156
|
+
ToleranceExceededError as ToleranceExceededError3
|
|
20
157
|
} from "@kaiord/core";
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
158
|
+
import { createAllProviders } from "@kaiord/all";
|
|
159
|
+
|
|
160
|
+
// src/utils/config-loader.ts
|
|
161
|
+
init_esm_shims();
|
|
162
|
+
import { readFile } from "fs/promises";
|
|
163
|
+
import { homedir } from "os";
|
|
164
|
+
import { join } from "path";
|
|
24
165
|
import { z as z2 } from "zod";
|
|
25
166
|
|
|
167
|
+
// src/utils/format-detector.ts
|
|
168
|
+
init_esm_shims();
|
|
169
|
+
import { extname } from "path";
|
|
170
|
+
import { z } from "zod";
|
|
171
|
+
var fileFormatSchema = z.enum(["fit", "krd", "tcx", "zwo"]);
|
|
172
|
+
var EXTENSION_TO_FORMAT = {
|
|
173
|
+
".fit": "fit",
|
|
174
|
+
".krd": "krd",
|
|
175
|
+
".tcx": "tcx",
|
|
176
|
+
".zwo": "zwo"
|
|
177
|
+
};
|
|
178
|
+
var detectFormat = (filePath) => {
|
|
179
|
+
const ext = extname(filePath).toLowerCase();
|
|
180
|
+
return EXTENSION_TO_FORMAT[ext] || null;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/utils/config-loader.ts
|
|
184
|
+
var configSchema = z2.object({
|
|
185
|
+
// Default formats
|
|
186
|
+
defaultInputFormat: fileFormatSchema.optional(),
|
|
187
|
+
defaultOutputFormat: fileFormatSchema.optional(),
|
|
188
|
+
// Default directories
|
|
189
|
+
defaultOutputDir: z2.string().optional(),
|
|
190
|
+
// Default tolerance config path
|
|
191
|
+
defaultToleranceConfig: z2.string().optional(),
|
|
192
|
+
// Default logging options
|
|
193
|
+
verbose: z2.boolean().optional(),
|
|
194
|
+
quiet: z2.boolean().optional(),
|
|
195
|
+
json: z2.boolean().optional(),
|
|
196
|
+
logFormat: z2.enum(["pretty", "structured"]).optional()
|
|
197
|
+
});
|
|
198
|
+
var loadConfigWithMetadata = async () => {
|
|
199
|
+
const configPaths = [
|
|
200
|
+
join(process.cwd(), ".kaiordrc.json"),
|
|
201
|
+
join(homedir(), ".kaiordrc.json")
|
|
202
|
+
];
|
|
203
|
+
for (const configPath of configPaths) {
|
|
204
|
+
try {
|
|
205
|
+
const configContent = await readFile(configPath, "utf-8");
|
|
206
|
+
const configJson = JSON.parse(configContent);
|
|
207
|
+
const config = configSchema.parse(configJson);
|
|
208
|
+
return { config, loadedFrom: configPath, searchedPaths: configPaths };
|
|
209
|
+
} catch {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { config: {}, loadedFrom: null, searchedPaths: configPaths };
|
|
214
|
+
};
|
|
215
|
+
var mergeWithConfig = (cliOptions, config) => {
|
|
216
|
+
const merged = { ...config };
|
|
217
|
+
for (const key in cliOptions) {
|
|
218
|
+
if (cliOptions[key] !== void 0) {
|
|
219
|
+
merged[key] = cliOptions[key];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return merged;
|
|
223
|
+
};
|
|
224
|
+
|
|
26
225
|
// src/utils/error-formatter.ts
|
|
226
|
+
init_esm_shims();
|
|
227
|
+
|
|
228
|
+
// src/utils/error-formatter-json.ts
|
|
229
|
+
init_esm_shims();
|
|
27
230
|
import {
|
|
28
231
|
FitParsingError,
|
|
29
232
|
KrdValidationError,
|
|
30
233
|
ToleranceExceededError
|
|
31
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();
|
|
32
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";
|
|
33
426
|
var shouldUseColors = () => {
|
|
34
427
|
return isTTY() || process.env.FORCE_COLOR === "1";
|
|
35
428
|
};
|
|
36
|
-
var formatError = (error, options = {}) => {
|
|
37
|
-
if (options.json) {
|
|
38
|
-
return formatErrorAsJson(error);
|
|
39
|
-
}
|
|
40
|
-
return formatErrorAsPretty(error);
|
|
41
|
-
};
|
|
42
429
|
var formatValidationErrors = (errors) => {
|
|
43
430
|
if (errors.length === 0) {
|
|
44
431
|
return "";
|
|
45
432
|
}
|
|
46
433
|
const useColors = shouldUseColors();
|
|
47
434
|
const lines = [
|
|
48
|
-
useColors ?
|
|
435
|
+
useColors ? chalk2.red("Validation errors:") : "Validation errors:"
|
|
49
436
|
];
|
|
50
437
|
for (const error of errors) {
|
|
51
|
-
const fieldPath = useColors ?
|
|
52
|
-
const bullet = useColors ?
|
|
438
|
+
const fieldPath = useColors ? chalk2.yellow(error.field) : error.field;
|
|
439
|
+
const bullet = useColors ? chalk2.red("\u2022") : "\u2022";
|
|
53
440
|
lines.push(` ${bullet} ${fieldPath}: ${error.message}`);
|
|
54
441
|
}
|
|
55
442
|
return lines.join("\n");
|
|
@@ -60,506 +447,848 @@ var formatToleranceViolations = (violations) => {
|
|
|
60
447
|
}
|
|
61
448
|
const useColors = shouldUseColors();
|
|
62
449
|
const lines = [
|
|
63
|
-
useColors ?
|
|
450
|
+
useColors ? chalk2.red("Tolerance violations:") : "Tolerance violations:"
|
|
64
451
|
];
|
|
65
452
|
for (const violation of violations) {
|
|
66
|
-
const fieldPath = useColors ?
|
|
67
|
-
const bullet = useColors ?
|
|
68
|
-
const expected = violation
|
|
69
|
-
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;
|
|
70
456
|
const deviation = Math.abs(violation.deviation);
|
|
71
|
-
const tolerance = violation.tolerance;
|
|
72
457
|
lines.push(
|
|
73
458
|
` ${bullet} ${fieldPath}: expected ${expected}, got ${actual} (deviation: ${deviation}, tolerance: \xB1${tolerance})`
|
|
74
459
|
);
|
|
75
460
|
}
|
|
76
461
|
return lines.join("\n");
|
|
77
462
|
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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) {
|
|
91
506
|
lines.push(
|
|
92
507
|
"",
|
|
93
|
-
useColors ?
|
|
94
|
-
|
|
95
|
-
" Try opening it in Garmin Connect to confirm."
|
|
508
|
+
useColors ? chalk5.cyan("Suggestion:") : "Suggestion:",
|
|
509
|
+
...suggestion.map((s) => ` ${s}`)
|
|
96
510
|
);
|
|
97
|
-
return lines.join("\n");
|
|
98
511
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
512
|
+
return lines.join("\n");
|
|
513
|
+
};
|
|
514
|
+
var formatUnknownError2 = (error, useColors) => {
|
|
515
|
+
return [
|
|
516
|
+
useColors ? chalk5.red("\u2716 Error: An unexpected error occurred") : "\u2716 Error: An unexpected error occurred",
|
|
517
|
+
"",
|
|
518
|
+
useColors ? chalk5.gray("Details:") : "Details:",
|
|
519
|
+
` ${String(error)}`
|
|
520
|
+
].join("\n");
|
|
521
|
+
};
|
|
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);
|
|
531
|
+
}
|
|
532
|
+
if (error instanceof KrdValidationError2) {
|
|
533
|
+
return formatKrdValidationError(error, useColors);
|
|
534
|
+
}
|
|
535
|
+
if (error instanceof ToleranceExceededError2) {
|
|
536
|
+
return formatToleranceError2(error, useColors);
|
|
537
|
+
}
|
|
538
|
+
if (error instanceof Error) {
|
|
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);
|
|
592
|
+
}
|
|
593
|
+
};
|
|
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
|
+
|
|
601
|
+
// src/utils/file-handler.ts
|
|
602
|
+
init_esm_shims();
|
|
603
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
|
604
|
+
import { glob } from "glob";
|
|
605
|
+
|
|
606
|
+
// src/utils/directory-handler.ts
|
|
607
|
+
init_esm_shims();
|
|
608
|
+
import { mkdir } from "fs/promises";
|
|
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}`
|
|
110
646
|
);
|
|
111
|
-
return lines.join("\n");
|
|
112
647
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// src/utils/file-handler.ts
|
|
651
|
+
var readFile2 = async (path2, format) => {
|
|
652
|
+
validatePathSecurity(path2);
|
|
653
|
+
try {
|
|
654
|
+
if (format === "fit") {
|
|
655
|
+
const buffer = await fsReadFile(path2);
|
|
656
|
+
return new Uint8Array(buffer);
|
|
657
|
+
} else {
|
|
658
|
+
return await fsReadFile(path2, "utf-8");
|
|
659
|
+
}
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (isNodeSystemError(error)) {
|
|
662
|
+
if (error.code === "ENOENT") {
|
|
663
|
+
throw new Error(`File not found: ${path2}`);
|
|
664
|
+
}
|
|
665
|
+
if (error.code === "EACCES") {
|
|
666
|
+
throw new Error(`Permission denied: ${path2}`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
throw new Error(`Failed to read file: ${path2}`);
|
|
670
|
+
}
|
|
671
|
+
};
|
|
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);
|
|
681
|
+
try {
|
|
682
|
+
if (format === "fit") {
|
|
683
|
+
await fsWriteFile(path2, data);
|
|
684
|
+
} else {
|
|
685
|
+
await fsWriteFile(path2, data, "utf-8");
|
|
686
|
+
}
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (isNodeSystemError(error)) {
|
|
689
|
+
if (error.code === "EACCES") {
|
|
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}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
throw new Error(`Failed to write file: ${path2}`);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
var findFiles = async (pattern) => {
|
|
700
|
+
const files = await glob(pattern, {
|
|
701
|
+
nodir: true,
|
|
702
|
+
absolute: false
|
|
703
|
+
});
|
|
704
|
+
return files.sort();
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// src/commands/convert/single-file.ts
|
|
708
|
+
init_esm_shims();
|
|
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`
|
|
124
718
|
);
|
|
125
|
-
return lines.join("\n");
|
|
126
719
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
""
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
` ${error.stack}`
|
|
137
|
-
] : []
|
|
138
|
-
].join("\n");
|
|
720
|
+
const fileData = await readFile2(filePath, detectedFormat);
|
|
721
|
+
return convertToKrd(fileData, detectedFormat, providers);
|
|
722
|
+
};
|
|
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 });
|
|
139
729
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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}`);
|
|
146
749
|
};
|
|
147
|
-
var
|
|
148
|
-
if (
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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`
|
|
161
790
|
);
|
|
791
|
+
error.name = "InvalidArgumentError";
|
|
792
|
+
throw error;
|
|
162
793
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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({
|
|
166
903
|
success: false,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
}
|
|
931
|
+
} else {
|
|
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
|
+
)
|
|
176
945
|
);
|
|
177
946
|
}
|
|
178
|
-
if (
|
|
179
|
-
return
|
|
180
|
-
{
|
|
181
|
-
success: false,
|
|
182
|
-
error: {
|
|
183
|
-
type: "ToleranceExceededError",
|
|
184
|
-
message: error.message,
|
|
185
|
-
violations: error.violations,
|
|
186
|
-
suggestion: "Consider adjusting tolerance values if acceptable."
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
null,
|
|
190
|
-
2
|
|
191
|
-
);
|
|
947
|
+
if (failed.length === 0) {
|
|
948
|
+
return ExitCode.SUCCESS;
|
|
192
949
|
}
|
|
193
|
-
if (
|
|
194
|
-
return
|
|
195
|
-
{
|
|
196
|
-
success: false,
|
|
197
|
-
error: {
|
|
198
|
-
type: error.name || "Error",
|
|
199
|
-
message: error.message,
|
|
200
|
-
stack: error.stack
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
null,
|
|
204
|
-
2
|
|
205
|
-
);
|
|
950
|
+
if (successful.length === 0) {
|
|
951
|
+
return ExitCode.INVALID_ARGUMENT;
|
|
206
952
|
}
|
|
207
|
-
return
|
|
208
|
-
{
|
|
209
|
-
success: false,
|
|
210
|
-
error: {
|
|
211
|
-
type: "UnknownError",
|
|
212
|
-
message: String(error)
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
null,
|
|
216
|
-
2
|
|
217
|
-
);
|
|
953
|
+
return ExitCode.PARTIAL_SUCCESS;
|
|
218
954
|
};
|
|
219
955
|
|
|
220
|
-
// src/
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
956
|
+
// src/commands/convert/types.ts
|
|
957
|
+
init_esm_shims();
|
|
958
|
+
import { z as z3 } from "zod";
|
|
959
|
+
var convertOptionsSchema = z3.object({
|
|
960
|
+
input: z3.string(),
|
|
961
|
+
output: z3.string().optional(),
|
|
962
|
+
outputDir: z3.string().optional(),
|
|
963
|
+
inputFormat: fileFormatSchema.optional(),
|
|
964
|
+
outputFormat: fileFormatSchema.optional(),
|
|
965
|
+
verbose: z3.boolean().optional(),
|
|
966
|
+
quiet: z3.boolean().optional(),
|
|
967
|
+
json: z3.boolean().optional(),
|
|
968
|
+
logFormat: z3.enum(["pretty", "structured"]).optional()
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// src/commands/convert/index.ts
|
|
972
|
+
var isBatchMode = (input) => {
|
|
973
|
+
return input.includes("*") || input.includes("?");
|
|
974
|
+
};
|
|
975
|
+
var convertCommand = async (options) => {
|
|
976
|
+
const configResult = await loadConfigWithMetadata();
|
|
977
|
+
const { config } = configResult;
|
|
978
|
+
const mergedOptions = mergeWithConfig(options, config);
|
|
979
|
+
const optionsWithDefaults = {
|
|
980
|
+
...mergedOptions,
|
|
981
|
+
inputFormat: mergedOptions.inputFormat || config.defaultInputFormat,
|
|
982
|
+
outputFormat: mergedOptions.outputFormat || config.defaultOutputFormat,
|
|
983
|
+
outputDir: mergedOptions.outputDir || config.defaultOutputDir,
|
|
984
|
+
verbose: mergedOptions.verbose ?? config.verbose,
|
|
985
|
+
quiet: mergedOptions.quiet ?? config.quiet,
|
|
986
|
+
json: mergedOptions.json ?? config.json,
|
|
987
|
+
logFormat: mergedOptions.logFormat || config.logFormat
|
|
988
|
+
};
|
|
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
|
+
}
|
|
997
|
+
const logger = await createLogger({
|
|
998
|
+
type: validatedOptions.logFormat,
|
|
999
|
+
level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
|
|
1000
|
+
quiet: validatedOptions.quiet
|
|
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
|
+
}
|
|
229
1009
|
try {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return
|
|
1010
|
+
const providers = createAllProviders(logger);
|
|
1011
|
+
if (isBatchMode(validatedOptions.input)) {
|
|
1012
|
+
return await executeBatchConversion(validatedOptions, providers, logger);
|
|
233
1013
|
} else {
|
|
234
|
-
|
|
1014
|
+
await executeSingleFileConversion(validatedOptions, providers, logger);
|
|
1015
|
+
return ExitCode.SUCCESS;
|
|
235
1016
|
}
|
|
236
1017
|
} catch (error) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
1018
|
+
logger.error("Conversion failed", { error });
|
|
1019
|
+
const formattedError = formatError(error, {
|
|
1020
|
+
json: validatedOptions.json
|
|
1021
|
+
});
|
|
1022
|
+
if (validatedOptions.json) {
|
|
1023
|
+
console.log(formattedError);
|
|
1024
|
+
} else {
|
|
1025
|
+
console.error(formattedError);
|
|
1026
|
+
}
|
|
1027
|
+
let exitCode = ExitCode.UNKNOWN_ERROR;
|
|
1028
|
+
if (error instanceof Error) {
|
|
1029
|
+
if (error.message.includes("File not found")) {
|
|
1030
|
+
exitCode = ExitCode.FILE_NOT_FOUND;
|
|
1031
|
+
} else if (error.message.includes("Permission denied")) {
|
|
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;
|
|
1039
|
+
} else if (error.name === "InvalidArgumentError") {
|
|
1040
|
+
exitCode = ExitCode.INVALID_ARGUMENT;
|
|
243
1041
|
}
|
|
244
1042
|
}
|
|
245
|
-
|
|
1043
|
+
return exitCode;
|
|
246
1044
|
}
|
|
247
1045
|
};
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
1046
|
+
|
|
1047
|
+
// src/commands/diff.ts
|
|
1048
|
+
init_esm_shims();
|
|
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();
|
|
1059
|
+
var compareSteps = (krd1, krd2) => {
|
|
1060
|
+
const workout1 = krd1.extensions?.workout;
|
|
1061
|
+
const workout2 = krd2.extensions?.workout;
|
|
1062
|
+
const steps1 = workout1?.steps || [];
|
|
1063
|
+
const steps2 = workout2?.steps || [];
|
|
1064
|
+
const differences = [];
|
|
1065
|
+
const maxSteps = Math.max(steps1.length, steps2.length);
|
|
1066
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
1067
|
+
const step1 = steps1[i];
|
|
1068
|
+
const step2 = steps2[i];
|
|
1069
|
+
if (!step1 && step2) {
|
|
1070
|
+
differences.push({
|
|
1071
|
+
stepIndex: i,
|
|
1072
|
+
field: "step",
|
|
1073
|
+
file1Value: void 0,
|
|
1074
|
+
file2Value: step2
|
|
1075
|
+
});
|
|
1076
|
+
continue;
|
|
268
1077
|
}
|
|
269
|
-
if (
|
|
270
|
-
|
|
1078
|
+
if (step1 && !step2) {
|
|
1079
|
+
differences.push({
|
|
1080
|
+
stepIndex: i,
|
|
1081
|
+
field: "step",
|
|
1082
|
+
file1Value: step1,
|
|
1083
|
+
file2Value: void 0
|
|
1084
|
+
});
|
|
1085
|
+
continue;
|
|
271
1086
|
}
|
|
272
|
-
|
|
1087
|
+
if (!step1 || !step2) continue;
|
|
1088
|
+
compareStepFields(i, step1, step2, differences);
|
|
273
1089
|
}
|
|
1090
|
+
return { file1Count: steps1.length, file2Count: steps2.length, differences };
|
|
274
1091
|
};
|
|
275
|
-
var
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
282
1115
|
|
|
283
|
-
// src/
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
"
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
};
|
|
293
|
-
var detectFormat = (filePath) => {
|
|
294
|
-
const ext = extname(filePath).toLowerCase();
|
|
295
|
-
return EXTENSION_TO_FORMAT[ext] || null;
|
|
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;
|
|
296
1125
|
};
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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;
|
|
301
1144
|
};
|
|
302
|
-
var
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1145
|
+
var compareExtensions = (krd1, krd2) => {
|
|
1146
|
+
const ext1 = krd1.extensions || {};
|
|
1147
|
+
const ext2 = krd2.extensions || {};
|
|
1148
|
+
const keys1 = Object.keys(ext1);
|
|
1149
|
+
const keys2 = Object.keys(ext2);
|
|
1150
|
+
const allKeys = /* @__PURE__ */ new Set([...keys1, ...keys2]);
|
|
1151
|
+
const differences = [];
|
|
1152
|
+
for (const key of allKeys) {
|
|
1153
|
+
if (isDifferent(ext1[key], ext2[key])) {
|
|
1154
|
+
differences.push({ key, file1Value: ext1[key], file2Value: ext2[key] });
|
|
1155
|
+
}
|
|
310
1156
|
}
|
|
1157
|
+
return { file1Keys: keys1, file2Keys: keys2, differences };
|
|
311
1158
|
};
|
|
312
1159
|
|
|
313
|
-
// src/commands/
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!(inputData instanceof Uint8Array)) {
|
|
333
|
-
throw new Error("FIT input must be Uint8Array");
|
|
334
|
-
}
|
|
335
|
-
krd = await providers.convertFitToKrd({ fitBuffer: inputData });
|
|
336
|
-
} else if (inputFormat === "tcx") {
|
|
337
|
-
if (typeof inputData !== "string") {
|
|
338
|
-
throw new Error("TCX input must be string");
|
|
1160
|
+
// src/commands/diff/formatter.ts
|
|
1161
|
+
init_esm_shims();
|
|
1162
|
+
import chalk8 from "chalk";
|
|
1163
|
+
var formatDiffPretty = (result, file1, file2) => {
|
|
1164
|
+
if (result.identical) {
|
|
1165
|
+
return chalk8.green("Files are identical");
|
|
1166
|
+
}
|
|
1167
|
+
const lines = [];
|
|
1168
|
+
lines.push(chalk8.yellow(`
|
|
1169
|
+
Comparing: ${file1} vs ${file2}
|
|
1170
|
+
`));
|
|
1171
|
+
if (result.metadataDiff && result.metadataDiff.length > 0) {
|
|
1172
|
+
lines.push(chalk8.bold("Metadata Differences:"));
|
|
1173
|
+
for (const diff of result.metadataDiff) {
|
|
1174
|
+
lines.push(
|
|
1175
|
+
` ${chalk8.cyan(diff.field)}:`,
|
|
1176
|
+
` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
1177
|
+
` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
1178
|
+
);
|
|
339
1179
|
}
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
1180
|
+
lines.push("");
|
|
1181
|
+
}
|
|
1182
|
+
if (result.stepsDiff && result.stepsDiff.differences.length > 0) {
|
|
1183
|
+
lines.push(chalk8.bold("Workout Steps Differences:"));
|
|
1184
|
+
lines.push(
|
|
1185
|
+
` Step count: ${result.stepsDiff.file1Count} vs ${result.stepsDiff.file2Count}`
|
|
1186
|
+
);
|
|
1187
|
+
for (const diff of result.stepsDiff.differences) {
|
|
1188
|
+
lines.push(
|
|
1189
|
+
` Step ${diff.stepIndex} - ${chalk8.cyan(diff.field)}:`,
|
|
1190
|
+
` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
1191
|
+
` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
1192
|
+
);
|
|
344
1193
|
}
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
1194
|
+
lines.push("");
|
|
1195
|
+
}
|
|
1196
|
+
if (result.extensionsDiff && result.extensionsDiff.differences.length > 0) {
|
|
1197
|
+
lines.push(chalk8.bold("Extensions Differences:"));
|
|
1198
|
+
lines.push(
|
|
1199
|
+
` Keys in file1: ${result.extensionsDiff.file1Keys.join(", ")}`,
|
|
1200
|
+
` Keys in file2: ${result.extensionsDiff.file2Keys.join(", ")}`
|
|
1201
|
+
);
|
|
1202
|
+
for (const diff of result.extensionsDiff.differences) {
|
|
1203
|
+
lines.push(
|
|
1204
|
+
` ${chalk8.cyan(diff.key)}:`,
|
|
1205
|
+
` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
1206
|
+
` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
1207
|
+
);
|
|
349
1208
|
}
|
|
350
|
-
|
|
351
|
-
} else {
|
|
352
|
-
throw new Error(`Unsupported input format: ${inputFormat}`);
|
|
353
|
-
}
|
|
354
|
-
let outputData;
|
|
355
|
-
if (outputFormat === "fit") {
|
|
356
|
-
outputData = await providers.convertKrdToFit({ krd });
|
|
357
|
-
} else if (outputFormat === "tcx") {
|
|
358
|
-
outputData = await providers.convertKrdToTcx({ krd });
|
|
359
|
-
} else if (outputFormat === "zwo") {
|
|
360
|
-
outputData = await providers.convertKrdToZwift({ krd });
|
|
361
|
-
} else if (outputFormat === "krd") {
|
|
362
|
-
outputData = JSON.stringify(krd, null, 2);
|
|
363
|
-
} else {
|
|
364
|
-
throw new Error(`Unsupported output format: ${outputFormat}`);
|
|
1209
|
+
lines.push("");
|
|
365
1210
|
}
|
|
366
|
-
|
|
1211
|
+
return lines.join("\n");
|
|
367
1212
|
};
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
1229
|
+
var diffCommand = async (options) => {
|
|
1230
|
+
const validatedOptions = diffOptionsSchema.parse(options);
|
|
370
1231
|
const logger = await createLogger({
|
|
371
1232
|
type: validatedOptions.logFormat,
|
|
372
1233
|
level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
|
|
373
1234
|
quiet: validatedOptions.quiet
|
|
374
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
|
+
}
|
|
375
1244
|
try {
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
});
|
|
407
|
-
const isTTY2 = process.stdout.isTTY && !validatedOptions.quiet && !validatedOptions.json;
|
|
408
|
-
const spinner = isTTY2 ? ora("Processing batch conversion...").start() : null;
|
|
409
|
-
const results = [];
|
|
410
|
-
for (const [index, file] of files.entries()) {
|
|
411
|
-
const fileNum = index + 1;
|
|
412
|
-
const fileName = basename(file);
|
|
413
|
-
if (spinner) {
|
|
414
|
-
spinner.text = `Converting ${fileNum}/${files.length}: ${fileName}`;
|
|
415
|
-
} else {
|
|
416
|
-
logger.info(`Converting ${fileNum}/${files.length}: ${fileName}`);
|
|
417
|
-
}
|
|
418
|
-
try {
|
|
419
|
-
const inputFormat = validatedOptions.inputFormat || detectFormat(file);
|
|
420
|
-
if (!inputFormat) {
|
|
421
|
-
throw new Error(`Unable to detect format for file: ${file}`);
|
|
422
|
-
}
|
|
423
|
-
const outputFileName = fileName.replace(
|
|
424
|
-
/\.(fit|krd|tcx|zwo)$/i,
|
|
425
|
-
`.${outputFormat}`
|
|
426
|
-
);
|
|
427
|
-
const outputFile = join(validatedOptions.outputDir, outputFileName);
|
|
428
|
-
await convertSingleFile(
|
|
429
|
-
file,
|
|
430
|
-
outputFile,
|
|
431
|
-
inputFormat,
|
|
432
|
-
outputFormat,
|
|
433
|
-
providers
|
|
434
|
-
);
|
|
435
|
-
results.push({
|
|
1245
|
+
const providers = createAllProviders2(logger);
|
|
1246
|
+
logger.debug("Loading files for comparison", {
|
|
1247
|
+
file1: validatedOptions.file1,
|
|
1248
|
+
file2: validatedOptions.file2
|
|
1249
|
+
});
|
|
1250
|
+
const krd1 = await loadFileAsKrd(
|
|
1251
|
+
validatedOptions.file1,
|
|
1252
|
+
validatedOptions.format1,
|
|
1253
|
+
providers
|
|
1254
|
+
);
|
|
1255
|
+
const krd2 = await loadFileAsKrd(
|
|
1256
|
+
validatedOptions.file2,
|
|
1257
|
+
validatedOptions.format2,
|
|
1258
|
+
providers
|
|
1259
|
+
);
|
|
1260
|
+
logger.debug("Files loaded successfully");
|
|
1261
|
+
const metadataDiff = compareMetadata(krd1, krd2);
|
|
1262
|
+
const stepsDiff = compareSteps(krd1, krd2);
|
|
1263
|
+
const extensionsDiff = compareExtensions(krd1, krd2);
|
|
1264
|
+
const identical = metadataDiff.length === 0 && stepsDiff.differences.length === 0 && extensionsDiff.differences.length === 0;
|
|
1265
|
+
const result = {
|
|
1266
|
+
identical,
|
|
1267
|
+
metadataDiff: metadataDiff.length > 0 ? metadataDiff : void 0,
|
|
1268
|
+
stepsDiff: stepsDiff.differences.length > 0 ? stepsDiff : void 0,
|
|
1269
|
+
extensionsDiff: extensionsDiff.differences.length > 0 ? extensionsDiff : void 0
|
|
1270
|
+
};
|
|
1271
|
+
if (validatedOptions.json) {
|
|
1272
|
+
console.log(
|
|
1273
|
+
JSON.stringify(
|
|
1274
|
+
{
|
|
436
1275
|
success: true,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
});
|
|
446
|
-
logger.error(`Failed to convert ${file}`, { error });
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
const totalTime = Date.now() - startTime;
|
|
450
|
-
const successful = results.filter((r) => r.success);
|
|
451
|
-
const failed = results.filter((r) => !r.success);
|
|
452
|
-
if (spinner) {
|
|
453
|
-
spinner.stop();
|
|
454
|
-
}
|
|
455
|
-
if (!validatedOptions.json) {
|
|
456
|
-
console.log("\nBatch conversion complete:");
|
|
457
|
-
console.log(
|
|
458
|
-
chalk2.green(` \u2713 Successful: ${successful.length}/${files.length}`)
|
|
459
|
-
);
|
|
460
|
-
if (failed.length > 0) {
|
|
461
|
-
console.log(
|
|
462
|
-
chalk2.red(` \u2717 Failed: ${failed.length}/${files.length}`)
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
console.log(` Total time: ${(totalTime / 1e3).toFixed(2)}s`);
|
|
466
|
-
if (failed.length > 0) {
|
|
467
|
-
console.log(chalk2.red("\nFailed conversions:"));
|
|
468
|
-
for (const result of failed) {
|
|
469
|
-
console.log(chalk2.red(` ${result.inputFile}: ${result.error}`));
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
console.log(
|
|
474
|
-
JSON.stringify(
|
|
475
|
-
{
|
|
476
|
-
success: failed.length === 0,
|
|
477
|
-
total: files.length,
|
|
478
|
-
successful: successful.length,
|
|
479
|
-
failed: failed.length,
|
|
480
|
-
totalTime,
|
|
481
|
-
results
|
|
482
|
-
},
|
|
483
|
-
null,
|
|
484
|
-
2
|
|
485
|
-
)
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
if (failed.length > 0) {
|
|
489
|
-
process.exit(1);
|
|
490
|
-
}
|
|
1276
|
+
file1: validatedOptions.file1,
|
|
1277
|
+
file2: validatedOptions.file2,
|
|
1278
|
+
...result
|
|
1279
|
+
},
|
|
1280
|
+
null,
|
|
1281
|
+
2
|
|
1282
|
+
)
|
|
1283
|
+
);
|
|
491
1284
|
} else {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
`Unable to detect input format from file: ${validatedOptions.input}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
496
|
-
);
|
|
497
|
-
error.name = "InvalidArgumentError";
|
|
498
|
-
throw error;
|
|
499
|
-
}
|
|
500
|
-
if (!validatedOptions.output) {
|
|
501
|
-
const error = new Error("Output file is required");
|
|
502
|
-
error.name = "InvalidArgumentError";
|
|
503
|
-
throw error;
|
|
504
|
-
}
|
|
505
|
-
const outputFormat = validatedOptions.outputFormat || detectFormat(validatedOptions.output);
|
|
506
|
-
if (!outputFormat) {
|
|
507
|
-
const error = new Error(
|
|
508
|
-
`Unable to detect output format from file: ${validatedOptions.output}. Supported formats: .fit, .krd, .tcx, .zwo`
|
|
509
|
-
);
|
|
510
|
-
error.name = "InvalidArgumentError";
|
|
511
|
-
throw error;
|
|
512
|
-
}
|
|
513
|
-
const providers = createDefaultProviders(logger);
|
|
514
|
-
logger.debug("Convert command initialized", {
|
|
515
|
-
input: validatedOptions.input,
|
|
516
|
-
output: validatedOptions.output,
|
|
517
|
-
inputFormat,
|
|
518
|
-
outputFormat
|
|
519
|
-
});
|
|
520
|
-
const isTTY2 = process.stdout.isTTY && !validatedOptions.quiet && !validatedOptions.json;
|
|
521
|
-
const spinner = isTTY2 ? ora("Converting...").start() : null;
|
|
522
|
-
try {
|
|
523
|
-
await convertSingleFile(
|
|
524
|
-
validatedOptions.input,
|
|
525
|
-
validatedOptions.output,
|
|
526
|
-
inputFormat,
|
|
527
|
-
outputFormat,
|
|
528
|
-
providers
|
|
529
|
-
);
|
|
530
|
-
if (validatedOptions.json) {
|
|
531
|
-
console.log(
|
|
532
|
-
JSON.stringify(
|
|
533
|
-
{
|
|
534
|
-
success: true,
|
|
535
|
-
inputFile: validatedOptions.input,
|
|
536
|
-
outputFile: validatedOptions.output,
|
|
537
|
-
inputFormat,
|
|
538
|
-
outputFormat
|
|
539
|
-
},
|
|
540
|
-
null,
|
|
541
|
-
2
|
|
542
|
-
)
|
|
543
|
-
);
|
|
544
|
-
} else if (spinner) {
|
|
545
|
-
spinner.succeed(
|
|
546
|
-
`Conversion complete: ${validatedOptions.input} \u2192 ${validatedOptions.output}`
|
|
547
|
-
);
|
|
548
|
-
} else {
|
|
549
|
-
logger.info("Conversion complete", {
|
|
550
|
-
input: validatedOptions.input,
|
|
551
|
-
output: validatedOptions.output
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
} catch (error) {
|
|
555
|
-
if (spinner) {
|
|
556
|
-
spinner.fail("Conversion failed");
|
|
557
|
-
}
|
|
558
|
-
throw error;
|
|
559
|
-
}
|
|
1285
|
+
console.log(
|
|
1286
|
+
formatDiffPretty(result, validatedOptions.file1, validatedOptions.file2)
|
|
1287
|
+
);
|
|
560
1288
|
}
|
|
1289
|
+
return identical ? ExitCode.SUCCESS : ExitCode.DIFFERENCES_FOUND;
|
|
561
1290
|
} catch (error) {
|
|
562
|
-
logger.error("
|
|
1291
|
+
logger.error("Diff command failed", { error });
|
|
563
1292
|
const formattedError = formatError(error, {
|
|
564
1293
|
json: validatedOptions.json
|
|
565
1294
|
});
|
|
@@ -568,53 +1297,61 @@ var convertCommand = async (options) => {
|
|
|
568
1297
|
} else {
|
|
569
1298
|
console.error(formattedError);
|
|
570
1299
|
}
|
|
571
|
-
|
|
572
|
-
if (error instanceof Error) {
|
|
573
|
-
if (error.message.includes("File not found")) {
|
|
574
|
-
exitCode = 1;
|
|
575
|
-
} else if (error.message.includes("Permission denied")) {
|
|
576
|
-
exitCode = 1;
|
|
577
|
-
} else if (error instanceof FitParsingError2) {
|
|
578
|
-
exitCode = 1;
|
|
579
|
-
} else if (error instanceof KrdValidationError2) {
|
|
580
|
-
exitCode = 1;
|
|
581
|
-
} else if (error instanceof ToleranceExceededError2) {
|
|
582
|
-
exitCode = 1;
|
|
583
|
-
} else if (error.name === "InvalidArgumentError") {
|
|
584
|
-
exitCode = 1;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
process.exit(exitCode);
|
|
1300
|
+
return ExitCode.UNKNOWN_ERROR;
|
|
588
1301
|
}
|
|
589
1302
|
};
|
|
590
1303
|
|
|
591
1304
|
// src/commands/validate.ts
|
|
1305
|
+
init_esm_shims();
|
|
592
1306
|
import {
|
|
593
|
-
createDefaultProviders as createDefaultProviders2,
|
|
594
1307
|
createToleranceChecker,
|
|
595
1308
|
toleranceConfigSchema,
|
|
596
1309
|
validateRoundTrip
|
|
597
1310
|
} from "@kaiord/core";
|
|
1311
|
+
import { createAllProviders as createAllProviders3 } from "@kaiord/all";
|
|
598
1312
|
import { readFile as fsReadFile2 } from "fs/promises";
|
|
599
|
-
import
|
|
600
|
-
import { z as
|
|
601
|
-
var validateOptionsSchema =
|
|
602
|
-
input:
|
|
603
|
-
toleranceConfig:
|
|
604
|
-
verbose:
|
|
605
|
-
quiet:
|
|
606
|
-
json:
|
|
607
|
-
logFormat:
|
|
1313
|
+
import ora3 from "ora";
|
|
1314
|
+
import { z as z5 } from "zod";
|
|
1315
|
+
var validateOptionsSchema = z5.object({
|
|
1316
|
+
input: z5.string(),
|
|
1317
|
+
toleranceConfig: z5.string().optional(),
|
|
1318
|
+
verbose: z5.boolean().optional(),
|
|
1319
|
+
quiet: z5.boolean().optional(),
|
|
1320
|
+
json: z5.boolean().optional(),
|
|
1321
|
+
logFormat: z5.enum(["pretty", "json"]).optional()
|
|
608
1322
|
});
|
|
609
1323
|
var validateCommand = async (options) => {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const logger = await createLogger({
|
|
613
|
-
type: loggerType,
|
|
614
|
-
level: opts.verbose ? "debug" : opts.quiet ? "error" : "info",
|
|
615
|
-
quiet: opts.quiet
|
|
616
|
-
});
|
|
1324
|
+
let logger;
|
|
1325
|
+
let spinner = null;
|
|
617
1326
|
try {
|
|
1327
|
+
const configResult = await loadConfigWithMetadata();
|
|
1328
|
+
const { config } = configResult;
|
|
1329
|
+
const mergedOptions = mergeWithConfig(
|
|
1330
|
+
options,
|
|
1331
|
+
config
|
|
1332
|
+
);
|
|
1333
|
+
const optionsWithDefaults = {
|
|
1334
|
+
...mergedOptions,
|
|
1335
|
+
toleranceConfig: mergedOptions.toleranceConfig || config.defaultToleranceConfig,
|
|
1336
|
+
verbose: mergedOptions.verbose ?? config.verbose,
|
|
1337
|
+
quiet: mergedOptions.quiet ?? config.quiet,
|
|
1338
|
+
json: mergedOptions.json ?? config.json,
|
|
1339
|
+
logFormat: mergedOptions.logFormat || config.logFormat
|
|
1340
|
+
};
|
|
1341
|
+
const opts = validateOptionsSchema.parse(optionsWithDefaults);
|
|
1342
|
+
const loggerType = opts.logFormat === "json" ? "structured" : opts.logFormat;
|
|
1343
|
+
logger = await createLogger({
|
|
1344
|
+
type: loggerType,
|
|
1345
|
+
level: opts.verbose ? "debug" : opts.quiet ? "error" : "info",
|
|
1346
|
+
quiet: opts.quiet
|
|
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
|
+
}
|
|
618
1355
|
const format = detectFormat(opts.input);
|
|
619
1356
|
if (!format) {
|
|
620
1357
|
throw new Error(`Unable to detect format from file: ${opts.input}`);
|
|
@@ -624,24 +1361,24 @@ var validateCommand = async (options) => {
|
|
|
624
1361
|
`Validation currently only supports FIT files. Got: ${format}`
|
|
625
1362
|
);
|
|
626
1363
|
}
|
|
627
|
-
logger
|
|
628
|
-
const inputData = await
|
|
1364
|
+
logger?.debug("Reading input file", { path: opts.input, format });
|
|
1365
|
+
const inputData = await readFile2(opts.input, format);
|
|
629
1366
|
if (typeof inputData === "string") {
|
|
630
1367
|
throw new Error("Expected binary data for FIT file");
|
|
631
1368
|
}
|
|
632
1369
|
let toleranceConfig;
|
|
633
1370
|
if (opts.toleranceConfig) {
|
|
634
|
-
logger
|
|
1371
|
+
logger?.debug("Loading custom tolerance config", {
|
|
635
1372
|
path: opts.toleranceConfig
|
|
636
1373
|
});
|
|
637
1374
|
const configContent = await fsReadFile2(opts.toleranceConfig, "utf-8");
|
|
638
1375
|
const configJson = JSON.parse(configContent);
|
|
639
1376
|
toleranceConfig = toleranceConfigSchema.parse(configJson);
|
|
640
|
-
logger
|
|
1377
|
+
logger?.debug("Custom tolerance config loaded", {
|
|
641
1378
|
config: toleranceConfig
|
|
642
1379
|
});
|
|
643
1380
|
}
|
|
644
|
-
const providers =
|
|
1381
|
+
const providers = createAllProviders3(logger);
|
|
645
1382
|
const toleranceChecker = toleranceConfig ? createToleranceChecker(toleranceConfig) : providers.toleranceChecker;
|
|
646
1383
|
const roundTripValidator = validateRoundTrip(
|
|
647
1384
|
providers.fitReader,
|
|
@@ -649,8 +1386,8 @@ var validateCommand = async (options) => {
|
|
|
649
1386
|
toleranceChecker,
|
|
650
1387
|
logger
|
|
651
1388
|
);
|
|
652
|
-
|
|
653
|
-
logger
|
|
1389
|
+
spinner = opts.quiet || opts.json ? null : ora3("Validating round-trip conversion...").start();
|
|
1390
|
+
logger?.info("Starting round-trip validation", { file: opts.input });
|
|
654
1391
|
const violations = await roundTripValidator.validateFitToKrdToFit({
|
|
655
1392
|
originalFit: inputData
|
|
656
1393
|
});
|
|
@@ -664,7 +1401,7 @@ var validateCommand = async (options) => {
|
|
|
664
1401
|
}
|
|
665
1402
|
}
|
|
666
1403
|
if (violations.length === 0) {
|
|
667
|
-
logger
|
|
1404
|
+
logger?.info("Round-trip validation passed");
|
|
668
1405
|
if (opts.json) {
|
|
669
1406
|
console.log(
|
|
670
1407
|
JSON.stringify(
|
|
@@ -679,11 +1416,11 @@ var validateCommand = async (options) => {
|
|
|
679
1416
|
)
|
|
680
1417
|
);
|
|
681
1418
|
} else if (!opts.quiet) {
|
|
682
|
-
console.log("
|
|
1419
|
+
console.log("Round-trip validation passed");
|
|
683
1420
|
}
|
|
684
|
-
|
|
1421
|
+
return ExitCode.SUCCESS;
|
|
685
1422
|
} else {
|
|
686
|
-
logger
|
|
1423
|
+
logger?.warn("Round-trip validation failed", {
|
|
687
1424
|
violationCount: violations.length
|
|
688
1425
|
});
|
|
689
1426
|
if (opts.json) {
|
|
@@ -700,19 +1437,27 @@ var validateCommand = async (options) => {
|
|
|
700
1437
|
)
|
|
701
1438
|
);
|
|
702
1439
|
} else {
|
|
703
|
-
console.error("
|
|
1440
|
+
console.error("Round-trip validation failed\n");
|
|
704
1441
|
console.error(formatToleranceViolations(violations));
|
|
705
1442
|
}
|
|
706
|
-
|
|
1443
|
+
return ExitCode.TOLERANCE_EXCEEDED;
|
|
707
1444
|
}
|
|
708
1445
|
} catch (error) {
|
|
709
|
-
logger
|
|
710
|
-
|
|
1446
|
+
logger?.error("Validation failed", { error });
|
|
1447
|
+
let jsonOutput = false;
|
|
1448
|
+
try {
|
|
1449
|
+
const opts = validateOptionsSchema.parse(options);
|
|
1450
|
+
jsonOutput = opts.json || false;
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
if (jsonOutput) {
|
|
1454
|
+
const errorObj = formatError(error, { json: true });
|
|
1455
|
+
const errorData = typeof errorObj === "string" ? JSON.parse(errorObj) : errorObj;
|
|
711
1456
|
console.log(
|
|
712
1457
|
JSON.stringify(
|
|
713
1458
|
{
|
|
714
1459
|
success: false,
|
|
715
|
-
error:
|
|
1460
|
+
error: errorData
|
|
716
1461
|
},
|
|
717
1462
|
null,
|
|
718
1463
|
2
|
|
@@ -721,43 +1466,28 @@ var validateCommand = async (options) => {
|
|
|
721
1466
|
} else {
|
|
722
1467
|
console.error(formatError(error, { json: false }));
|
|
723
1468
|
}
|
|
724
|
-
|
|
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;
|
|
1478
|
+
} finally {
|
|
1479
|
+
spinner?.stop();
|
|
725
1480
|
}
|
|
726
1481
|
};
|
|
727
1482
|
|
|
728
1483
|
// src/bin/kaiord.ts
|
|
729
|
-
var __filename2 =
|
|
1484
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
730
1485
|
var __dirname2 = dirname2(__filename2);
|
|
731
|
-
var packageJsonPath = __dirname2.includes("/dist") ?
|
|
1486
|
+
var packageJsonPath = __dirname2.includes("/dist") ? join3(__dirname2, "../../package.json") : join3(__dirname2, "../../package.json");
|
|
732
1487
|
var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
733
1488
|
var version = packageJson.version;
|
|
734
|
-
var showKiroEasterEgg = () => {
|
|
735
|
-
console.log(
|
|
736
|
-
chalk3.cyan(`
|
|
737
|
-
\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
|
|
738
|
-
\u2551 \u2551
|
|
739
|
-
\u2551 \u{1F47B} Built with Kiro AI during Kiroween Hackathon \u{1F47B} \u2551
|
|
740
|
-
\u2551 \u2551
|
|
741
|
-
\u2551 Kiro helped design, architect, and implement this \u2551
|
|
742
|
-
\u2551 entire CLI tool through spec-driven development. \u2551
|
|
743
|
-
\u2551 \u2551
|
|
744
|
-
\u2551 Learn more about Kiroween: \u2551
|
|
745
|
-
\u2551 \u{1F449} http://kiroween.devpost.com/ \u2551
|
|
746
|
-
\u2551 \u2551
|
|
747
|
-
\u2551 Kiro: Your AI pair programmer for building better \u2551
|
|
748
|
-
\u2551 software, faster. \u{1F680} \u2551
|
|
749
|
-
\u2551 \u2551
|
|
750
|
-
\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
|
|
751
|
-
`)
|
|
752
|
-
);
|
|
753
|
-
process.exit(0);
|
|
754
|
-
};
|
|
755
1489
|
var main = async () => {
|
|
756
1490
|
try {
|
|
757
|
-
const args = process.argv.slice(2);
|
|
758
|
-
if (args.includes("--kiro") || args.includes("--kiroween")) {
|
|
759
|
-
showKiroEasterEgg();
|
|
760
|
-
}
|
|
761
1491
|
await yargs(hideBin(process.argv)).scriptName("kaiord").usage("$0 <command> [options]").command(
|
|
762
1492
|
"convert",
|
|
763
1493
|
"Convert workout files between formats",
|
|
@@ -794,7 +1524,7 @@ var main = async () => {
|
|
|
794
1524
|
);
|
|
795
1525
|
},
|
|
796
1526
|
async (argv) => {
|
|
797
|
-
await convertCommand({
|
|
1527
|
+
const exitCode = await convertCommand({
|
|
798
1528
|
input: argv.input,
|
|
799
1529
|
output: argv.output,
|
|
800
1530
|
outputDir: argv.outputDir,
|
|
@@ -805,10 +1535,13 @@ var main = async () => {
|
|
|
805
1535
|
json: argv.json,
|
|
806
1536
|
logFormat: argv.logFormat
|
|
807
1537
|
});
|
|
1538
|
+
if (exitCode !== 0) {
|
|
1539
|
+
process.exit(exitCode);
|
|
1540
|
+
}
|
|
808
1541
|
}
|
|
809
1542
|
).command(
|
|
810
1543
|
"validate",
|
|
811
|
-
"Validate round-trip conversion of
|
|
1544
|
+
"Validate round-trip conversion of FIT files (FIT only)",
|
|
812
1545
|
(yargs2) => {
|
|
813
1546
|
return yargs2.option("input", {
|
|
814
1547
|
alias: "i",
|
|
@@ -827,7 +1560,7 @@ var main = async () => {
|
|
|
827
1560
|
);
|
|
828
1561
|
},
|
|
829
1562
|
async (argv) => {
|
|
830
|
-
await validateCommand({
|
|
1563
|
+
const exitCode = await validateCommand({
|
|
831
1564
|
input: argv.input,
|
|
832
1565
|
toleranceConfig: argv.toleranceConfig,
|
|
833
1566
|
verbose: argv.verbose,
|
|
@@ -835,6 +1568,60 @@ var main = async () => {
|
|
|
835
1568
|
json: argv.json,
|
|
836
1569
|
logFormat: argv.logFormat
|
|
837
1570
|
});
|
|
1571
|
+
if (exitCode !== ExitCode.SUCCESS) {
|
|
1572
|
+
process.exit(exitCode);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
).command(
|
|
1576
|
+
"diff",
|
|
1577
|
+
"Compare two workout files and show differences",
|
|
1578
|
+
(yargs2) => {
|
|
1579
|
+
return yargs2.option("file1", {
|
|
1580
|
+
alias: "1",
|
|
1581
|
+
type: "string",
|
|
1582
|
+
description: "First file to compare",
|
|
1583
|
+
demandOption: true
|
|
1584
|
+
}).option("file2", {
|
|
1585
|
+
alias: "2",
|
|
1586
|
+
type: "string",
|
|
1587
|
+
description: "Second file to compare",
|
|
1588
|
+
demandOption: true
|
|
1589
|
+
}).option("format1", {
|
|
1590
|
+
type: "string",
|
|
1591
|
+
choices: ["fit", "krd", "tcx", "zwo"],
|
|
1592
|
+
description: "Override format detection for first file"
|
|
1593
|
+
}).option("format2", {
|
|
1594
|
+
type: "string",
|
|
1595
|
+
choices: ["fit", "krd", "tcx", "zwo"],
|
|
1596
|
+
description: "Override format detection for second file"
|
|
1597
|
+
}).example(
|
|
1598
|
+
"$0 diff --file1 workout1.fit --file2 workout2.fit",
|
|
1599
|
+
"Compare two FIT files"
|
|
1600
|
+
).example(
|
|
1601
|
+
"$0 diff --file1 workout.fit --file2 workout.krd",
|
|
1602
|
+
"Compare FIT and KRD files"
|
|
1603
|
+
).example(
|
|
1604
|
+
"$0 diff --file1 workout1.krd --file2 workout2.krd --json",
|
|
1605
|
+
"Compare with JSON output"
|
|
1606
|
+
);
|
|
1607
|
+
},
|
|
1608
|
+
async (argv) => {
|
|
1609
|
+
const exitCode = await diffCommand({
|
|
1610
|
+
file1: argv.file1,
|
|
1611
|
+
file2: argv.file2,
|
|
1612
|
+
format1: argv.format1,
|
|
1613
|
+
format2: argv.format2,
|
|
1614
|
+
verbose: argv.verbose,
|
|
1615
|
+
quiet: argv.quiet,
|
|
1616
|
+
json: argv.json,
|
|
1617
|
+
logFormat: argv.logFormat
|
|
1618
|
+
});
|
|
1619
|
+
if (exitCode !== ExitCode.SUCCESS && exitCode !== ExitCode.DIFFERENCES_FOUND) {
|
|
1620
|
+
process.exit(exitCode);
|
|
1621
|
+
}
|
|
1622
|
+
if (exitCode === ExitCode.DIFFERENCES_FOUND) {
|
|
1623
|
+
process.exit(exitCode);
|
|
1624
|
+
}
|
|
838
1625
|
}
|
|
839
1626
|
).option("verbose", {
|
|
840
1627
|
type: "boolean",
|
|
@@ -860,25 +1647,27 @@ var main = async () => {
|
|
|
860
1647
|
if (error && typeof error === "object" && "name" in error) {
|
|
861
1648
|
const errorName = error.name;
|
|
862
1649
|
if (errorName === "FitParsingError") {
|
|
863
|
-
process.exit(
|
|
1650
|
+
process.exit(ExitCode.PARSING_ERROR);
|
|
864
1651
|
} else if (errorName === "KrdValidationError") {
|
|
865
|
-
process.exit(
|
|
1652
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
866
1653
|
} else if (errorName === "ToleranceExceededError") {
|
|
867
|
-
process.exit(
|
|
1654
|
+
process.exit(ExitCode.TOLERANCE_EXCEEDED);
|
|
1655
|
+
} else if (errorName === "InvalidArgumentError") {
|
|
1656
|
+
process.exit(ExitCode.INVALID_ARGUMENT);
|
|
868
1657
|
}
|
|
869
1658
|
}
|
|
870
|
-
process.exit(
|
|
1659
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
871
1660
|
}
|
|
872
1661
|
};
|
|
873
1662
|
process.on("unhandledRejection", (reason) => {
|
|
874
1663
|
console.error("Unhandled rejection:", reason);
|
|
875
|
-
process.exit(
|
|
1664
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
876
1665
|
});
|
|
877
1666
|
process.on("uncaughtException", (error) => {
|
|
878
1667
|
console.error("Uncaught exception:", error);
|
|
879
|
-
process.exit(
|
|
1668
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
880
1669
|
});
|
|
881
1670
|
main().catch((error) => {
|
|
882
1671
|
console.error("Fatal error:", error);
|
|
883
|
-
process.exit(
|
|
1672
|
+
process.exit(ExitCode.UNKNOWN_ERROR);
|
|
884
1673
|
});
|