@push.rocks/smartregistry 1.5.0 → 1.7.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.
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/classes.smartregistry.d.ts +2 -2
- package/dist_ts/classes.smartregistry.js +37 -2
- package/dist_ts/core/classes.registrystorage.d.ts +77 -0
- package/dist_ts/core/classes.registrystorage.js +200 -9
- package/dist_ts/core/interfaces.core.d.ts +3 -7
- package/dist_ts/index.d.ts +3 -1
- package/dist_ts/index.js +6 -2
- package/dist_ts/plugins.d.ts +2 -0
- package/dist_ts/plugins.js +4 -1
- package/dist_ts/pypi/classes.pypiregistry.js +22 -10
- package/dist_ts/rubygems/classes.rubygemsregistry.d.ts +86 -0
- package/dist_ts/rubygems/classes.rubygemsregistry.js +475 -0
- package/dist_ts/rubygems/helpers.rubygems.d.ts +143 -0
- package/dist_ts/rubygems/helpers.rubygems.js +312 -0
- package/dist_ts/rubygems/index.d.ts +7 -0
- package/dist_ts/rubygems/index.js +8 -0
- package/dist_ts/rubygems/interfaces.rubygems.d.ts +236 -0
- package/dist_ts/rubygems/interfaces.rubygems.js +6 -0
- package/package.json +3 -2
- package/readme.hints.md +108 -4
- package/readme.md +301 -17
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/classes.smartregistry.ts +41 -2
- package/ts/core/classes.registrystorage.ts +238 -8
- package/ts/core/interfaces.core.ts +4 -7
- package/ts/index.ts +7 -1
- package/ts/plugins.ts +5 -0
- package/ts/pypi/classes.pypiregistry.ts +24 -8
- package/ts/rubygems/classes.rubygemsregistry.ts +598 -0
- package/ts/rubygems/helpers.rubygems.ts +398 -0
- package/ts/rubygems/index.ts +8 -0
- package/ts/rubygems/interfaces.rubygems.ts +251 -0
package/readme.hints.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# Project
|
|
1
|
+
# Project Implementation Notes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file contains technical implementation details for PyPI and RubyGems protocols.
|
|
4
|
+
|
|
5
|
+
## Python (PyPI) Protocol Implementation ✅
|
|
4
6
|
|
|
5
7
|
### PEP 503: Simple Repository API (HTML-based)
|
|
6
8
|
|
|
@@ -114,7 +116,7 @@ Format: `#<hashname>=<hashvalue>`
|
|
|
114
116
|
|
|
115
117
|
---
|
|
116
118
|
|
|
117
|
-
## Ruby (RubyGems) Protocol Implementation
|
|
119
|
+
## Ruby (RubyGems) Protocol Implementation ✅
|
|
118
120
|
|
|
119
121
|
### Compact Index Format
|
|
120
122
|
|
|
@@ -222,7 +224,16 @@ gemname3
|
|
|
222
224
|
|
|
223
225
|
---
|
|
224
226
|
|
|
225
|
-
## Implementation
|
|
227
|
+
## Implementation Details
|
|
228
|
+
|
|
229
|
+
### Completed Protocols
|
|
230
|
+
- ✅ OCI Distribution Spec v1.1
|
|
231
|
+
- ✅ NPM Registry API
|
|
232
|
+
- ✅ Maven Repository
|
|
233
|
+
- ✅ Cargo/crates.io Registry
|
|
234
|
+
- ✅ Composer/Packagist
|
|
235
|
+
- ✅ PyPI (Python Package Index) - PEP 503/691
|
|
236
|
+
- ✅ RubyGems - Compact Index
|
|
226
237
|
|
|
227
238
|
### Storage Paths
|
|
228
239
|
|
|
@@ -333,3 +344,96 @@ rubygems:gem:{name}:{read|write|yank}
|
|
|
333
344
|
6. **HTML escaping** - Prevent XSS in generated HTML
|
|
334
345
|
7. **Metadata sanitization** - Clean user-provided strings
|
|
335
346
|
8. **Rate limiting** - Consider upload frequency limits
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Implementation Status (Completed)
|
|
351
|
+
|
|
352
|
+
### PyPI Implementation ✅
|
|
353
|
+
- **Files Created:**
|
|
354
|
+
- `ts/pypi/interfaces.pypi.ts` - Type definitions (354 lines)
|
|
355
|
+
- `ts/pypi/helpers.pypi.ts` - Helper functions (280 lines)
|
|
356
|
+
- `ts/pypi/classes.pypiregistry.ts` - Main registry (650 lines)
|
|
357
|
+
- `ts/pypi/index.ts` - Module exports
|
|
358
|
+
|
|
359
|
+
- **Features Implemented:**
|
|
360
|
+
- ✅ PEP 503 Simple API (HTML)
|
|
361
|
+
- ✅ PEP 691 JSON API
|
|
362
|
+
- ✅ Content negotiation (Accept header)
|
|
363
|
+
- ✅ Package name normalization
|
|
364
|
+
- ✅ File upload with multipart/form-data
|
|
365
|
+
- ✅ Hash verification (SHA256, MD5, Blake2b)
|
|
366
|
+
- ✅ Package metadata management
|
|
367
|
+
- ✅ JSON API endpoints (/pypi/{package}/json)
|
|
368
|
+
- ✅ Token-based authentication
|
|
369
|
+
- ✅ Scope-based permissions (read/write/delete)
|
|
370
|
+
|
|
371
|
+
- **Security Enhancements:**
|
|
372
|
+
- ✅ Hash verification on upload (validates client-provided hashes)
|
|
373
|
+
- ✅ Package name validation (regex check)
|
|
374
|
+
- ✅ HTML escaping in generated pages
|
|
375
|
+
- ✅ Permission checks on all mutating operations
|
|
376
|
+
|
|
377
|
+
### RubyGems Implementation ✅
|
|
378
|
+
- **Files Created:**
|
|
379
|
+
- `ts/rubygems/interfaces.rubygems.ts` - Type definitions (215 lines)
|
|
380
|
+
- `ts/rubygems/helpers.rubygems.ts` - Helper functions (350 lines)
|
|
381
|
+
- `ts/rubygems/classes.rubygemsregistry.ts` - Main registry (580 lines)
|
|
382
|
+
- `ts/rubygems/index.ts` - Module exports
|
|
383
|
+
|
|
384
|
+
- **Features Implemented:**
|
|
385
|
+
- ✅ Compact Index format (modern Bundler)
|
|
386
|
+
- ✅ /versions endpoint (all gems list)
|
|
387
|
+
- ✅ /info/{gem} endpoint (gem-specific metadata)
|
|
388
|
+
- ✅ /names endpoint (gem names list)
|
|
389
|
+
- ✅ Gem upload API
|
|
390
|
+
- ✅ Yank/unyank functionality
|
|
391
|
+
- ✅ Platform-specific gems support
|
|
392
|
+
- ✅ JSON API endpoints
|
|
393
|
+
- ✅ Legacy endpoints (specs.4.8.gz, Marshal.4.8)
|
|
394
|
+
- ✅ Token-based authentication
|
|
395
|
+
- ✅ Scope-based permissions
|
|
396
|
+
|
|
397
|
+
### Integration ✅
|
|
398
|
+
- **Core Updates:**
|
|
399
|
+
- ✅ Updated `IRegistryConfig` interface
|
|
400
|
+
- ✅ Updated `TRegistryProtocol` type
|
|
401
|
+
- ✅ Added authentication methods to `AuthManager`
|
|
402
|
+
- ✅ Added 30+ storage methods to `RegistryStorage`
|
|
403
|
+
- ✅ Updated `SmartRegistry` initialization and routing
|
|
404
|
+
- ✅ Module exports from `ts/index.ts`
|
|
405
|
+
|
|
406
|
+
- **Test Coverage:**
|
|
407
|
+
- ✅ `test/test.pypi.ts` - 25+ tests covering all PyPI endpoints
|
|
408
|
+
- ✅ `test/test.rubygems.ts` - 30+ tests covering all RubyGems endpoints
|
|
409
|
+
- ✅ `test/test.integration.pypi-rubygems.ts` - Integration tests
|
|
410
|
+
- ✅ Updated test helpers with PyPI and RubyGems support
|
|
411
|
+
|
|
412
|
+
### Known Limitations
|
|
413
|
+
1. **PyPI:**
|
|
414
|
+
- Does not implement legacy XML-RPC API
|
|
415
|
+
- No support for PGP signatures (data-gpg-sig always false)
|
|
416
|
+
- Metadata extraction from wheel files not implemented
|
|
417
|
+
|
|
418
|
+
2. **RubyGems:**
|
|
419
|
+
- Gem spec extraction from .gem files returns placeholder (Ruby Marshal parsing not implemented)
|
|
420
|
+
- Legacy Marshal endpoints return basic data only
|
|
421
|
+
- No support for gem dependencies resolution
|
|
422
|
+
|
|
423
|
+
### Configuration Example
|
|
424
|
+
```typescript
|
|
425
|
+
{
|
|
426
|
+
pypi: {
|
|
427
|
+
enabled: true,
|
|
428
|
+
basePath: '/pypi', // Also handles /simple
|
|
429
|
+
},
|
|
430
|
+
rubygems: {
|
|
431
|
+
enabled: true,
|
|
432
|
+
basePath: '/rubygems',
|
|
433
|
+
},
|
|
434
|
+
auth: {
|
|
435
|
+
pypiTokens: { enabled: true },
|
|
436
|
+
rubygemsTokens: { enabled: true },
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
package/readme.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @push.rocks/smartregistry
|
|
2
2
|
|
|
3
|
-
> 🚀 A composable TypeScript library implementing **OCI Distribution Specification v1.1**, **NPM Registry API**, **Maven Repository**, **Cargo/crates.io Registry**,
|
|
3
|
+
> 🚀 A composable TypeScript library implementing **OCI Distribution Specification v1.1**, **NPM Registry API**, **Maven Repository**, **Cargo/crates.io Registry**, **Composer/Packagist**, **PyPI (Python Package Index)**, and **RubyGems Registry** for building unified container and package registries.
|
|
4
|
+
|
|
5
|
+
## Issue Reporting and Security
|
|
6
|
+
|
|
7
|
+
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
4
8
|
|
|
5
9
|
## ✨ Features
|
|
6
10
|
|
|
@@ -10,12 +14,14 @@
|
|
|
10
14
|
- **Maven Repository**: Java/JVM artifact management with POM support
|
|
11
15
|
- **Cargo/crates.io Registry**: Rust crate registry with sparse HTTP protocol
|
|
12
16
|
- **Composer/Packagist**: PHP package registry with Composer v2 protocol
|
|
17
|
+
- **PyPI (Python Package Index)**: Python package registry with PEP 503/691 support
|
|
18
|
+
- **RubyGems Registry**: Ruby gem registry with compact index protocol
|
|
13
19
|
|
|
14
20
|
### 🏗️ Unified Architecture
|
|
15
21
|
- **Composable Design**: Core infrastructure with protocol plugins
|
|
16
|
-
- **Shared Storage**: Cloud-agnostic S3-compatible backend
|
|
22
|
+
- **Shared Storage**: Cloud-agnostic S3-compatible backend using [@push.rocks/smartbucket](https://www.npmjs.com/package/@push.rocks/smartbucket) with standardized `IS3Descriptor` from [@tsclass/tsclass](https://www.npmjs.com/package/@tsclass/tsclass)
|
|
17
23
|
- **Unified Authentication**: Scope-based permissions across all protocols
|
|
18
|
-
- **Path-based Routing**: `/oci/*` for containers, `/npm/*` for packages, `/maven/*` for Java artifacts, `/cargo/*` for Rust crates, `/composer/*` for PHP packages
|
|
24
|
+
- **Path-based Routing**: `/oci/*` for containers, `/npm/*` for packages, `/maven/*` for Java artifacts, `/cargo/*` for Rust crates, `/composer/*` for PHP packages, `/pypi/*` for Python packages, `/rubygems/*` for Ruby gems
|
|
19
25
|
|
|
20
26
|
### 🔐 Authentication & Authorization
|
|
21
27
|
- NPM UUID tokens for package operations
|
|
@@ -59,6 +65,23 @@
|
|
|
59
65
|
- ✅ Dependency resolution
|
|
60
66
|
- ✅ PSR-4/PSR-0 autoloading support
|
|
61
67
|
|
|
68
|
+
**PyPI Features:**
|
|
69
|
+
- ✅ PEP 503 Simple Repository API (HTML)
|
|
70
|
+
- ✅ PEP 691 JSON-based Simple API
|
|
71
|
+
- ✅ Package upload (wheel and sdist)
|
|
72
|
+
- ✅ Package name normalization
|
|
73
|
+
- ✅ Hash verification (SHA256, MD5, Blake2b)
|
|
74
|
+
- ✅ Content negotiation (JSON/HTML)
|
|
75
|
+
- ✅ Metadata API (JSON endpoints)
|
|
76
|
+
|
|
77
|
+
**RubyGems Features:**
|
|
78
|
+
- ✅ Compact Index protocol (modern Bundler)
|
|
79
|
+
- ✅ Gem publish/download (.gem files)
|
|
80
|
+
- ✅ Version yank/unyank
|
|
81
|
+
- ✅ Platform-specific gems
|
|
82
|
+
- ✅ Dependency resolution
|
|
83
|
+
- ✅ Legacy API compatibility
|
|
84
|
+
|
|
62
85
|
## 📥 Installation
|
|
63
86
|
|
|
64
87
|
```bash
|
|
@@ -114,6 +137,14 @@ const config: IRegistryConfig = {
|
|
|
114
137
|
enabled: true,
|
|
115
138
|
basePath: '/composer',
|
|
116
139
|
},
|
|
140
|
+
pypi: {
|
|
141
|
+
enabled: true,
|
|
142
|
+
basePath: '/pypi',
|
|
143
|
+
},
|
|
144
|
+
rubygems: {
|
|
145
|
+
enabled: true,
|
|
146
|
+
basePath: '/rubygems',
|
|
147
|
+
},
|
|
117
148
|
};
|
|
118
149
|
|
|
119
150
|
const registry = new SmartRegistry(config);
|
|
@@ -145,6 +176,11 @@ ts/
|
|
|
145
176
|
├── npm/ # NPM implementation
|
|
146
177
|
│ ├── classes.npmregistry.ts
|
|
147
178
|
│ └── interfaces.npm.ts
|
|
179
|
+
├── maven/ # Maven implementation
|
|
180
|
+
├── cargo/ # Cargo implementation
|
|
181
|
+
├── composer/ # Composer implementation
|
|
182
|
+
├── pypi/ # PyPI implementation
|
|
183
|
+
├── rubygems/ # RubyGems implementation
|
|
148
184
|
└── classes.smartregistry.ts # Main orchestrator
|
|
149
185
|
```
|
|
150
186
|
|
|
@@ -157,7 +193,12 @@ SmartRegistry (orchestrator)
|
|
|
157
193
|
↓
|
|
158
194
|
Path-based routing
|
|
159
195
|
├─→ /oci/* → OciRegistry
|
|
160
|
-
|
|
196
|
+
├─→ /npm/* → NpmRegistry
|
|
197
|
+
├─→ /maven/* → MavenRegistry
|
|
198
|
+
├─→ /cargo/* → CargoRegistry
|
|
199
|
+
├─→ /composer/* → ComposerRegistry
|
|
200
|
+
├─→ /pypi/* → PypiRegistry
|
|
201
|
+
└─→ /rubygems/* → RubyGemsRegistry
|
|
161
202
|
↓
|
|
162
203
|
Shared Storage & Auth
|
|
163
204
|
↓
|
|
@@ -409,6 +450,171 @@ composer require vendor/package
|
|
|
409
450
|
composer update
|
|
410
451
|
```
|
|
411
452
|
|
|
453
|
+
### 🐍 PyPI Registry (Python Packages)
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// Get package index (PEP 503 HTML format)
|
|
457
|
+
const htmlIndex = await registry.handleRequest({
|
|
458
|
+
method: 'GET',
|
|
459
|
+
path: '/simple/requests/',
|
|
460
|
+
headers: { 'Accept': 'text/html' },
|
|
461
|
+
query: {},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Get package index (PEP 691 JSON format)
|
|
465
|
+
const jsonIndex = await registry.handleRequest({
|
|
466
|
+
method: 'GET',
|
|
467
|
+
path: '/simple/requests/',
|
|
468
|
+
headers: { 'Accept': 'application/vnd.pypi.simple.v1+json' },
|
|
469
|
+
query: {},
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Upload a Python package (wheel or sdist)
|
|
473
|
+
const formData = new FormData();
|
|
474
|
+
formData.append(':action', 'file_upload');
|
|
475
|
+
formData.append('protocol_version', '1');
|
|
476
|
+
formData.append('name', 'my-package');
|
|
477
|
+
formData.append('version', '1.0.0');
|
|
478
|
+
formData.append('filetype', 'bdist_wheel');
|
|
479
|
+
formData.append('pyversion', 'py3');
|
|
480
|
+
formData.append('metadata_version', '2.1');
|
|
481
|
+
formData.append('sha256_digest', 'abc123...');
|
|
482
|
+
formData.append('content', packageFile, { filename: 'my_package-1.0.0-py3-none-any.whl' });
|
|
483
|
+
|
|
484
|
+
const upload = await registry.handleRequest({
|
|
485
|
+
method: 'POST',
|
|
486
|
+
path: '/pypi/legacy/',
|
|
487
|
+
headers: {
|
|
488
|
+
'Authorization': `Bearer <pypi-token>`,
|
|
489
|
+
'Content-Type': 'multipart/form-data',
|
|
490
|
+
},
|
|
491
|
+
query: {},
|
|
492
|
+
body: formData,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Get package metadata (PyPI JSON API)
|
|
496
|
+
const metadata = await registry.handleRequest({
|
|
497
|
+
method: 'GET',
|
|
498
|
+
path: '/pypi/my-package/json',
|
|
499
|
+
headers: {},
|
|
500
|
+
query: {},
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// Download a specific version
|
|
504
|
+
const download = await registry.handleRequest({
|
|
505
|
+
method: 'GET',
|
|
506
|
+
path: '/packages/my-package/my_package-1.0.0-py3-none-any.whl',
|
|
507
|
+
headers: {},
|
|
508
|
+
query: {},
|
|
509
|
+
});
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Using with pip:**
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
# Install from custom registry
|
|
516
|
+
pip install --index-url https://registry.example.com/simple/ my-package
|
|
517
|
+
|
|
518
|
+
# Upload to custom registry
|
|
519
|
+
python -m twine upload --repository-url https://registry.example.com/pypi/legacy/ dist/*
|
|
520
|
+
|
|
521
|
+
# Configure in pip.conf or pip.ini
|
|
522
|
+
[global]
|
|
523
|
+
index-url = https://registry.example.com/simple/
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### 💎 RubyGems Registry (Ruby Gems)
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// Get versions file (compact index)
|
|
530
|
+
const versions = await registry.handleRequest({
|
|
531
|
+
method: 'GET',
|
|
532
|
+
path: '/rubygems/versions',
|
|
533
|
+
headers: {},
|
|
534
|
+
query: {},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Get gem-specific info
|
|
538
|
+
const gemInfo = await registry.handleRequest({
|
|
539
|
+
method: 'GET',
|
|
540
|
+
path: '/rubygems/info/rails',
|
|
541
|
+
headers: {},
|
|
542
|
+
query: {},
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Get list of all gem names
|
|
546
|
+
const names = await registry.handleRequest({
|
|
547
|
+
method: 'GET',
|
|
548
|
+
path: '/rubygems/names',
|
|
549
|
+
headers: {},
|
|
550
|
+
query: {},
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Upload a gem file
|
|
554
|
+
const gemBuffer = await readFile('my-gem-1.0.0.gem');
|
|
555
|
+
const uploadGem = await registry.handleRequest({
|
|
556
|
+
method: 'POST',
|
|
557
|
+
path: '/rubygems/api/v1/gems',
|
|
558
|
+
headers: { 'Authorization': '<rubygems-api-key>' },
|
|
559
|
+
query: {},
|
|
560
|
+
body: gemBuffer,
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// Yank a version (make unavailable for install)
|
|
564
|
+
const yank = await registry.handleRequest({
|
|
565
|
+
method: 'DELETE',
|
|
566
|
+
path: '/rubygems/api/v1/gems/yank',
|
|
567
|
+
headers: { 'Authorization': '<rubygems-api-key>' },
|
|
568
|
+
query: { gem_name: 'my-gem', version: '1.0.0' },
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Unyank a version
|
|
572
|
+
const unyank = await registry.handleRequest({
|
|
573
|
+
method: 'PUT',
|
|
574
|
+
path: '/rubygems/api/v1/gems/unyank',
|
|
575
|
+
headers: { 'Authorization': '<rubygems-api-key>' },
|
|
576
|
+
query: { gem_name: 'my-gem', version: '1.0.0' },
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Get gem version metadata
|
|
580
|
+
const versionMeta = await registry.handleRequest({
|
|
581
|
+
method: 'GET',
|
|
582
|
+
path: '/rubygems/api/v1/versions/rails.json',
|
|
583
|
+
headers: {},
|
|
584
|
+
query: {},
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Download gem file
|
|
588
|
+
const gemDownload = await registry.handleRequest({
|
|
589
|
+
method: 'GET',
|
|
590
|
+
path: '/rubygems/gems/rails-7.0.0.gem',
|
|
591
|
+
headers: {},
|
|
592
|
+
query: {},
|
|
593
|
+
});
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
**Using with Bundler:**
|
|
597
|
+
|
|
598
|
+
```ruby
|
|
599
|
+
# Gemfile
|
|
600
|
+
source 'https://registry.example.com/rubygems' do
|
|
601
|
+
gem 'my-gem'
|
|
602
|
+
gem 'rails'
|
|
603
|
+
end
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
# Install gems
|
|
608
|
+
bundle install
|
|
609
|
+
|
|
610
|
+
# Push gem to custom registry
|
|
611
|
+
gem push my-gem-1.0.0.gem --host https://registry.example.com/rubygems
|
|
612
|
+
|
|
613
|
+
# Configure gem source
|
|
614
|
+
gem sources --add https://registry.example.com/rubygems/
|
|
615
|
+
gem sources --remove https://rubygems.org/
|
|
616
|
+
```
|
|
617
|
+
|
|
412
618
|
### 🔐 Authentication
|
|
413
619
|
|
|
414
620
|
```typescript
|
|
@@ -446,15 +652,24 @@ const canWrite = await authManager.authorize(
|
|
|
446
652
|
|
|
447
653
|
### Storage Configuration
|
|
448
654
|
|
|
655
|
+
The storage configuration extends `IS3Descriptor` from `@tsclass/tsclass` for standardized S3 configuration:
|
|
656
|
+
|
|
449
657
|
```typescript
|
|
658
|
+
import type { IS3Descriptor } from '@tsclass/tsclass';
|
|
659
|
+
|
|
660
|
+
storage: IS3Descriptor & {
|
|
661
|
+
bucketName: string; // Bucket name for registry storage
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Example:
|
|
450
665
|
storage: {
|
|
451
666
|
accessKey: string; // S3 access key
|
|
452
667
|
accessSecret: string; // S3 secret key
|
|
453
|
-
endpoint: string; // S3 endpoint
|
|
668
|
+
endpoint: string; // S3 endpoint (e.g., 's3.amazonaws.com')
|
|
454
669
|
port?: number; // Default: 443
|
|
455
670
|
useSsl?: boolean; // Default: true
|
|
456
|
-
region?: string; //
|
|
457
|
-
bucketName: string; // Bucket name
|
|
671
|
+
region?: string; // AWS region (e.g., 'us-east-1')
|
|
672
|
+
bucketName: string; // Bucket name for this registry
|
|
458
673
|
}
|
|
459
674
|
```
|
|
460
675
|
|
|
@@ -530,6 +745,20 @@ Unified storage abstraction for both OCI and NPM content.
|
|
|
530
745
|
- `getNpmTarball(name, version)` - Get tarball
|
|
531
746
|
- `putNpmTarball(name, version, data)` - Store tarball
|
|
532
747
|
|
|
748
|
+
**PyPI Methods:**
|
|
749
|
+
- `getPypiPackageMetadata(name)` - Get package metadata
|
|
750
|
+
- `putPypiPackageMetadata(name, data)` - Store package metadata
|
|
751
|
+
- `getPypiPackageFile(name, filename)` - Get package file
|
|
752
|
+
- `putPypiPackageFile(name, filename, data)` - Store package file
|
|
753
|
+
|
|
754
|
+
**RubyGems Methods:**
|
|
755
|
+
- `getRubyGemsVersions()` - Get versions index
|
|
756
|
+
- `putRubyGemsVersions(data)` - Store versions index
|
|
757
|
+
- `getRubyGemsInfo(gemName)` - Get gem info
|
|
758
|
+
- `putRubyGemsInfo(gemName, data)` - Store gem info
|
|
759
|
+
- `getRubyGem(gemName, version)` - Get .gem file
|
|
760
|
+
- `putRubyGem(gemName, version, data)` - Store .gem file
|
|
761
|
+
|
|
533
762
|
#### AuthManager
|
|
534
763
|
|
|
535
764
|
Unified authentication manager supporting both NPM and OCI authentication schemes.
|
|
@@ -607,11 +836,45 @@ Composer v2 repository API compliant implementation.
|
|
|
607
836
|
- `DELETE /packages/{vendor}/{package}` - Delete entire package
|
|
608
837
|
- `DELETE /packages/{vendor}/{package}/{version}` - Delete specific version
|
|
609
838
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
839
|
+
#### PypiRegistry
|
|
840
|
+
|
|
841
|
+
PyPI (Python Package Index) registry implementing PEP 503 and PEP 691.
|
|
842
|
+
|
|
843
|
+
**Endpoints:**
|
|
844
|
+
- `GET /simple/` - List all packages (HTML or JSON)
|
|
845
|
+
- `GET /simple/{package}/` - List package files (HTML or JSON)
|
|
846
|
+
- `POST /legacy/` - Upload package (multipart/form-data)
|
|
847
|
+
- `GET /pypi/{package}/json` - Package metadata API
|
|
848
|
+
- `GET /pypi/{package}/{version}/json` - Version-specific metadata
|
|
849
|
+
- `GET /packages/{package}/{filename}` - Download package file
|
|
850
|
+
|
|
851
|
+
**Features:**
|
|
852
|
+
- PEP 503 Simple Repository API (HTML)
|
|
853
|
+
- PEP 691 JSON-based Simple API
|
|
854
|
+
- Content negotiation via Accept header
|
|
855
|
+
- Package name normalization
|
|
856
|
+
- Hash verification (SHA256, MD5, Blake2b)
|
|
857
|
+
|
|
858
|
+
#### RubyGemsRegistry
|
|
859
|
+
|
|
860
|
+
RubyGems registry with compact index protocol for modern Bundler.
|
|
861
|
+
|
|
862
|
+
**Endpoints:**
|
|
863
|
+
- `GET /versions` - Master versions file (all gems)
|
|
864
|
+
- `GET /info/{gem}` - Gem-specific info file
|
|
865
|
+
- `GET /names` - List of all gem names
|
|
866
|
+
- `POST /api/v1/gems` - Upload gem file
|
|
867
|
+
- `DELETE /api/v1/gems/yank` - Yank (deprecate) version
|
|
868
|
+
- `PUT /api/v1/gems/unyank` - Unyank version
|
|
869
|
+
- `GET /api/v1/versions/{gem}.json` - Version metadata
|
|
870
|
+
- `GET /gems/{gem}-{version}.gem` - Download gem file
|
|
871
|
+
|
|
872
|
+
**Features:**
|
|
873
|
+
- Compact Index format (append-only text files)
|
|
874
|
+
- Platform-specific gems support
|
|
875
|
+
- Yank/unyank functionality
|
|
876
|
+
- Checksum calculations (MD5 for index, SHA256 for gems)
|
|
877
|
+
- Legacy Marshal API compatibility
|
|
615
878
|
|
|
616
879
|
## 🗄️ Storage Structure
|
|
617
880
|
|
|
@@ -651,11 +914,24 @@ bucket/
|
|
|
651
914
|
│ │ └── {p1}/{p2}/{name} # 4+ char (e.g., "se/rd/serde")
|
|
652
915
|
│ └── crates/
|
|
653
916
|
│ └── {name}/{name}-{version}.crate # Gzipped tar archives
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
917
|
+
├── composer/
|
|
918
|
+
│ └── packages/
|
|
919
|
+
│ └── {vendor}/{package}/
|
|
920
|
+
│ ├── metadata.json # All versions metadata
|
|
921
|
+
│ └── {reference}.zip # Package ZIP files
|
|
922
|
+
├── pypi/
|
|
923
|
+
│ ├── simple/ # PEP 503 HTML files
|
|
924
|
+
│ │ ├── index.html # All packages list
|
|
925
|
+
│ │ └── {package}/index.html # Package versions list
|
|
926
|
+
│ ├── packages/
|
|
927
|
+
│ │ └── {package}/{filename} # .whl and .tar.gz files
|
|
928
|
+
│ └── metadata/
|
|
929
|
+
│ └── {package}/metadata.json # Package metadata
|
|
930
|
+
└── rubygems/
|
|
931
|
+
├── versions # Master versions file
|
|
932
|
+
├── info/{gemname} # Per-gem info files
|
|
933
|
+
├── names # All gem names
|
|
934
|
+
└── gems/{gemname}-{version}.gem # .gem files
|
|
659
935
|
```
|
|
660
936
|
|
|
661
937
|
## 🎯 Scope Format
|
|
@@ -685,6 +961,14 @@ Examples:
|
|
|
685
961
|
composer:package:vendor/package:read # Read Composer package
|
|
686
962
|
composer:package:*:write # Write any package
|
|
687
963
|
composer:*:*:* # Full Composer access
|
|
964
|
+
|
|
965
|
+
pypi:package:my-package:read # Read PyPI package
|
|
966
|
+
pypi:package:*:write # Write any package
|
|
967
|
+
pypi:*:*:* # Full PyPI access
|
|
968
|
+
|
|
969
|
+
rubygems:gem:rails:read # Read RubyGems gem
|
|
970
|
+
rubygems:gem:*:write # Write any gem
|
|
971
|
+
rubygems:*:*:* # Full RubyGems access
|
|
688
972
|
```
|
|
689
973
|
|
|
690
974
|
## 🔌 Integration Examples
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartregistry',
|
|
6
|
-
version: '1.
|
|
7
|
-
description: '
|
|
6
|
+
version: '1.7.0',
|
|
7
|
+
description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
|
|
8
8
|
}
|
|
@@ -7,10 +7,12 @@ import { NpmRegistry } from './npm/classes.npmregistry.js';
|
|
|
7
7
|
import { MavenRegistry } from './maven/classes.mavenregistry.js';
|
|
8
8
|
import { CargoRegistry } from './cargo/classes.cargoregistry.js';
|
|
9
9
|
import { ComposerRegistry } from './composer/classes.composerregistry.js';
|
|
10
|
+
import { PypiRegistry } from './pypi/classes.pypiregistry.js';
|
|
11
|
+
import { RubyGemsRegistry } from './rubygems/classes.rubygemsregistry.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Main registry orchestrator
|
|
13
|
-
* Routes requests to appropriate protocol handlers (OCI, NPM, Maven, Cargo, or
|
|
15
|
+
* Routes requests to appropriate protocol handlers (OCI, NPM, Maven, Cargo, Composer, PyPI, or RubyGems)
|
|
14
16
|
*/
|
|
15
17
|
export class SmartRegistry {
|
|
16
18
|
private storage: RegistryStorage;
|
|
@@ -81,6 +83,24 @@ export class SmartRegistry {
|
|
|
81
83
|
this.registries.set('composer', composerRegistry);
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
// Initialize PyPI registry if enabled
|
|
87
|
+
if (this.config.pypi?.enabled) {
|
|
88
|
+
const pypiBasePath = this.config.pypi.basePath || '/pypi';
|
|
89
|
+
const registryUrl = `http://localhost:5000`; // TODO: Make configurable
|
|
90
|
+
const pypiRegistry = new PypiRegistry(this.storage, this.authManager, pypiBasePath, registryUrl);
|
|
91
|
+
await pypiRegistry.init();
|
|
92
|
+
this.registries.set('pypi', pypiRegistry);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Initialize RubyGems registry if enabled
|
|
96
|
+
if (this.config.rubygems?.enabled) {
|
|
97
|
+
const rubygemsBasePath = this.config.rubygems.basePath || '/rubygems';
|
|
98
|
+
const registryUrl = `http://localhost:5000${rubygemsBasePath}`; // TODO: Make configurable
|
|
99
|
+
const rubygemsRegistry = new RubyGemsRegistry(this.storage, this.authManager, rubygemsBasePath, registryUrl);
|
|
100
|
+
await rubygemsRegistry.init();
|
|
101
|
+
this.registries.set('rubygems', rubygemsRegistry);
|
|
102
|
+
}
|
|
103
|
+
|
|
84
104
|
this.initialized = true;
|
|
85
105
|
}
|
|
86
106
|
|
|
@@ -131,6 +151,25 @@ export class SmartRegistry {
|
|
|
131
151
|
}
|
|
132
152
|
}
|
|
133
153
|
|
|
154
|
+
// Route to PyPI registry (also handles /simple prefix)
|
|
155
|
+
if (this.config.pypi?.enabled) {
|
|
156
|
+
const pypiBasePath = this.config.pypi.basePath || '/pypi';
|
|
157
|
+
if (path.startsWith(pypiBasePath) || path.startsWith('/simple')) {
|
|
158
|
+
const pypiRegistry = this.registries.get('pypi');
|
|
159
|
+
if (pypiRegistry) {
|
|
160
|
+
return pypiRegistry.handleRequest(context);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Route to RubyGems registry
|
|
166
|
+
if (this.config.rubygems?.enabled && path.startsWith(this.config.rubygems.basePath)) {
|
|
167
|
+
const rubygemsRegistry = this.registries.get('rubygems');
|
|
168
|
+
if (rubygemsRegistry) {
|
|
169
|
+
return rubygemsRegistry.handleRequest(context);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
134
173
|
// No matching registry
|
|
135
174
|
return {
|
|
136
175
|
status: 404,
|
|
@@ -159,7 +198,7 @@ export class SmartRegistry {
|
|
|
159
198
|
/**
|
|
160
199
|
* Get a specific registry handler
|
|
161
200
|
*/
|
|
162
|
-
public getRegistry(protocol: 'oci' | 'npm' | 'maven' | 'cargo' | 'composer'): BaseRegistry | undefined {
|
|
201
|
+
public getRegistry(protocol: 'oci' | 'npm' | 'maven' | 'cargo' | 'composer' | 'pypi' | 'rubygems'): BaseRegistry | undefined {
|
|
163
202
|
return this.registries.get(protocol);
|
|
164
203
|
}
|
|
165
204
|
|