@push.rocks/smartregistry 1.4.1 → 1.5.0

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.
@@ -601,4 +601,231 @@ export class RegistryStorage implements IStorageBackend {
601
601
  private getComposerZipPath(vendorPackage: string, reference: string): string {
602
602
  return `composer/packages/${vendorPackage}/${reference}.zip`;
603
603
  }
604
+
605
+ // ========================================================================
606
+ // PYPI STORAGE METHODS
607
+ // ========================================================================
608
+
609
+ /**
610
+ * Get PyPI package metadata
611
+ */
612
+ public async getPypiPackageMetadata(packageName: string): Promise<any | null> {
613
+ const path = this.getPypiMetadataPath(packageName);
614
+ const data = await this.getObject(path);
615
+ return data ? JSON.parse(data.toString('utf-8')) : null;
616
+ }
617
+
618
+ /**
619
+ * Store PyPI package metadata
620
+ */
621
+ public async putPypiPackageMetadata(packageName: string, metadata: any): Promise<void> {
622
+ const path = this.getPypiMetadataPath(packageName);
623
+ const data = Buffer.from(JSON.stringify(metadata, null, 2), 'utf-8');
624
+ return this.putObject(path, data, { 'Content-Type': 'application/json' });
625
+ }
626
+
627
+ /**
628
+ * Check if PyPI package metadata exists
629
+ */
630
+ public async pypiPackageMetadataExists(packageName: string): Promise<boolean> {
631
+ const path = this.getPypiMetadataPath(packageName);
632
+ return this.objectExists(path);
633
+ }
634
+
635
+ /**
636
+ * Delete PyPI package metadata
637
+ */
638
+ public async deletePypiPackageMetadata(packageName: string): Promise<void> {
639
+ const path = this.getPypiMetadataPath(packageName);
640
+ return this.deleteObject(path);
641
+ }
642
+
643
+ /**
644
+ * Get PyPI Simple API index (HTML)
645
+ */
646
+ public async getPypiSimpleIndex(packageName: string): Promise<string | null> {
647
+ const path = this.getPypiSimpleIndexPath(packageName);
648
+ const data = await this.getObject(path);
649
+ return data ? data.toString('utf-8') : null;
650
+ }
651
+
652
+ /**
653
+ * Store PyPI Simple API index (HTML)
654
+ */
655
+ public async putPypiSimpleIndex(packageName: string, html: string): Promise<void> {
656
+ const path = this.getPypiSimpleIndexPath(packageName);
657
+ const data = Buffer.from(html, 'utf-8');
658
+ return this.putObject(path, data, { 'Content-Type': 'text/html; charset=utf-8' });
659
+ }
660
+
661
+ /**
662
+ * Get PyPI root Simple API index (HTML)
663
+ */
664
+ public async getPypiSimpleRootIndex(): Promise<string | null> {
665
+ const path = this.getPypiSimpleRootIndexPath();
666
+ const data = await this.getObject(path);
667
+ return data ? data.toString('utf-8') : null;
668
+ }
669
+
670
+ /**
671
+ * Store PyPI root Simple API index (HTML)
672
+ */
673
+ public async putPypiSimpleRootIndex(html: string): Promise<void> {
674
+ const path = this.getPypiSimpleRootIndexPath();
675
+ const data = Buffer.from(html, 'utf-8');
676
+ return this.putObject(path, data, { 'Content-Type': 'text/html; charset=utf-8' });
677
+ }
678
+
679
+ /**
680
+ * Get PyPI package file (wheel, sdist)
681
+ */
682
+ public async getPypiPackageFile(packageName: string, filename: string): Promise<Buffer | null> {
683
+ const path = this.getPypiPackageFilePath(packageName, filename);
684
+ return this.getObject(path);
685
+ }
686
+
687
+ /**
688
+ * Store PyPI package file (wheel, sdist)
689
+ */
690
+ public async putPypiPackageFile(
691
+ packageName: string,
692
+ filename: string,
693
+ data: Buffer
694
+ ): Promise<void> {
695
+ const path = this.getPypiPackageFilePath(packageName, filename);
696
+ return this.putObject(path, data, { 'Content-Type': 'application/octet-stream' });
697
+ }
698
+
699
+ /**
700
+ * Check if PyPI package file exists
701
+ */
702
+ public async pypiPackageFileExists(packageName: string, filename: string): Promise<boolean> {
703
+ const path = this.getPypiPackageFilePath(packageName, filename);
704
+ return this.objectExists(path);
705
+ }
706
+
707
+ /**
708
+ * Delete PyPI package file
709
+ */
710
+ public async deletePypiPackageFile(packageName: string, filename: string): Promise<void> {
711
+ const path = this.getPypiPackageFilePath(packageName, filename);
712
+ return this.deleteObject(path);
713
+ }
714
+
715
+ /**
716
+ * List all PyPI packages
717
+ */
718
+ public async listPypiPackages(): Promise<string[]> {
719
+ const prefix = 'pypi/metadata/';
720
+ const objects = await this.listObjects(prefix);
721
+ const packages = new Set<string>();
722
+
723
+ // Extract package names from paths like: pypi/metadata/package-name/metadata.json
724
+ for (const obj of objects) {
725
+ const match = obj.match(/^pypi\/metadata\/([^\/]+)\/metadata\.json$/);
726
+ if (match) {
727
+ packages.add(match[1]);
728
+ }
729
+ }
730
+
731
+ return Array.from(packages).sort();
732
+ }
733
+
734
+ /**
735
+ * List all versions of a PyPI package
736
+ */
737
+ public async listPypiPackageVersions(packageName: string): Promise<string[]> {
738
+ const prefix = `pypi/packages/${packageName}/`;
739
+ const objects = await this.listObjects(prefix);
740
+ const versions = new Set<string>();
741
+
742
+ // Extract versions from filenames
743
+ for (const obj of objects) {
744
+ const filename = obj.split('/').pop();
745
+ if (!filename) continue;
746
+
747
+ // Extract version from wheel filename: package-1.0.0-py3-none-any.whl
748
+ // or sdist filename: package-1.0.0.tar.gz
749
+ const wheelMatch = filename.match(/^[^-]+-([^-]+)-.*\.whl$/);
750
+ const sdistMatch = filename.match(/^[^-]+-([^.]+)\.(tar\.gz|zip)$/);
751
+
752
+ if (wheelMatch) versions.add(wheelMatch[1]);
753
+ else if (sdistMatch) versions.add(sdistMatch[1]);
754
+ }
755
+
756
+ return Array.from(versions).sort();
757
+ }
758
+
759
+ /**
760
+ * Delete entire PyPI package (all versions and files)
761
+ */
762
+ public async deletePypiPackage(packageName: string): Promise<void> {
763
+ // Delete metadata
764
+ await this.deletePypiPackageMetadata(packageName);
765
+
766
+ // Delete Simple API index
767
+ const simpleIndexPath = this.getPypiSimpleIndexPath(packageName);
768
+ try {
769
+ await this.deleteObject(simpleIndexPath);
770
+ } catch (error) {
771
+ // Ignore if doesn't exist
772
+ }
773
+
774
+ // Delete all package files
775
+ const prefix = `pypi/packages/${packageName}/`;
776
+ const objects = await this.listObjects(prefix);
777
+ for (const obj of objects) {
778
+ await this.deleteObject(obj);
779
+ }
780
+ }
781
+
782
+ /**
783
+ * Delete specific version of a PyPI package
784
+ */
785
+ public async deletePypiPackageVersion(packageName: string, version: string): Promise<void> {
786
+ const prefix = `pypi/packages/${packageName}/`;
787
+ const objects = await this.listObjects(prefix);
788
+
789
+ // Delete all files matching this version
790
+ for (const obj of objects) {
791
+ const filename = obj.split('/').pop();
792
+ if (!filename) continue;
793
+
794
+ // Check if filename contains this version
795
+ const wheelMatch = filename.match(/^[^-]+-([^-]+)-.*\.whl$/);
796
+ const sdistMatch = filename.match(/^[^-]+-([^.]+)\.(tar\.gz|zip)$/);
797
+
798
+ const fileVersion = wheelMatch?.[1] || sdistMatch?.[1];
799
+ if (fileVersion === version) {
800
+ await this.deleteObject(obj);
801
+ }
802
+ }
803
+
804
+ // Update metadata to remove this version
805
+ const metadata = await this.getPypiPackageMetadata(packageName);
806
+ if (metadata && metadata.versions) {
807
+ delete metadata.versions[version];
808
+ await this.putPypiPackageMetadata(packageName, metadata);
809
+ }
810
+ }
811
+
812
+ // ========================================================================
813
+ // PYPI PATH HELPERS
814
+ // ========================================================================
815
+
816
+ private getPypiMetadataPath(packageName: string): string {
817
+ return `pypi/metadata/${packageName}/metadata.json`;
818
+ }
819
+
820
+ private getPypiSimpleIndexPath(packageName: string): string {
821
+ return `pypi/simple/${packageName}/index.html`;
822
+ }
823
+
824
+ private getPypiSimpleRootIndexPath(): string {
825
+ return `pypi/simple/index.html`;
826
+ }
827
+
828
+ private getPypiPackageFilePath(packageName: string, filename: string): string {
829
+ return `pypi/packages/${packageName}/${filename}`;
830
+ }
604
831
  }
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Registry protocol types
7
7
  */
8
- export type TRegistryProtocol = 'oci' | 'npm' | 'maven' | 'cargo' | 'composer';
8
+ export type TRegistryProtocol = 'oci' | 'npm' | 'maven' | 'cargo' | 'composer' | 'pypi' | 'rubygems';
9
9
 
10
10
  /**
11
11
  * Unified action types across protocols
@@ -70,6 +70,16 @@ export interface IAuthConfig {
70
70
  realm: string;
71
71
  service: string;
72
72
  };
73
+ /** PyPI token settings */
74
+ pypiTokens?: {
75
+ enabled: boolean;
76
+ defaultReadonly?: boolean;
77
+ };
78
+ /** RubyGems token settings */
79
+ rubygemsTokens?: {
80
+ enabled: boolean;
81
+ defaultReadonly?: boolean;
82
+ };
73
83
  }
74
84
 
75
85
  /**
@@ -92,6 +102,8 @@ export interface IRegistryConfig {
92
102
  maven?: IProtocolConfig;
93
103
  cargo?: IProtocolConfig;
94
104
  composer?: IProtocolConfig;
105
+ pypi?: IProtocolConfig;
106
+ rubygems?: IProtocolConfig;
95
107
  }
96
108
 
97
109
  /**