@trops/dash-core 0.1.428 → 0.1.429

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
 
@@ -73790,6 +73792,151 @@ async function scanWidgetConfigs(widgetPath) {
73790
73792
  }
73791
73793
  }
73792
73794
 
73795
+ // ─── Publish-time defaults scan + staged rewrite ─────────────────────────────
73796
+
73797
+ /**
73798
+ * Scan a widget package's `.dash.js` files and return every non-empty
73799
+ * `userConfig[field].defaultValue` as a structured ref. Powers the
73800
+ * publish modal's "Verify defaults" step — surfaces values the
73801
+ * developer set during testing (regional paths, test tokens, etc.)
73802
+ * so the publisher can keep, blank, or edit each one before the ZIP
73803
+ * ships.
73804
+ *
73805
+ * @param {string} packageId e.g. "@ai-built/pipeline"
73806
+ * @returns {Promise<{success: boolean, defaults: Array, error?: string}>}
73807
+ */
73808
+ async function scanWidgetDefaults$1(packageId) {
73809
+ try {
73810
+ const registry = widgetRegistryModule.getWidgetRegistry();
73811
+ const widget = findWidget(registry, packageId);
73812
+ if (!widget || !widget.path) {
73813
+ return {
73814
+ success: false,
73815
+ error: `Widget package not found locally: ${packageId}`,
73816
+ };
73817
+ }
73818
+
73819
+ const configs = await scanWidgetConfigs(widget.path);
73820
+ const defaults = [];
73821
+ for (const cfg of configs) {
73822
+ const widgetName = cfg.component || cfg.name;
73823
+ if (!widgetName) continue;
73824
+ const userConfig = cfg.userConfig;
73825
+ if (!userConfig || typeof userConfig !== "object") continue;
73826
+ for (const [field, spec] of Object.entries(userConfig)) {
73827
+ if (!spec || typeof spec !== "object") continue;
73828
+ const value = spec.defaultValue;
73829
+ // "non-empty" = not nullish, not empty-string. `false` and `0`
73830
+ // are legitimate defaults (checkbox-off, numeric zero) so we
73831
+ // keep them. Arrays/objects only surface if non-empty.
73832
+ const isEmpty =
73833
+ value === null ||
73834
+ value === undefined ||
73835
+ value === "" ||
73836
+ (Array.isArray(value) && value.length === 0) ||
73837
+ (typeof value === "object" &&
73838
+ !Array.isArray(value) &&
73839
+ Object.keys(value).length === 0);
73840
+ if (isEmpty) continue;
73841
+ defaults.push({
73842
+ widgetName,
73843
+ field,
73844
+ currentDefault: value,
73845
+ displayName: spec.displayName || field,
73846
+ type: spec.type || "text",
73847
+ instructions: spec.instructions || "",
73848
+ });
73849
+ }
73850
+ }
73851
+ return { success: true, defaults };
73852
+ } catch (error) {
73853
+ console.error("[widgetRegistry] scanWidgetDefaults failed:", error);
73854
+ return { success: false, error: error.message };
73855
+ }
73856
+ }
73857
+
73858
+ /**
73859
+ * Return the exported-default object from a `.dash.js` serialized as
73860
+ * pretty-printed JS. We use JSON.stringify (plus a couple of minor
73861
+ * touch-ups) because dash configs are pure data — no functions, no
73862
+ * imports, no regex literals. The source file shape we emit matches
73863
+ * the scaffolded template so dynamicWidgetLoader reads it back
73864
+ * unchanged.
73865
+ */
73866
+ function serializeDashConfig(config) {
73867
+ const json = JSON.stringify(config, null, 4);
73868
+ return `export default ${json};\n`;
73869
+ }
73870
+
73871
+ /**
73872
+ * Copy a source widget directory into `dstDir`, then rewrite the
73873
+ * `userConfig[field].defaultValue` for every entry in `overrides`.
73874
+ * `overrides` shape: `{ [widgetName]: { [field]: newValue } }`.
73875
+ *
73876
+ * Returns the list of files that were actually rewritten (useful for
73877
+ * the UI / logs). Pure file-system side effect; does NOT touch the
73878
+ * original source directory.
73879
+ */
73880
+ async function stageOverrides(srcDir, dstDir, overrides) {
73881
+ // Copy the whole package tree into dstDir.
73882
+ fs.cpSync(srcDir, dstDir, {
73883
+ recursive: true,
73884
+ filter: (src) => {
73885
+ const base = path.basename(src);
73886
+ if (ZIP_EXCLUDE_DIRS.has(base)) return false;
73887
+ if (base.startsWith(".")) return false;
73888
+ return true;
73889
+ },
73890
+ });
73891
+
73892
+ if (!overrides || Object.keys(overrides).length === 0) return [];
73893
+
73894
+ const widgetsDir = findWidgetsDir(dstDir) || path.join(dstDir, "widgets");
73895
+ if (!fs.existsSync(widgetsDir)) return [];
73896
+
73897
+ const rewritten = [];
73898
+ const files = fs.readdirSync(widgetsDir);
73899
+ for (const file of files) {
73900
+ if (!file.endsWith(".dash.js")) continue;
73901
+ const filePath = path.join(widgetsDir, file);
73902
+ let cfg;
73903
+ try {
73904
+ // eslint-disable-next-line no-await-in-loop
73905
+ cfg = await dynamicWidgetLoader$1.loadConfigFile(filePath);
73906
+ } catch (err) {
73907
+ console.warn(
73908
+ `[widgetRegistry] Could not load ${file} for override: ${err.message}`,
73909
+ );
73910
+ continue;
73911
+ }
73912
+ if (!cfg || typeof cfg !== "object") continue;
73913
+ const widgetName = cfg.component || cfg.name;
73914
+ if (!widgetName || !overrides[widgetName]) continue;
73915
+ const fieldOverrides = overrides[widgetName];
73916
+
73917
+ if (!cfg.userConfig || typeof cfg.userConfig !== "object") continue;
73918
+ let changed = false;
73919
+ for (const [field, newValue] of Object.entries(fieldOverrides)) {
73920
+ if (!cfg.userConfig[field] || typeof cfg.userConfig[field] !== "object") {
73921
+ continue;
73922
+ }
73923
+ // Undefined = "no change" from the UI side. Explicit null / ""
73924
+ // = user wants to blank it out.
73925
+ if (newValue === undefined) continue;
73926
+ cfg.userConfig[field] = {
73927
+ ...cfg.userConfig[field],
73928
+ defaultValue: newValue,
73929
+ };
73930
+ changed = true;
73931
+ }
73932
+ if (!changed) continue;
73933
+
73934
+ fs.writeFileSync(filePath, serializeDashConfig(cfg));
73935
+ rewritten.push(file);
73936
+ }
73937
+ return rewritten;
73938
+ }
73939
+
73793
73940
  // ─── ZIP builder ─────────────────────────────────────────────────────────────
