@jlcpcb/cli 0.1.1 → 0.3.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/CHANGELOG.md +72 -0
- package/dist/assets/search.html +528 -0
- package/dist/index.js +5432 -1442
- package/package.json +3 -2
- package/src/app/App.tsx +7 -3
- package/src/app/components/DetailView.tsx +2 -2
- package/src/app/components/EasyEDADetailView.tsx +132 -0
- package/src/app/navigation/NavigationContext.tsx +5 -1
- package/src/app/navigation/types.ts +8 -2
- package/src/app/screens/EasyEDAInfoScreen.tsx +150 -0
- package/src/app/screens/InfoScreen.tsx +23 -28
- package/src/commands/easyeda.ts +149 -0
- package/src/commands/info.ts +1 -1
- package/src/commands/install.ts +22 -15
- package/src/commands/library.ts +51 -2
- package/src/commands/search.ts +1 -1
- package/src/commands/validate.ts +287 -0
- package/src/index.ts +72 -3
package/src/commands/install.ts
CHANGED
|
@@ -11,6 +11,11 @@ import { renderApp } from '../app/App.js';
|
|
|
11
11
|
const componentService = createComponentService();
|
|
12
12
|
const libraryService = createLibraryService();
|
|
13
13
|
|
|
14
|
+
// LCSC IDs match pattern: C followed by digits (e.g., C2040, C5446)
|
|
15
|
+
function isLcscId(id: string): boolean {
|
|
16
|
+
return /^C\d+$/i.test(id);
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
interface InstallOptions {
|
|
15
20
|
projectPath?: string;
|
|
16
21
|
include3d?: boolean;
|
|
@@ -18,6 +23,13 @@ interface InstallOptions {
|
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
export async function installCommand(id: string | undefined, options: InstallOptions): Promise<void> {
|
|
26
|
+
// Check if ID looks like an EasyEDA UUID (not an LCSC ID)
|
|
27
|
+
if (id && !isLcscId(id)) {
|
|
28
|
+
p.log.error(`"${id}" is not an LCSC part number (e.g., C2040).`);
|
|
29
|
+
p.log.info(`For EasyEDA community components, use: ${chalk.cyan(`jlc easyeda install ${id}`)}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
// If ID provided with --force, do direct install (non-interactive)
|
|
22
34
|
if (id && options.force) {
|
|
23
35
|
const spinner = p.spinner();
|
|
@@ -56,18 +68,15 @@ export async function installCommand(id: string | undefined, options: InstallOpt
|
|
|
56
68
|
|
|
57
69
|
// If ID provided (without --force), fetch component and launch TUI for install
|
|
58
70
|
if (id) {
|
|
59
|
-
|
|
60
|
-
spinner.start(`Fetching component ${id}...`);
|
|
71
|
+
console.log(`Fetching component ${id}...`);
|
|
61
72
|
|
|
62
73
|
try {
|
|
63
74
|
const details = await componentService.getDetails(id);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
renderApp('info', { componentId: id, component: details as any });
|
|
75
|
+
// Clear the "Fetching..." line and launch interactive UI
|
|
76
|
+
process.stdout.write('\x1b[1A\x1b[2K');
|
|
77
|
+
await renderApp('info', { componentId: id, component: details as any });
|
|
68
78
|
} catch (error) {
|
|
69
|
-
|
|
70
|
-
p.log.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
79
|
+
console.error(`Failed to fetch component: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
71
80
|
process.exit(1);
|
|
72
81
|
}
|
|
73
82
|
return;
|
|
@@ -88,8 +97,7 @@ export async function installCommand(id: string | undefined, options: InstallOpt
|
|
|
88
97
|
process.exit(0);
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
spinner.start(`Searching for "${query}"...`);
|
|
100
|
+
console.log(`Searching for "${query}"...`);
|
|
93
101
|
|
|
94
102
|
const searchOptions: SearchOptions = { limit: 20 };
|
|
95
103
|
let results = await componentService.search(query as string, searchOptions);
|
|
@@ -101,13 +109,12 @@ export async function installCommand(id: string | undefined, options: InstallOpt
|
|
|
101
109
|
return 0;
|
|
102
110
|
});
|
|
103
111
|
|
|
104
|
-
spinner.stop(`Found ${results.length} results`);
|
|
105
|
-
|
|
106
112
|
if (results.length === 0) {
|
|
107
|
-
|
|
113
|
+
console.log('No components found. Try a different search term.');
|
|
108
114
|
return;
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
//
|
|
112
|
-
|
|
117
|
+
// Clear the "Searching..." line and launch interactive UI
|
|
118
|
+
process.stdout.write('\x1b[1A\x1b[2K');
|
|
119
|
+
await renderApp('search', { query: query as string, results });
|
|
113
120
|
}
|
package/src/commands/library.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as p from '@clack/prompts';
|
|
7
|
-
import
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { createLibraryService, type InstalledComponent } from '@jlcpcb/core';
|
|
8
9
|
import { renderApp } from '../app/App.js';
|
|
9
10
|
|
|
10
11
|
const libraryService = createLibraryService();
|
|
@@ -52,5 +53,53 @@ export async function libraryCommand(options: LibraryOptions): Promise<void> {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// Interactive mode - launch TUI
|
|
55
|
-
renderApp('library', {});
|
|
56
|
+
await renderApp('library', {});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface RegenerateOptions {
|
|
60
|
+
projectPath?: string;
|
|
61
|
+
include3d?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function regenerateCommand(options: RegenerateOptions): Promise<void> {
|
|
65
|
+
const include3d = options.include3d ?? true;
|
|
66
|
+
|
|
67
|
+
// First get the count
|
|
68
|
+
const components = await libraryService.listInstalled({ projectPath: options.projectPath });
|
|
69
|
+
|
|
70
|
+
if (components.length === 0) {
|
|
71
|
+
p.log.warn('No installed components found to regenerate.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
p.log.info(`Found ${chalk.cyan(components.length)} installed components to regenerate.`);
|
|
76
|
+
console.log();
|
|
77
|
+
|
|
78
|
+
let currentSpinner: ReturnType<typeof p.spinner> | null = null;
|
|
79
|
+
|
|
80
|
+
const result = await libraryService.regenerate({
|
|
81
|
+
projectPath: options.projectPath,
|
|
82
|
+
include3d,
|
|
83
|
+
onProgress: (current: number, total: number, component: InstalledComponent, status: 'start' | 'success' | 'error', error?: string) => {
|
|
84
|
+
if (status === 'start') {
|
|
85
|
+
currentSpinner = p.spinner();
|
|
86
|
+
currentSpinner.start(`[${current}/${total}] Regenerating ${chalk.cyan(component.name)} (${component.lcscId})...`);
|
|
87
|
+
} else if (status === 'success') {
|
|
88
|
+
currentSpinner?.stop(chalk.green(`✓ [${current}/${total}] ${component.name} (${component.lcscId})`));
|
|
89
|
+
} else if (status === 'error') {
|
|
90
|
+
currentSpinner?.stop(chalk.red(`✗ [${current}/${total}] ${component.name} (${component.lcscId}): ${error}`));
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log();
|
|
96
|
+
p.log.success(`Regeneration complete: ${chalk.green(result.success)} succeeded, ${chalk.red(result.failed)} failed`);
|
|
97
|
+
|
|
98
|
+
if (result.failed > 0) {
|
|
99
|
+
console.log();
|
|
100
|
+
p.log.warn('Failed components:');
|
|
101
|
+
for (const comp of result.components.filter(c => c.status === 'failed')) {
|
|
102
|
+
console.log(chalk.red(` • ${comp.name} (${comp.id}): ${comp.error}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
56
105
|
}
|
package/src/commands/search.ts
CHANGED
|
@@ -30,7 +30,7 @@ export async function searchCommand(query: string, options: SearchOptions): Prom
|
|
|
30
30
|
|
|
31
31
|
// Clear the "Searching..." line and launch interactive UI
|
|
32
32
|
process.stdout.write('\x1b[1A\x1b[2K');
|
|
33
|
-
renderApp('search', { query, results });
|
|
33
|
+
await renderApp('search', { query, results });
|
|
34
34
|
} catch (error) {
|
|
35
35
|
console.error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
36
36
|
process.exit(1);
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate command
|
|
3
|
+
* Compare generated footprints/symbols against JLCPCB reference
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as p from '@clack/prompts';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { writeFileSync } from 'fs';
|
|
9
|
+
import {
|
|
10
|
+
fetchReferenceSVG,
|
|
11
|
+
extractFromReferenceSVG,
|
|
12
|
+
extractFromKiCadFootprint,
|
|
13
|
+
compareFootprints,
|
|
14
|
+
formatComparisonResult,
|
|
15
|
+
extractSymbolFromReferenceSVG,
|
|
16
|
+
extractFromKiCadSymbol,
|
|
17
|
+
compareSymbols,
|
|
18
|
+
formatSymbolComparisonResult,
|
|
19
|
+
generateValidationReport,
|
|
20
|
+
generateBatchReport,
|
|
21
|
+
TEST_CATEGORIES,
|
|
22
|
+
getAllTestComponents,
|
|
23
|
+
getTestComponentsByCategory,
|
|
24
|
+
getCategoryNames,
|
|
25
|
+
renderFootprintSvg,
|
|
26
|
+
renderSymbolSvg,
|
|
27
|
+
type ValidationResult,
|
|
28
|
+
type ReportSvgs,
|
|
29
|
+
easyedaClient,
|
|
30
|
+
footprintConverter,
|
|
31
|
+
symbolConverter,
|
|
32
|
+
} from '@jlcpcb/core';
|
|
33
|
+
|
|
34
|
+
interface ValidateOptions {
|
|
35
|
+
footprintOnly?: boolean;
|
|
36
|
+
symbolOnly?: boolean;
|
|
37
|
+
category?: string;
|
|
38
|
+
all?: boolean;
|
|
39
|
+
html?: string;
|
|
40
|
+
json?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate a single component
|
|
45
|
+
*/
|
|
46
|
+
async function validateComponent(
|
|
47
|
+
lcscCode: string,
|
|
48
|
+
options: { footprint: boolean; symbol: boolean }
|
|
49
|
+
): Promise<ValidationResult> {
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
const normalizedCode = lcscCode.replace(/^C/i, '');
|
|
52
|
+
const fullCode = `C${normalizedCode}`;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Fetch reference SVG
|
|
56
|
+
const reference = await fetchReferenceSVG(fullCode);
|
|
57
|
+
|
|
58
|
+
// Fetch component data
|
|
59
|
+
const componentData = await easyedaClient.getComponentData(fullCode);
|
|
60
|
+
if (!componentData) {
|
|
61
|
+
return {
|
|
62
|
+
lcscCode: fullCode,
|
|
63
|
+
componentName: fullCode,
|
|
64
|
+
passed: false,
|
|
65
|
+
footprint: null,
|
|
66
|
+
symbol: null,
|
|
67
|
+
timestamp: new Date(),
|
|
68
|
+
durationMs: Date.now() - startTime,
|
|
69
|
+
error: 'Failed to fetch component data from EasyEDA',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let footprintResult = null;
|
|
74
|
+
let symbolResult = null;
|
|
75
|
+
|
|
76
|
+
// Validate footprint
|
|
77
|
+
if (options.footprint && reference.footprintSvg) {
|
|
78
|
+
const kicadContent = footprintConverter.convert(componentData, {});
|
|
79
|
+
|
|
80
|
+
const refData = extractFromReferenceSVG(reference.footprintSvg);
|
|
81
|
+
const genData = extractFromKiCadFootprint(kicadContent);
|
|
82
|
+
|
|
83
|
+
footprintResult = compareFootprints(refData, genData, {
|
|
84
|
+
sizeWarningsOnly: true,
|
|
85
|
+
positionTolerance: 50,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validate symbol
|
|
90
|
+
if (options.symbol && reference.symbolSvg) {
|
|
91
|
+
const kicadContent = symbolConverter.convert(componentData, {
|
|
92
|
+
symbolName: componentData.info.name,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const refData = extractSymbolFromReferenceSVG(reference.symbolSvg);
|
|
96
|
+
const genData = extractFromKiCadSymbol(kicadContent);
|
|
97
|
+
|
|
98
|
+
symbolResult = compareSymbols(refData, genData, {
|
|
99
|
+
positionTolerance: 50,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const passed =
|
|
104
|
+
(!footprintResult || footprintResult.passed) && (!symbolResult || symbolResult.passed);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
lcscCode: fullCode,
|
|
108
|
+
componentName: componentData.info.name || fullCode,
|
|
109
|
+
passed,
|
|
110
|
+
footprint: footprintResult,
|
|
111
|
+
symbol: symbolResult,
|
|
112
|
+
timestamp: new Date(),
|
|
113
|
+
durationMs: Date.now() - startTime,
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
lcscCode: fullCode,
|
|
118
|
+
componentName: fullCode,
|
|
119
|
+
passed: false,
|
|
120
|
+
footprint: null,
|
|
121
|
+
symbol: null,
|
|
122
|
+
timestamp: new Date(),
|
|
123
|
+
durationMs: Date.now() - startTime,
|
|
124
|
+
error: error instanceof Error ? error.message : String(error),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function validateCommand(
|
|
130
|
+
id: string | undefined,
|
|
131
|
+
options: ValidateOptions
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const validateFootprint = !options.symbolOnly;
|
|
134
|
+
const validateSymbol = !options.footprintOnly;
|
|
135
|
+
|
|
136
|
+
// Single component validation
|
|
137
|
+
if (id && !options.category && !options.all) {
|
|
138
|
+
const spinner = p.spinner();
|
|
139
|
+
spinner.start(`Validating ${id}...`);
|
|
140
|
+
|
|
141
|
+
const result = await validateComponent(id, {
|
|
142
|
+
footprint: validateFootprint,
|
|
143
|
+
symbol: validateSymbol,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
spinner.stop(result.passed ? chalk.green('✓ Validation passed') : chalk.red('✗ Validation failed'));
|
|
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
|
+
}
|
|
162
|
+
|
|
163
|
+
if (result.symbol) {
|
|
164
|
+
console.log(formatSymbolComparisonResult(result.symbol));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// JSON output
|
|
168
|
+
if (options.json) {
|
|
169
|
+
console.log(JSON.stringify(result, null, 2));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// HTML output
|
|
173
|
+
if (options.html) {
|
|
174
|
+
const reference = await fetchReferenceSVG(id);
|
|
175
|
+
const componentData = await easyedaClient.getComponentData(id);
|
|
176
|
+
|
|
177
|
+
const svgs: ReportSvgs = {
|
|
178
|
+
footprintRef: reference.footprintSvg || undefined,
|
|
179
|
+
symbolRef: reference.symbolSvg || undefined,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Generate our KiCad output and render to SVG
|
|
183
|
+
if (componentData) {
|
|
184
|
+
if (validateFootprint) {
|
|
185
|
+
const kicadFootprint = footprintConverter.convert(componentData, {});
|
|
186
|
+
svgs.footprintGen = renderFootprintSvg(kicadFootprint);
|
|
187
|
+
}
|
|
188
|
+
if (validateSymbol) {
|
|
189
|
+
const kicadSymbol = symbolConverter.convert(componentData, {
|
|
190
|
+
symbolName: componentData.info.name,
|
|
191
|
+
});
|
|
192
|
+
svgs.symbolGen = renderSymbolSvg(kicadSymbol);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const html = generateValidationReport(result, svgs);
|
|
197
|
+
writeFileSync(options.html, html);
|
|
198
|
+
console.log(chalk.dim(`HTML report: ${options.html}`));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
process.exit(result.passed ? 0 : 1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Batch validation
|
|
205
|
+
let components = options.all
|
|
206
|
+
? getAllTestComponents()
|
|
207
|
+
: options.category
|
|
208
|
+
? getTestComponentsByCategory(options.category)
|
|
209
|
+
: [];
|
|
210
|
+
|
|
211
|
+
if (components.length === 0 && !id) {
|
|
212
|
+
// Interactive category selection
|
|
213
|
+
const categories = getCategoryNames();
|
|
214
|
+
const selected = await p.select({
|
|
215
|
+
message: 'Select a category to validate:',
|
|
216
|
+
options: [
|
|
217
|
+
{ value: 'all', label: 'All categories' },
|
|
218
|
+
...categories.map((c) => ({ value: c, label: c })),
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (p.isCancel(selected)) {
|
|
223
|
+
p.cancel('Validation cancelled');
|
|
224
|
+
process.exit(0);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
components =
|
|
228
|
+
selected === 'all' ? getAllTestComponents() : getTestComponentsByCategory(selected as string);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Filter out skipped components
|
|
232
|
+
components = components.filter((c) => !c.skip);
|
|
233
|
+
|
|
234
|
+
if (components.length === 0) {
|
|
235
|
+
p.log.warn('No components to validate');
|
|
236
|
+
process.exit(0);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log(chalk.bold(`\nValidating ${components.length} components...\n`));
|
|
240
|
+
|
|
241
|
+
const results: ValidationResult[] = [];
|
|
242
|
+
let passed = 0;
|
|
243
|
+
let failed = 0;
|
|
244
|
+
let errors = 0;
|
|
245
|
+
|
|
246
|
+
for (const component of components) {
|
|
247
|
+
process.stdout.write(` ${component.lcsc.padEnd(12)} ${component.name.slice(0, 40).padEnd(42)} `);
|
|
248
|
+
|
|
249
|
+
const result = await validateComponent(component.lcsc, {
|
|
250
|
+
footprint: validateFootprint,
|
|
251
|
+
symbol: validateSymbol,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
results.push(result);
|
|
255
|
+
|
|
256
|
+
if (result.error) {
|
|
257
|
+
errors++;
|
|
258
|
+
console.log(chalk.yellow('⚠ ERROR'));
|
|
259
|
+
} else if (result.passed) {
|
|
260
|
+
passed++;
|
|
261
|
+
console.log(chalk.green('✓ PASS'));
|
|
262
|
+
} else {
|
|
263
|
+
failed++;
|
|
264
|
+
console.log(chalk.red('✗ FAIL'));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Summary
|
|
269
|
+
console.log();
|
|
270
|
+
console.log(chalk.bold('Summary:'));
|
|
271
|
+
console.log(` ${chalk.green(`${passed} passed`)} ${chalk.red(`${failed} failed`)} ${chalk.yellow(`${errors} errors`)}`);
|
|
272
|
+
console.log();
|
|
273
|
+
|
|
274
|
+
// JSON output
|
|
275
|
+
if (options.json) {
|
|
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}`));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
process.exit(failed > 0 || errors > 0 ? 1 : 0);
|
|
287
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,9 @@ import { Command } from 'commander';
|
|
|
9
9
|
import { searchCommand } from './commands/search.js';
|
|
10
10
|
import { infoCommand } from './commands/info.js';
|
|
11
11
|
import { installCommand } from './commands/install.js';
|
|
12
|
-
import { libraryCommand } from './commands/library.js';
|
|
12
|
+
import { libraryCommand, regenerateCommand } from './commands/library.js';
|
|
13
|
+
import { easyedaSearchCommand, easyedaInstallCommand } from './commands/easyeda.js';
|
|
14
|
+
import { validateCommand } from './commands/validate.js';
|
|
13
15
|
|
|
14
16
|
const program = new Command();
|
|
15
17
|
|
|
@@ -57,9 +59,14 @@ program
|
|
|
57
59
|
});
|
|
58
60
|
});
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
// Library subcommand group
|
|
63
|
+
const library = program
|
|
61
64
|
.command('library')
|
|
62
|
-
.description('
|
|
65
|
+
.description('JLC-MCP library management');
|
|
66
|
+
|
|
67
|
+
library
|
|
68
|
+
.command('status', { isDefault: true })
|
|
69
|
+
.description('View library status and installed components')
|
|
63
70
|
.option('--json', 'Output as JSON')
|
|
64
71
|
.action(async (options) => {
|
|
65
72
|
await libraryCommand({
|
|
@@ -67,4 +74,66 @@ program
|
|
|
67
74
|
});
|
|
68
75
|
});
|
|
69
76
|
|
|
77
|
+
library
|
|
78
|
+
.command('regenerate')
|
|
79
|
+
.description('Regenerate all installed components (refetch and reconvert symbols, footprints, 3D models)')
|
|
80
|
+
.option('-p, --project <path>', 'Regenerate project-local library')
|
|
81
|
+
.option('--no-3d', 'Skip 3D model download')
|
|
82
|
+
.action(async (options) => {
|
|
83
|
+
await regenerateCommand({
|
|
84
|
+
projectPath: options.project,
|
|
85
|
+
include3d: options['3d'],
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
program
|
|
90
|
+
.command('validate [id]')
|
|
91
|
+
.description('Validate footprint/symbol generation against JLCPCB reference')
|
|
92
|
+
.option('--footprint-only', 'Only validate footprint')
|
|
93
|
+
.option('--symbol-only', 'Only validate symbol')
|
|
94
|
+
.option('-c, --category <name>', 'Validate all components in category')
|
|
95
|
+
.option('-a, --all', 'Validate all test fixtures')
|
|
96
|
+
.option('--html <path>', 'Output HTML report to file')
|
|
97
|
+
.option('--json', 'Output as JSON')
|
|
98
|
+
.action(async (id, options) => {
|
|
99
|
+
await validateCommand(id, {
|
|
100
|
+
footprintOnly: options.footprintOnly,
|
|
101
|
+
symbolOnly: options.symbolOnly,
|
|
102
|
+
category: options.category,
|
|
103
|
+
all: options.all,
|
|
104
|
+
html: options.html,
|
|
105
|
+
json: options.json,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// EasyEDA subcommand group
|
|
110
|
+
const easyeda = program
|
|
111
|
+
.command('easyeda')
|
|
112
|
+
.description('EasyEDA community component browser');
|
|
113
|
+
|
|
114
|
+
easyeda
|
|
115
|
+
.command('search <query...>')
|
|
116
|
+
.description('Open browser-based component search')
|
|
117
|
+
.option('-p, --port <number>', 'HTTP server port', '3847')
|
|
118
|
+
.action(async (queryParts: string[], options) => {
|
|
119
|
+
const query = queryParts.join(' ');
|
|
120
|
+
await easyedaSearchCommand(query, {
|
|
121
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
easyeda
|
|
126
|
+
.command('install [uuid]')
|
|
127
|
+
.description('Install EasyEDA community component to KiCad libraries')
|
|
128
|
+
.option('-p, --project <path>', 'Install to project-local library')
|
|
129
|
+
.option('--with-3d', 'Include 3D model')
|
|
130
|
+
.option('-f, --force', 'Force reinstall (regenerate symbol and footprint)')
|
|
131
|
+
.action(async (uuid, options) => {
|
|
132
|
+
await easyedaInstallCommand(uuid, {
|
|
133
|
+
projectPath: options.project,
|
|
134
|
+
include3d: options.with3d,
|
|
135
|
+
force: options.force,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
70
139
|
program.parse();
|