@nmakarov/cli-toolkit 0.11.4 → 0.14.2

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/dist/index.cjs CHANGED
@@ -684,6 +684,8 @@ async function showScreen(config2) {
684
684
  keyMatches = true;
685
685
  } else if (input === binding.key) {
686
686
  keyMatches = true;
687
+ } else if (key?.name === binding.key) {
688
+ keyMatches = true;
687
689
  }
688
690
  if (keyMatches) {
689
691
  if (binding.enabled === false) {
@@ -1120,6 +1122,8 @@ __export(src_exports, {
1120
1122
  h: () => import_react5.createElement,
1121
1123
  joiEdateType: () => joiEdateType,
1122
1124
  joiStringArrayType: () => joiStringArrayType,
1125
+ listSources: () => listSources,
1126
+ listTables: () => listTables,
1123
1127
  load: () => load,
1124
1128
  organizeFooterMessages: () => organizeFooterMessages,
1125
1129
  setupContext: () => setupContext,
@@ -2587,20 +2591,48 @@ var FileDatabase = class {
2587
2591
  }
2588
2592
  /**
2589
2593
  * Figure out what data to write and which file to use (for pagination)
2594
+ * @param data - Data to write
2595
+ * @param targetFileIndex - Optional index of existing file to overwrite (when customMetadata matches)
2596
+ * @param forceNewFile - If true, always create a new file (when customMetadata provided but no match)
2590
2597
  */
2591
- figureOutDataAndFileToWrite(data) {
2598
+ figureOutDataAndFileToWrite(data, targetFileIndex = null, forceNewFile = false) {
2592
2599
  let dataToWrite;
2593
2600
  let dataLeftOver;
2594
2601
  const incomingDataType = detectDataType(data);
2595
2602
  if (this.metadata.dataType !== incomingDataType) {
2596
2603
  this.metadata.dataType = incomingDataType;
2597
2604
  }
2598
- if (this.metadata.files.length === 0) {
2605
+ if (targetFileIndex !== null && targetFileIndex < this.metadata.files.length) {
2606
+ const targetFile = this.metadata.files[targetFileIndex];
2607
+ if (!Array.isArray(data)) {
2608
+ dataToWrite = data;
2609
+ dataLeftOver = null;
2610
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
2611
+ } else {
2612
+ dataToWrite = data.slice(0, this.pageSize);
2613
+ dataLeftOver = data.slice(this.pageSize);
2614
+ this.lastFileData = dataToWrite;
2615
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
2616
+ }
2617
+ }
2618
+ let newlyCreatedFileIndex = null;
2619
+ if (forceNewFile) {
2620
+ const filesBeforeCreate = this.metadata.files.length;
2621
+ this.makeNewFile();
2622
+ newlyCreatedFileIndex = filesBeforeCreate;
2623
+ this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
2624
+ } else if (this.metadata.files.length === 0) {
2599
2625
  this.makeNewFile();
2600
2626
  }
2601
2627
  const lastFile = this.metadata.files[this.metadata.files.length - 1];
2602
2628
  const lastFileRecordsCount = lastFile.recordsCount;
2603
- if (!Array.isArray(data)) {
2629
+ if (forceNewFile && newlyCreatedFileIndex !== null) {
2630
+ const newlyCreatedFile = this.metadata.files[newlyCreatedFileIndex];
2631
+ if (newlyCreatedFile && newlyCreatedFile.fileName !== lastFile.fileName) {
2632
+ this.logger.warn?.(`[FileDatabase] Warning: Newly created file ${newlyCreatedFile.fileName} doesn't match last file ${lastFile.fileName}`);
2633
+ }
2634
+ }
2635
+ if (!Array.isArray(data) && !forceNewFile) {
2604
2636
  const lastFileExtension = import_path4.default.extname(lastFile.fileName);
2605
2637
  const expectedExtension = `.${getFileExtension(incomingDataType)}`;
2606
2638
  if (lastFileExtension !== expectedExtension) {
@@ -2610,24 +2642,35 @@ var FileDatabase = class {
2610
2642
  lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
2611
2643
  }
2612
2644
  }
2645
+ } else if (!Array.isArray(data) && forceNewFile) {
2646
+ const lastFileExtension = import_path4.default.extname(lastFile.fileName);
2647
+ const expectedExtension = `.${getFileExtension(incomingDataType)}`;
2648
+ if (lastFileExtension !== expectedExtension) {
2649
+ lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
2650
+ }
2613
2651
  }
2614
2652
  if (Array.isArray(data)) {
2615
- if (lastFileRecordsCount < this.pageSize) {
2653
+ if (forceNewFile) {
2654
+ dataToWrite = data.slice(0, this.pageSize);
2655
+ dataLeftOver = data.slice(this.pageSize);
2656
+ this.lastFileData = dataToWrite;
2657
+ } else if (lastFileRecordsCount < this.pageSize) {
2616
2658
  dataToWrite = [...this.lastFileData || [], ...data.slice(0, this.pageSize - lastFileRecordsCount)];
2617
2659
  dataLeftOver = data.slice(this.pageSize - lastFileRecordsCount);
2660
+ this.lastFileData = dataToWrite;
2618
2661
  } else {
2619
2662
  this.makeNewFile();
2620
2663
  dataToWrite = data.slice(0, this.pageSize);
2621
2664
  dataLeftOver = data.slice(this.pageSize);
2665
+ this.lastFileData = dataToWrite;
2622
2666
  }
2623
- this.lastFileData = dataToWrite;
2624
2667
  } else {
2625
2668
  dataToWrite = data;
2626
2669
  dataLeftOver = null;
2627
2670
  }
2628
2671
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
2629
2672
  this.logger.debug?.(
2630
- `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
2673
+ `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
2631
2674
  );
2632
2675
  return { dataToWrite, dataLeftOver, fileName };
2633
2676
  }
@@ -2655,7 +2698,7 @@ var FileDatabase = class {
2655
2698
  /**
2656
2699
  * Update metadata after writing data
2657
2700
  */
2658
- updateMetadata(dataToWrite, fileName) {
2701
+ updateMetadata(dataToWrite, fileName, customMetadata) {
2659
2702
  let currentFile;
2660
2703
  if (fileName) {
2661
2704
  const foundFile = this.metadata.files.find((file) => file.fileName === fileName);
@@ -2670,6 +2713,9 @@ var FileDatabase = class {
2670
2713
  }
2671
2714
  const recordsCount = Array.isArray(dataToWrite) ? dataToWrite.length : 1;
2672
2715
  currentFile.recordsCount = recordsCount;
2716
+ if (customMetadata) {
2717
+ Object.assign(currentFile, customMetadata);
2718
+ }
2673
2719
  const fileIndex = this.metadata.files.indexOf(currentFile);
2674
2720
  if (fileIndex !== -1) {
2675
2721
  this.calculateFileSynopsis(dataToWrite, fileIndex);
@@ -2722,6 +2768,11 @@ var FileDatabase = class {
2722
2768
  } else {
2723
2769
  if (!this.metadata.files.length) {
2724
2770
  this.metadata = await this.figureMetadata(this.currentVersion);
2771
+ if (this.metadata.files && this.metadata.files.length > 0) {
2772
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2773
+ } else {
2774
+ this.currentFileNumber = 0;
2775
+ }
2725
2776
  }
2726
2777
  }
2727
2778
  } else {
@@ -2732,16 +2783,22 @@ var FileDatabase = class {
2732
2783
  try {
2733
2784
  const rawData = await import_fs4.default.promises.readFile(metadataPath, "utf8");
2734
2785
  this.metadata = JSON.parse(rawData);
2786
+ if (this.metadata.files && this.metadata.files.length > 0) {
2787
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2788
+ } else {
2789
+ this.currentFileNumber = 0;
2790
+ }
2735
2791
  } catch (e) {
2736
2792
  this.metadata = this.getDefaultMetadata();
2793
+ this.currentFileNumber = 0;
2737
2794
  }
2738
2795
  } else {
2739
2796
  this.metadata = this.getDefaultMetadata();
2740
- this.makeNewFile();
2797
+ this.currentFileNumber = 0;
2741
2798
  }
2742
2799
  } else {
2743
2800
  this.metadata = this.getDefaultMetadata();
2744
- this.makeNewFile();
2801
+ this.currentFileNumber = 0;
2745
2802
  }
2746
2803
  }
2747
2804
  } else if (read) {
@@ -2760,6 +2817,11 @@ var FileDatabase = class {
2760
2817
  }
2761
2818
  if (!this.metadata.files.length) {
2762
2819
  this.metadata = await this.figureMetadata(this.currentVersion);
2820
+ if (this.metadata.files && this.metadata.files.length > 0) {
2821
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2822
+ } else {
2823
+ this.currentFileNumber = 0;
2824
+ }
2763
2825
  }
2764
2826
  } else {
2765
2827
  this.currentVersion = null;
@@ -2773,6 +2835,11 @@ var FileDatabase = class {
2773
2835
  try {
2774
2836
  const rawData = await import_fs4.default.promises.readFile(metadataPath, "utf8");
2775
2837
  this.metadata = JSON.parse(rawData);
2838
+ if (this.metadata.files && this.metadata.files.length > 0) {
2839
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2840
+ } else {
2841
+ this.currentFileNumber = 0;
2842
+ }
2776
2843
  } catch (e) {
2777
2844
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
2778
2845
  }
@@ -2781,6 +2848,11 @@ var FileDatabase = class {
2781
2848
  }
2782
2849
  } else {
2783
2850
  this.metadata = await this.figureMetadataFromVersionFiles("");
2851
+ if (this.metadata.files && this.metadata.files.length > 0) {
2852
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2853
+ } else {
2854
+ this.currentFileNumber = 0;
2855
+ }
2784
2856
  }
2785
2857
  }
2786
2858
  }
@@ -2802,14 +2874,44 @@ var FileDatabase = class {
2802
2874
  this.metadata.dataType = incomingDataType;
2803
2875
  this.makeNewFile();
2804
2876
  }
2805
- let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data);
2877
+ let targetFileIndex = null;
2878
+ const hasCustomMetadata = options.customMetadata && Object.keys(options.customMetadata).length > 0;
2879
+ if (hasCustomMetadata) {
2880
+ for (let i = 0; i < this.metadata.files.length; i++) {
2881
+ const fileEntry = this.metadata.files[i];
2882
+ const matches = Object.keys(options.customMetadata).every((key) => {
2883
+ return key in fileEntry && fileEntry[key] === options.customMetadata[key];
2884
+ });
2885
+ if (matches) {
2886
+ targetFileIndex = i;
2887
+ this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
2888
+ break;
2889
+ } else {
2890
+ this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
2891
+ }
2892
+ }
2893
+ if (targetFileIndex === null) {
2894
+ this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
2895
+ }
2896
+ } else {
2897
+ this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
2898
+ }
2899
+ if (targetFileIndex !== null) {
2900
+ const targetFile = this.metadata.files[targetFileIndex];
2901
+ this.currentFileNumber = targetFile.number;
2902
+ this.lastFileData = null;
2903
+ this.currentRecord = 0;
2904
+ this.hasReadFirstPage = false;
2905
+ }
2906
+ const forceNewFile = hasCustomMetadata && targetFileIndex === null;
2907
+ let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data, targetFileIndex, forceNewFile);
2806
2908
  const destPath = this.getDestinationPath(this.currentVersion || void 0);
2807
2909
  await this.safeWrite(import_path4.default.join(destPath, fileName), dataToWrite);
2808
- this.updateMetadata(dataToWrite, fileName);
2809
- while (dataLeftOver && dataLeftOver.length > 0) {
2910
+ this.updateMetadata(dataToWrite, fileName, options.customMetadata);
2911
+ while (dataLeftOver && dataLeftOver.length > 0 && targetFileIndex === null) {
2810
2912
  const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
2811
2913
  await this.safeWrite(import_path4.default.join(destPath, writeContext.fileName), writeContext.dataToWrite);
2812
- this.updateMetadata(writeContext.dataToWrite, writeContext.fileName);
2914
+ this.updateMetadata(writeContext.dataToWrite, writeContext.fileName, options.customMetadata);
2813
2915
  dataLeftOver = writeContext.dataLeftOver;
2814
2916
  }
2815
2917
  this.calculateVersionSynopsis();
@@ -2926,7 +3028,85 @@ var FileDatabase = class {
2926
3028
  getMetadata() {
2927
3029
  return { ...this.metadata };
2928
3030
  }
3031
+ /**
3032
+ * Find data by custom metadata fields
3033
+ * Searches through all versions and files to find entries matching the search criteria
3034
+ *
3035
+ * @param searchCriteria - Object with field names and values to search for (e.g., { ListingKey: "123", id: "456" })
3036
+ * @returns Array of found entries with their file paths and metadata
3037
+ */
3038
+ async findData(searchCriteria) {
3039
+ const results = [];
3040
+ if (!this.versioned) {
3041
+ await this.prepare({ read: true });
3042
+ const metadata = this.getMetadata();
3043
+ for (const fileEntry of metadata.files) {
3044
+ const matches = Object.keys(searchCriteria).every((key) => {
3045
+ return fileEntry[key] === searchCriteria[key];
3046
+ });
3047
+ if (matches) {
3048
+ const destPath = this.getDestinationPath();
3049
+ const filePath = import_path4.default.join(destPath, fileEntry.fileName);
3050
+ const fileData = await import_fs4.default.promises.readFile(filePath, "utf8");
3051
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
3052
+ results.push({
3053
+ filePath,
3054
+ fileName: fileEntry.fileName,
3055
+ version: null,
3056
+ metadata: fileEntry,
3057
+ data
3058
+ });
3059
+ }
3060
+ }
3061
+ } else {
3062
+ const versions = await this.getVersions();
3063
+ for (const version of versions) {
3064
+ await this.prepare({ read: true, version });
3065
+ const metadata = this.getMetadata();
3066
+ for (const fileEntry of metadata.files) {
3067
+ const matches = Object.keys(searchCriteria).every((key) => {
3068
+ return fileEntry[key] === searchCriteria[key];
3069
+ });
3070
+ if (matches) {
3071
+ const destPath = this.getDestinationPath(version);
3072
+ const filePath = import_path4.default.join(destPath, fileEntry.fileName);
3073
+ const fileData = await import_fs4.default.promises.readFile(filePath, "utf8");
3074
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
3075
+ results.push({
3076
+ filePath,
3077
+ fileName: fileEntry.fileName,
3078
+ version,
3079
+ metadata: fileEntry,
3080
+ data
3081
+ });
3082
+ }
3083
+ }
3084
+ }
3085
+ }
3086
+ return results;
3087
+ }
2929
3088
  };
3089
+ function listTables(basePath, namespace) {
3090
+ const namespacePath = import_path4.default.join(basePath, namespace);
3091
+ if (!import_fs4.default.existsSync(namespacePath)) {
3092
+ return [];
3093
+ }
3094
+ try {
3095
+ return import_fs4.default.readdirSync(namespacePath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
3096
+ } catch (error) {
3097
+ return [];
3098
+ }
3099
+ }
3100
+ function listSources(basePath) {
3101
+ if (!import_fs4.default.existsSync(basePath)) {
3102
+ return [];
3103
+ }
3104
+ try {
3105
+ return import_fs4.default.readdirSync(basePath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
3106
+ } catch (error) {
3107
+ return [];
3108
+ }
3109
+ }
2930
3110
  function fileDatabaseInit(context, options = {}) {
2931
3111
  return new FileDatabase(context, options);
2932
3112
  }
@@ -3719,6 +3899,8 @@ function setupContext(opts = {}) {
3719
3899
  h,
3720
3900
  joiEdateType,
3721
3901
  joiStringArrayType,
3902
+ listSources,
3903
+ listTables,
3722
3904
  load,
3723
3905
  organizeFooterMessages,
3724
3906
  setupContext,