@ship-ui/core 0.19.2 → 0.19.4

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/bin/mcp/index.js CHANGED
@@ -13684,13 +13684,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
13684
13684
  const { name, arguments: args } = request.params;
13685
13685
  if (name === "search_components") {
13686
13686
  const { query } = args;
13687
- const results = components.filter((c) => c.name.toLowerCase().includes(query.toLowerCase()) || c.selector.toLowerCase().includes(query.toLowerCase()));
13687
+ const results = components.filter((c) => {
13688
+ const q = query.toLowerCase();
13689
+ return c.name.toLowerCase().includes(q) || c.selector.toLowerCase().includes(q) || c.keywords?.some((k) => k.toLowerCase().includes(q));
13690
+ });
13688
13691
  return {
13689
13692
  content: [
13690
13693
  {
13691
13694
  type: "text",
13692
- text: JSON.stringify(results.map((c) => ({ name: c.name, selector: c.selector, description: c.description?.split(`
13693
- `)[0] })), null, 2)
13695
+ text: JSON.stringify(results.map((c) => ({
13696
+ name: c.name,
13697
+ selector: c.selector,
13698
+ description: c.description?.split(`
13699
+ `)[0],
13700
+ keywords: c.keywords
13701
+ })), null, 2)
13694
13702
  }
13695
13703
  ]
13696
13704
  };
@@ -13760,6 +13768,9 @@ Here are the component details:
13760
13768
  Description:
13761
13769
  ${component.description || "No description available"}
13762
13770
 
13771
+ Keywords:
13772
+ ${component.keywords?.join(", ") || "None"}
13773
+
13763
13774
  Inputs:
13764
13775
  ${component.inputs.map((i) => `- ${i.name} (${i.type})${i.defaultValue ? ` (default: ${i.defaultValue})` : ""}${i.options ? ` [options: ${i.options.join(", ")}]` : ""}${i.description ? `: ${i.description}` : ""}`).join(`
13765
13776
  `)}
Binary file
package/bin/ship-fg.mjs CHANGED
@@ -3235,7 +3235,7 @@ import { parseArgs } from "util";
3235
3235
  // bin/src/ship-fg.ts
3236
3236
  import { spawnSync } from "child_process";
3237
3237
  import { watch } from "fs";
3238
- import { readFile as readFile2, writeFile } from "fs/promises";
3238
+ import { readFile as readFile2, writeFile, readdir, mkdir } from "fs/promises";
3239
3239
  import { dirname, join, resolve } from "path";
3240
3240
  import { fileURLToPath } from "url";
3241
3241
  import { gzipSync } from "zlib";
@@ -3326,6 +3326,104 @@ function formatFileSize(bytes, dm = 2) {
3326
3326
  var _dirname = dirname(fileURLToPath(import.meta.url));
3327
3327
  var CWD_PATH = process.cwd();
3328
3328
  var PHOSPHOR_SRC_PATH = resolve(CWD_PATH, "node_modules", "@phosphor-icons", "web", "src");
3329
+ var jsScannerFallback = async (PROJECT_SRC, shipUiDir, CWD_PATH2) => {
3330
+ const uniqueIcons = new Set;
3331
+ const isValidIcon = (s) => /^[a-z0-9-]+$/.test(s);
3332
+ try {
3333
+ const pkgPath = resolve(shipUiDir, "package.json");
3334
+ const pkgData = JSON.parse(await readFile2(pkgPath, "utf8"));
3335
+ if (pkgData.libraryIcons) {
3336
+ pkgData.libraryIcons.forEach((i) => uniqueIcons.add(i));
3337
+ }
3338
+ } catch (e) {}
3339
+ async function scanDir(dir) {
3340
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
3341
+ for (const entry of entries) {
3342
+ if (entry.name === "node_modules" || entry.name.startsWith("."))
3343
+ continue;
3344
+ const fullPath = resolve(dir, entry.name);
3345
+ if (entry.isDirectory()) {
3346
+ await scanDir(fullPath);
3347
+ } else if (entry.name.endsWith(".html") || entry.name.endsWith(".ts")) {
3348
+ const contents = await readFile2(fullPath, "utf8");
3349
+ const htmlStartMatches = contents.matchAll(/<sh-icon[^>]*icon=['"]([^'"]+)['"][^>]*>/g);
3350
+ for (const m of htmlStartMatches) {
3351
+ const icon = m[1].trim();
3352
+ if (isValidIcon(icon))
3353
+ uniqueIcons.add(icon);
3354
+ }
3355
+ const htmlContentMatches = contents.matchAll(/<sh-icon[^>]*>([^<]+)<\/sh-icon>/g);
3356
+ for (const m of htmlContentMatches) {
3357
+ const icon = m[1].trim();
3358
+ if (isValidIcon(icon))
3359
+ uniqueIcons.add(icon);
3360
+ }
3361
+ const tsMatches = contents.matchAll(/shicon:([^'"]+)['"]/g);
3362
+ for (const m of tsMatches) {
3363
+ const icon = m[1].trim();
3364
+ if (isValidIcon(icon))
3365
+ uniqueIcons.add(icon);
3366
+ }
3367
+ }
3368
+ }
3369
+ }
3370
+ await scanDir(PROJECT_SRC);
3371
+ const variants = ["bold", "thin", "light", "fill", "regular", "duotone"];
3372
+ const glyphMaps = new Map;
3373
+ for (const variant of variants) {
3374
+ try {
3375
+ const selPath = resolve(CWD_PATH2, "node_modules", "@phosphor-icons", "web", "src", variant, "selection.json");
3376
+ const parsed = JSON.parse(await readFile2(selPath, "utf8"));
3377
+ const isDuotone = variant === "duotone";
3378
+ for (const icon of parsed.icons || []) {
3379
+ const hexCode = icon.properties.code.toString(16);
3380
+ const unicodeStr = `U+${hexCode}`;
3381
+ const glyph = String.fromCodePoint(icon.properties.code);
3382
+ let glyphName = isDuotone ? icon.properties.name : icon.properties.ligatures;
3383
+ if (glyphName)
3384
+ glyphMaps.set(glyphName, [glyph, unicodeStr]);
3385
+ }
3386
+ } catch (e) {}
3387
+ }
3388
+ const grouped = {
3389
+ bold: [],
3390
+ thin: [],
3391
+ light: [],
3392
+ fill: [],
3393
+ regular: [],
3394
+ duotone: [],
3395
+ text: [],
3396
+ missing: []
3397
+ };
3398
+ for (const icon of uniqueIcons) {
3399
+ const isBold = icon.endsWith("-bold");
3400
+ const isThin = icon.endsWith("-thin");
3401
+ const isLight = icon.endsWith("-light");
3402
+ const isFill = icon.endsWith("-fill");
3403
+ const isDuotone = icon.endsWith("-duotone");
3404
+ const isRegular = !isBold && !isThin && !isLight && !isFill && !isDuotone;
3405
+ const mapEntry = glyphMaps.get(icon);
3406
+ if (mapEntry) {
3407
+ const tuple1 = [icon, ""];
3408
+ const tuple2 = mapEntry;
3409
+ if (isBold)
3410
+ grouped.bold.push(tuple1, tuple2);
3411
+ else if (isThin)
3412
+ grouped.thin.push(tuple1, tuple2);
3413
+ else if (isLight)
3414
+ grouped.light.push(tuple1, tuple2);
3415
+ else if (isFill)
3416
+ grouped.fill.push(tuple1, tuple2);
3417
+ else if (isDuotone)
3418
+ grouped.duotone.push(tuple1, tuple2);
3419
+ else
3420
+ grouped.regular.push(tuple1, tuple2);
3421
+ } else {
3422
+ grouped.missing.push(icon);
3423
+ }
3424
+ }
3425
+ return grouped;
3426
+ };
3329
3427
  var writtenCssSize = 0;
3330
3428
  var compressedCssSize = 0;
3331
3429
  var watchers = [];
@@ -3339,15 +3437,20 @@ var run = async (PROJECT_SRC, PROJECT_PUBLIC, TARGET_FONT_TYPE, values) => {
3339
3437
  try {
3340
3438
  const proc = spawnSync(scannerPath, [PROJECT_SRC, shipUiDir, CWD_PATH]);
3341
3439
  if (proc.error || proc.status !== 0) {
3342
- console.error("Error running scanner:", proc.stderr?.toString() || proc.error);
3343
3440
  throw new Error("Native scanner failed");
3344
3441
  }
3345
- const parsed = JSON.parse(proc.stdout?.toString() || "{}");
3346
- groupedIcons = parsed;
3347
- missingIconsArray = parsed.missing || [];
3442
+ const { missing, ...rest } = JSON.parse(proc.stdout?.toString() || "{}");
3443
+ groupedIcons = rest;
3444
+ missingIconsArray = missing || [];
3348
3445
  } catch (err) {
3349
- console.error("Failed to run high-performance zig scanner:", err);
3350
- throw err;
3446
+ try {
3447
+ const { missing, ...rest } = await jsScannerFallback(PROJECT_SRC, shipUiDir, CWD_PATH);
3448
+ groupedIcons = rest;
3449
+ missingIconsArray = missing || [];
3450
+ } catch (fallbackErr) {
3451
+ console.error("Failed to run high-performance zig scanner and the javascript fallback failed:", fallbackErr);
3452
+ throw err;
3453
+ }
3351
3454
  }
3352
3455
  if (missingIconsArray.length) {
3353
3456
  console.log(`Following icons does not exist in font:
@@ -3473,7 +3576,12 @@ var textMateSnippet = async (GLYPH_MAP) => {
3473
3576
  }
3474
3577
  }
3475
3578
  `;
3476
- await writeFile("./.vscode/html.code-snippets", iconsSnippetContent);
3579
+ try {
3580
+ await mkdir("./.vscode", { recursive: true });
3581
+ await writeFile("./.vscode/html.code-snippets", iconsSnippetContent);
3582
+ } catch (error) {
3583
+ console.warn("⚠️ Could not generate VS Code snippets:", error);
3584
+ }
3477
3585
  };
3478
3586
  function capitalize(str) {
3479
3587
  return str.charAt(0).toUpperCase() + str.slice(1);
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { spawnSync } from 'child_process';
4
4
  import { FSWatcher, watch } from 'fs';
5
- import { readFile, writeFile } from 'fs/promises';
5
+ import { readFile, writeFile, readdir, mkdir } from 'fs/promises';
6
6
  import { dirname, join, resolve } from 'path';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { gzipSync } from 'zlib';
@@ -16,6 +16,107 @@ import { formatFileSize, InputArguments, SupportedFontTypes } from './utilities'
16
16
  const CWD_PATH = process.cwd();
17
17
  const PHOSPHOR_SRC_PATH = resolve(CWD_PATH, 'node_modules', '@phosphor-icons', 'web', 'src');
18
18
 
19
+ const jsScannerFallback = async (PROJECT_SRC: string, shipUiDir: string, CWD_PATH: string) => {
20
+ const uniqueIcons = new Set<string>();
21
+
22
+ const isValidIcon = (s: string) => /^[a-z0-9-]+$/.test(s);
23
+
24
+ try {
25
+ const pkgPath = resolve(shipUiDir, 'package.json');
26
+ const pkgData = JSON.parse(await readFile(pkgPath, 'utf8'));
27
+ if (pkgData.libraryIcons) {
28
+ pkgData.libraryIcons.forEach((i: string) => uniqueIcons.add(i));
29
+ }
30
+ } catch (e) {}
31
+
32
+ async function scanDir(dir: string) {
33
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
34
+ for (const entry of entries) {
35
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
36
+ const fullPath = resolve(dir, entry.name);
37
+ if (entry.isDirectory()) {
38
+ await scanDir(fullPath);
39
+ } else if (entry.name.endsWith('.html') || entry.name.endsWith('.ts')) {
40
+ const contents = await readFile(fullPath, 'utf8');
41
+
42
+ const htmlStartMatches = contents.matchAll(/<sh-icon[^>]*icon=['"]([^'"]+)['"][^>]*>/g);
43
+ for (const m of htmlStartMatches) {
44
+ const icon = m[1].trim();
45
+ if (isValidIcon(icon)) uniqueIcons.add(icon);
46
+ }
47
+ const htmlContentMatches = contents.matchAll(/<sh-icon[^>]*>([^<]+)<\/sh-icon>/g);
48
+ for (const m of htmlContentMatches) {
49
+ const icon = m[1].trim();
50
+ if (isValidIcon(icon)) uniqueIcons.add(icon);
51
+ }
52
+
53
+ const tsMatches = contents.matchAll(/shicon:([^'"]+)['"]/g);
54
+ for (const m of tsMatches) {
55
+ const icon = m[1].trim();
56
+ if (isValidIcon(icon)) uniqueIcons.add(icon);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ await scanDir(PROJECT_SRC);
62
+
63
+ const variants = ['bold', 'thin', 'light', 'fill', 'regular', 'duotone'];
64
+ const glyphMaps = new Map<string, [string, string]>();
65
+
66
+ for (const variant of variants) {
67
+ try {
68
+ const selPath = resolve(CWD_PATH, 'node_modules', '@phosphor-icons', 'web', 'src', variant, 'selection.json');
69
+ const parsed = JSON.parse(await readFile(selPath, 'utf8'));
70
+ const isDuotone = variant === 'duotone';
71
+
72
+ for (const icon of parsed.icons || []) {
73
+ const hexCode = icon.properties.code.toString(16);
74
+ const unicodeStr = `U+${hexCode}`;
75
+ const glyph = String.fromCodePoint(icon.properties.code);
76
+
77
+ let glyphName = isDuotone ? icon.properties.name : icon.properties.ligatures;
78
+ if (glyphName) glyphMaps.set(glyphName, [glyph, unicodeStr]);
79
+ }
80
+ } catch (e) {}
81
+ }
82
+
83
+ const grouped = {
84
+ bold: [] as [string, string][],
85
+ thin: [] as [string, string][],
86
+ light: [] as [string, string][],
87
+ fill: [] as [string, string][],
88
+ regular: [] as [string, string][],
89
+ duotone: [] as [string, string][],
90
+ text: [] as [string, string][],
91
+ missing: [] as string[],
92
+ };
93
+
94
+ for (const icon of uniqueIcons) {
95
+ const isBold = icon.endsWith('-bold');
96
+ const isThin = icon.endsWith('-thin');
97
+ const isLight = icon.endsWith('-light');
98
+ const isFill = icon.endsWith('-fill');
99
+ const isDuotone = icon.endsWith('-duotone');
100
+ const isRegular = !isBold && !isThin && !isLight && !isFill && !isDuotone;
101
+
102
+ const mapEntry = glyphMaps.get(icon);
103
+ if (mapEntry) {
104
+ const tuple1: [string, string] = [icon, ''];
105
+ const tuple2 = mapEntry; // [glyph, unicodeStr]
106
+ if (isBold) grouped.bold.push(tuple1, tuple2);
107
+ else if (isThin) grouped.thin.push(tuple1, tuple2);
108
+ else if (isLight) grouped.light.push(tuple1, tuple2);
109
+ else if (isFill) grouped.fill.push(tuple1, tuple2);
110
+ else if (isDuotone) grouped.duotone.push(tuple1, tuple2);
111
+ else grouped.regular.push(tuple1, tuple2);
112
+ } else {
113
+ grouped.missing.push(icon);
114
+ }
115
+ }
116
+
117
+ return grouped;
118
+ };
119
+
19
120
  let writtenCssSize = 0;
20
121
  let compressedCssSize = 0;
21
122
  let watchers: FSWatcher[] = [];
@@ -37,16 +138,21 @@ const run = async (
37
138
  try {
38
139
  const proc = spawnSync(scannerPath, [PROJECT_SRC, shipUiDir, CWD_PATH]);
39
140
  if (proc.error || proc.status !== 0) {
40
- console.error('Error running scanner:', proc.stderr?.toString() || proc.error);
41
141
  throw new Error('Native scanner failed');
42
142
  }
43
143
 
44
- const parsed = JSON.parse(proc.stdout?.toString() || '{}');
45
- groupedIcons = parsed;
46
- missingIconsArray = parsed.missing || [];
144
+ const { missing, ...rest } = JSON.parse(proc.stdout?.toString() || '{}');
145
+ groupedIcons = rest;
146
+ missingIconsArray = missing || [];
47
147
  } catch (err) {
48
- console.error('Failed to run high-performance zig scanner:', err);
49
- throw err;
148
+ try {
149
+ const { missing, ...rest } = await jsScannerFallback(PROJECT_SRC, shipUiDir, CWD_PATH);
150
+ groupedIcons = rest;
151
+ missingIconsArray = missing || [];
152
+ } catch (fallbackErr) {
153
+ console.error('Failed to run high-performance zig scanner and the javascript fallback failed:', fallbackErr);
154
+ throw err;
155
+ }
50
156
  }
51
157
 
52
158
  if (missingIconsArray.length) {
@@ -203,7 +309,13 @@ const textMateSnippet = async (GLYPH_MAP: Record<string, [string, string]>) => {
203
309
  }
204
310
  `;
205
311
 
206
- await writeFile('./.vscode/html.code-snippets', iconsSnippetContent);
312
+ try {
313
+ await mkdir('./.vscode', { recursive: true });
314
+ await writeFile('./.vscode/html.code-snippets', iconsSnippetContent);
315
+ } catch (error) {
316
+ // Gracefully ignore snippet generation failures (e.g. in read-only CI environments)
317
+ console.warn('⚠️ Could not generate VS Code snippets:', error);
318
+ }
207
319
  };
208
320
 
209
321
  function capitalize(str: string) {