@kaiord/cli 0.1.1 → 1.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/README.md +96 -63
- package/dist/bin/kaiord.js +661 -99
- package/dist/package.json +3 -0
- package/package.json +9 -8
- package/dist/chunk-TI3WVGXE.js +0 -10
- package/dist/pretty-logger-P2OWMOGW.js +0 -58
- package/dist/structured-logger-HMEQGEES.js +0 -37
package/README.md
CHANGED
|
@@ -87,6 +87,62 @@ Example `tolerance.json`:
|
|
|
87
87
|
}
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
## Configuration File
|
|
91
|
+
|
|
92
|
+
You can create a `.kaiordrc.json` file to set default options. The CLI will search for this file in:
|
|
93
|
+
|
|
94
|
+
1. Current working directory
|
|
95
|
+
2. User home directory (`~/.kaiordrc.json`)
|
|
96
|
+
|
|
97
|
+
CLI options always take precedence over config file defaults.
|
|
98
|
+
|
|
99
|
+
### Example Configuration
|
|
100
|
+
|
|
101
|
+
Create `.kaiordrc.json` in your project or home directory:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"defaultInputFormat": "fit",
|
|
106
|
+
"defaultOutputFormat": "krd",
|
|
107
|
+
"defaultOutputDir": "./converted",
|
|
108
|
+
"defaultToleranceConfig": "./tolerance.json",
|
|
109
|
+
"verbose": false,
|
|
110
|
+
"quiet": false,
|
|
111
|
+
"json": false,
|
|
112
|
+
"logFormat": "pretty"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Configuration Options
|
|
117
|
+
|
|
118
|
+
- **defaultInputFormat**: Default input format (`fit`, `krd`, `tcx`, `zwo`)
|
|
119
|
+
- **defaultOutputFormat**: Default output format (`fit`, `krd`, `tcx`, `zwo`)
|
|
120
|
+
- **defaultOutputDir**: Default output directory for batch conversions
|
|
121
|
+
- **defaultToleranceConfig**: Path to default tolerance configuration file
|
|
122
|
+
- **verbose**: Enable verbose logging by default
|
|
123
|
+
- **quiet**: Enable quiet mode by default
|
|
124
|
+
- **json**: Enable JSON output by default
|
|
125
|
+
- **logFormat**: Default log format (`pretty` or `structured`)
|
|
126
|
+
|
|
127
|
+
### Usage with Config File
|
|
128
|
+
|
|
129
|
+
With a config file, you can simplify your commands:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Without config file
|
|
133
|
+
kaiord convert --input workout.fit --output workout.krd --output-format krd
|
|
134
|
+
|
|
135
|
+
# With config file (defaultOutputFormat: "krd")
|
|
136
|
+
kaiord convert --input workout.fit --output workout.krd
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
CLI options override config defaults:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Config has verbose: true, but --quiet overrides it
|
|
143
|
+
kaiord convert --input workout.fit --output workout.krd --quiet
|
|
144
|
+
```
|
|
145
|
+
|
|
90
146
|
## Global Options
|
|
91
147
|
|
|
92
148
|
### Verbosity Control
|
|
@@ -112,14 +168,6 @@ kaiord convert --input workout.fit --output workout.krd --log-format pretty
|
|
|
112
168
|
kaiord convert --input workout.fit --output workout.krd --log-format json
|
|
113
169
|
```
|
|
114
170
|
|
|
115
|
-
## Environment Variables
|
|
116
|
-
|
|
117
|
-
The CLI automatically detects the environment and adjusts its behavior:
|
|
118
|
-
|
|
119
|
-
- **CI=true**: Enables structured JSON logging
|
|
120
|
-
- **NODE_ENV=production**: Enables structured JSON logging
|
|
121
|
-
- **TTY detection**: Automatically disables colors and spinners in non-interactive environments
|
|
122
|
-
|
|
123
171
|
## Supported Formats
|
|
124
172
|
|
|
125
173
|
- **FIT** (.fit) - Garmin's binary workout file format
|
|
@@ -127,47 +175,63 @@ The CLI automatically detects the environment and adjusts its behavior:
|
|
|
127
175
|
- **TCX** (.tcx) - Training Center XML format
|
|
128
176
|
- **ZWO** (.zwo) - Zwift workout XML format
|
|
129
177
|
|
|
130
|
-
|
|
178
|
+
### Plugin System (Future Enhancement)
|
|
131
179
|
|
|
132
|
-
-
|
|
133
|
-
- **1**: Error (invalid arguments, file not found, parsing error, validation error)
|
|
180
|
+
The CLI is designed with a plugin architecture that will allow third-party developers to add support for custom workout file formats without modifying the core library. See [Plugin Architecture Documentation](./docs/plugin-architecture.md) for details on the design.
|
|
134
181
|
|
|
135
|
-
|
|
182
|
+
**Planned Features:**
|
|
136
183
|
|
|
137
|
-
|
|
184
|
+
- Dynamic plugin discovery and loading
|
|
185
|
+
- Type-safe plugin interface
|
|
186
|
+
- Automatic format detection for plugin formats
|
|
187
|
+
- Plugin configuration via `.kaiordrc.json`
|
|
188
|
+
- Plugin management commands (`kaiord plugins list`, `install`, etc.)
|
|
138
189
|
|
|
139
|
-
|
|
190
|
+
**Example Plugin Usage (Future):**
|
|
140
191
|
|
|
141
192
|
```bash
|
|
142
|
-
|
|
143
|
-
|
|
193
|
+
# Install a plugin
|
|
194
|
+
npm install -g @kaiord/plugin-gpx
|
|
195
|
+
|
|
196
|
+
# Use plugin format
|
|
197
|
+
kaiord convert --input workout.gpx --output workout.krd
|
|
144
198
|
```
|
|
145
199
|
|
|
146
|
-
|
|
200
|
+
See [Example GPX Plugin](./docs/example-plugin-gpx.md) for a complete plugin implementation example.
|
|
147
201
|
|
|
148
|
-
|
|
202
|
+
## Exit Codes
|
|
149
203
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
kaiord convert --input workout.fit --output workout.krd
|
|
153
|
-
```
|
|
204
|
+
- **0**: Success
|
|
205
|
+
- **1**: Error (invalid arguments, file not found, parsing error, validation error)
|
|
154
206
|
|
|
155
|
-
|
|
207
|
+
## Documentation
|
|
156
208
|
|
|
157
|
-
|
|
209
|
+
### Main Documentation
|
|
158
210
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
211
|
+
- **[Getting Started](../../docs/getting-started.md)** - Installation and basic usage
|
|
212
|
+
- **[KRD Format Specification](../../docs/krd-format.md)** - Complete format documentation
|
|
213
|
+
- **[Deployment Guide](../../docs/deployment.md)** - CI/CD and npm publishing
|
|
214
|
+
|
|
215
|
+
### Package-Specific Documentation
|
|
162
216
|
|
|
163
|
-
|
|
217
|
+
- **[npm Publish Verification](./docs/npm-publish-verification.md)** - Publishing checklist and verification
|
|
164
218
|
|
|
165
|
-
|
|
219
|
+
## Development
|
|
166
220
|
|
|
167
221
|
```bash
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
222
|
+
# Install dependencies
|
|
223
|
+
pnpm install
|
|
224
|
+
|
|
225
|
+
# Build the CLI
|
|
226
|
+
pnpm build
|
|
227
|
+
|
|
228
|
+
# Run in development mode
|
|
229
|
+
pnpm dev -- convert --input workout.fit --output workout.krd
|
|
230
|
+
|
|
231
|
+
# Link for local testing
|
|
232
|
+
npm link
|
|
233
|
+
kaiord --version
|
|
234
|
+
npm unlink -g
|
|
171
235
|
```
|
|
172
236
|
|
|
173
237
|
## Testing
|
|
@@ -191,37 +255,6 @@ pnpm test:smoke
|
|
|
191
255
|
pnpm test:watch
|
|
192
256
|
```
|
|
193
257
|
|
|
194
|
-
## Development
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
# Install dependencies
|
|
198
|
-
pnpm install
|
|
199
|
-
|
|
200
|
-
# Build the CLI
|
|
201
|
-
pnpm build
|
|
202
|
-
|
|
203
|
-
# Run in development mode
|
|
204
|
-
pnpm dev -- convert --input workout.fit --output workout.krd
|
|
205
|
-
|
|
206
|
-
# Link for local testing
|
|
207
|
-
npm link
|
|
208
|
-
kaiord --version
|
|
209
|
-
npm unlink -g
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## Documentation
|
|
213
|
-
|
|
214
|
-
For more information about the Kaiord project and the KRD format, see:
|
|
215
|
-
|
|
216
|
-
- [Kaiord Core Library](https://github.com/your-org/kaiord/tree/main/packages/core)
|
|
217
|
-
- [KRD Format Specification](https://github.com/your-org/kaiord/blob/main/docs/KRD_FORMAT.md)
|
|
218
|
-
- [Contributing Guide](https://github.com/your-org/kaiord/blob/main/CONTRIBUTING.md)
|
|
219
|
-
|
|
220
258
|
## License
|
|
221
259
|
|
|
222
260
|
MIT - See [LICENSE](../../LICENSE) file for details.
|
|
223
|
-
|
|
224
|
-
## Support
|
|
225
|
-
|
|
226
|
-
- Report issues: [GitHub Issues](https://github.com/your-org/kaiord/issues)
|
|
227
|
-
- Ask questions: [GitHub Discussions](https://github.com/your-org/kaiord/discussions)
|
package/dist/bin/kaiord.js
CHANGED
|
@@ -1,29 +1,232 @@
|
|
|
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 chalk2 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 ? chalk2.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 ? chalk2.gray(formatted) : formatted);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
info: (message, context) => {
|
|
118
|
+
if (shouldLog("info")) {
|
|
119
|
+
const formatted = `\u2139 ${message}${formatContext(context)}`;
|
|
120
|
+
console.log(useColors ? chalk2.blue(formatted) : formatted);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
warn: (message, context) => {
|
|
124
|
+
if (shouldLog("warn")) {
|
|
125
|
+
const formatted = `\u26A0 ${message}${formatContext(context)}`;
|
|
126
|
+
console.warn(useColors ? chalk2.yellow(formatted) : formatted);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
error: (message, context) => {
|
|
130
|
+
if (shouldLog("error")) {
|
|
131
|
+
const formatted = `\u2716 ${message}${formatContext(context)}`;
|
|
132
|
+
console.error(useColors ? chalk2.red(formatted) : formatted);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
});
|
|
5
139
|
|
|
6
140
|
// src/bin/kaiord.ts
|
|
7
|
-
|
|
141
|
+
init_esm_shims();
|
|
142
|
+
import chalk5 from "chalk";
|
|
8
143
|
import { readFileSync } from "fs";
|
|
9
|
-
import { dirname as dirname2, join as
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
144
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
145
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11
146
|
import yargs from "yargs";
|
|
12
147
|
import { hideBin } from "yargs/helpers";
|
|
13
148
|
|
|
14
149
|
// src/commands/convert.ts
|
|
150
|
+
init_esm_shims();
|
|
15
151
|
import {
|
|
16
152
|
createDefaultProviders,
|
|
17
153
|
FitParsingError as FitParsingError2,
|
|
18
154
|
KrdValidationError as KrdValidationError2,
|
|
19
155
|
ToleranceExceededError as ToleranceExceededError2
|
|
20
156
|
} from "@kaiord/core";
|
|
21
|
-
import
|
|
157
|
+
import chalk3 from "chalk";
|
|
22
158
|
import ora from "ora";
|
|
23
|
-
import { basename, join } from "path";
|
|
159
|
+
import { basename, join as join2 } from "path";
|
|
160
|
+
import { z as z3 } from "zod";
|
|
161
|
+
|
|
162
|
+
// src/utils/config-loader.ts
|
|
163
|
+
init_esm_shims();
|
|
164
|
+
import { readFile } from "fs/promises";
|
|
165
|
+
import { homedir } from "os";
|
|
166
|
+
import { join } from "path";
|
|
24
167
|
import { z as z2 } from "zod";
|
|
25
168
|
|
|
169
|
+
// src/utils/format-detector.ts
|
|
170
|
+
init_esm_shims();
|
|
171
|
+
import { extname } from "path";
|
|
172
|
+
import { z } from "zod";
|
|
173
|
+
var fileFormatSchema = z.enum(["fit", "krd", "tcx", "zwo"]);
|
|
174
|
+
var EXTENSION_TO_FORMAT = {
|
|
175
|
+
".fit": "fit",
|
|
176
|
+
".krd": "krd",
|
|
177
|
+
".tcx": "tcx",
|
|
178
|
+
".zwo": "zwo"
|
|
179
|
+
};
|
|
180
|
+
var detectFormat = (filePath) => {
|
|
181
|
+
const ext = extname(filePath).toLowerCase();
|
|
182
|
+
return EXTENSION_TO_FORMAT[ext] || null;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/utils/config-loader.ts
|
|
186
|
+
var configSchema = z2.object({
|
|
187
|
+
// Default formats
|
|
188
|
+
defaultInputFormat: fileFormatSchema.optional(),
|
|
189
|
+
defaultOutputFormat: fileFormatSchema.optional(),
|
|
190
|
+
// Default directories
|
|
191
|
+
defaultOutputDir: z2.string().optional(),
|
|
192
|
+
// Default tolerance config path
|
|
193
|
+
defaultToleranceConfig: z2.string().optional(),
|
|
194
|
+
// Default logging options
|
|
195
|
+
verbose: z2.boolean().optional(),
|
|
196
|
+
quiet: z2.boolean().optional(),
|
|
197
|
+
json: z2.boolean().optional(),
|
|
198
|
+
logFormat: z2.enum(["pretty", "structured"]).optional()
|
|
199
|
+
});
|
|
200
|
+
var loadConfig = async () => {
|
|
201
|
+
const configPaths = [
|
|
202
|
+
join(process.cwd(), ".kaiordrc.json"),
|
|
203
|
+
join(homedir(), ".kaiordrc.json")
|
|
204
|
+
];
|
|
205
|
+
for (const configPath of configPaths) {
|
|
206
|
+
try {
|
|
207
|
+
const configContent = await readFile(configPath, "utf-8");
|
|
208
|
+
const configJson = JSON.parse(configContent);
|
|
209
|
+
const config = configSchema.parse(configJson);
|
|
210
|
+
return config;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return {};
|
|
216
|
+
};
|
|
217
|
+
var mergeWithConfig = (cliOptions, config) => {
|
|
218
|
+
const merged = { ...config };
|
|
219
|
+
for (const key in cliOptions) {
|
|
220
|
+
if (cliOptions[key] !== void 0) {
|
|
221
|
+
merged[key] = cliOptions[key];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return merged;
|
|
225
|
+
};
|
|
226
|
+
|
|
26
227
|
// src/utils/error-formatter.ts
|
|
228
|
+
init_esm_shims();
|
|
229
|
+
init_is_tty();
|
|
27
230
|
import {
|
|
28
231
|
FitParsingError,
|
|
29
232
|
KrdValidationError,
|
|
@@ -218,6 +421,7 @@ var formatErrorAsJson = (error) => {
|
|
|
218
421
|
};
|
|
219
422
|
|
|
220
423
|
// src/utils/file-handler.ts
|
|
424
|
+
init_esm_shims();
|
|
221
425
|
import {
|
|
222
426
|
readFile as fsReadFile,
|
|
223
427
|
writeFile as fsWriteFile,
|
|
@@ -225,51 +429,51 @@ import {
|
|
|
225
429
|
} from "fs/promises";
|
|
226
430
|
import { glob } from "glob";
|
|
227
431
|
import { dirname } from "path";
|
|
228
|
-
var
|
|
432
|
+
var readFile2 = async (path2, format) => {
|
|
229
433
|
try {
|
|
230
434
|
if (format === "fit") {
|
|
231
|
-
const buffer = await fsReadFile(
|
|
435
|
+
const buffer = await fsReadFile(path2);
|
|
232
436
|
return new Uint8Array(buffer);
|
|
233
437
|
} else {
|
|
234
|
-
return await fsReadFile(
|
|
438
|
+
return await fsReadFile(path2, "utf-8");
|
|
235
439
|
}
|
|
236
440
|
} catch (error) {
|
|
237
441
|
if (error instanceof Error && "code" in error) {
|
|
238
442
|
if (error.code === "ENOENT") {
|
|
239
|
-
throw new Error(`File not found: ${
|
|
443
|
+
throw new Error(`File not found: ${path2}`);
|
|
240
444
|
}
|
|
241
445
|
if (error.code === "EACCES") {
|
|
242
|
-
throw new Error(`Permission denied: ${
|
|
446
|
+
throw new Error(`Permission denied: ${path2}`);
|
|
243
447
|
}
|
|
244
448
|
}
|
|
245
|
-
throw new Error(`Failed to read file: ${
|
|
449
|
+
throw new Error(`Failed to read file: ${path2}`);
|
|
246
450
|
}
|
|
247
451
|
};
|
|
248
|
-
var writeFile = async (
|
|
452
|
+
var writeFile = async (path2, data, format) => {
|
|
249
453
|
try {
|
|
250
|
-
const dir = dirname(
|
|
454
|
+
const dir = dirname(path2);
|
|
251
455
|
await mkdir(dir, { recursive: true });
|
|
252
456
|
if (format === "fit") {
|
|
253
457
|
if (!(data instanceof Uint8Array)) {
|
|
254
458
|
throw new Error("FIT files require Uint8Array data");
|
|
255
459
|
}
|
|
256
|
-
await fsWriteFile(
|
|
460
|
+
await fsWriteFile(path2, data);
|
|
257
461
|
} else {
|
|
258
462
|
if (typeof data !== "string") {
|
|
259
463
|
throw new Error("Text files require string data");
|
|
260
464
|
}
|
|
261
|
-
await fsWriteFile(
|
|
465
|
+
await fsWriteFile(path2, data, "utf-8");
|
|
262
466
|
}
|
|
263
467
|
} catch (error) {
|
|
264
468
|
if (error instanceof Error && "code" in error) {
|
|
265
469
|
if (error.code === "EACCES") {
|
|
266
|
-
throw new Error(`Permission denied: ${
|
|
470
|
+
throw new Error(`Permission denied: ${path2}`);
|
|
267
471
|
}
|
|
268
472
|
}
|
|
269
473
|
if (error instanceof Error && error.message.includes("require")) {
|
|
270
474
|
throw error;
|
|
271
475
|
}
|
|
272
|
-
throw new Error(`Failed to write file: ${
|
|
476
|
+
throw new Error(`Failed to write file: ${path2}`);
|
|
273
477
|
}
|
|
274
478
|
};
|
|
275
479
|
var findFiles = async (pattern) => {
|
|
@@ -280,53 +484,39 @@ var findFiles = async (pattern) => {
|
|
|
280
484
|
return files.sort();
|
|
281
485
|
};
|
|
282
486
|
|
|
283
|
-
// src/utils/format-detector.ts
|
|
284
|
-
import { extname } from "path";
|
|
285
|
-
import { z } from "zod";
|
|
286
|
-
var fileFormatSchema = z.enum(["fit", "krd", "tcx", "zwo"]);
|
|
287
|
-
var EXTENSION_TO_FORMAT = {
|
|
288
|
-
".fit": "fit",
|
|
289
|
-
".krd": "krd",
|
|
290
|
-
".tcx": "tcx",
|
|
291
|
-
".zwo": "zwo"
|
|
292
|
-
};
|
|
293
|
-
var detectFormat = (filePath) => {
|
|
294
|
-
const ext = extname(filePath).toLowerCase();
|
|
295
|
-
return EXTENSION_TO_FORMAT[ext] || null;
|
|
296
|
-
};
|
|
297
|
-
|
|
298
487
|
// src/utils/logger-factory.ts
|
|
488
|
+
init_esm_shims();
|
|
299
489
|
var isCI = () => {
|
|
300
490
|
return process.env.CI === "true" || process.env.NODE_ENV === "production" || !process.stdout.isTTY;
|
|
301
491
|
};
|
|
302
492
|
var createLogger = async (options = {}) => {
|
|
303
493
|
const loggerType = options.type || (isCI() ? "structured" : "pretty");
|
|
304
494
|
if (loggerType === "structured") {
|
|
305
|
-
const { createStructuredLogger } = await
|
|
306
|
-
return
|
|
495
|
+
const { createStructuredLogger: createStructuredLogger2 } = await Promise.resolve().then(() => (init_structured_logger(), structured_logger_exports));
|
|
496
|
+
return createStructuredLogger2(options);
|
|
307
497
|
} else {
|
|
308
|
-
const { createPrettyLogger } = await
|
|
309
|
-
return
|
|
498
|
+
const { createPrettyLogger: createPrettyLogger2 } = await Promise.resolve().then(() => (init_pretty_logger(), pretty_logger_exports));
|
|
499
|
+
return createPrettyLogger2(options);
|
|
310
500
|
}
|
|
311
501
|
};
|
|
312
502
|
|
|
313
503
|
// src/commands/convert.ts
|
|
314
|
-
var convertOptionsSchema =
|
|
315
|
-
input:
|
|
316
|
-
output:
|
|
317
|
-
outputDir:
|
|
504
|
+
var convertOptionsSchema = z3.object({
|
|
505
|
+
input: z3.string(),
|
|
506
|
+
output: z3.string().optional(),
|
|
507
|
+
outputDir: z3.string().optional(),
|
|
318
508
|
inputFormat: fileFormatSchema.optional(),
|
|
319
509
|
outputFormat: fileFormatSchema.optional(),
|
|
320
|
-
verbose:
|
|
321
|
-
quiet:
|
|
322
|
-
json:
|
|
323
|
-
logFormat:
|
|
510
|
+
verbose: z3.boolean().optional(),
|
|
511
|
+
quiet: z3.boolean().optional(),
|
|
512
|
+
json: z3.boolean().optional(),
|
|
513
|
+
logFormat: z3.enum(["pretty", "structured"]).optional()
|
|
324
514
|
});
|
|
325
515
|
var isBatchMode = (input) => {
|
|
326
516
|
return input.includes("*") || input.includes("?");
|
|
327
517
|
};
|
|
328
518
|
var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat, providers) => {
|
|
329
|
-
const inputData = await
|
|
519
|
+
const inputData = await readFile2(inputFile, inputFormat);
|
|
330
520
|
let krd;
|
|
331
521
|
if (inputFormat === "fit") {
|
|
332
522
|
if (!(inputData instanceof Uint8Array)) {
|
|
@@ -366,7 +556,19 @@ var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat,
|
|
|
366
556
|
await writeFile(outputFile, outputData, outputFormat);
|
|
367
557
|
};
|
|
368
558
|
var convertCommand = async (options) => {
|
|
369
|
-
const
|
|
559
|
+
const config = await loadConfig();
|
|
560
|
+
const mergedOptions = mergeWithConfig(options, config);
|
|
561
|
+
const optionsWithDefaults = {
|
|
562
|
+
...mergedOptions,
|
|
563
|
+
inputFormat: mergedOptions.inputFormat || config.defaultInputFormat,
|
|
564
|
+
outputFormat: mergedOptions.outputFormat || config.defaultOutputFormat,
|
|
565
|
+
outputDir: mergedOptions.outputDir || config.defaultOutputDir,
|
|
566
|
+
verbose: mergedOptions.verbose ?? config.verbose,
|
|
567
|
+
quiet: mergedOptions.quiet ?? config.quiet,
|
|
568
|
+
json: mergedOptions.json ?? config.json,
|
|
569
|
+
logFormat: mergedOptions.logFormat || config.logFormat
|
|
570
|
+
};
|
|
571
|
+
const validatedOptions = convertOptionsSchema.parse(optionsWithDefaults);
|
|
370
572
|
const logger = await createLogger({
|
|
371
573
|
type: validatedOptions.logFormat,
|
|
372
574
|
level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
|
|
@@ -424,7 +626,7 @@ var convertCommand = async (options) => {
|
|
|
424
626
|
/\.(fit|krd|tcx|zwo)$/i,
|
|
425
627
|
`.${outputFormat}`
|
|
426
628
|
);
|
|
427
|
-
const outputFile =
|
|
629
|
+
const outputFile = join2(validatedOptions.outputDir, outputFileName);
|
|
428
630
|
await convertSingleFile(
|
|
429
631
|
file,
|
|
430
632
|
outputFile,
|
|
@@ -455,18 +657,18 @@ var convertCommand = async (options) => {
|
|
|
455
657
|
if (!validatedOptions.json) {
|
|
456
658
|
console.log("\nBatch conversion complete:");
|
|
457
659
|
console.log(
|
|
458
|
-
|
|
660
|
+
chalk3.green(` \u2713 Successful: ${successful.length}/${files.length}`)
|
|
459
661
|
);
|
|
460
662
|
if (failed.length > 0) {
|
|
461
663
|
console.log(
|
|
462
|
-
|
|
664
|
+
chalk3.red(` \u2717 Failed: ${failed.length}/${files.length}`)
|
|
463
665
|
);
|
|
464
666
|
}
|
|
465
667
|
console.log(` Total time: ${(totalTime / 1e3).toFixed(2)}s`);
|
|
466
668
|
if (failed.length > 0) {
|
|
467
|
-
console.log(
|
|
669
|
+
console.log(chalk3.red("\nFailed conversions:"));
|
|
468
670
|
for (const result of failed) {
|
|
469
|
-
console.log(
|
|
671
|
+
console.log(chalk3.red(` ${result.inputFile}: ${result.error}`));
|
|
470
672
|
}
|
|
471
673
|
}
|
|
472
674
|
} else {
|
|
@@ -568,53 +770,354 @@ var convertCommand = async (options) => {
|
|
|
568
770
|
} else {
|
|
569
771
|
console.error(formattedError);
|
|
570
772
|
}
|
|
571
|
-
let exitCode =
|
|
773
|
+
let exitCode = 99;
|
|
572
774
|
if (error instanceof Error) {
|
|
573
775
|
if (error.message.includes("File not found")) {
|
|
574
|
-
exitCode =
|
|
776
|
+
exitCode = 2;
|
|
575
777
|
} else if (error.message.includes("Permission denied")) {
|
|
576
|
-
exitCode =
|
|
778
|
+
exitCode = 3;
|
|
577
779
|
} else if (error instanceof FitParsingError2) {
|
|
578
|
-
exitCode =
|
|
780
|
+
exitCode = 4;
|
|
579
781
|
} else if (error instanceof KrdValidationError2) {
|
|
580
|
-
exitCode =
|
|
782
|
+
exitCode = 5;
|
|
581
783
|
} else if (error instanceof ToleranceExceededError2) {
|
|
582
|
-
exitCode =
|
|
784
|
+
exitCode = 6;
|
|
583
785
|
} else if (error.name === "InvalidArgumentError") {
|
|
584
786
|
exitCode = 1;
|
|
585
787
|
}
|
|
586
788
|
}
|
|
587
|
-
|
|
789
|
+
return exitCode;
|
|
790
|
+
}
|
|
791
|
+
return 0;
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// src/commands/diff.ts
|
|
795
|
+
init_esm_shims();
|
|
796
|
+
import { createDefaultProviders as createDefaultProviders2 } from "@kaiord/core";
|
|
797
|
+
import chalk4 from "chalk";
|
|
798
|
+
import { z as z4 } from "zod";
|
|
799
|
+
var diffOptionsSchema = z4.object({
|
|
800
|
+
file1: z4.string(),
|
|
801
|
+
file2: z4.string(),
|
|
802
|
+
format1: fileFormatSchema.optional(),
|
|
803
|
+
format2: fileFormatSchema.optional(),
|
|
804
|
+
verbose: z4.boolean().optional(),
|
|
805
|
+
quiet: z4.boolean().optional(),
|
|
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
|
+
};
|
|
875
|
+
var compareSteps = (krd1, krd2) => {
|
|
876
|
+
const workout1 = krd1.extensions?.workout;
|
|
877
|
+
const workout2 = krd2.extensions?.workout;
|
|
878
|
+
const steps1 = workout1?.steps || [];
|
|
879
|
+
const steps2 = workout2?.steps || [];
|
|
880
|
+
const differences = [];
|
|
881
|
+
const maxSteps = Math.max(steps1.length, steps2.length);
|
|
882
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
883
|
+
const step1 = steps1[i];
|
|
884
|
+
const step2 = steps2[i];
|
|
885
|
+
if (!step1 && step2) {
|
|
886
|
+
differences.push({
|
|
887
|
+
stepIndex: i,
|
|
888
|
+
field: "step",
|
|
889
|
+
file1Value: void 0,
|
|
890
|
+
file2Value: step2
|
|
891
|
+
});
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (step1 && !step2) {
|
|
895
|
+
differences.push({
|
|
896
|
+
stepIndex: i,
|
|
897
|
+
field: "step",
|
|
898
|
+
file1Value: step1,
|
|
899
|
+
file2Value: void 0
|
|
900
|
+
});
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (!step1 || !step2) continue;
|
|
904
|
+
const stepFields = [
|
|
905
|
+
"stepIndex",
|
|
906
|
+
"name",
|
|
907
|
+
"durationType",
|
|
908
|
+
"duration",
|
|
909
|
+
"targetType",
|
|
910
|
+
"target",
|
|
911
|
+
"intensity",
|
|
912
|
+
"notes",
|
|
913
|
+
"equipment"
|
|
914
|
+
];
|
|
915
|
+
for (const field of stepFields) {
|
|
916
|
+
const value1 = step1[field];
|
|
917
|
+
const value2 = step2[field];
|
|
918
|
+
if (isDifferent(value1, value2)) {
|
|
919
|
+
differences.push({
|
|
920
|
+
stepIndex: i,
|
|
921
|
+
field,
|
|
922
|
+
file1Value: value1,
|
|
923
|
+
file2Value: value2
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
file1Count: steps1.length,
|
|
930
|
+
file2Count: steps2.length,
|
|
931
|
+
differences
|
|
932
|
+
};
|
|
933
|
+
};
|
|
934
|
+
var compareExtensions = (krd1, krd2) => {
|
|
935
|
+
const ext1 = krd1.extensions || {};
|
|
936
|
+
const ext2 = krd2.extensions || {};
|
|
937
|
+
const keys1 = Object.keys(ext1);
|
|
938
|
+
const keys2 = Object.keys(ext2);
|
|
939
|
+
const allKeys = /* @__PURE__ */ new Set([...keys1, ...keys2]);
|
|
940
|
+
const differences = [];
|
|
941
|
+
for (const key of allKeys) {
|
|
942
|
+
const value1 = ext1[key];
|
|
943
|
+
const value2 = ext2[key];
|
|
944
|
+
if (isDifferent(value1, value2)) {
|
|
945
|
+
differences.push({
|
|
946
|
+
key,
|
|
947
|
+
file1Value: value1,
|
|
948
|
+
file2Value: value2
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
file1Keys: keys1,
|
|
954
|
+
file2Keys: keys2,
|
|
955
|
+
differences
|
|
956
|
+
};
|
|
957
|
+
};
|
|
958
|
+
var formatDiffPretty = (result, file1, file2) => {
|
|
959
|
+
if (result.identical) {
|
|
960
|
+
return chalk4.green("\u2713 Files are identical");
|
|
961
|
+
}
|
|
962
|
+
const lines = [];
|
|
963
|
+
lines.push(chalk4.yellow(`
|
|
964
|
+
Comparing: ${file1} vs ${file2}
|
|
965
|
+
`));
|
|
966
|
+
if (result.metadataDiff && result.metadataDiff.length > 0) {
|
|
967
|
+
lines.push(chalk4.bold("Metadata Differences:"));
|
|
968
|
+
for (const diff of result.metadataDiff) {
|
|
969
|
+
lines.push(
|
|
970
|
+
` ${chalk4.cyan(diff.field)}:`,
|
|
971
|
+
` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
972
|
+
` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
lines.push("");
|
|
976
|
+
}
|
|
977
|
+
if (result.stepsDiff && result.stepsDiff.differences.length > 0) {
|
|
978
|
+
lines.push(chalk4.bold("Workout Steps Differences:"));
|
|
979
|
+
lines.push(
|
|
980
|
+
` Step count: ${result.stepsDiff.file1Count} vs ${result.stepsDiff.file2Count}`
|
|
981
|
+
);
|
|
982
|
+
for (const diff of result.stepsDiff.differences) {
|
|
983
|
+
lines.push(
|
|
984
|
+
` Step ${diff.stepIndex} - ${chalk4.cyan(diff.field)}:`,
|
|
985
|
+
` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
986
|
+
` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
lines.push("");
|
|
990
|
+
}
|
|
991
|
+
if (result.extensionsDiff && result.extensionsDiff.differences.length > 0) {
|
|
992
|
+
lines.push(chalk4.bold("Extensions Differences:"));
|
|
993
|
+
lines.push(
|
|
994
|
+
` Keys in file1: ${result.extensionsDiff.file1Keys.join(", ")}`,
|
|
995
|
+
` Keys in file2: ${result.extensionsDiff.file2Keys.join(", ")}`
|
|
996
|
+
);
|
|
997
|
+
for (const diff of result.extensionsDiff.differences) {
|
|
998
|
+
lines.push(
|
|
999
|
+
` ${chalk4.cyan(diff.key)}:`,
|
|
1000
|
+
` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
|
|
1001
|
+
` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
lines.push("");
|
|
1005
|
+
}
|
|
1006
|
+
return lines.join("\n");
|
|
1007
|
+
};
|
|
1008
|
+
var diffCommand = async (options) => {
|
|
1009
|
+
const validatedOptions = diffOptionsSchema.parse(options);
|
|
1010
|
+
const logger = await createLogger({
|
|
1011
|
+
type: validatedOptions.logFormat,
|
|
1012
|
+
level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
|
|
1013
|
+
quiet: validatedOptions.quiet
|
|
1014
|
+
});
|
|
1015
|
+
try {
|
|
1016
|
+
const providers = createDefaultProviders2(logger);
|
|
1017
|
+
logger.debug("Loading files for comparison", {
|
|
1018
|
+
file1: validatedOptions.file1,
|
|
1019
|
+
file2: validatedOptions.file2
|
|
1020
|
+
});
|
|
1021
|
+
const krd1 = await loadFileAsKrd(
|
|
1022
|
+
validatedOptions.file1,
|
|
1023
|
+
validatedOptions.format1,
|
|
1024
|
+
providers
|
|
1025
|
+
);
|
|
1026
|
+
const krd2 = await loadFileAsKrd(
|
|
1027
|
+
validatedOptions.file2,
|
|
1028
|
+
validatedOptions.format2,
|
|
1029
|
+
providers
|
|
1030
|
+
);
|
|
1031
|
+
logger.debug("Files loaded successfully");
|
|
1032
|
+
const metadataDiff = compareMetadata(krd1, krd2);
|
|
1033
|
+
const stepsDiff = compareSteps(krd1, krd2);
|
|
1034
|
+
const extensionsDiff = compareExtensions(krd1, krd2);
|
|
1035
|
+
const identical = metadataDiff.length === 0 && stepsDiff.differences.length === 0 && extensionsDiff.differences.length === 0;
|
|
1036
|
+
const result = {
|
|
1037
|
+
identical,
|
|
1038
|
+
metadataDiff: metadataDiff.length > 0 ? metadataDiff : void 0,
|
|
1039
|
+
stepsDiff: stepsDiff.differences.length > 0 ? stepsDiff : void 0,
|
|
1040
|
+
extensionsDiff: extensionsDiff.differences.length > 0 ? extensionsDiff : void 0
|
|
1041
|
+
};
|
|
1042
|
+
if (validatedOptions.json) {
|
|
1043
|
+
console.log(
|
|
1044
|
+
JSON.stringify(
|
|
1045
|
+
{
|
|
1046
|
+
success: true,
|
|
1047
|
+
file1: validatedOptions.file1,
|
|
1048
|
+
file2: validatedOptions.file2,
|
|
1049
|
+
...result
|
|
1050
|
+
},
|
|
1051
|
+
null,
|
|
1052
|
+
2
|
|
1053
|
+
)
|
|
1054
|
+
);
|
|
1055
|
+
} else {
|
|
1056
|
+
const output = formatDiffPretty(
|
|
1057
|
+
result,
|
|
1058
|
+
validatedOptions.file1,
|
|
1059
|
+
validatedOptions.file2
|
|
1060
|
+
);
|
|
1061
|
+
console.log(output);
|
|
1062
|
+
}
|
|
1063
|
+
return identical ? 0 : 1;
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
logger.error("Diff command failed", { error });
|
|
1066
|
+
const formattedError = formatError(error, {
|
|
1067
|
+
json: validatedOptions.json
|
|
1068
|
+
});
|
|
1069
|
+
if (validatedOptions.json) {
|
|
1070
|
+
console.log(formattedError);
|
|
1071
|
+
} else {
|
|
1072
|
+
console.error(formattedError);
|
|
1073
|
+
}
|
|
1074
|
+
return 99;
|
|
588
1075
|
}
|
|
589
1076
|
};
|
|
590
1077
|
|
|
591
1078
|
// src/commands/validate.ts
|
|
1079
|
+
init_esm_shims();
|
|
592
1080
|
import {
|
|
593
|
-
createDefaultProviders as
|
|
1081
|
+
createDefaultProviders as createDefaultProviders3,
|
|
594
1082
|
createToleranceChecker,
|
|
595
1083
|
toleranceConfigSchema,
|
|
596
1084
|
validateRoundTrip
|
|
597
1085
|
} from "@kaiord/core";
|
|
598
1086
|
import { readFile as fsReadFile2 } from "fs/promises";
|
|
599
1087
|
import ora2 from "ora";
|
|
600
|
-
import { z as
|
|
601
|
-
var validateOptionsSchema =
|
|
602
|
-
input:
|
|
603
|
-
toleranceConfig:
|
|
604
|
-
verbose:
|
|
605
|
-
quiet:
|
|
606
|
-
json:
|
|
607
|
-
logFormat:
|
|
1088
|
+
import { z as z5 } from "zod";
|
|
1089
|
+
var validateOptionsSchema = z5.object({
|
|
1090
|
+
input: z5.string(),
|
|
1091
|
+
toleranceConfig: z5.string().optional(),
|
|
1092
|
+
verbose: z5.boolean().optional(),
|
|
1093
|
+
quiet: z5.boolean().optional(),
|
|
1094
|
+
json: z5.boolean().optional(),
|
|
1095
|
+
logFormat: z5.enum(["pretty", "json"]).optional()
|
|
608
1096
|
});
|
|
609
1097
|
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
|
-
});
|
|
1098
|
+
let logger;
|
|
1099
|
+
let spinner = null;
|
|
617
1100
|
try {
|
|
1101
|
+
const config = await loadConfig();
|
|
1102
|
+
const mergedOptions = mergeWithConfig(
|
|
1103
|
+
options,
|
|
1104
|
+
config
|
|
1105
|
+
);
|
|
1106
|
+
const optionsWithDefaults = {
|
|
1107
|
+
...mergedOptions,
|
|
1108
|
+
toleranceConfig: mergedOptions.toleranceConfig || config.defaultToleranceConfig,
|
|
1109
|
+
verbose: mergedOptions.verbose ?? config.verbose,
|
|
1110
|
+
quiet: mergedOptions.quiet ?? config.quiet,
|
|
1111
|
+
json: mergedOptions.json ?? config.json,
|
|
1112
|
+
logFormat: mergedOptions.logFormat || config.logFormat
|
|
1113
|
+
};
|
|
1114
|
+
const opts = validateOptionsSchema.parse(optionsWithDefaults);
|
|
1115
|
+
const loggerType = opts.logFormat === "json" ? "structured" : opts.logFormat;
|
|
1116
|
+
logger = await createLogger({
|
|
1117
|
+
type: loggerType,
|
|
1118
|
+
level: opts.verbose ? "debug" : opts.quiet ? "error" : "info",
|
|
1119
|
+
quiet: opts.quiet
|
|
1120
|
+
});
|
|
618
1121
|
const format = detectFormat(opts.input);
|
|
619
1122
|
if (!format) {
|
|
620
1123
|
throw new Error(`Unable to detect format from file: ${opts.input}`);
|
|
@@ -624,24 +1127,24 @@ var validateCommand = async (options) => {
|
|
|
624
1127
|
`Validation currently only supports FIT files. Got: ${format}`
|
|
625
1128
|
);
|
|
626
1129
|
}
|
|
627
|
-
logger
|
|
628
|
-
const inputData = await
|
|
1130
|
+
logger?.debug("Reading input file", { path: opts.input, format });
|
|
1131
|
+
const inputData = await readFile2(opts.input, format);
|
|
629
1132
|
if (typeof inputData === "string") {
|
|
630
1133
|
throw new Error("Expected binary data for FIT file");
|
|
631
1134
|
}
|
|
632
1135
|
let toleranceConfig;
|
|
633
1136
|
if (opts.toleranceConfig) {
|
|
634
|
-
logger
|
|
1137
|
+
logger?.debug("Loading custom tolerance config", {
|
|
635
1138
|
path: opts.toleranceConfig
|
|
636
1139
|
});
|
|
637
1140
|
const configContent = await fsReadFile2(opts.toleranceConfig, "utf-8");
|
|
638
1141
|
const configJson = JSON.parse(configContent);
|
|
639
1142
|
toleranceConfig = toleranceConfigSchema.parse(configJson);
|
|
640
|
-
logger
|
|
1143
|
+
logger?.debug("Custom tolerance config loaded", {
|
|
641
1144
|
config: toleranceConfig
|
|
642
1145
|
});
|
|
643
1146
|
}
|
|
644
|
-
const providers =
|
|
1147
|
+
const providers = createDefaultProviders3(logger);
|
|
645
1148
|
const toleranceChecker = toleranceConfig ? createToleranceChecker(toleranceConfig) : providers.toleranceChecker;
|
|
646
1149
|
const roundTripValidator = validateRoundTrip(
|
|
647
1150
|
providers.fitReader,
|
|
@@ -649,8 +1152,8 @@ var validateCommand = async (options) => {
|
|
|
649
1152
|
toleranceChecker,
|
|
650
1153
|
logger
|
|
651
1154
|
);
|
|
652
|
-
|
|
653
|
-
logger
|
|
1155
|
+
spinner = opts.quiet || opts.json ? null : ora2("Validating round-trip conversion...").start();
|
|
1156
|
+
logger?.info("Starting round-trip validation", { file: opts.input });
|
|
654
1157
|
const violations = await roundTripValidator.validateFitToKrdToFit({
|
|
655
1158
|
originalFit: inputData
|
|
656
1159
|
});
|
|
@@ -664,7 +1167,7 @@ var validateCommand = async (options) => {
|
|
|
664
1167
|
}
|
|
665
1168
|
}
|
|
666
1169
|
if (violations.length === 0) {
|
|
667
|
-
logger
|
|
1170
|
+
logger?.info("Round-trip validation passed");
|
|
668
1171
|
if (opts.json) {
|
|
669
1172
|
console.log(
|
|
670
1173
|
JSON.stringify(
|
|
@@ -683,7 +1186,7 @@ var validateCommand = async (options) => {
|
|
|
683
1186
|
}
|
|
684
1187
|
process.exit(0);
|
|
685
1188
|
} else {
|
|
686
|
-
logger
|
|
1189
|
+
logger?.warn("Round-trip validation failed", {
|
|
687
1190
|
violationCount: violations.length
|
|
688
1191
|
});
|
|
689
1192
|
if (opts.json) {
|
|
@@ -706,13 +1209,21 @@ var validateCommand = async (options) => {
|
|
|
706
1209
|
process.exit(1);
|
|
707
1210
|
}
|
|
708
1211
|
} catch (error) {
|
|
709
|
-
logger
|
|
710
|
-
|
|
1212
|
+
logger?.error("Validation failed", { error });
|
|
1213
|
+
let jsonOutput = false;
|
|
1214
|
+
try {
|
|
1215
|
+
const opts = validateOptionsSchema.parse(options);
|
|
1216
|
+
jsonOutput = opts.json || false;
|
|
1217
|
+
} catch {
|
|
1218
|
+
}
|
|
1219
|
+
if (jsonOutput) {
|
|
1220
|
+
const errorObj = formatError(error, { json: true });
|
|
1221
|
+
const errorData = typeof errorObj === "string" ? JSON.parse(errorObj) : errorObj;
|
|
711
1222
|
console.log(
|
|
712
1223
|
JSON.stringify(
|
|
713
1224
|
{
|
|
714
1225
|
success: false,
|
|
715
|
-
error:
|
|
1226
|
+
error: errorData
|
|
716
1227
|
},
|
|
717
1228
|
null,
|
|
718
1229
|
2
|
|
@@ -722,29 +1233,31 @@ var validateCommand = async (options) => {
|
|
|
722
1233
|
console.error(formatError(error, { json: false }));
|
|
723
1234
|
}
|
|
724
1235
|
process.exit(1);
|
|
1236
|
+
} finally {
|
|
1237
|
+
spinner?.stop();
|
|
725
1238
|
}
|
|
726
1239
|
};
|
|
727
1240
|
|
|
728
1241
|
// src/bin/kaiord.ts
|
|
729
|
-
var __filename2 =
|
|
1242
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
730
1243
|
var __dirname2 = dirname2(__filename2);
|
|
731
|
-
var packageJsonPath = __dirname2.includes("/dist") ?
|
|
1244
|
+
var packageJsonPath = __dirname2.includes("/dist") ? join3(__dirname2, "../../package.json") : join3(__dirname2, "../../package.json");
|
|
732
1245
|
var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
733
1246
|
var version = packageJson.version;
|
|
734
1247
|
var showKiroEasterEgg = () => {
|
|
735
1248
|
console.log(
|
|
736
|
-
|
|
1249
|
+
chalk5.cyan(`
|
|
737
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
|
|
738
1251
|
\u2551 \u2551
|
|
739
|
-
\u2551 \u{1F47B} Built with Kiro AI during Kiroween Hackathon \u{1F47B}
|
|
1252
|
+
\u2551 \u{1F47B} Built with Kiro AI during Kiroween Hackathon \u{1F47B} \u2551
|
|
740
1253
|
\u2551 \u2551
|
|
741
|
-
\u2551 Kiro helped design, architect, and implement this
|
|
742
|
-
\u2551 entire CLI tool through spec-driven development.
|
|
1254
|
+
\u2551 Kiro helped design, architect, and implement this \u2551
|
|
1255
|
+
\u2551 entire CLI tool through spec-driven development. \u2551
|
|
743
1256
|
\u2551 \u2551
|
|
744
|
-
\u2551 Learn more about Kiroween:
|
|
745
|
-
\u2551 \u{1F449} http://kiroween.devpost.com/
|
|
1257
|
+
\u2551 Learn more about Kiroween: \u2551
|
|
1258
|
+
\u2551 \u{1F449} http://kiroween.devpost.com/ \u2551
|
|
746
1259
|
\u2551 \u2551
|
|
747
|
-
\u2551 Kiro: Your AI pair programmer for building better
|
|
1260
|
+
\u2551 Kiro: Your AI pair programmer for building better \u2551
|
|
748
1261
|
\u2551 software, faster. \u{1F680} \u2551
|
|
749
1262
|
\u2551 \u2551
|
|
750
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
|
|
@@ -794,7 +1307,7 @@ var main = async () => {
|
|
|
794
1307
|
);
|
|
795
1308
|
},
|
|
796
1309
|
async (argv) => {
|
|
797
|
-
await convertCommand({
|
|
1310
|
+
const exitCode = await convertCommand({
|
|
798
1311
|
input: argv.input,
|
|
799
1312
|
output: argv.output,
|
|
800
1313
|
outputDir: argv.outputDir,
|
|
@@ -805,6 +1318,9 @@ var main = async () => {
|
|
|
805
1318
|
json: argv.json,
|
|
806
1319
|
logFormat: argv.logFormat
|
|
807
1320
|
});
|
|
1321
|
+
if (exitCode !== 0) {
|
|
1322
|
+
process.exit(exitCode);
|
|
1323
|
+
}
|
|
808
1324
|
}
|
|
809
1325
|
).command(
|
|
810
1326
|
"validate",
|
|
@@ -836,6 +1352,52 @@ var main = async () => {
|
|
|
836
1352
|
logFormat: argv.logFormat
|
|
837
1353
|
});
|
|
838
1354
|
}
|
|
1355
|
+
).command(
|
|
1356
|
+
"diff",
|
|
1357
|
+
"Compare two workout files and show differences",
|
|
1358
|
+
(yargs2) => {
|
|
1359
|
+
return yargs2.option("file1", {
|
|
1360
|
+
type: "string",
|
|
1361
|
+
description: "First file to compare",
|
|
1362
|
+
demandOption: true
|
|
1363
|
+
}).option("file2", {
|
|
1364
|
+
type: "string",
|
|
1365
|
+
description: "Second file to compare",
|
|
1366
|
+
demandOption: true
|
|
1367
|
+
}).option("format1", {
|
|
1368
|
+
type: "string",
|
|
1369
|
+
choices: ["fit", "krd", "tcx", "zwo"],
|
|
1370
|
+
description: "Override format detection for first file"
|
|
1371
|
+
}).option("format2", {
|
|
1372
|
+
type: "string",
|
|
1373
|
+
choices: ["fit", "krd", "tcx", "zwo"],
|
|
1374
|
+
description: "Override format detection for second file"
|
|
1375
|
+
}).example(
|
|
1376
|
+
"$0 diff --file1 workout1.fit --file2 workout2.fit",
|
|
1377
|
+
"Compare two FIT files"
|
|
1378
|
+
).example(
|
|
1379
|
+
"$0 diff --file1 workout.fit --file2 workout.krd",
|
|
1380
|
+
"Compare FIT and KRD files"
|
|
1381
|
+
).example(
|
|
1382
|
+
"$0 diff --file1 workout1.krd --file2 workout2.krd --json",
|
|
1383
|
+
"Compare with JSON output"
|
|
1384
|
+
);
|
|
1385
|
+
},
|
|
1386
|
+
async (argv) => {
|
|
1387
|
+
const exitCode = await diffCommand({
|
|
1388
|
+
file1: argv.file1,
|
|
1389
|
+
file2: argv.file2,
|
|
1390
|
+
format1: argv.format1,
|
|
1391
|
+
format2: argv.format2,
|
|
1392
|
+
verbose: argv.verbose,
|
|
1393
|
+
quiet: argv.quiet,
|
|
1394
|
+
json: argv.json,
|
|
1395
|
+
logFormat: argv.logFormat
|
|
1396
|
+
});
|
|
1397
|
+
if (exitCode !== 0 && exitCode !== 1) {
|
|
1398
|
+
process.exit(exitCode);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
839
1401
|
).option("verbose", {
|
|
840
1402
|
type: "boolean",
|
|
841
1403
|
description: "Enable verbose logging",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaiord/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Command-line interface for Kaiord workout file conversion",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsup",
|
|
16
|
-
"test": "vitest run",
|
|
16
|
+
"test": "pnpm build && vitest run",
|
|
17
17
|
"test:unit": "vitest run --exclude '**/*-{integration,smoke,snapshot}.test.ts'",
|
|
18
|
-
"test:integration": "vitest run src/commands/convert-integration.test.ts src/commands/validate-integration.test.ts",
|
|
19
|
-
"test:smoke": "vitest run src/tests/cli-smoke.test.ts",
|
|
18
|
+
"test:integration": "pnpm build && vitest run src/commands/convert-integration.test.ts src/commands/validate-integration.test.ts",
|
|
19
|
+
"test:smoke": "pnpm build && vitest run src/tests/cli-smoke.test.ts",
|
|
20
20
|
"test:watch": "vitest",
|
|
21
21
|
"dev": "tsx src/bin/kaiord.ts",
|
|
22
|
+
"prebuild": "echo 'Building CLI...'",
|
|
22
23
|
"prepublishOnly": "pnpm build && pnpm test",
|
|
23
24
|
"check-licenses": "license-checker --onlyAllow 'MIT;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;ISC' --production"
|
|
24
25
|
},
|
|
@@ -37,18 +38,18 @@
|
|
|
37
38
|
"license": "MIT",
|
|
38
39
|
"repository": {
|
|
39
40
|
"type": "git",
|
|
40
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "https://github.com/pablo-albaladejo/kaiord.git",
|
|
41
42
|
"directory": "packages/cli"
|
|
42
43
|
},
|
|
43
44
|
"homepage": "https://www.npmjs.com/package/@kaiord/cli",
|
|
44
45
|
"bugs": {
|
|
45
|
-
"url": "https://github.com/
|
|
46
|
+
"url": "https://github.com/pablo-albaladejo/kaiord/issues"
|
|
46
47
|
},
|
|
47
48
|
"publishConfig": {
|
|
48
49
|
"access": "public"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@kaiord/core": "
|
|
52
|
+
"@kaiord/core": "^1.0.0",
|
|
52
53
|
"chalk": "^5.3.0",
|
|
53
54
|
"glob": "^10.3.10",
|
|
54
55
|
"ora": "^8.0.1",
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
"license-checker": "^25.0.1",
|
|
63
64
|
"strip-ansi": "^7.1.0",
|
|
64
65
|
"tmp-promise": "^3.0.3",
|
|
65
|
-
"tsup": "^8.
|
|
66
|
+
"tsup": "^8.5.1",
|
|
66
67
|
"tsx": "^4.7.0",
|
|
67
68
|
"typescript": "^5.3.3",
|
|
68
69
|
"vitest": "^1.2.0"
|
package/dist/chunk-TI3WVGXE.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
isTTY
|
|
4
|
-
} from "./chunk-TI3WVGXE.js";
|
|
5
|
-
|
|
6
|
-
// src/adapters/logger/pretty-logger.ts
|
|
7
|
-
import chalk from "chalk";
|
|
8
|
-
var createPrettyLogger = (options = {}) => {
|
|
9
|
-
const level = options.level || "info";
|
|
10
|
-
const quiet = options.quiet || false;
|
|
11
|
-
const forceColor = process.env.FORCE_COLOR === "1";
|
|
12
|
-
const useColors = isTTY() || forceColor;
|
|
13
|
-
const levels = ["debug", "info", "warn", "error"];
|
|
14
|
-
const minLevelIndex = levels.indexOf(level);
|
|
15
|
-
const shouldLog = (messageLevel) => {
|
|
16
|
-
if (quiet && messageLevel !== "error") {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
const messageLevelIndex = levels.indexOf(messageLevel);
|
|
20
|
-
return messageLevelIndex >= minLevelIndex;
|
|
21
|
-
};
|
|
22
|
-
const formatContext = (context) => {
|
|
23
|
-
if (!context || Object.keys(context).length === 0) {
|
|
24
|
-
return "";
|
|
25
|
-
}
|
|
26
|
-
const contextStr = JSON.stringify(context);
|
|
27
|
-
return " " + (useColors ? chalk.gray(contextStr) : contextStr);
|
|
28
|
-
};
|
|
29
|
-
return {
|
|
30
|
-
debug: (message, context) => {
|
|
31
|
-
if (shouldLog("debug")) {
|
|
32
|
-
const formatted = `\u{1F41B} ${message}${formatContext(context)}`;
|
|
33
|
-
console.log(useColors ? chalk.gray(formatted) : formatted);
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
info: (message, context) => {
|
|
37
|
-
if (shouldLog("info")) {
|
|
38
|
-
const formatted = `\u2139 ${message}${formatContext(context)}`;
|
|
39
|
-
console.log(useColors ? chalk.blue(formatted) : formatted);
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
warn: (message, context) => {
|
|
43
|
-
if (shouldLog("warn")) {
|
|
44
|
-
const formatted = `\u26A0 ${message}${formatContext(context)}`;
|
|
45
|
-
console.warn(useColors ? chalk.yellow(formatted) : formatted);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
error: (message, context) => {
|
|
49
|
-
if (shouldLog("error")) {
|
|
50
|
-
const formatted = `\u2716 ${message}${formatContext(context)}`;
|
|
51
|
-
console.error(useColors ? chalk.red(formatted) : formatted);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
export {
|
|
57
|
-
createPrettyLogger
|
|
58
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/adapters/logger/structured-logger.ts
|
|
4
|
-
import winston from "winston";
|
|
5
|
-
var createStructuredLogger = (options = {}) => {
|
|
6
|
-
const level = options.level || "info";
|
|
7
|
-
const quiet = options.quiet || false;
|
|
8
|
-
const winstonLogger = winston.createLogger({
|
|
9
|
-
level: quiet ? "error" : level,
|
|
10
|
-
format: winston.format.combine(
|
|
11
|
-
winston.format.timestamp(),
|
|
12
|
-
winston.format.json()
|
|
13
|
-
),
|
|
14
|
-
transports: [
|
|
15
|
-
new winston.transports.Console({
|
|
16
|
-
stderrLevels: ["error", "warn", "info", "debug"]
|
|
17
|
-
})
|
|
18
|
-
]
|
|
19
|
-
});
|
|
20
|
-
return {
|
|
21
|
-
debug: (message, context) => {
|
|
22
|
-
winstonLogger.debug(message, context);
|
|
23
|
-
},
|
|
24
|
-
info: (message, context) => {
|
|
25
|
-
winstonLogger.info(message, context);
|
|
26
|
-
},
|
|
27
|
-
warn: (message, context) => {
|
|
28
|
-
winstonLogger.warn(message, context);
|
|
29
|
-
},
|
|
30
|
-
error: (message, context) => {
|
|
31
|
-
winstonLogger.error(message, context);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
export {
|
|
36
|
-
createStructuredLogger
|
|
37
|
-
};
|