@push.rocks/smartregistry 1.1.1 → 1.4.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.
Files changed (51) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/cargo/classes.cargoregistry.d.ts +79 -0
  3. package/dist_ts/cargo/classes.cargoregistry.js +490 -0
  4. package/dist_ts/cargo/index.d.ts +5 -0
  5. package/dist_ts/cargo/index.js +6 -0
  6. package/dist_ts/cargo/interfaces.cargo.d.ts +160 -0
  7. package/dist_ts/cargo/interfaces.cargo.js +6 -0
  8. package/dist_ts/classes.smartregistry.d.ts +2 -2
  9. package/dist_ts/classes.smartregistry.js +50 -2
  10. package/dist_ts/composer/classes.composerregistry.d.ts +26 -0
  11. package/dist_ts/composer/classes.composerregistry.js +366 -0
  12. package/dist_ts/composer/helpers.composer.d.ts +35 -0
  13. package/dist_ts/composer/helpers.composer.js +120 -0
  14. package/dist_ts/composer/index.d.ts +7 -0
  15. package/dist_ts/composer/index.js +8 -0
  16. package/dist_ts/composer/interfaces.composer.d.ts +102 -0
  17. package/dist_ts/composer/interfaces.composer.js +6 -0
  18. package/dist_ts/core/classes.authmanager.d.ts +46 -1
  19. package/dist_ts/core/classes.authmanager.js +121 -12
  20. package/dist_ts/core/classes.registrystorage.d.ts +103 -0
  21. package/dist_ts/core/classes.registrystorage.js +253 -1
  22. package/dist_ts/core/interfaces.core.d.ts +4 -1
  23. package/dist_ts/index.d.ts +4 -1
  24. package/dist_ts/index.js +8 -2
  25. package/dist_ts/maven/classes.mavenregistry.d.ts +35 -0
  26. package/dist_ts/maven/classes.mavenregistry.js +407 -0
  27. package/dist_ts/maven/helpers.maven.d.ts +68 -0
  28. package/dist_ts/maven/helpers.maven.js +286 -0
  29. package/dist_ts/maven/index.d.ts +6 -0
  30. package/dist_ts/maven/index.js +7 -0
  31. package/dist_ts/maven/interfaces.maven.d.ts +116 -0
  32. package/dist_ts/maven/interfaces.maven.js +6 -0
  33. package/package.json +3 -2
  34. package/readme.md +288 -14
  35. package/ts/00_commitinfo_data.ts +1 -1
  36. package/ts/cargo/classes.cargoregistry.ts +604 -0
  37. package/ts/cargo/index.ts +6 -0
  38. package/ts/cargo/interfaces.cargo.ts +169 -0
  39. package/ts/classes.smartregistry.ts +56 -2
  40. package/ts/composer/classes.composerregistry.ts +475 -0
  41. package/ts/composer/helpers.composer.ts +139 -0
  42. package/ts/composer/index.ts +8 -0
  43. package/ts/composer/interfaces.composer.ts +111 -0
  44. package/ts/core/classes.authmanager.ts +145 -12
  45. package/ts/core/classes.registrystorage.ts +334 -0
  46. package/ts/core/interfaces.core.ts +4 -1
  47. package/ts/index.ts +10 -1
  48. package/ts/maven/classes.mavenregistry.ts +580 -0
  49. package/ts/maven/helpers.maven.ts +346 -0
  50. package/ts/maven/index.ts +7 -0
  51. package/ts/maven/interfaces.maven.ts +127 -0
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Composer Registry Implementation
3
+ * Compliant with Composer v2 repository API
4
+ */
5
+
6
+ import { BaseRegistry } from '../core/classes.baseregistry.js';
7
+ import type { RegistryStorage } from '../core/classes.registrystorage.js';
8
+ import type { AuthManager } from '../core/classes.authmanager.js';
9
+ import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
10
+ import type {
11
+ IComposerPackage,
12
+ IComposerPackageMetadata,
13
+ IComposerRepository,
14
+ } from './interfaces.composer.js';
15
+ import {
16
+ normalizeVersion,
17
+ validateComposerJson,
18
+ extractComposerJsonFromZip,
19
+ calculateSha1,
20
+ parseVendorPackage,
21
+ generatePackagesJson,
22
+ sortVersions,
23
+ } from './helpers.composer.js';
24
+
25
+ export class ComposerRegistry extends BaseRegistry {
26
+ private storage: RegistryStorage;
27
+ private authManager: AuthManager;
28
+ private basePath: string = '/composer';
29
+ private registryUrl: string;
30
+
31
+ constructor(
32
+ storage: RegistryStorage,
33
+ authManager: AuthManager,
34
+ basePath: string = '/composer',
35
+ registryUrl: string = 'http://localhost:5000/composer'
36
+ ) {
37
+ super();
38
+ this.storage = storage;
39
+ this.authManager = authManager;
40
+ this.basePath = basePath;
41
+ this.registryUrl = registryUrl;
42
+ }
43
+
44
+ public async init(): Promise<void> {
45
+ // Composer registry initialization
46
+ }
47
+
48
+ public getBasePath(): string {
49
+ return this.basePath;
50
+ }
51
+
52
+ public async handleRequest(context: IRequestContext): Promise<IResponse> {
53
+ const path = context.path.replace(this.basePath, '');
54
+
55
+ // Extract token from Authorization header
56
+ const authHeader = context.headers['authorization'] || context.headers['Authorization'];
57
+ let token: IAuthToken | null = null;
58
+
59
+ if (authHeader) {
60
+ if (authHeader.startsWith('Bearer ')) {
61
+ const tokenString = authHeader.replace(/^Bearer\s+/i, '');
62
+ token = await this.authManager.validateToken(tokenString, 'composer');
63
+ } else if (authHeader.startsWith('Basic ')) {
64
+ // Handle HTTP Basic Auth
65
+ const credentials = Buffer.from(authHeader.replace(/^Basic\s+/i, ''), 'base64').toString('utf-8');
66
+ const [username, password] = credentials.split(':');
67
+ const userId = await this.authManager.authenticate({ username, password });
68
+ if (userId) {
69
+ // Create temporary token for this request
70
+ token = {
71
+ type: 'composer',
72
+ userId,
73
+ scopes: ['composer:*:*:read'],
74
+ readonly: true,
75
+ };
76
+ }
77
+ }
78
+ }
79
+
80
+ // Root packages.json
81
+ if (path === '/packages.json' || path === '' || path === '/') {
82
+ return this.handlePackagesJson();
83
+ }
84
+
85
+ // Package metadata: /p2/{vendor}/{package}.json or /p2/{vendor}/{package}~dev.json
86
+ const metadataMatch = path.match(/^\/p2\/([^\/]+\/[^\/]+?)(~dev)?\.json$/);
87
+ if (metadataMatch) {
88
+ const [, vendorPackage, devSuffix] = metadataMatch;
89
+ const includeDev = !!devSuffix;
90
+ return this.handlePackageMetadata(vendorPackage, includeDev, token);
91
+ }
92
+
93
+ // Package list: /packages/list.json?filter=vendor/*
94
+ if (path.startsWith('/packages/list.json')) {
95
+ const filter = context.query['filter'];
96
+ return this.handlePackageList(filter, token);
97
+ }
98
+
99
+ // Package ZIP download: /dists/{vendor}/{package}/{reference}.zip
100
+ const distMatch = path.match(/^\/dists\/([^\/]+\/[^\/]+)\/([^\/]+)\.zip$/);
101
+ if (distMatch) {
102
+ const [, vendorPackage, reference] = distMatch;
103
+ return this.handlePackageDownload(vendorPackage, reference, token);
104
+ }
105
+
106
+ // Package upload: PUT /packages/{vendor}/{package}
107
+ const uploadMatch = path.match(/^\/packages\/([^\/]+\/[^\/]+)$/);
108
+ if (uploadMatch && context.method === 'PUT') {
109
+ const vendorPackage = uploadMatch[1];
110
+ return this.handlePackageUpload(vendorPackage, context.body, token);
111
+ }
112
+
113
+ // Package delete: DELETE /packages/{vendor}/{package}
114
+ if (uploadMatch && context.method === 'DELETE') {
115
+ const vendorPackage = uploadMatch[1];
116
+ return this.handlePackageDelete(vendorPackage, token);
117
+ }
118
+
119
+ // Version delete: DELETE /packages/{vendor}/{package}/{version}
120
+ const versionDeleteMatch = path.match(/^\/packages\/([^\/]+\/[^\/]+)\/(.+)$/);
121
+ if (versionDeleteMatch && context.method === 'DELETE') {
122
+ const [, vendorPackage, version] = versionDeleteMatch;
123
+ return this.handleVersionDelete(vendorPackage, version, token);
124
+ }
125
+
126
+ return {
127
+ status: 404,
128
+ headers: { 'Content-Type': 'application/json' },
129
+ body: { status: 'error', message: 'Not found' },
130
+ };
131
+ }
132
+
133
+ protected async checkPermission(
134
+ token: IAuthToken | null,
135
+ resource: string,
136
+ action: string
137
+ ): Promise<boolean> {
138
+ if (!token) return false;
139
+ return this.authManager.authorize(token, `composer:package:${resource}`, action);
140
+ }
141
+
142
+ // ========================================================================
143
+ // REQUEST HANDLERS
144
+ // ========================================================================
145
+
146
+ private async handlePackagesJson(): Promise<IResponse> {
147
+ const availablePackages = await this.storage.listComposerPackages();
148
+ const packagesJson = generatePackagesJson(this.registryUrl, availablePackages);
149
+
150
+ return {
151
+ status: 200,
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: packagesJson,
154
+ };
155
+ }
156
+
157
+ private async handlePackageMetadata(
158
+ vendorPackage: string,
159
+ includeDev: boolean,
160
+ token: IAuthToken | null
161
+ ): Promise<IResponse> {
162
+ // Check read permission
163
+ if (!await this.checkPermission(token, vendorPackage, 'read')) {
164
+ return {
165
+ status: 401,
166
+ headers: { 'WWW-Authenticate': 'Bearer realm="composer"' },
167
+ body: { status: 'error', message: 'Authentication required' },
168
+ };
169
+ }
170
+
171
+ const metadata = await this.storage.getComposerPackageMetadata(vendorPackage);
172
+
173
+ if (!metadata) {
174
+ return {
175
+ status: 404,
176
+ headers: { 'Content-Type': 'application/json' },
177
+ body: { status: 'error', message: 'Package not found' },
178
+ };
179
+ }
180
+
181
+ // Filter dev versions if needed
182
+ let packages = metadata.packages[vendorPackage] || [];
183
+ if (!includeDev) {
184
+ packages = packages.filter((pkg: IComposerPackage) =>
185
+ !pkg.version.includes('dev') && !pkg.version.includes('alpha') && !pkg.version.includes('beta')
186
+ );
187
+ }
188
+
189
+ const response: IComposerPackageMetadata = {
190
+ minified: 'composer/2.0',
191
+ packages: {
192
+ [vendorPackage]: packages,
193
+ },
194
+ };
195
+
196
+ return {
197
+ status: 200,
198
+ headers: {
199
+ 'Content-Type': 'application/json',
200
+ 'Last-Modified': metadata.lastModified || new Date().toUTCString(),
201
+ },
202
+ body: response,
203
+ };
204
+ }
205
+
206
+ private async handlePackageList(
207
+ filter: string | undefined,
208
+ token: IAuthToken | null
209
+ ): Promise<IResponse> {
210
+ let packages = await this.storage.listComposerPackages();
211
+
212
+ // Apply filter if provided
213
+ if (filter) {
214
+ const regex = new RegExp('^' + filter.replace(/\*/g, '.*') + '$');
215
+ packages = packages.filter(pkg => regex.test(pkg));
216
+ }
217
+
218
+ return {
219
+ status: 200,
220
+ headers: { 'Content-Type': 'application/json' },
221
+ body: { packageNames: packages },
222
+ };
223
+ }
224
+
225
+ private async handlePackageDownload(
226
+ vendorPackage: string,
227
+ reference: string,
228
+ token: IAuthToken | null
229
+ ): Promise<IResponse> {
230
+ // Check read permission
231
+ if (!await this.checkPermission(token, vendorPackage, 'read')) {
232
+ return {
233
+ status: 401,
234
+ headers: { 'WWW-Authenticate': 'Bearer realm="composer"' },
235
+ body: { status: 'error', message: 'Authentication required' },
236
+ };
237
+ }
238
+
239
+ const zipData = await this.storage.getComposerPackageZip(vendorPackage, reference);
240
+
241
+ if (!zipData) {
242
+ return {
243
+ status: 404,
244
+ headers: {},
245
+ body: { status: 'error', message: 'Package file not found' },
246
+ };
247
+ }
248
+
249
+ return {
250
+ status: 200,
251
+ headers: {
252
+ 'Content-Type': 'application/zip',
253
+ 'Content-Length': zipData.length.toString(),
254
+ 'Content-Disposition': `attachment; filename="${reference}.zip"`,
255
+ },
256
+ body: zipData,
257
+ };
258
+ }
259
+
260
+ private async handlePackageUpload(
261
+ vendorPackage: string,
262
+ body: any,
263
+ token: IAuthToken | null
264
+ ): Promise<IResponse> {
265
+ // Check write permission
266
+ if (!await this.checkPermission(token, vendorPackage, 'write')) {
267
+ return {
268
+ status: 401,
269
+ headers: {},
270
+ body: { status: 'error', message: 'Write permission required' },
271
+ };
272
+ }
273
+
274
+ if (!body || !Buffer.isBuffer(body)) {
275
+ return {
276
+ status: 400,
277
+ headers: {},
278
+ body: { status: 'error', message: 'ZIP file required' },
279
+ };
280
+ }
281
+
282
+ // Extract and validate composer.json from ZIP
283
+ const composerJson = await extractComposerJsonFromZip(body);
284
+ if (!composerJson || !validateComposerJson(composerJson)) {
285
+ return {
286
+ status: 400,
287
+ headers: {},
288
+ body: { status: 'error', message: 'Invalid composer.json in ZIP' },
289
+ };
290
+ }
291
+
292
+ // Verify package name matches
293
+ if (composerJson.name !== vendorPackage) {
294
+ return {
295
+ status: 400,
296
+ headers: {},
297
+ body: { status: 'error', message: 'Package name mismatch' },
298
+ };
299
+ }
300
+
301
+ const version = composerJson.version;
302
+ if (!version) {
303
+ return {
304
+ status: 400,
305
+ headers: {},
306
+ body: { status: 'error', message: 'Version required in composer.json' },
307
+ };
308
+ }
309
+
310
+ // Calculate SHA-1 hash
311
+ const shasum = await calculateSha1(body);
312
+
313
+ // Generate reference (use version or commit hash)
314
+ const reference = composerJson.source?.reference || version.replace(/[^a-zA-Z0-9.-]/g, '-');
315
+
316
+ // Store ZIP file
317
+ await this.storage.putComposerPackageZip(vendorPackage, reference, body);
318
+
319
+ // Get or create metadata
320
+ let metadata = await this.storage.getComposerPackageMetadata(vendorPackage);
321
+ if (!metadata) {
322
+ metadata = {
323
+ packages: {
324
+ [vendorPackage]: [],
325
+ },
326
+ lastModified: new Date().toUTCString(),
327
+ };
328
+ }
329
+
330
+ // Build package entry
331
+ const packageEntry: IComposerPackage = {
332
+ ...composerJson,
333
+ version_normalized: normalizeVersion(version),
334
+ dist: {
335
+ type: 'zip',
336
+ url: `${this.registryUrl}/dists/${vendorPackage}/${reference}.zip`,
337
+ reference,
338
+ shasum,
339
+ },
340
+ time: new Date().toISOString(),
341
+ };
342
+
343
+ // Add to metadata (check if version already exists)
344
+ const packages = metadata.packages[vendorPackage] || [];
345
+ const existingIndex = packages.findIndex((p: IComposerPackage) => p.version === version);
346
+
347
+ if (existingIndex >= 0) {
348
+ return {
349
+ status: 409,
350
+ headers: {},
351
+ body: { status: 'error', message: 'Version already exists' },
352
+ };
353
+ }
354
+
355
+ packages.push(packageEntry);
356
+
357
+ // Sort by version
358
+ const sortedVersions = sortVersions(packages.map((p: IComposerPackage) => p.version));
359
+ packages.sort((a: IComposerPackage, b: IComposerPackage) => {
360
+ return sortedVersions.indexOf(a.version) - sortedVersions.indexOf(b.version);
361
+ });
362
+
363
+ metadata.packages[vendorPackage] = packages;
364
+ metadata.lastModified = new Date().toUTCString();
365
+
366
+ // Store updated metadata
367
+ await this.storage.putComposerPackageMetadata(vendorPackage, metadata);
368
+
369
+ return {
370
+ status: 201,
371
+ headers: {},
372
+ body: {
373
+ status: 'success',
374
+ message: 'Package uploaded successfully',
375
+ package: vendorPackage,
376
+ version,
377
+ },
378
+ };
379
+ }
380
+
381
+ private async handlePackageDelete(
382
+ vendorPackage: string,
383
+ token: IAuthToken | null
384
+ ): Promise<IResponse> {
385
+ // Check delete permission
386
+ if (!await this.checkPermission(token, vendorPackage, 'delete')) {
387
+ return {
388
+ status: 401,
389
+ headers: {},
390
+ body: { status: 'error', message: 'Delete permission required' },
391
+ };
392
+ }
393
+
394
+ const metadata = await this.storage.getComposerPackageMetadata(vendorPackage);
395
+ if (!metadata) {
396
+ return {
397
+ status: 404,
398
+ headers: {},
399
+ body: { status: 'error', message: 'Package not found' },
400
+ };
401
+ }
402
+
403
+ // Delete all ZIP files
404
+ const packages = metadata.packages[vendorPackage] || [];
405
+ for (const pkg of packages) {
406
+ if (pkg.dist?.reference) {
407
+ await this.storage.deleteComposerPackageZip(vendorPackage, pkg.dist.reference);
408
+ }
409
+ }
410
+
411
+ // Delete metadata
412
+ await this.storage.deleteComposerPackageMetadata(vendorPackage);
413
+
414
+ return {
415
+ status: 204,
416
+ headers: {},
417
+ body: null,
418
+ };
419
+ }
420
+
421
+ private async handleVersionDelete(
422
+ vendorPackage: string,
423
+ version: string,
424
+ token: IAuthToken | null
425
+ ): Promise<IResponse> {
426
+ // Check delete permission
427
+ if (!await this.checkPermission(token, vendorPackage, 'delete')) {
428
+ return {
429
+ status: 401,
430
+ headers: {},
431
+ body: { status: 'error', message: 'Delete permission required' },
432
+ };
433
+ }
434
+
435
+ const metadata = await this.storage.getComposerPackageMetadata(vendorPackage);
436
+ if (!metadata) {
437
+ return {
438
+ status: 404,
439
+ headers: {},
440
+ body: { status: 'error', message: 'Package not found' },
441
+ };
442
+ }
443
+
444
+ const packages = metadata.packages[vendorPackage] || [];
445
+ const versionIndex = packages.findIndex((p: IComposerPackage) => p.version === version);
446
+
447
+ if (versionIndex === -1) {
448
+ return {
449
+ status: 404,
450
+ headers: {},
451
+ body: { status: 'error', message: 'Version not found' },
452
+ };
453
+ }
454
+
455
+ // Delete ZIP file
456
+ const pkg = packages[versionIndex];
457
+ if (pkg.dist?.reference) {
458
+ await this.storage.deleteComposerPackageZip(vendorPackage, pkg.dist.reference);
459
+ }
460
+
461
+ // Remove from metadata
462
+ packages.splice(versionIndex, 1);
463
+ metadata.packages[vendorPackage] = packages;
464
+ metadata.lastModified = new Date().toUTCString();
465
+
466
+ // Save updated metadata
467
+ await this.storage.putComposerPackageMetadata(vendorPackage, metadata);
468
+
469
+ return {
470
+ status: 204,
471
+ headers: {},
472
+ body: null,
473
+ };
474
+ }
475
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Composer Registry Helper Functions
3
+ */
4
+
5
+ import type { IComposerPackage } from './interfaces.composer.js';
6
+
7
+ /**
8
+ * Normalize version string to Composer format
9
+ * Example: "1.0.0" -> "1.0.0.0", "v2.3.1" -> "2.3.1.0"
10
+ */
11
+ export function normalizeVersion(version: string): string {
12
+ // Remove 'v' prefix if present
13
+ let normalized = version.replace(/^v/i, '');
14
+
15
+ // Handle special versions (dev, alpha, beta, rc)
16
+ if (normalized.includes('dev') || normalized.includes('alpha') || normalized.includes('beta') || normalized.includes('RC')) {
17
+ // For dev versions, just return as-is with .0 appended if needed
18
+ const parts = normalized.split(/[-+]/)[0].split('.');
19
+ while (parts.length < 4) {
20
+ parts.push('0');
21
+ }
22
+ return parts.slice(0, 4).join('.');
23
+ }
24
+
25
+ // Split by dots
26
+ const parts = normalized.split('.');
27
+
28
+ // Ensure 4 parts (major.minor.patch.build)
29
+ while (parts.length < 4) {
30
+ parts.push('0');
31
+ }
32
+
33
+ return parts.slice(0, 4).join('.');
34
+ }
35
+
36
+ /**
37
+ * Validate composer.json structure
38
+ */
39
+ export function validateComposerJson(composerJson: any): boolean {
40
+ return !!(
41
+ composerJson &&
42
+ typeof composerJson.name === 'string' &&
43
+ composerJson.name.includes('/') &&
44
+ (composerJson.version || composerJson.require)
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Extract composer.json from ZIP buffer
50
+ */
51
+ export async function extractComposerJsonFromZip(zipBuffer: Buffer): Promise<any | null> {
52
+ try {
53
+ const AdmZip = (await import('adm-zip')).default;
54
+ const zip = new AdmZip(zipBuffer);
55
+ const entries = zip.getEntries();
56
+
57
+ // Look for composer.json in root or first-level directory
58
+ for (const entry of entries) {
59
+ if (entry.entryName.endsWith('composer.json')) {
60
+ const parts = entry.entryName.split('/');
61
+ if (parts.length <= 2) { // Root or first-level dir
62
+ const content = entry.getData().toString('utf-8');
63
+ return JSON.parse(content);
64
+ }
65
+ }
66
+ }
67
+
68
+ return null;
69
+ } catch (error) {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Calculate SHA-1 hash for ZIP file
76
+ */
77
+ export async function calculateSha1(data: Buffer): Promise<string> {
78
+ const crypto = await import('crypto');
79
+ return crypto.createHash('sha1').update(data).digest('hex');
80
+ }
81
+
82
+ /**
83
+ * Parse vendor/package format
84
+ */
85
+ export function parseVendorPackage(name: string): { vendor: string; package: string } | null {
86
+ const parts = name.split('/');
87
+ if (parts.length !== 2) {
88
+ return null;
89
+ }
90
+ return { vendor: parts[0], package: parts[1] };
91
+ }
92
+
93
+ /**
94
+ * Generate packages.json root repository file
95
+ */
96
+ export function generatePackagesJson(
97
+ registryUrl: string,
98
+ availablePackages: string[]
99
+ ): any {
100
+ return {
101
+ 'metadata-url': `${registryUrl}/p2/%package%.json`,
102
+ 'available-packages': availablePackages,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Sort versions in semantic version order
108
+ */
109
+ export function sortVersions(versions: string[]): string[] {
110
+ return versions.sort((a, b) => {
111
+ const aParts = a.replace(/^v/i, '').split(/[.-]/).map(part => {
112
+ const num = parseInt(part, 10);
113
+ return isNaN(num) ? part : num;
114
+ });
115
+ const bParts = b.replace(/^v/i, '').split(/[.-]/).map(part => {
116
+ const num = parseInt(part, 10);
117
+ return isNaN(num) ? part : num;
118
+ });
119
+
120
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
121
+ const aPart = aParts[i] ?? 0;
122
+ const bPart = bParts[i] ?? 0;
123
+
124
+ // Compare numbers numerically, strings lexicographically
125
+ if (typeof aPart === 'number' && typeof bPart === 'number') {
126
+ if (aPart !== bPart) {
127
+ return aPart - bPart;
128
+ }
129
+ } else {
130
+ const aStr = String(aPart);
131
+ const bStr = String(bPart);
132
+ if (aStr !== bStr) {
133
+ return aStr.localeCompare(bStr);
134
+ }
135
+ }
136
+ }
137
+ return 0;
138
+ });
139
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Composer Registry Module
3
+ * Export all public interfaces, classes, and helpers
4
+ */
5
+
6
+ export { ComposerRegistry } from './classes.composerregistry.js';
7
+ export * from './interfaces.composer.js';
8
+ export * from './helpers.composer.js';