@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.js CHANGED
@@ -656,6 +656,8 @@ async function showScreen(config2) {
656
656
  keyMatches = true;
657
657
  } else if (input === binding.key) {
658
658
  keyMatches = true;
659
+ } else if (key?.name === binding.key) {
660
+ keyMatches = true;
659
661
  }
660
662
  if (keyMatches) {
661
663
  if (binding.enabled === false) {
@@ -2502,20 +2504,48 @@ var FileDatabase = class {
2502
2504
  }
2503
2505
  /**
2504
2506
  * Figure out what data to write and which file to use (for pagination)
2507
+ * @param data - Data to write
2508
+ * @param targetFileIndex - Optional index of existing file to overwrite (when customMetadata matches)
2509
+ * @param forceNewFile - If true, always create a new file (when customMetadata provided but no match)
2505
2510
  */
2506
- figureOutDataAndFileToWrite(data) {
2511
+ figureOutDataAndFileToWrite(data, targetFileIndex = null, forceNewFile = false) {
2507
2512
  let dataToWrite;
2508
2513
  let dataLeftOver;
2509
2514
  const incomingDataType = detectDataType(data);
2510
2515
  if (this.metadata.dataType !== incomingDataType) {
2511
2516
  this.metadata.dataType = incomingDataType;
2512
2517
  }
2513
- if (this.metadata.files.length === 0) {
2518
+ if (targetFileIndex !== null && targetFileIndex < this.metadata.files.length) {
2519
+ const targetFile = this.metadata.files[targetFileIndex];
2520
+ if (!Array.isArray(data)) {
2521
+ dataToWrite = data;
2522
+ dataLeftOver = null;
2523
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
2524
+ } else {
2525
+ dataToWrite = data.slice(0, this.pageSize);
2526
+ dataLeftOver = data.slice(this.pageSize);
2527
+ this.lastFileData = dataToWrite;
2528
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
2529
+ }
2530
+ }
2531
+ let newlyCreatedFileIndex = null;
2532
+ if (forceNewFile) {
2533
+ const filesBeforeCreate = this.metadata.files.length;
2534
+ this.makeNewFile();
2535
+ newlyCreatedFileIndex = filesBeforeCreate;
2536
+ this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
2537
+ } else if (this.metadata.files.length === 0) {
2514
2538
  this.makeNewFile();
2515
2539
  }
2516
2540
  const lastFile = this.metadata.files[this.metadata.files.length - 1];
2517
2541
  const lastFileRecordsCount = lastFile.recordsCount;
2518
- if (!Array.isArray(data)) {
2542
+ if (forceNewFile && newlyCreatedFileIndex !== null) {
2543
+ const newlyCreatedFile = this.metadata.files[newlyCreatedFileIndex];
2544
+ if (newlyCreatedFile && newlyCreatedFile.fileName !== lastFile.fileName) {
2545
+ this.logger.warn?.(`[FileDatabase] Warning: Newly created file ${newlyCreatedFile.fileName} doesn't match last file ${lastFile.fileName}`);
2546
+ }
2547
+ }
2548
+ if (!Array.isArray(data) && !forceNewFile) {
2519
2549
  const lastFileExtension = path3.extname(lastFile.fileName);
2520
2550
  const expectedExtension = `.${getFileExtension(incomingDataType)}`;
2521
2551
  if (lastFileExtension !== expectedExtension) {
@@ -2525,24 +2555,35 @@ var FileDatabase = class {
2525
2555
  lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
2526
2556
  }
2527
2557
  }
2558
+ } else if (!Array.isArray(data) && forceNewFile) {
2559
+ const lastFileExtension = path3.extname(lastFile.fileName);
2560
+ const expectedExtension = `.${getFileExtension(incomingDataType)}`;
2561
+ if (lastFileExtension !== expectedExtension) {
2562
+ lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
2563
+ }
2528
2564
  }
2529
2565
  if (Array.isArray(data)) {
2530
- if (lastFileRecordsCount < this.pageSize) {
2566
+ if (forceNewFile) {
2567
+ dataToWrite = data.slice(0, this.pageSize);
2568
+ dataLeftOver = data.slice(this.pageSize);
2569
+ this.lastFileData = dataToWrite;
2570
+ } else if (lastFileRecordsCount < this.pageSize) {
2531
2571
  dataToWrite = [...this.lastFileData || [], ...data.slice(0, this.pageSize - lastFileRecordsCount)];
2532
2572
  dataLeftOver = data.slice(this.pageSize - lastFileRecordsCount);
2573
+ this.lastFileData = dataToWrite;
2533
2574
  } else {
2534
2575
  this.makeNewFile();
2535
2576
  dataToWrite = data.slice(0, this.pageSize);
2536
2577
  dataLeftOver = data.slice(this.pageSize);
2578
+ this.lastFileData = dataToWrite;
2537
2579
  }
2538
- this.lastFileData = dataToWrite;
2539
2580
  } else {
2540
2581
  dataToWrite = data;
2541
2582
  dataLeftOver = null;
2542
2583
  }
2543
2584
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
2544
2585
  this.logger.debug?.(
2545
- `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
2586
+ `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
2546
2587
  );
2547
2588
  return { dataToWrite, dataLeftOver, fileName };
2548
2589
  }
@@ -2570,7 +2611,7 @@ var FileDatabase = class {
2570
2611
  /**
2571
2612
  * Update metadata after writing data
2572
2613
  */
2573
- updateMetadata(dataToWrite, fileName) {
2614
+ updateMetadata(dataToWrite, fileName, customMetadata) {
2574
2615
  let currentFile;
2575
2616
  if (fileName) {
2576
2617
  const foundFile = this.metadata.files.find((file) => file.fileName === fileName);
@@ -2585,6 +2626,9 @@ var FileDatabase = class {
2585
2626
  }
2586
2627
  const recordsCount = Array.isArray(dataToWrite) ? dataToWrite.length : 1;
2587
2628
  currentFile.recordsCount = recordsCount;
2629
+ if (customMetadata) {
2630
+ Object.assign(currentFile, customMetadata);
2631
+ }
2588
2632
  const fileIndex = this.metadata.files.indexOf(currentFile);
2589
2633
  if (fileIndex !== -1) {
2590
2634
  this.calculateFileSynopsis(dataToWrite, fileIndex);
@@ -2637,6 +2681,11 @@ var FileDatabase = class {
2637
2681
  } else {
2638
2682
  if (!this.metadata.files.length) {
2639
2683
  this.metadata = await this.figureMetadata(this.currentVersion);
2684
+ if (this.metadata.files && this.metadata.files.length > 0) {
2685
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2686
+ } else {
2687
+ this.currentFileNumber = 0;
2688
+ }
2640
2689
  }
2641
2690
  }
2642
2691
  } else {
@@ -2647,16 +2696,22 @@ var FileDatabase = class {
2647
2696
  try {
2648
2697
  const rawData = await fs3.promises.readFile(metadataPath, "utf8");
2649
2698
  this.metadata = JSON.parse(rawData);
2699
+ if (this.metadata.files && this.metadata.files.length > 0) {
2700
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2701
+ } else {
2702
+ this.currentFileNumber = 0;
2703
+ }
2650
2704
  } catch (e) {
2651
2705
  this.metadata = this.getDefaultMetadata();
2706
+ this.currentFileNumber = 0;
2652
2707
  }
2653
2708
  } else {
2654
2709
  this.metadata = this.getDefaultMetadata();
2655
- this.makeNewFile();
2710
+ this.currentFileNumber = 0;
2656
2711
  }
2657
2712
  } else {
2658
2713
  this.metadata = this.getDefaultMetadata();
2659
- this.makeNewFile();
2714
+ this.currentFileNumber = 0;
2660
2715
  }
2661
2716
  }
2662
2717
  } else if (read) {
@@ -2675,6 +2730,11 @@ var FileDatabase = class {
2675
2730
  }
2676
2731
  if (!this.metadata.files.length) {
2677
2732
  this.metadata = await this.figureMetadata(this.currentVersion);
2733
+ if (this.metadata.files && this.metadata.files.length > 0) {
2734
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2735
+ } else {
2736
+ this.currentFileNumber = 0;
2737
+ }
2678
2738
  }
2679
2739
  } else {
2680
2740
  this.currentVersion = null;
@@ -2688,6 +2748,11 @@ var FileDatabase = class {
2688
2748
  try {
2689
2749
  const rawData = await fs3.promises.readFile(metadataPath, "utf8");
2690
2750
  this.metadata = JSON.parse(rawData);
2751
+ if (this.metadata.files && this.metadata.files.length > 0) {
2752
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2753
+ } else {
2754
+ this.currentFileNumber = 0;
2755
+ }
2691
2756
  } catch (e) {
2692
2757
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
2693
2758
  }
@@ -2696,6 +2761,11 @@ var FileDatabase = class {
2696
2761
  }
2697
2762
  } else {
2698
2763
  this.metadata = await this.figureMetadataFromVersionFiles("");
2764
+ if (this.metadata.files && this.metadata.files.length > 0) {
2765
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2766
+ } else {
2767
+ this.currentFileNumber = 0;
2768
+ }
2699
2769
  }
2700
2770
  }
2701
2771
  }
@@ -2717,14 +2787,44 @@ var FileDatabase = class {
2717
2787
  this.metadata.dataType = incomingDataType;
2718
2788
  this.makeNewFile();
2719
2789
  }
2720
- let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data);
2790
+ let targetFileIndex = null;
2791
+ const hasCustomMetadata = options.customMetadata && Object.keys(options.customMetadata).length > 0;
2792
+ if (hasCustomMetadata) {
2793
+ for (let i = 0; i < this.metadata.files.length; i++) {
2794
+ const fileEntry = this.metadata.files[i];
2795
+ const matches = Object.keys(options.customMetadata).every((key) => {
2796
+ return key in fileEntry && fileEntry[key] === options.customMetadata[key];
2797
+ });
2798
+ if (matches) {
2799
+ targetFileIndex = i;
2800
+ this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
2801
+ break;
2802
+ } else {
2803
+ this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
2804
+ }
2805
+ }
2806
+ if (targetFileIndex === null) {
2807
+ this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
2808
+ }
2809
+ } else {
2810
+ this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
2811
+ }
2812
+ if (targetFileIndex !== null) {
2813
+ const targetFile = this.metadata.files[targetFileIndex];
2814
+ this.currentFileNumber = targetFile.number;
2815
+ this.lastFileData = null;
2816
+ this.currentRecord = 0;
2817
+ this.hasReadFirstPage = false;
2818
+ }
2819
+ const forceNewFile = hasCustomMetadata && targetFileIndex === null;
2820
+ let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data, targetFileIndex, forceNewFile);
2721
2821
  const destPath = this.getDestinationPath(this.currentVersion || void 0);
2722
2822
  await this.safeWrite(path3.join(destPath, fileName), dataToWrite);
2723
- this.updateMetadata(dataToWrite, fileName);
2724
- while (dataLeftOver && dataLeftOver.length > 0) {
2823
+ this.updateMetadata(dataToWrite, fileName, options.customMetadata);
2824
+ while (dataLeftOver && dataLeftOver.length > 0 && targetFileIndex === null) {
2725
2825
  const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
2726
2826
  await this.safeWrite(path3.join(destPath, writeContext.fileName), writeContext.dataToWrite);
2727
- this.updateMetadata(writeContext.dataToWrite, writeContext.fileName);
2827
+ this.updateMetadata(writeContext.dataToWrite, writeContext.fileName, options.customMetadata);
2728
2828
  dataLeftOver = writeContext.dataLeftOver;
2729
2829
  }
2730
2830
  this.calculateVersionSynopsis();
@@ -2841,7 +2941,85 @@ var FileDatabase = class {
2841
2941
  getMetadata() {
2842
2942
  return { ...this.metadata };
2843
2943
  }
2944
+ /**
2945
+ * Find data by custom metadata fields
2946
+ * Searches through all versions and files to find entries matching the search criteria
2947
+ *
2948
+ * @param searchCriteria - Object with field names and values to search for (e.g., { ListingKey: "123", id: "456" })
2949
+ * @returns Array of found entries with their file paths and metadata
2950
+ */
2951
+ async findData(searchCriteria) {
2952
+ const results = [];
2953
+ if (!this.versioned) {
2954
+ await this.prepare({ read: true });
2955
+ const metadata = this.getMetadata();
2956
+ for (const fileEntry of metadata.files) {
2957
+ const matches = Object.keys(searchCriteria).every((key) => {
2958
+ return fileEntry[key] === searchCriteria[key];
2959
+ });
2960
+ if (matches) {
2961
+ const destPath = this.getDestinationPath();
2962
+ const filePath = path3.join(destPath, fileEntry.fileName);
2963
+ const fileData = await fs3.promises.readFile(filePath, "utf8");
2964
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
2965
+ results.push({
2966
+ filePath,
2967
+ fileName: fileEntry.fileName,
2968
+ version: null,
2969
+ metadata: fileEntry,
2970
+ data
2971
+ });
2972
+ }
2973
+ }
2974
+ } else {
2975
+ const versions = await this.getVersions();
2976
+ for (const version of versions) {
2977
+ await this.prepare({ read: true, version });
2978
+ const metadata = this.getMetadata();
2979
+ for (const fileEntry of metadata.files) {
2980
+ const matches = Object.keys(searchCriteria).every((key) => {
2981
+ return fileEntry[key] === searchCriteria[key];
2982
+ });
2983
+ if (matches) {
2984
+ const destPath = this.getDestinationPath(version);
2985
+ const filePath = path3.join(destPath, fileEntry.fileName);
2986
+ const fileData = await fs3.promises.readFile(filePath, "utf8");
2987
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
2988
+ results.push({
2989
+ filePath,
2990
+ fileName: fileEntry.fileName,
2991
+ version,
2992
+ metadata: fileEntry,
2993
+ data
2994
+ });
2995
+ }
2996
+ }
2997
+ }
2998
+ }
2999
+ return results;
3000
+ }
2844
3001
  };
3002
+ function listTables(basePath, namespace) {
3003
+ const namespacePath = path3.join(basePath, namespace);
3004
+ if (!fs3.existsSync(namespacePath)) {
3005
+ return [];
3006
+ }
3007
+ try {
3008
+ return fs3.readdirSync(namespacePath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
3009
+ } catch (error) {
3010
+ return [];
3011
+ }
3012
+ }
3013
+ function listSources(basePath) {
3014
+ if (!fs3.existsSync(basePath)) {
3015
+ return [];
3016
+ }
3017
+ try {
3018
+ return fs3.readdirSync(basePath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
3019
+ } catch (error) {
3020
+ return [];
3021
+ }
3022
+ }
2845
3023
  function fileDatabaseInit(context, options = {}) {
2846
3024
  return new FileDatabase(context, options);
2847
3025
  }
@@ -3633,6 +3811,8 @@ export {
3633
3811
  createElement2 as h,
3634
3812
  joiEdateType,
3635
3813
  joiStringArrayType,
3814
+ listSources,
3815
+ listTables,
3636
3816
  load,
3637
3817
  organizeFooterMessages,
3638
3818
  setupContext,