@trops/dash-core 0.1.428 → 0.1.431

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.
@@ -472,6 +472,7 @@ const REGISTRY_SEARCH_DASHBOARDS = "registry:search-dashboards";
472
472
  const REGISTRY_SEARCH_THEMES = "registry:search-themes";
473
473
  const REGISTRY_PUBLISH_WIDGET = "registry:publish-widget";
474
474
  const REGISTRY_INSPECT_WIDGET_PACKAGE = "registry:inspect-widget-package";
475
+ const REGISTRY_SCAN_WIDGET_DEFAULTS = "registry:scan-widget-defaults";
475
476
  const REGISTRY_PREVIEW_FETCH = "registry:preview-fetch";
476
477
 
477
478
  var registryEvents$1 = {
@@ -483,6 +484,7 @@ var registryEvents$1 = {
483
484
  REGISTRY_SEARCH_THEMES,
484
485
  REGISTRY_PUBLISH_WIDGET,
485
486
  REGISTRY_INSPECT_WIDGET_PACKAGE,
487
+ REGISTRY_SCAN_WIDGET_DEFAULTS,
486
488
  REGISTRY_PREVIEW_FETCH,
487
489
  };
488
490
 
@@ -27268,7 +27270,7 @@ function findWidgetsDir$2(widgetPath) {
27268
27270
  * @param {string} widgetPath - Absolute path to the widget directory
27269
27271
  * @returns {Promise<string|null>} Path to the compiled bundle, or null if nothing to compile
27270
27272
  */
27271
- async function compileWidget(widgetPath) {
27273
+ async function compileWidget$1(widgetPath) {
27272
27274
  const widgetsDir = findWidgetsDir$2(widgetPath);
27273
27275
 
27274
27276
  if (!widgetsDir) {
@@ -27400,7 +27402,7 @@ async function compileWidget(widgetPath) {
27400
27402
  }
27401
27403
  }
27402
27404
 
27403
- var widgetCompiler$1 = { compileWidget, findWidgetsDir: findWidgetsDir$2 };
27405
+ var widgetCompiler$1 = { compileWidget: compileWidget$1, findWidgetsDir: findWidgetsDir$2 };
27404
27406
 
27405
27407
  /**
27406
27408
  * Dynamic Widget Loader
@@ -29416,25 +29418,31 @@ var schedulerController_1 = schedulerController$2;
29416
29418
 
29417
29419
  let bundlePath = findBundlePath(widget.path);
29418
29420
 
29419
- // Auto-compile if no bundle exists (same as read-all-bundles)
29421
+ // Auto-compile if no bundle exists (registry installs ship the
29422
+ // bundle pre-compiled, so this fallback only fires for older
29423
+ // packages or local installs).
29424
+ let compileError = null;
29420
29425
  if (!bundlePath) {
29421
29426
  try {
29422
29427
  const compiled = await compileWidget(widget.path);
29423
29428
  if (compiled) {
29424
29429
  bundlePath = compiled;
29425
29430
  }
29426
- } catch (compileError) {
29427
- console.warn(
29428
- `[WidgetRegistry] Could not compile ${widgetName}:`,
29429
- compileError,
29430
- );
29431
+ } catch (err) {
29432
+ compileError = err;
29433
+ // dash-core's electron build strips console.* — surface
29434
+ // the actual cause through the IPC return so the renderer
29435
+ // can show it to the user.
29431
29436
  }
29432
29437
  }
29433
29438
 
29434
29439
  if (!bundlePath) {
29440
+ const detail = compileError
29441
+ ? ` (auto-compile failed: ${compileError.message})`
29442
+ : "";
29435
29443
  return {
29436
29444
  success: false,
29437
- error: `No bundle found in: ${widget.path}`,
29445
+ error: `No bundle found in: ${widget.path}${detail}`,
29438
29446
  };
29439
29447
  }
29440
29448
 
@@ -64502,6 +64510,74 @@ function stripPersonalizationFromWorkspace$1(workspace) {
64502
64510
  return cleaned;
64503
64511
  }
64504
64512
 
64513
+ /**
64514
+ * Remap each layout item's `packageId` from a local-only scope (e.g.
64515
+ * `@ai-built/foo`) to the caller's published scope (`@<callerScope>/foo`)
64516
+ * so the installer's ComponentManager — which registers widgets under
64517
+ * the published scope — can look them up.
64518
+ *
64519
+ * Mirrors the scope-remap that `generateRegistryManifest` already does
64520
+ * for the dashboard's widget DEPENDENCIES list. Without this, the deps
64521
+ * list is correct (`@callerScope/foo`) but the per-instance layout
64522
+ * items still say `packageId: "@ai-built/foo"` — every Dependencies
64523
+ * tab + publish-flow attribution on the installer's machine misses.
64524
+ *
64525
+ * Returns a deep copy. Idempotent: items already under the caller
64526
+ * scope (or any non-local scope) pass through untouched.
64527
+ *
64528
+ * @param {Object} workspace
64529
+ * @param {string} callerScope - Publisher's registry username (e.g. "trops")
64530
+ * @param {string[]} [localOnlyScopes=["ai-built"]] - Scopes that must be remapped
64531
+ */
64532
+ function remapLayoutPackageScopes$1(
64533
+ workspace,
64534
+ callerScope,
64535
+ localOnlyScopes = ["ai-built"],
64536
+ ) {
64537
+ if (!workspace || !callerScope) return workspace;
64538
+ const localScopeSet = new Set(
64539
+ localOnlyScopes.map((s) => String(s).replace(/^@/, "")),
64540
+ );
64541
+ const remapPackageId = (pkgId) => {
64542
+ if (typeof pkgId !== "string" || pkgId.length === 0) return pkgId;
64543
+ // Match `@<scope>/<rest>` or `<scope>/<rest>`. Tolerant of either form.
64544
+ const m = pkgId.match(/^@?([^/]+)\/(.+)$/);
64545
+ if (!m) return pkgId;
64546
+ const scope = m[1];
64547
+ const rest = m[2];
64548
+ if (!localScopeSet.has(scope)) return pkgId;
64549
+ return `@${callerScope.replace(/^@/, "")}/${rest}`;
64550
+ };
64551
+ const remapItem = (item) => {
64552
+ if (!item || typeof item !== "object") return item;
64553
+ const next = { ...item };
64554
+ if (item.packageId) {
64555
+ const remapped = remapPackageId(item.packageId);
64556
+ if (remapped !== item.packageId) next.packageId = remapped;
64557
+ }
64558
+ if (item._sourcePackage) {
64559
+ const remapped = remapPackageId(item._sourcePackage);
64560
+ if (remapped !== item._sourcePackage) next._sourcePackage = remapped;
64561
+ }
64562
+ if (Array.isArray(item.items)) next.items = item.items.map(remapItem);
64563
+ if (Array.isArray(item.layout)) next.layout = item.layout.map(remapItem);
64564
+ return next;
64565
+ };
64566
+ const next = { ...workspace };
64567
+ if (Array.isArray(next.layout)) next.layout = next.layout.map(remapItem);
64568
+ if (Array.isArray(next.sidebarLayout)) {
64569
+ next.sidebarLayout = next.sidebarLayout.map(remapItem);
64570
+ }
64571
+ if (Array.isArray(next.pages)) {
64572
+ next.pages = next.pages.map((page) =>
64573
+ page && Array.isArray(page.layout)
64574
+ ? { ...page, layout: page.layout.map(remapItem) }
64575
+ : page,
64576
+ );
64577
+ }
64578
+ return next;
64579
+ }
64580
+
64505
64581
  var dashboardConfigUtils$1 = {
64506
64582
  collectComponentNames: collectComponentNames$1,
64507
64583
  collectComponentNamesFromWorkspace: collectComponentNamesFromWorkspace$1,
@@ -64518,6 +64594,7 @@ var dashboardConfigUtils$1 = {
64518
64594
  buildProviderSetupManifest,
64519
64595
  checkApiCompatibility,
64520
64596
  stripPersonalizationFromWorkspace: stripPersonalizationFromWorkspace$1,
64597
+ remapLayoutPackageScopes: remapLayoutPackageScopes$1,
64521
64598
  };
64522
64599
 
64523
64600
  /**
@@ -65559,6 +65636,7 @@ const {
65559
65636
  buildProviderRequirements,
65560
65637
  applyEventWiringToLayout,
65561
65638
  stripPersonalizationFromWorkspace,
65639
+ remapLayoutPackageScopes,
65562
65640
  } = dashboardConfigUtils$1;
65563
65641
  const { searchRegistry, getPackage } = registryController$3;
65564
65642
  const { getStoredToken, clearToken } = registryAuthController$2;
@@ -66978,7 +67056,33 @@ async function prepareDashboardForPublish$1(
66978
67056
  // widget's own `defaultValue` on each field never gets a chance.
66979
67057
  // Layout position, ordering, nested containers, and any title text
66980
67058
  // are preserved (they're part of the template, not personal).
66981
- const sharedWorkspace = stripPersonalizationFromWorkspace(workspace);
67059
+ let sharedWorkspace = stripPersonalizationFromWorkspace(workspace);
67060
+
67061
+ // Remap layout-item `packageId` from any local-only scope (e.g.
67062
+ // `@ai-built/foo`) to the publisher's scope (`@<callerScope>/foo`).
67063
+ // The widget-deps array in the dashboard manifest already gets
67064
+ // remapped (see generateRegistryManifest), but the per-instance
67065
+ // `packageId` on the layout items themselves was being shipped
67066
+ // as-is — so installers' ComponentManager (which registers
67067
+ // widgets under the published scope) couldn't reverse-look-up by
67068
+ // `packageId` on a layout item, breaking the Dependencies tab and
67069
+ // the publish-flow attribution on chained republishes.
67070
+ let resolvedCallerScope = options.callerScope || options.githubUser || "";
67071
+ if (!resolvedCallerScope) {
67072
+ try {
67073
+ const { getRegistryProfile } = registryAuthController$2;
67074
+ const profile = await getRegistryProfile();
67075
+ resolvedCallerScope = profile?.username || options.authorId || "";
67076
+ } catch {
67077
+ resolvedCallerScope = options.authorId || "";
67078
+ }
67079
+ }
67080
+ if (resolvedCallerScope) {
67081
+ sharedWorkspace = remapLayoutPackageScopes(
67082
+ sharedWorkspace,
67083
+ resolvedCallerScope,
67084
+ );
67085
+ }
66982
67086
  const layout = sharedWorkspace.layout || [];
66983
67087
 
66984
67088
  // 3. Build the dashboard config — walk main + pages + sidebar
@@ -73729,7 +73833,7 @@ const {
73729
73833
  } = registryAuthController$2;
73730
73834
  const widgetRegistryModule = widgetRegistryExports;
73731
73835
  const { dynamicWidgetLoader: dynamicWidgetLoader$1 } = dynamicWidgetLoaderExports;
73732
- const { findWidgetsDir } = widgetCompiler$1;
73836
+ const { findWidgetsDir, compileWidget } = widgetCompiler$1;
73733
73837
  const {
73734
73838
  resolveNextVersion,
73735
73839
  parsePackageName,
@@ -73790,11 +73894,160 @@ async function scanWidgetConfigs(widgetPath) {
73790
73894
  }
73791
73895
  }
73792
73896
 
73897
+ // ─── Publish-time defaults scan + staged rewrite ─────────────────────────────
73898
+
73899
+ /**
73900
+ * Scan a widget package's `.dash.js` files and return every non-empty
73901
+ * `userConfig[field].defaultValue` as a structured ref. Powers the
73902
+ * publish modal's "Verify defaults" step — surfaces values the
73903
+ * developer set during testing (regional paths, test tokens, etc.)
73904
+ * so the publisher can keep, blank, or edit each one before the ZIP
73905
+ * ships.
73906
+ *
73907
+ * @param {string} packageId e.g. "@ai-built/pipeline"
73908
+ * @returns {Promise<{success: boolean, defaults: Array, error?: string}>}
73909
+ */
73910
+ async function scanWidgetDefaults$1(packageId) {
73911
+ try {
73912
+ const registry = widgetRegistryModule.getWidgetRegistry();
73913
+ const widget = findWidget(registry, packageId);
73914
+ if (!widget || !widget.path) {
73915
+ return {
73916
+ success: false,
73917
+ error: `Widget package not found locally: ${packageId}`,
73918
+ };
73919
+ }
73920
+
73921
+ const configs = await scanWidgetConfigs(widget.path);
73922
+ const defaults = [];
73923
+ for (const cfg of configs) {
73924
+ const widgetName = cfg.component || cfg.name;
73925
+ if (!widgetName) continue;
73926
+ const userConfig = cfg.userConfig;
73927
+ if (!userConfig || typeof userConfig !== "object") continue;
73928
+ for (const [field, spec] of Object.entries(userConfig)) {
73929
+ if (!spec || typeof spec !== "object") continue;
73930
+ const value = spec.defaultValue;
73931
+ // "non-empty" = not nullish, not empty-string. `false` and `0`
73932
+ // are legitimate defaults (checkbox-off, numeric zero) so we
73933
+ // keep them. Arrays/objects only surface if non-empty.
73934
+ const isEmpty =
73935
+ value === null ||
73936
+ value === undefined ||
73937
+ value === "" ||
73938
+ (Array.isArray(value) && value.length === 0) ||
73939
+ (typeof value === "object" &&
73940
+ !Array.isArray(value) &&
73941
+ Object.keys(value).length === 0);
73942
+ if (isEmpty) continue;
73943
+ defaults.push({
73944
+ widgetName,
73945
+ field,
73946
+ currentDefault: value,
73947
+ displayName: spec.displayName || field,
73948
+ type: spec.type || "text",
73949
+ instructions: spec.instructions || "",
73950
+ });
73951
+ }
73952
+ }
73953
+ return { success: true, defaults };
73954
+ } catch (error) {
73955
+ console.error("[widgetRegistry] scanWidgetDefaults failed:", error);
73956
+ return { success: false, error: error.message };
73957
+ }
73958
+ }
73959
+
73960
+ /**
73961
+ * Return the exported-default object from a `.dash.js` serialized as
73962
+ * pretty-printed JS. We use JSON.stringify (plus a couple of minor
73963
+ * touch-ups) because dash configs are pure data — no functions, no
73964
+ * imports, no regex literals. The source file shape we emit matches
73965
+ * the scaffolded template so dynamicWidgetLoader reads it back
73966
+ * unchanged.
73967
+ */
73968
+ function serializeDashConfig(config) {
73969
+ const json = JSON.stringify(config, null, 4);
73970
+ return `export default ${json};\n`;
73971
+ }
73972
+
73973
+ /**
73974
+ * Copy a source widget directory into `dstDir`, then rewrite the
73975
+ * `userConfig[field].defaultValue` for every entry in `overrides`.
73976
+ * `overrides` shape: `{ [widgetName]: { [field]: newValue } }`.
73977
+ *
73978
+ * Returns the list of files that were actually rewritten (useful for
73979
+ * the UI / logs). Pure file-system side effect; does NOT touch the
73980
+ * original source directory.
73981
+ */
73982
+ async function stageOverrides(srcDir, dstDir, overrides) {
73983
+ // Copy the whole package tree into dstDir.
73984
+ fs.cpSync(srcDir, dstDir, {
73985
+ recursive: true,
73986
+ filter: (src) => {
73987
+ const base = path.basename(src);
73988
+ if (ZIP_EXCLUDE_DIRS.has(base)) return false;
73989
+ if (base.startsWith(".")) return false;
73990
+ return true;
73991
+ },
73992
+ });
73993
+
73994
+ if (!overrides || Object.keys(overrides).length === 0) return [];
73995
+
73996
+ const widgetsDir = findWidgetsDir(dstDir) || path.join(dstDir, "widgets");
73997
+ if (!fs.existsSync(widgetsDir)) return [];
73998
+
73999
+ const rewritten = [];
74000
+ const files = fs.readdirSync(widgetsDir);
74001
+ for (const file of files) {
74002
+ if (!file.endsWith(".dash.js")) continue;
74003
+ const filePath = path.join(widgetsDir, file);
74004
+ let cfg;
74005
+ try {
74006
+ // eslint-disable-next-line no-await-in-loop
74007
+ cfg = await dynamicWidgetLoader$1.loadConfigFile(filePath);
74008
+ } catch (err) {
74009
+ console.warn(
74010
+ `[widgetRegistry] Could not load ${file} for override: ${err.message}`,
74011
+ );
74012
+ continue;
74013
+ }
74014
+ if (!cfg || typeof cfg !== "object") continue;
74015
+ const widgetName = cfg.component || cfg.name;
74016
+ if (!widgetName || !overrides[widgetName]) continue;
74017
+ const fieldOverrides = overrides[widgetName];
74018
+
74019
+ if (!cfg.userConfig || typeof cfg.userConfig !== "object") continue;
74020
+ let changed = false;
74021
+ for (const [field, newValue] of Object.entries(fieldOverrides)) {
74022
+ if (!cfg.userConfig[field] || typeof cfg.userConfig[field] !== "object") {
74023
+ continue;
74024
+ }
74025
+ // Undefined = "no change" from the UI side. Explicit null / ""
74026
+ // = user wants to blank it out.
74027
+ if (newValue === undefined) continue;
74028
+ cfg.userConfig[field] = {
74029
+ ...cfg.userConfig[field],
74030
+ defaultValue: newValue,
74031
+ };
74032
+ changed = true;
74033
+ }
74034
+ if (!changed) continue;
74035
+
74036
+ fs.writeFileSync(filePath, serializeDashConfig(cfg));
74037
+ rewritten.push(file);
74038
+ }
74039
+ return rewritten;
74040
+ }
74041
+
73793
74042
  // ─── ZIP builder ─────────────────────────────────────────────────────────────
73794
74043
 
74044
+ // `dist` is intentionally NOT excluded — the publish flow runs
74045
+ // `compileWidget` against the staged package right before zipping so
74046
+ // the installer doesn't have to recompile (and fail silently because
74047
+ // `console.*` is stripped from dash-core's electron build, masking
74048
+ // any esbuild error). Shipping the bundle makes install zero-config.
73795
74049
  const ZIP_EXCLUDE_DIRS = new Set([
73796
74050
  "node_modules",
73797
- "dist",
73798
74051
  ".git",
73799
74052
  ".DS_Store",
73800
74053
  ".next",
@@ -73983,45 +74236,92 @@ async function prepareWidgetForPublish$1(appId, packageId, options = {}) {
73983
74236
  appOrigin: appId,
73984
74237
  });
73985
74238
 
73986
- // 7. Zip the widget directory to a temp file
74239
+ // 7. Zip the widget directory to a temp file. When the caller
74240
+ // supplied `defaultsOverride`, stage a copy of the package
74241
+ // under os.tmpdir() and rewrite the targeted
74242
+ // `userConfig[field].defaultValue` entries there before
74243
+ // zipping — source files on the publisher's machine stay
74244
+ // untouched.
73987
74245
  const zipName = `widget-${manifest.scope}-${manifest.name}-v${manifest.version}.zip`;
73988
74246
  const zipPath = path.join(app.getPath("temp"), zipName);
73989
- const zip = new AdmZip();
73990
- addDirToZip(zip, widget.path);
73991
- zip.writeZip(zipPath);
74247
+ const hasOverrides =
74248
+ options.defaultsOverride &&
74249
+ typeof options.defaultsOverride === "object" &&
74250
+ Object.keys(options.defaultsOverride).length > 0;
74251
+ const stagedDir = hasOverrides
74252
+ ? fs.mkdtempSync(path.join(app.getPath("temp"), `dash-publish-stage-`))
74253
+ : null;
74254
+ let registryResult;
74255
+ try {
74256
+ let sourceDir = widget.path;
74257
+ if (stagedDir) {
74258
+ await stageOverrides(widget.path, stagedDir, options.defaultsOverride);
74259
+ sourceDir = stagedDir;
74260
+ }
73992
74261
 
73993
- // 8. Publish to registry
73994
- const registryResult = await registryApiController$1.publishToRegistry(
73995
- zipPath,
73996
- manifest,
73997
- );
74262
+ // Compile the widget into `dist/index.cjs.js` before zipping.
74263
+ // The installer also runs compileWidget at first-load, but
74264
+ // dash-core strips `console.*` from its electron bundle, so any
74265
+ // esbuild error during install-time compile vanishes silently
74266
+ // and the user just sees `No bundle found`. Compiling at
74267
+ // publish time (where errors WILL surface to the publisher who
74268
+ // can fix them) and shipping the bundle in the ZIP makes
74269
+ // install zero-config.
74270
+ try {
74271
+ await compileWidget(sourceDir);
74272
+ } catch (compileErr) {
74273
+ return {
74274
+ success: false,
74275
+ error: `Widget compilation failed: ${compileErr.message}. Fix the source error and republish; otherwise installers won't be able to load the widget.`,
74276
+ manifest,
74277
+ };
74278
+ }
74279
+
74280
+ const zip = new AdmZip();
74281
+ addDirToZip(zip, sourceDir);
74282
+ zip.writeZip(zipPath);
74283
+
74284
+ // 8. Publish to registry
74285
+ registryResult = await registryApiController$1.publishToRegistry(
74286
+ zipPath,
74287
+ manifest,
74288
+ );
74289
+
74290
+ // 9. On failure: revert package.json (if we bumped) and surface details
74291
+ if (!registryResult.success) {
74292
+ if (newVersion !== previousVersion) {
74293
+ try {
74294
+ pkgJson.version = previousVersion;
74295
+ fs.writeFileSync(
74296
+ pkgJsonPath,
74297
+ JSON.stringify(pkgJson, null, 2) + "\n",
74298
+ );
74299
+ } catch {
74300
+ /* best effort */
74301
+ }
74302
+ }
74303
+ return {
74304
+ success: false,
74305
+ error: registryResult.error,
74306
+ details: registryResult.details,
74307
+ manifest,
74308
+ };
74309
+ }
73998
74310
 
73999
- // 9. On failure: revert package.json (if we bumped) and surface details
74000
- if (!registryResult.success) {
74001
- if (newVersion !== previousVersion) {
74311
+ // Clean up the temp zip on success.
74312
+ try {
74313
+ fs.unlinkSync(zipPath);
74314
+ } catch {
74315
+ /* ignore */
74316
+ }
74317
+ } finally {
74318
+ if (stagedDir) {
74002
74319
  try {
74003
- pkgJson.version = previousVersion;
74004
- fs.writeFileSync(
74005
- pkgJsonPath,
74006
- JSON.stringify(pkgJson, null, 2) + "\n",
74007
- );
74320
+ fs.rmSync(stagedDir, { recursive: true, force: true });
74008
74321
  } catch {
74009
74322
  /* best effort */
74010
74323
  }
74011
74324
  }
74012
- return {
74013
- success: false,
74014
- error: registryResult.error,
74015
- details: registryResult.details,
74016
- manifest,
74017
- };
74018
- }
74019
-
74020
- // Clean up the temp zip
74021
- try {
74022
- fs.unlinkSync(zipPath);
74023
- } catch {
74024
- /* ignore */
74025
74325
  }
74026
74326
 
74027
74327
  return {
@@ -74098,6 +74398,7 @@ async function inspectWidgetPackage$1(packageId) {
74098
74398
  var widgetRegistryController = {
74099
74399
  prepareWidgetForPublish: prepareWidgetForPublish$1,
74100
74400
  inspectWidgetPackage: inspectWidgetPackage$1,
74401
+ scanWidgetDefaults: scanWidgetDefaults$1,
74101
74402
  };
74102
74403
 
74103
74404
  /**
@@ -74215,6 +74516,7 @@ const {
74215
74516
  const {
74216
74517
  prepareWidgetForPublish,
74217
74518
  inspectWidgetPackage,
74519
+ scanWidgetDefaults,
74218
74520
  } = widgetRegistryController;
74219
74521
  const {
74220
74522
  assignRoles,
@@ -74307,6 +74609,7 @@ var controller = {
74307
74609
  checkThemeUpdatesForApp,
74308
74610
  prepareWidgetForPublish,
74309
74611
  inspectWidgetPackage,
74612
+ scanWidgetDefaults,
74310
74613
  assignRoles,
74311
74614
  matchTailwindFamily,
74312
74615
  generateThemeFromPalette,
@@ -75456,6 +75759,27 @@ const registryApi$2 = {
75456
75759
  }
75457
75760
  },
75458
75761
 
75762
+ /**
75763
+ * Scan a widget package's `.dash.js` configs and return the list of
75764
+ * non-empty `userConfig[field].defaultValue` entries. Powers the
75765
+ * publish modal's "Verify defaults" step — lets the publisher
75766
+ * review (and optionally blank / edit) values set during
75767
+ * development before the ZIP ships.
75768
+ *
75769
+ * @param {string} packageId - Widget packageId (e.g. "@scope/name")
75770
+ * @returns {Promise<Object>} { success, defaults: [{widgetName, field, currentDefault, displayName, type, instructions}], error? }
75771
+ */
75772
+ scanWidgetDefaults: async (packageId) => {
75773
+ try {
75774
+ return await ipcRenderer$h.invoke("registry:scan-widget-defaults", {
75775
+ packageId,
75776
+ });
75777
+ } catch (error) {
75778
+ console.error("[RegistryApi] Error scanning widget defaults:", error);
75779
+ throw error;
75780
+ }
75781
+ },
75782
+
75459
75783
  /**
75460
75784
  * Fetch a registry package's source (component + config + bundle) into a
75461
75785
  * temp directory and return the source strings without installing the