@jlcpcb/cli 0.3.2 → 0.4.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/CHANGELOG.md +26 -0
- package/README.md +23 -5
- package/dist/assets/search.html +32 -31
- package/dist/index.js +7053 -6459
- package/package.json +1 -1
- package/src/app/App.tsx +1 -1
- package/src/app/__fixtures__/interactive-install-scenarios.tsx +149 -0
- package/src/app/__fixtures__/navigation-scenarios.tsx +103 -0
- package/src/app/__fixtures__/terminal.tsx +57 -0
- package/src/app/components/DetailView.tsx +8 -5
- package/src/app/components/ListView.tsx +1 -1
- package/src/app/components/list-view-format.test.ts +2 -0
- package/src/app/navigation/NavigationContext.tsx +1 -1
- package/src/app/navigation/types.ts +8 -1
- package/src/app/navigation.test.ts +24 -0
- package/src/app/screens/EasyEDAInfoScreen.tsx +7 -9
- package/src/app/screens/InfoScreen.tsx +37 -19
- package/src/app/screens/InstallScreen.tsx +3 -5
- package/src/app/screens/InstalledScreen.tsx +3 -3
- package/src/app/screens/LibrarySetupScreen.tsx +12 -5
- package/src/app/screens/SearchScreen.tsx +22 -18
- package/src/app/state/AppStateContext.tsx +0 -23
- package/src/commands/__fixtures__/failure-scenarios.ts +67 -0
- package/src/commands/agent-json.test.ts +194 -0
- package/src/commands/easyeda.ts +67 -14
- package/src/commands/failures.test.ts +105 -0
- package/src/commands/info.ts +6 -9
- package/src/commands/install.ts +43 -12
- package/src/commands/interactive-install.test.ts +29 -0
- package/src/commands/library.ts +14 -23
- package/src/commands/search.ts +36 -4
- package/src/commands/validate.ts +114 -53
- package/src/index.ts +38 -6
- package/src/utils/agent-output.ts +35 -0
- package/src/utils/search-result-output.ts +20 -0
package/src/commands/validate.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
formatSymbolComparisonResult,
|
|
19
19
|
generateValidationReport,
|
|
20
20
|
generateBatchReport,
|
|
21
|
-
TEST_CATEGORIES,
|
|
22
21
|
getAllTestComponents,
|
|
23
22
|
getTestComponentsByCategory,
|
|
24
23
|
getCategoryNames,
|
|
@@ -26,10 +25,12 @@ import {
|
|
|
26
25
|
renderSymbolSvg,
|
|
27
26
|
type ValidationResult,
|
|
28
27
|
type ReportSvgs,
|
|
28
|
+
type TestComponent,
|
|
29
29
|
easyedaClient,
|
|
30
30
|
footprintConverter,
|
|
31
31
|
symbolConverter,
|
|
32
32
|
} from '@jlcpcb/core';
|
|
33
|
+
import { printJson, printJsonError } from '../utils/agent-output.js';
|
|
33
34
|
|
|
34
35
|
interface ValidateOptions {
|
|
35
36
|
footprintOnly?: boolean;
|
|
@@ -45,7 +46,8 @@ interface ValidateOptions {
|
|
|
45
46
|
*/
|
|
46
47
|
async function validateComponent(
|
|
47
48
|
lcscCode: string,
|
|
48
|
-
options: { footprint: boolean; symbol: boolean }
|
|
49
|
+
options: { footprint: boolean; symbol: boolean },
|
|
50
|
+
identity?: Pick<TestComponent, 'expectedMpn' | 'expectedPackage'>
|
|
49
51
|
): Promise<ValidationResult> {
|
|
50
52
|
const startTime = Date.now();
|
|
51
53
|
const normalizedCode = lcscCode.replace(/^C/i, '');
|
|
@@ -69,9 +71,18 @@ async function validateComponent(
|
|
|
69
71
|
error: 'Failed to fetch component data from EasyEDA',
|
|
70
72
|
};
|
|
71
73
|
}
|
|
74
|
+
if (identity?.expectedMpn !== undefined && componentData.info.name !== identity.expectedMpn) {
|
|
75
|
+
throw new Error(`Fixture MPN differs: expected ${identity.expectedMpn}, got ${componentData.info.name}`);
|
|
76
|
+
}
|
|
77
|
+
if (identity?.expectedPackage !== undefined && componentData.footprint.name !== identity.expectedPackage) {
|
|
78
|
+
throw new Error(`Fixture package differs: expected ${identity.expectedPackage}, got ${componentData.footprint.name}`);
|
|
79
|
+
}
|
|
72
80
|
|
|
73
81
|
let footprintResult = null;
|
|
74
82
|
let symbolResult = null;
|
|
83
|
+
const missingReferences: string[] = [];
|
|
84
|
+
if (options.footprint && !reference.footprintSvg) missingReferences.push('footprint');
|
|
85
|
+
if (options.symbol && !reference.symbolSvg) missingReferences.push('symbol');
|
|
75
86
|
|
|
76
87
|
// Validate footprint
|
|
77
88
|
if (options.footprint && reference.footprintSvg) {
|
|
@@ -80,10 +91,7 @@ async function validateComponent(
|
|
|
80
91
|
const refData = extractFromReferenceSVG(reference.footprintSvg);
|
|
81
92
|
const genData = extractFromKiCadFootprint(kicadContent);
|
|
82
93
|
|
|
83
|
-
footprintResult = compareFootprints(refData, genData
|
|
84
|
-
sizeWarningsOnly: true,
|
|
85
|
-
positionTolerance: 50,
|
|
86
|
-
});
|
|
94
|
+
footprintResult = compareFootprints(refData, genData);
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
// Validate symbol
|
|
@@ -95,12 +103,11 @@ async function validateComponent(
|
|
|
95
103
|
const refData = extractSymbolFromReferenceSVG(reference.symbolSvg);
|
|
96
104
|
const genData = extractFromKiCadSymbol(kicadContent);
|
|
97
105
|
|
|
98
|
-
symbolResult = compareSymbols(refData, genData
|
|
99
|
-
positionTolerance: 50,
|
|
100
|
-
});
|
|
106
|
+
symbolResult = compareSymbols(refData, genData);
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
const passed =
|
|
110
|
+
missingReferences.length === 0 &&
|
|
104
111
|
(!footprintResult || footprintResult.passed) && (!symbolResult || symbolResult.passed);
|
|
105
112
|
|
|
106
113
|
return {
|
|
@@ -111,6 +118,9 @@ async function validateComponent(
|
|
|
111
118
|
symbol: symbolResult,
|
|
112
119
|
timestamp: new Date(),
|
|
113
120
|
durationMs: Date.now() - startTime,
|
|
121
|
+
...(missingReferences.length > 0
|
|
122
|
+
? { error: `Missing reference SVG: ${missingReferences.join(', ')}` }
|
|
123
|
+
: {}),
|
|
114
124
|
};
|
|
115
125
|
} catch (error) {
|
|
116
126
|
return {
|
|
@@ -130,46 +140,32 @@ export async function validateCommand(
|
|
|
130
140
|
id: string | undefined,
|
|
131
141
|
options: ValidateOptions
|
|
132
142
|
): Promise<void> {
|
|
143
|
+
if (options.footprintOnly && options.symbolOnly) {
|
|
144
|
+
if (options.json) {
|
|
145
|
+
printJsonError('invalid_options', '--footprint-only and --symbol-only are mutually exclusive');
|
|
146
|
+
} else {
|
|
147
|
+
p.log.error('--footprint-only and --symbol-only are mutually exclusive');
|
|
148
|
+
}
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
const validateFootprint = !options.symbolOnly;
|
|
134
153
|
const validateSymbol = !options.footprintOnly;
|
|
135
154
|
|
|
136
155
|
// Single component validation
|
|
137
156
|
if (id && !options.category && !options.all) {
|
|
138
|
-
const spinner = p.spinner();
|
|
139
|
-
spinner
|
|
157
|
+
const spinner = options.json ? null : p.spinner();
|
|
158
|
+
spinner?.start(`Validating ${id}...`);
|
|
140
159
|
|
|
141
160
|
const result = await validateComponent(id, {
|
|
142
161
|
footprint: validateFootprint,
|
|
143
162
|
symbol: validateSymbol,
|
|
144
163
|
});
|
|
145
164
|
|
|
146
|
-
spinner
|
|
147
|
-
|
|
148
|
-
// Display results
|
|
149
|
-
console.log();
|
|
150
|
-
console.log(chalk.bold(`${result.componentName} (${result.lcscCode})`));
|
|
151
|
-
console.log(chalk.dim(`Duration: ${result.durationMs}ms`));
|
|
152
|
-
console.log();
|
|
153
|
-
|
|
154
|
-
if (result.error) {
|
|
155
|
-
console.log(chalk.red(`Error: ${result.error}`));
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (result.footprint) {
|
|
160
|
-
console.log(formatComparisonResult(result.footprint));
|
|
161
|
-
}
|
|
165
|
+
spinner?.stop(result.passed ? chalk.green('✓ Validation passed') : chalk.red('✗ Validation failed'));
|
|
162
166
|
|
|
163
|
-
|
|
164
|
-
console.log(formatSymbolComparisonResult(result.symbol));
|
|
165
|
-
}
|
|
167
|
+
let htmlPath: string | undefined;
|
|
166
168
|
|
|
167
|
-
// JSON output
|
|
168
|
-
if (options.json) {
|
|
169
|
-
console.log(JSON.stringify(result, null, 2));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// HTML output
|
|
173
169
|
if (options.html) {
|
|
174
170
|
const reference = await fetchReferenceSVG(id);
|
|
175
171
|
const componentData = await easyedaClient.getComponentData(id);
|
|
@@ -195,9 +191,42 @@ export async function validateCommand(
|
|
|
195
191
|
|
|
196
192
|
const html = generateValidationReport(result, svgs);
|
|
197
193
|
writeFileSync(options.html, html);
|
|
198
|
-
|
|
194
|
+
htmlPath = options.html;
|
|
199
195
|
}
|
|
200
196
|
|
|
197
|
+
if (options.json) {
|
|
198
|
+
printJson({
|
|
199
|
+
success: result.passed && !result.error,
|
|
200
|
+
result,
|
|
201
|
+
html: htmlPath,
|
|
202
|
+
});
|
|
203
|
+
process.exit(result.passed && !result.error ? 0 : 1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Display results
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(chalk.bold(`${result.componentName} (${result.lcscCode})`));
|
|
209
|
+
console.log(chalk.dim(`Duration: ${result.durationMs}ms`));
|
|
210
|
+
console.log();
|
|
211
|
+
|
|
212
|
+
if (result.error) {
|
|
213
|
+
console.log(chalk.red(`Error: ${result.error}`));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (result.footprint) {
|
|
218
|
+
console.log(formatComparisonResult(result.footprint));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (result.symbol) {
|
|
222
|
+
console.log(formatSymbolComparisonResult(result.symbol));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (htmlPath) {
|
|
226
|
+
console.log(chalk.dim(`HTML report: ${htmlPath}`));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
201
230
|
process.exit(result.passed ? 0 : 1);
|
|
202
231
|
}
|
|
203
232
|
|
|
@@ -209,6 +238,11 @@ export async function validateCommand(
|
|
|
209
238
|
: [];
|
|
210
239
|
|
|
211
240
|
if (components.length === 0 && !id) {
|
|
241
|
+
if (options.json) {
|
|
242
|
+
printJsonError('missing_validation_target', 'JSON validation requires an id, --category, or --all');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
212
246
|
// Interactive category selection
|
|
213
247
|
const categories = getCategoryNames();
|
|
214
248
|
const selected = await p.select({
|
|
@@ -232,11 +266,22 @@ export async function validateCommand(
|
|
|
232
266
|
components = components.filter((c) => !c.skip);
|
|
233
267
|
|
|
234
268
|
if (components.length === 0) {
|
|
269
|
+
if (options.json) {
|
|
270
|
+
printJson({
|
|
271
|
+
success: true,
|
|
272
|
+
summary: { total: 0, passed: 0, failed: 0, errors: 0 },
|
|
273
|
+
results: [],
|
|
274
|
+
});
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
|
|
235
278
|
p.log.warn('No components to validate');
|
|
236
279
|
process.exit(0);
|
|
237
280
|
}
|
|
238
281
|
|
|
239
|
-
|
|
282
|
+
if (!options.json) {
|
|
283
|
+
console.log(chalk.bold(`\nValidating ${components.length} components...\n`));
|
|
284
|
+
}
|
|
240
285
|
|
|
241
286
|
const results: ValidationResult[] = [];
|
|
242
287
|
let passed = 0;
|
|
@@ -244,43 +289,59 @@ export async function validateCommand(
|
|
|
244
289
|
let errors = 0;
|
|
245
290
|
|
|
246
291
|
for (const component of components) {
|
|
247
|
-
|
|
292
|
+
if (!options.json) {
|
|
293
|
+
process.stdout.write(` ${component.lcsc.padEnd(12)} ${component.name.slice(0, 40).padEnd(42)} `);
|
|
294
|
+
}
|
|
248
295
|
|
|
249
296
|
const result = await validateComponent(component.lcsc, {
|
|
250
297
|
footprint: validateFootprint,
|
|
251
298
|
symbol: validateSymbol,
|
|
252
|
-
});
|
|
299
|
+
}, component);
|
|
253
300
|
|
|
254
301
|
results.push(result);
|
|
255
302
|
|
|
256
303
|
if (result.error) {
|
|
257
304
|
errors++;
|
|
258
|
-
console.log(chalk.yellow(
|
|
305
|
+
if (!options.json) console.log(chalk.yellow(`ERROR: ${result.error}`));
|
|
259
306
|
} else if (result.passed) {
|
|
260
307
|
passed++;
|
|
261
|
-
console.log(chalk.green('✓ PASS'));
|
|
308
|
+
if (!options.json) console.log(chalk.green('✓ PASS'));
|
|
262
309
|
} else {
|
|
263
310
|
failed++;
|
|
264
|
-
console.log(chalk.red('✗ FAIL'));
|
|
311
|
+
if (!options.json) console.log(chalk.red('✗ FAIL'));
|
|
265
312
|
}
|
|
266
313
|
}
|
|
267
314
|
|
|
315
|
+
let htmlPath: string | undefined;
|
|
316
|
+
if (options.html) {
|
|
317
|
+
const html = generateBatchReport(results, 'JLC-CLI Validation Report');
|
|
318
|
+
writeFileSync(options.html, html);
|
|
319
|
+
htmlPath = options.html;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (options.json) {
|
|
323
|
+
printJson({
|
|
324
|
+
success: failed === 0 && errors === 0,
|
|
325
|
+
summary: {
|
|
326
|
+
total: results.length,
|
|
327
|
+
passed,
|
|
328
|
+
failed,
|
|
329
|
+
errors,
|
|
330
|
+
},
|
|
331
|
+
results,
|
|
332
|
+
html: htmlPath,
|
|
333
|
+
});
|
|
334
|
+
process.exit(failed > 0 || errors > 0 ? 1 : 0);
|
|
335
|
+
}
|
|
336
|
+
|
|
268
337
|
// Summary
|
|
269
338
|
console.log();
|
|
270
339
|
console.log(chalk.bold('Summary:'));
|
|
271
340
|
console.log(` ${chalk.green(`${passed} passed`)} ${chalk.red(`${failed} failed`)} ${chalk.yellow(`${errors} errors`)}`);
|
|
272
341
|
console.log();
|
|
273
342
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
console.log(JSON.stringify(results, null, 2));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// HTML output
|
|
280
|
-
if (options.html) {
|
|
281
|
-
const html = generateBatchReport(results, 'JLC-CLI Validation Report');
|
|
282
|
-
writeFileSync(options.html, html);
|
|
283
|
-
console.log(chalk.dim(`HTML report: ${options.html}`));
|
|
343
|
+
if (htmlPath) {
|
|
344
|
+
console.log(chalk.dim(`HTML report: ${htmlPath}`));
|
|
284
345
|
}
|
|
285
346
|
|
|
286
347
|
process.exit(failed > 0 || errors > 0 ? 1 : 0);
|
package/src/index.ts
CHANGED
|
@@ -6,34 +6,49 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Command } from 'commander';
|
|
9
|
+
import { createRequire } from 'module';
|
|
9
10
|
import { searchCommand } from './commands/search.js';
|
|
10
11
|
import { infoCommand } from './commands/info.js';
|
|
11
12
|
import { installCommand } from './commands/install.js';
|
|
12
13
|
import { libraryCommand, regenerateCommand } from './commands/library.js';
|
|
13
14
|
import { easyedaSearchCommand, easyedaInstallCommand } from './commands/easyeda.js';
|
|
14
15
|
import { validateCommand } from './commands/validate.js';
|
|
16
|
+
import { getErrorMessage } from './utils/agent-output.js';
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const { version } = require('../package.json');
|
|
15
20
|
|
|
16
21
|
const program = new Command();
|
|
17
22
|
|
|
23
|
+
function parsePositiveInt(value: string): number {
|
|
24
|
+
const parsed = Number.parseInt(value, 10);
|
|
25
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
26
|
+
throw new Error(`Expected a positive integer, got "${value}"`);
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
program
|
|
19
32
|
.name('jlc')
|
|
20
33
|
.description('JLC/EasyEDA component sourcing and KiCad library management')
|
|
21
|
-
.version(
|
|
34
|
+
.version(version);
|
|
22
35
|
|
|
23
36
|
program
|
|
24
37
|
.command('search <query...>')
|
|
25
38
|
.description('Search for components (basic parts sorted first)')
|
|
26
|
-
.option('-l, --limit <number>', 'Maximum results',
|
|
39
|
+
.option('-l, --limit <number>', 'Maximum results', parsePositiveInt, 20)
|
|
27
40
|
.option('--in-stock', 'Only show in-stock components')
|
|
28
41
|
.option('--basic-only', 'Only show basic parts (no extended)')
|
|
29
42
|
.option('--community', 'Search EasyEDA community library')
|
|
43
|
+
.option('--json', 'Output clean JSON and do not launch the TUI')
|
|
30
44
|
.action(async (queryParts: string[], options) => {
|
|
31
45
|
const query = queryParts.join(' ');
|
|
32
46
|
await searchCommand(query, {
|
|
33
|
-
limit:
|
|
47
|
+
limit: options.limit,
|
|
34
48
|
inStock: options.inStock,
|
|
35
49
|
basicOnly: options.basicOnly || false,
|
|
36
50
|
source: options.community ? 'easyeda-community' : 'lcsc',
|
|
51
|
+
json: options.json,
|
|
37
52
|
});
|
|
38
53
|
});
|
|
39
54
|
|
|
@@ -50,11 +65,15 @@ program
|
|
|
50
65
|
.description('Install component to KiCad libraries')
|
|
51
66
|
.option('-p, --project <path>', 'Install to project-local library')
|
|
52
67
|
.option('--with-3d', 'Include 3D model')
|
|
68
|
+
.option('-y, --yes', 'Install directly without launching the TUI')
|
|
69
|
+
.option('--json', 'Output clean JSON and install directly')
|
|
53
70
|
.option('-f, --force', 'Force reinstall (regenerate symbol and footprint)')
|
|
54
71
|
.action(async (id, options) => {
|
|
55
72
|
await installCommand(id, {
|
|
56
73
|
projectPath: options.project,
|
|
57
74
|
include3d: options.with3d,
|
|
75
|
+
yes: options.yes,
|
|
76
|
+
json: options.json,
|
|
58
77
|
force: options.force,
|
|
59
78
|
});
|
|
60
79
|
});
|
|
@@ -114,11 +133,17 @@ const easyeda = program
|
|
|
114
133
|
easyeda
|
|
115
134
|
.command('search <query...>')
|
|
116
135
|
.description('Open browser-based component search')
|
|
117
|
-
.option('-p, --port <number>', 'HTTP server port',
|
|
136
|
+
.option('-p, --port <number>', 'HTTP server port', parsePositiveInt, 3847)
|
|
137
|
+
.option('--json', 'Output clean JSON search results and do not open a browser')
|
|
138
|
+
.option('--no-open', 'Start the browser server without opening a browser')
|
|
139
|
+
.option('--once', 'Print the browser URL and exit after the server is ready')
|
|
118
140
|
.action(async (queryParts: string[], options) => {
|
|
119
141
|
const query = queryParts.join(' ');
|
|
120
142
|
await easyedaSearchCommand(query, {
|
|
121
|
-
port: options.port
|
|
143
|
+
port: options.port,
|
|
144
|
+
json: options.json,
|
|
145
|
+
open: options.open,
|
|
146
|
+
once: options.once,
|
|
122
147
|
});
|
|
123
148
|
});
|
|
124
149
|
|
|
@@ -127,13 +152,20 @@ easyeda
|
|
|
127
152
|
.description('Install EasyEDA community component to KiCad libraries')
|
|
128
153
|
.option('-p, --project <path>', 'Install to project-local library')
|
|
129
154
|
.option('--with-3d', 'Include 3D model')
|
|
155
|
+
.option('-y, --yes', 'Install directly without launching the TUI')
|
|
156
|
+
.option('--json', 'Output clean JSON and install directly')
|
|
130
157
|
.option('-f, --force', 'Force reinstall (regenerate symbol and footprint)')
|
|
131
158
|
.action(async (uuid, options) => {
|
|
132
159
|
await easyedaInstallCommand(uuid, {
|
|
133
160
|
projectPath: options.project,
|
|
134
161
|
include3d: options.with3d,
|
|
162
|
+
yes: options.yes,
|
|
163
|
+
json: options.json,
|
|
135
164
|
force: options.force,
|
|
136
165
|
});
|
|
137
166
|
});
|
|
138
167
|
|
|
139
|
-
program.
|
|
168
|
+
program.parseAsync().catch((error) => {
|
|
169
|
+
console.error(`Error: ${getErrorMessage(error)}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface JsonError {
|
|
2
|
+
success: false;
|
|
3
|
+
error: {
|
|
4
|
+
code: string;
|
|
5
|
+
message: string;
|
|
6
|
+
retryable: boolean;
|
|
7
|
+
details?: unknown;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function printJson(value: unknown): void {
|
|
12
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function printJsonError(
|
|
16
|
+
code: string,
|
|
17
|
+
message: string,
|
|
18
|
+
options: { retryable?: boolean; details?: unknown } = {}
|
|
19
|
+
): void {
|
|
20
|
+
const payload: JsonError = {
|
|
21
|
+
success: false,
|
|
22
|
+
error: {
|
|
23
|
+
code,
|
|
24
|
+
message,
|
|
25
|
+
retryable: options.retryable ?? false,
|
|
26
|
+
details: options.details,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
printJson(payload);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getErrorMessage(error: unknown): string {
|
|
34
|
+
return error instanceof Error ? error.message : String(error);
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ComponentSearchResult } from '@jlcpcb/core';
|
|
2
|
+
|
|
3
|
+
export function formatSearchResultForJson(result: ComponentSearchResult) {
|
|
4
|
+
return {
|
|
5
|
+
id: result.id,
|
|
6
|
+
id_type: result.idType,
|
|
7
|
+
lcsc_id: result.idType === 'lcsc' ? result.lcscId : undefined,
|
|
8
|
+
easyeda_uuid: result.idType === 'easyeda_uuid' ? result.easyedaUuid ?? result.id : undefined,
|
|
9
|
+
name: result.name,
|
|
10
|
+
manufacturer: result.manufacturer,
|
|
11
|
+
description: result.description,
|
|
12
|
+
package: result.package,
|
|
13
|
+
datasheet: result.datasheetPdf,
|
|
14
|
+
stock: result.stock,
|
|
15
|
+
price: result.price,
|
|
16
|
+
library_type: result.libraryType,
|
|
17
|
+
category: result.category,
|
|
18
|
+
attributes: result.attributes,
|
|
19
|
+
};
|
|
20
|
+
}
|