73794
73941
 
73795
73942
  const ZIP_EXCLUDE_DIRS = new Set([
@@ -73983,45 +74130,73 @@ async function prepareWidgetForPublish$1(appId, packageId, options = {}) {
73983
74130
  appOrigin: appId,
73984
74131
  });
73985
74132
 
73986
- // 7. Zip the widget directory to a temp file
74133
+ // 7. Zip the widget directory to a temp file. When the caller
74134
+ // supplied `defaultsOverride`, stage a copy of the package
74135
+ // under os.tmpdir() and rewrite the targeted
74136
+ // `userConfig[field].defaultValue` entries there before
74137
+ // zipping — source files on the publisher's machine stay
74138
+ // untouched.
73987
74139
  const zipName = `widget-${manifest.scope}-${manifest.name}-v${manifest.version}.zip`;
73988
74140
  const zipPath = path.join(app.getPath("temp"), zipName);
73989
- const zip = new AdmZip();
73990
- addDirToZip(zip, widget.path);
73991
- zip.writeZip(zipPath);
74141
+ const hasOverrides =
74142
+ options.defaultsOverride &&
74143
+ typeof options.defaultsOverride === "object" &&
74144
+ Object.keys(options.defaultsOverride).length > 0;
74145
+ const stagedDir = hasOverrides
74146
+ ? fs.mkdtempSync(path.join(app.getPath("temp"), `dash-publish-stage-`))
74147
+ : null;
74148
+ let registryResult;
74149
+ try {
74150
+ let sourceDir = widget.path;
74151
+ if (stagedDir) {
74152
+ await stageOverrides(widget.path, stagedDir, options.defaultsOverride);
74153
+ sourceDir = stagedDir;
74154
+ }
74155
+ const zip = new AdmZip();
74156
+ addDirToZip(zip, sourceDir);
74157
+ zip.writeZip(zipPath);
73992
74158
 
73993
- // 8. Publish to registry
73994
- const registryResult = await registryApiController$1.publishToRegistry(
73995
- zipPath,
73996
- manifest,
73997
- );
74159
+ // 8. Publish to registry
74160
+ registryResult = await registryApiController$1.publishToRegistry(
74161
+ zipPath,
74162
+ manifest,
74163
+ );
73998
74164
 
73999
- // 9. On failure: revert package.json (if we bumped) and surface details
74000
- if (!registryResult.success) {
74001
- if (newVersion !== previousVersion) {
74165
+ // 9. On failure: revert package.json (if we bumped) and surface details
74166
+ if (!registryResult.success) {
74167
+ if (newVersion !== previousVersion) {
74168
+ try {
74169
+ pkgJson.version = previousVersion;
74170
+ fs.writeFileSync(
74171
+ pkgJsonPath,
74172
+ JSON.stringify(pkgJson, null, 2) + "\n",
74173
+ );
74174
+ } catch {
74175
+ /* best effort */
74176
+ }
74177
+ }
74178
+ return {
74179
+ success: false,
74180
+ error: registryResult.error,
74181
+ details: registryResult.details,
74182
+ manifest,
74183
+ };
74184
+ }
74185
+
74186
+ // Clean up the temp zip on success.
74187
+ try {
74188
+ fs.unlinkSync(zipPath);
74189
+ } catch {
74190
+ /* ignore */
74191
+ }
74192
+ } finally {
74193
+ if (stagedDir) {
74002
74194
  try {
74003
- pkgJson.version = previousVersion;
74004
- fs.writeFileSync(
74005
- pkgJsonPath,
74006
- JSON.stringify(pkgJson, null, 2) + "\n",
74007
- );
74195
+ fs.rmSync(stagedDir, { recursive: true, force: true });
74008
74196
  } catch {
74009
74197
  /* best effort */
74010
74198
  }
74011
74199
  }
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
74200
  }
74026
74201
 
74027
74202
  return {
@@ -74098,6 +74273,7 @@ async function inspectWidgetPackage$1(packageId) {
74098
74273
  var widgetRegistryController = {
74099
74274
  prepareWidgetForPublish: prepareWidgetForPublish$1,
74100
74275
  inspectWidgetPackage: inspectWidgetPackage$1,
74276
+ scanWidgetDefaults: scanWidgetDefaults$1,
74101
74277
  };
74102
74278
 
74103
74279
  /**
@@ -74215,6 +74391,7 @@ const {
74215
74391
  const {
74216
74392
  prepareWidgetForPublish,
74217
74393
  inspectWidgetPackage,
74394
+ scanWidgetDefaults,
74218
74395
  } = widgetRegistryController;
74219
74396
  const {
74220
74397
  assignRoles,
@@ -74307,6 +74484,7 @@ var controller = {
74307
74484
  checkThemeUpdatesForApp,
74308
74485
  prepareWidgetForPublish,
74309
74486
  inspectWidgetPackage,
74487
+ scanWidgetDefaults,
74310
74488
  assignRoles,
74311
74489
  matchTailwindFamily,
74312
74490
  generateThemeFromPalette,
@@ -75456,6 +75634,27 @@ const registryApi$2 = {
75456
75634
  }
75457
75635
  },
75458
75636
 
75637
+ /**
75638
+ * Scan a widget package's `.dash.js` configs and return the list of
75639
+ * non-empty `userConfig[field].defaultValue` entries. Powers the
75640
+ * publish modal's "Verify defaults" step — lets the publisher
75641
+ * review (and optionally blank / edit) values set during
75642
+ * development before the ZIP ships.
75643
+ *
75644
+ * @param {string} packageId - Widget packageId (e.g. "@scope/name")
75645
+ * @returns {Promise<Object>} { success, defaults: [{widgetName, field, currentDefault, displayName, type, instructions}], error? }
75646
+ */
75647
+ scanWidgetDefaults: async (packageId) => {
75648
+ try {
75649
+ return await ipcRenderer$h.invoke("registry:scan-widget-defaults", {
75650
+ packageId,
75651
+ });
75652
+ } catch (error) {
75653
+ console.error("[RegistryApi] Error scanning widget defaults:", error);
75654
+ throw error;
75655
+ }
75656
+ },
75657
+
75459
75658
  /**
75460
75659
  * Fetch a registry package's source (component + config + bundle) into a
75461
75660
  * temp directory and return the source strings without installing the