@powerhousedao/registry 6.2.3-dev.9 → 6.2.3
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/README.md +30 -0
- package/dist/cli.d.mts +6 -10
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +649 -85
- package/dist/cli.mjs.map +1 -1
- package/dist/plugins/verdaccio-registry-auth.js +2 -2
- package/package.json +4 -6
package/dist/cli.mjs
CHANGED
|
@@ -9,8 +9,10 @@ import { Pool } from "pg";
|
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import { pipeline } from "node:stream/promises";
|
|
11
11
|
import { Readable } from "node:stream";
|
|
12
|
-
import { extract } from "tar";
|
|
12
|
+
import { create, extract, list } from "tar";
|
|
13
13
|
import { slimManifest } from "@powerhousedao/shared/registry";
|
|
14
|
+
import { promisify } from "node:util";
|
|
15
|
+
import { gunzip } from "node:zlib";
|
|
14
16
|
import { fileURLToPath } from "node:url";
|
|
15
17
|
//#endregion
|
|
16
18
|
//#region src/auth/pg-store.ts
|
|
@@ -106,27 +108,48 @@ function wasStoreLoaded(token) {
|
|
|
106
108
|
}
|
|
107
109
|
//#endregion
|
|
108
110
|
//#region src/semver.ts
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
function splitVersion(version) {
|
|
112
|
+
const withoutBuild = version.split("+", 1)[0];
|
|
113
|
+
const dash = withoutBuild.indexOf("-");
|
|
114
|
+
const core = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
|
|
115
|
+
const pre = dash === -1 ? "" : withoutBuild.slice(dash + 1);
|
|
116
|
+
return {
|
|
117
|
+
core: core.split(".").map(Number),
|
|
118
|
+
pre: pre ? pre.split(".") : []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const NUMERIC = /^\d+$/;
|
|
122
|
+
function comparePrerelease(a, b) {
|
|
123
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
124
|
+
if (i >= a.length) return -1;
|
|
125
|
+
if (i >= b.length) return 1;
|
|
126
|
+
const x = a[i];
|
|
127
|
+
const y = b[i];
|
|
128
|
+
const xNum = NUMERIC.test(x);
|
|
129
|
+
const yNum = NUMERIC.test(y);
|
|
130
|
+
if (xNum && yNum) {
|
|
131
|
+
const diff = Number(x) - Number(y);
|
|
132
|
+
if (diff !== 0) return diff;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (xNum) return -1;
|
|
136
|
+
if (yNum) return 1;
|
|
137
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
/** Semver sort order: negative if a < b, positive if a > b, 0 if equal. */
|
|
116
142
|
function compareSemver(a, b) {
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const na = partsA[i] ?? 0;
|
|
123
|
-
const nb = partsB[i] ?? 0;
|
|
143
|
+
const va = splitVersion(a);
|
|
144
|
+
const vb = splitVersion(b);
|
|
145
|
+
for (let i = 0; i < Math.max(va.core.length, vb.core.length); i++) {
|
|
146
|
+
const na = va.core[i] ?? 0;
|
|
147
|
+
const nb = vb.core[i] ?? 0;
|
|
124
148
|
if (na !== nb) return na - nb;
|
|
125
149
|
}
|
|
126
|
-
if (
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
return 0;
|
|
150
|
+
if (va.pre.length === 0 && vb.pre.length > 0) return 1;
|
|
151
|
+
if (va.pre.length > 0 && vb.pre.length === 0) return -1;
|
|
152
|
+
return comparePrerelease(va.pre, vb.pre);
|
|
130
153
|
}
|
|
131
154
|
//#endregion
|
|
132
155
|
//#region src/cdn.ts
|
|
@@ -165,10 +188,15 @@ function isExactVersion(tag) {
|
|
|
165
188
|
return !!tag && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(tag);
|
|
166
189
|
}
|
|
167
190
|
var CdnCache = class {
|
|
191
|
+
cdnCachePath;
|
|
168
192
|
#extractionLocks = /* @__PURE__ */ new Map();
|
|
193
|
+
#registryUrl;
|
|
169
194
|
constructor(registryUrl, cdnCachePath) {
|
|
170
|
-
this.registryUrl = registryUrl;
|
|
171
195
|
this.cdnCachePath = cdnCachePath;
|
|
196
|
+
this.#registryUrl = registryUrl;
|
|
197
|
+
}
|
|
198
|
+
get registryUrl() {
|
|
199
|
+
return typeof this.#registryUrl === "function" ? this.#registryUrl() : this.#registryUrl;
|
|
172
200
|
}
|
|
173
201
|
async getFileByVersion(packageName, version, filePath) {
|
|
174
202
|
const versionDir = path.join(this.cdnCachePath, packageName, version);
|
|
@@ -354,9 +382,15 @@ function readPackageMetadata(storagePath, packageName) {
|
|
|
354
382
|
const distTags = parsed["dist-tags"];
|
|
355
383
|
const versions = (parsed.versions ? Object.keys(parsed.versions) : []).slice().sort(compareSemver);
|
|
356
384
|
const locallyPublished = !!parsed._attachments && Object.keys(parsed._attachments).length > 0;
|
|
385
|
+
const time = {};
|
|
386
|
+
for (const version of versions) {
|
|
387
|
+
const at = parsed.time?.[version];
|
|
388
|
+
if (typeof at === "string") time[version] = at;
|
|
389
|
+
}
|
|
357
390
|
return {
|
|
358
391
|
distTags: distTags && Object.keys(distTags).length > 0 ? distTags : void 0,
|
|
359
392
|
versions: versions.length > 0 ? versions : void 0,
|
|
393
|
+
time,
|
|
360
394
|
locallyPublished
|
|
361
395
|
};
|
|
362
396
|
} catch {
|
|
@@ -392,16 +426,19 @@ function readPackageJsonVersion(dir) {
|
|
|
392
426
|
return;
|
|
393
427
|
}
|
|
394
428
|
}
|
|
395
|
-
|
|
429
|
+
/** Extracted version directories of a package, oldest first. */
|
|
430
|
+
function listVersionDirs(pkgDir) {
|
|
396
431
|
let entries;
|
|
397
432
|
try {
|
|
398
433
|
entries = fs.readdirSync(pkgDir, { withFileTypes: true });
|
|
399
434
|
} catch {
|
|
400
|
-
return
|
|
435
|
+
return [];
|
|
401
436
|
}
|
|
402
|
-
|
|
437
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort(compareSemver);
|
|
438
|
+
}
|
|
439
|
+
function getLatestVersionDir(pkgDir) {
|
|
440
|
+
const versions = listVersionDirs(pkgDir);
|
|
403
441
|
if (versions.length === 0) return null;
|
|
404
|
-
versions.sort(compareSemver);
|
|
405
442
|
return path.join(pkgDir, versions[versions.length - 1]);
|
|
406
443
|
}
|
|
407
444
|
function loadPackage(cdnCachePath, name, version, storagePath) {
|
|
@@ -448,63 +485,70 @@ function getDocumentTypesFromManifest(manifest) {
|
|
|
448
485
|
documentTypes.push(...(documentModels ?? []).map((dm) => dm.id), ...(editors ?? []).flatMap((e) => e.documentTypes).filter((dt) => dt !== void 0), ...(subgraphs ?? []).flatMap((e) => e.documentTypes).filter((dt) => dt !== void 0));
|
|
449
486
|
return documentTypes;
|
|
450
487
|
}
|
|
451
|
-
function
|
|
488
|
+
function readPackageDir(cdnCachePath, dirName, storagePath) {
|
|
489
|
+
const pkgDir = path.join(cdnCachePath, dirName);
|
|
490
|
+
const versionDirs = listVersionDirs(pkgDir);
|
|
491
|
+
const latest = versionDirs.at(-1);
|
|
492
|
+
const manifestDir = latest ? path.join(pkgDir, latest) : pkgDir;
|
|
493
|
+
const manifest = readManifest(manifestDir);
|
|
494
|
+
const name = manifest?.name || dirName;
|
|
495
|
+
const { distTags, versions, time, locallyPublished } = readPackageMetadata(storagePath, name);
|
|
496
|
+
return {
|
|
497
|
+
dirName,
|
|
498
|
+
packageDir: pkgDir,
|
|
499
|
+
manifestDir,
|
|
500
|
+
versionDirs,
|
|
501
|
+
manifest,
|
|
502
|
+
name,
|
|
503
|
+
version: readPackageJsonVersion(manifestDir),
|
|
504
|
+
distTags,
|
|
505
|
+
versions,
|
|
506
|
+
time,
|
|
507
|
+
locallyPublished
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function* scanPackageDirs(cdnCachePath, storagePath) {
|
|
452
511
|
const absDir = path.resolve(cdnCachePath);
|
|
453
|
-
const packages = [];
|
|
454
512
|
let entries;
|
|
455
513
|
try {
|
|
456
514
|
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
457
515
|
} catch {
|
|
458
|
-
return
|
|
516
|
+
return;
|
|
459
517
|
}
|
|
460
518
|
for (const entry of entries) {
|
|
461
519
|
if (!entry.isDirectory()) continue;
|
|
462
|
-
if (entry.name.startsWith("@")) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const name = manifest?.name || dirName;
|
|
478
|
-
const { distTags, versions, locallyPublished } = readPackageMetadata(storagePath, name);
|
|
479
|
-
if (locallyPublished === false) continue;
|
|
480
|
-
packages.push({
|
|
481
|
-
name,
|
|
482
|
-
path: `/-/cdn/${dirName}`,
|
|
483
|
-
manifest,
|
|
484
|
-
documentTypes: getDocumentTypesFromManifest(manifest),
|
|
485
|
-
version: readPackageJsonVersion(manifestDir),
|
|
486
|
-
distTags,
|
|
487
|
-
versions
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
} else {
|
|
491
|
-
const pkgDir = path.join(absDir, entry.name);
|
|
492
|
-
const manifestDir = getLatestVersionDir(pkgDir) ?? pkgDir;
|
|
493
|
-
const manifest = readManifest(manifestDir);
|
|
494
|
-
const name = manifest?.name || entry.name;
|
|
495
|
-
const { distTags, versions, locallyPublished } = readPackageMetadata(storagePath, name);
|
|
496
|
-
if (locallyPublished === false) continue;
|
|
497
|
-
packages.push({
|
|
498
|
-
name,
|
|
499
|
-
path: `/-/cdn/${entry.name}`,
|
|
500
|
-
manifest,
|
|
501
|
-
documentTypes: getDocumentTypesFromManifest(manifest),
|
|
502
|
-
version: readPackageJsonVersion(manifestDir),
|
|
503
|
-
distTags,
|
|
504
|
-
versions
|
|
505
|
-
});
|
|
520
|
+
if (!entry.name.startsWith("@")) {
|
|
521
|
+
yield readPackageDir(absDir, entry.name, storagePath);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const scopeDir = path.join(absDir, entry.name);
|
|
525
|
+
let scopedEntries;
|
|
526
|
+
try {
|
|
527
|
+
scopedEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
|
|
528
|
+
} catch (error) {
|
|
529
|
+
console.log(error);
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
for (const scopedEntry of scopedEntries) {
|
|
533
|
+
if (!scopedEntry.isDirectory()) continue;
|
|
534
|
+
yield readPackageDir(absDir, `${entry.name}/${scopedEntry.name}`, storagePath);
|
|
506
535
|
}
|
|
507
536
|
}
|
|
537
|
+
}
|
|
538
|
+
function scanPackages(cdnCachePath, storagePath) {
|
|
539
|
+
const packages = [];
|
|
540
|
+
for (const pkg of scanPackageDirs(cdnCachePath, storagePath)) {
|
|
541
|
+
if (pkg.locallyPublished === false) continue;
|
|
542
|
+
packages.push({
|
|
543
|
+
name: pkg.name,
|
|
544
|
+
path: `/-/cdn/${pkg.dirName}`,
|
|
545
|
+
manifest: pkg.manifest,
|
|
546
|
+
documentTypes: getDocumentTypesFromManifest(pkg.manifest),
|
|
547
|
+
version: pkg.version,
|
|
548
|
+
distTags: pkg.distTags,
|
|
549
|
+
versions: pkg.versions
|
|
550
|
+
});
|
|
551
|
+
}
|
|
508
552
|
return packages;
|
|
509
553
|
}
|
|
510
554
|
function findPackagesByDocumentType(packagesDir, documentType) {
|
|
@@ -514,6 +558,377 @@ function findPackagesByDocumentType(packagesDir, documentType) {
|
|
|
514
558
|
});
|
|
515
559
|
}
|
|
516
560
|
//#endregion
|
|
561
|
+
//#region src/pieces.ts
|
|
562
|
+
const gunzip$1 = promisify(gunzip);
|
|
563
|
+
/** Piece names in this scope belong to Activepieces; no package may claim one. */
|
|
564
|
+
const RESERVED_PIECE_SCOPE = "@activepieces/";
|
|
565
|
+
const INDEX_TTL_MS = 3e4;
|
|
566
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
567
|
+
const versionPieces = /* @__PURE__ */ new Map();
|
|
568
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
569
|
+
const materializing = /* @__PURE__ */ new Map();
|
|
570
|
+
/** Drop the cached index and descriptors — a publish or unpublish moved them. */
|
|
571
|
+
function invalidatePieceIndex() {
|
|
572
|
+
indexes.clear();
|
|
573
|
+
versionPieces.clear();
|
|
574
|
+
descriptors.clear();
|
|
575
|
+
}
|
|
576
|
+
function indexKey(cdnCachePath, storagePath) {
|
|
577
|
+
return `${path.resolve(cdnCachePath)}\0${storagePath ?? ""}`;
|
|
578
|
+
}
|
|
579
|
+
function piecesOf(manifest) {
|
|
580
|
+
return manifest?.pieces ?? [];
|
|
581
|
+
}
|
|
582
|
+
function piecesAt(versionDir) {
|
|
583
|
+
const hit = versionPieces.get(versionDir);
|
|
584
|
+
if (hit) return hit;
|
|
585
|
+
if (!fs.existsSync(path.join(versionDir, "package.json"))) return [];
|
|
586
|
+
const manifest = readManifest(versionDir);
|
|
587
|
+
if (!manifest) return [];
|
|
588
|
+
const pieces = piecesOf(manifest);
|
|
589
|
+
versionPieces.set(versionDir, pieces);
|
|
590
|
+
return pieces;
|
|
591
|
+
}
|
|
592
|
+
function toIndexEntry(piece, packageName, packageVersion, packageDir, publishedAt) {
|
|
593
|
+
const { id, bundle } = piece;
|
|
594
|
+
if (!id || !bundle) return null;
|
|
595
|
+
return {
|
|
596
|
+
name: id,
|
|
597
|
+
displayName: piece.name || id,
|
|
598
|
+
...piece.description ? { description: piece.description } : {},
|
|
599
|
+
version: packageVersion,
|
|
600
|
+
packageName,
|
|
601
|
+
packageVersion,
|
|
602
|
+
packageDir,
|
|
603
|
+
bundle,
|
|
604
|
+
descriptor: piece.descriptor ?? `${bundle}/descriptor.json`,
|
|
605
|
+
...publishedAt ? { publishedAt } : {}
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function indexVersion(index, pkg, version) {
|
|
609
|
+
const versionDir = path.join(pkg.dir, version);
|
|
610
|
+
const held = [];
|
|
611
|
+
for (const piece of piecesAt(versionDir)) {
|
|
612
|
+
const entry = toIndexEntry(piece, pkg.name, version, versionDir, pkg.publishedAt[version]);
|
|
613
|
+
if (!entry) continue;
|
|
614
|
+
let line = index.get(entry.name);
|
|
615
|
+
if (line && line.packageName !== pkg.name) {
|
|
616
|
+
console.warn(`[registry] piece "${entry.name}" is claimed by both ${line.packageName} and ${pkg.name}; keeping ${line.packageName}`);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (!line) {
|
|
620
|
+
line = {
|
|
621
|
+
packageName: pkg.name,
|
|
622
|
+
versions: /* @__PURE__ */ new Map(),
|
|
623
|
+
pending: /* @__PURE__ */ new Set(),
|
|
624
|
+
publishedAt: pkg.publishedAt
|
|
625
|
+
};
|
|
626
|
+
index.set(entry.name, line);
|
|
627
|
+
}
|
|
628
|
+
line.versions.set(version, entry);
|
|
629
|
+
held.push(entry.name);
|
|
630
|
+
}
|
|
631
|
+
return held;
|
|
632
|
+
}
|
|
633
|
+
function buildPieceIndex(cdnCachePath, storagePath) {
|
|
634
|
+
const index = /* @__PURE__ */ new Map();
|
|
635
|
+
for (const scanned of scanPackageDirs(cdnCachePath, storagePath)) {
|
|
636
|
+
if (scanned.locallyPublished === false) continue;
|
|
637
|
+
const pkg = {
|
|
638
|
+
name: scanned.name,
|
|
639
|
+
dir: scanned.packageDir,
|
|
640
|
+
publishedAt: scanned.time ?? {}
|
|
641
|
+
};
|
|
642
|
+
const names = /* @__PURE__ */ new Set();
|
|
643
|
+
const extracted = /* @__PURE__ */ new Set();
|
|
644
|
+
for (const version of scanned.versionDirs) {
|
|
645
|
+
const held = indexVersion(index, pkg, version);
|
|
646
|
+
if (fs.existsSync(path.join(pkg.dir, version, "package.json"))) extracted.add(version);
|
|
647
|
+
for (const name of held) names.add(name);
|
|
648
|
+
}
|
|
649
|
+
const pending = (scanned.versions ?? []).filter((v) => !extracted.has(v));
|
|
650
|
+
for (const name of names) {
|
|
651
|
+
const line = index.get(name);
|
|
652
|
+
for (const version of pending) line?.pending.add(version);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return index;
|
|
656
|
+
}
|
|
657
|
+
/** The piece index, rebuilt only when the cached one has gone stale. */
|
|
658
|
+
function pieceIndex(cdnCachePath, storagePath) {
|
|
659
|
+
const key = indexKey(cdnCachePath, storagePath);
|
|
660
|
+
const cached = indexes.get(key);
|
|
661
|
+
if (cached && Date.now() - cached.builtAt < INDEX_TTL_MS) return cached.index;
|
|
662
|
+
const index = buildPieceIndex(cdnCachePath, storagePath);
|
|
663
|
+
indexes.set(key, {
|
|
664
|
+
index,
|
|
665
|
+
builtAt: Date.now()
|
|
666
|
+
});
|
|
667
|
+
return index;
|
|
668
|
+
}
|
|
669
|
+
/** The highest extracted version of a piece. */
|
|
670
|
+
function latestOf(line) {
|
|
671
|
+
let latest;
|
|
672
|
+
for (const entry of line.versions.values()) if (!latest || compareSemver(entry.version, latest.version) > 0) latest = entry;
|
|
673
|
+
return latest;
|
|
674
|
+
}
|
|
675
|
+
/** The latest version of a piece. */
|
|
676
|
+
function findPiece(cdnCachePath, name, storagePath) {
|
|
677
|
+
const line = pieceIndex(cdnCachePath, storagePath).get(name);
|
|
678
|
+
return line ? latestOf(line) : void 0;
|
|
679
|
+
}
|
|
680
|
+
async function materialize(extractor, cdnCachePath, index, line, version) {
|
|
681
|
+
const key = `${line.packageName}@${version}`;
|
|
682
|
+
const running = materializing.get(key);
|
|
683
|
+
if (running) return running;
|
|
684
|
+
const started = (async () => {
|
|
685
|
+
try {
|
|
686
|
+
await extractor.getFileByVersion(line.packageName, version, "package.json");
|
|
687
|
+
} catch (error) {
|
|
688
|
+
console.error(`[registry] failed to extract ${key}:`, error);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const dir = path.join(cdnCachePath, line.packageName);
|
|
692
|
+
if (!fs.existsSync(path.join(dir, version, "package.json"))) return;
|
|
693
|
+
indexVersion(index, {
|
|
694
|
+
name: line.packageName,
|
|
695
|
+
dir,
|
|
696
|
+
publishedAt: line.publishedAt
|
|
697
|
+
}, version);
|
|
698
|
+
for (const other of index.values()) if (other.packageName === line.packageName) other.pending.delete(version);
|
|
699
|
+
})().finally(() => materializing.delete(key));
|
|
700
|
+
materializing.set(key, started);
|
|
701
|
+
return started;
|
|
702
|
+
}
|
|
703
|
+
async function materializeAll(extractor, cdnCachePath, index, line) {
|
|
704
|
+
await Promise.all([...line.pending].map((version) => materialize(extractor, cdnCachePath, index, line, version)));
|
|
705
|
+
}
|
|
706
|
+
function newestFirst(line) {
|
|
707
|
+
return [...line.versions.values()].sort((a, b) => compareSemver(b.version, a.version));
|
|
708
|
+
}
|
|
709
|
+
/** Every version of a piece, newest first; undefined for an unknown name. */
|
|
710
|
+
async function pieceVersions(extractor, cdnCachePath, name, storagePath) {
|
|
711
|
+
const index = pieceIndex(cdnCachePath, storagePath);
|
|
712
|
+
const line = index.get(name);
|
|
713
|
+
if (!line) return void 0;
|
|
714
|
+
await materializeAll(extractor, cdnCachePath, index, line);
|
|
715
|
+
return newestFirst(line);
|
|
716
|
+
}
|
|
717
|
+
function versionSummary(entry) {
|
|
718
|
+
return {
|
|
719
|
+
version: entry.version,
|
|
720
|
+
packageVersion: entry.packageVersion,
|
|
721
|
+
publishedAt: entry.publishedAt ?? null
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
/** One exact version of a piece, extracting its package version if needed. */
|
|
725
|
+
async function findPieceVersion(extractor, cdnCachePath, name, version, storagePath) {
|
|
726
|
+
const index = pieceIndex(cdnCachePath, storagePath);
|
|
727
|
+
const line = index.get(name);
|
|
728
|
+
if (!line) return { kind: "unknown-piece" };
|
|
729
|
+
if (!line.versions.has(version) && line.pending.has(version)) await materialize(extractor, cdnCachePath, index, line, version);
|
|
730
|
+
const entry = line.versions.get(version);
|
|
731
|
+
if (entry) return {
|
|
732
|
+
kind: "found",
|
|
733
|
+
entry
|
|
734
|
+
};
|
|
735
|
+
await materializeAll(extractor, cdnCachePath, index, line);
|
|
736
|
+
return {
|
|
737
|
+
kind: "unknown-version",
|
|
738
|
+
available: newestFirst(line).map((e) => e.version)
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function insidePackage(entry, relative) {
|
|
742
|
+
const root = path.resolve(entry.packageDir);
|
|
743
|
+
const resolved = path.resolve(root, relative);
|
|
744
|
+
if (resolved !== root && !resolved.startsWith(root + path.sep)) return null;
|
|
745
|
+
return resolved;
|
|
746
|
+
}
|
|
747
|
+
function readDescriptor(entry) {
|
|
748
|
+
const file = insidePackage(entry, entry.descriptor);
|
|
749
|
+
if (!file) return null;
|
|
750
|
+
const hit = descriptors.get(file);
|
|
751
|
+
if (hit !== void 0) return hit;
|
|
752
|
+
let parsed;
|
|
753
|
+
try {
|
|
754
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
755
|
+
} catch {
|
|
756
|
+
parsed = null;
|
|
757
|
+
}
|
|
758
|
+
descriptors.set(file, parsed);
|
|
759
|
+
return parsed;
|
|
760
|
+
}
|
|
761
|
+
const count = (record) => record ? Object.keys(record).length : 0;
|
|
762
|
+
function suggested(blocks) {
|
|
763
|
+
return Object.entries(blocks ?? {}).map(([name, block]) => ({
|
|
764
|
+
name,
|
|
765
|
+
displayName: block.displayName ?? name,
|
|
766
|
+
description: block.description ?? ""
|
|
767
|
+
}));
|
|
768
|
+
}
|
|
769
|
+
function catalogEntry(entry, descriptor, suggestions) {
|
|
770
|
+
return {
|
|
771
|
+
name: entry.name,
|
|
772
|
+
displayName: descriptor.displayName || entry.displayName,
|
|
773
|
+
description: descriptor.description ?? entry.description ?? "",
|
|
774
|
+
logoUrl: descriptor.logoUrl ?? "",
|
|
775
|
+
version: entry.version,
|
|
776
|
+
authors: descriptor.authors ?? [],
|
|
777
|
+
categories: descriptor.categories ?? [],
|
|
778
|
+
auth: descriptor.auth ?? null,
|
|
779
|
+
actions: count(descriptor.actions),
|
|
780
|
+
triggers: count(descriptor.triggers),
|
|
781
|
+
...descriptor.minimumSupportedRelease ? { minimumSupportedRelease: descriptor.minimumSupportedRelease } : {},
|
|
782
|
+
...descriptor.maximumSupportedRelease ? { maximumSupportedRelease: descriptor.maximumSupportedRelease } : {},
|
|
783
|
+
package: entry.packageName,
|
|
784
|
+
packageVersion: entry.packageVersion,
|
|
785
|
+
...suggestions ? {
|
|
786
|
+
suggestedActions: suggested(descriptor.actions),
|
|
787
|
+
suggestedTriggers: Object.entries(descriptor.triggers ?? {}).map(([name, block]) => ({
|
|
788
|
+
name,
|
|
789
|
+
displayName: block.displayName ?? name,
|
|
790
|
+
description: block.description ?? "",
|
|
791
|
+
type: block.type ?? ""
|
|
792
|
+
}))
|
|
793
|
+
} : {}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/** The catalog of latest versions, sorted by display name. */
|
|
797
|
+
function pieceCatalog(cdnCachePath, options = {}) {
|
|
798
|
+
const entries = [];
|
|
799
|
+
for (const line of pieceIndex(cdnCachePath, options.storagePath).values()) {
|
|
800
|
+
const entry = latestOf(line);
|
|
801
|
+
const descriptor = entry ? readDescriptor(entry) : null;
|
|
802
|
+
if (!entry || !descriptor) continue;
|
|
803
|
+
entries.push(catalogEntry(entry, descriptor, options.suggestions === true));
|
|
804
|
+
}
|
|
805
|
+
return entries.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
806
|
+
}
|
|
807
|
+
/** The CDN path of a file inside the package version that ships the piece. */
|
|
808
|
+
function cdnPath(entry, relative) {
|
|
809
|
+
return `/-/cdn/${entry.packageName}@${entry.packageVersion}/${relative}`;
|
|
810
|
+
}
|
|
811
|
+
/** The tarball filename cdn.activepieces.com would use for the same piece. */
|
|
812
|
+
function pieceTarballName(name, version) {
|
|
813
|
+
return `${name.replace("/", "-")}-${version}.tgz`;
|
|
814
|
+
}
|
|
815
|
+
function pieceTarballPath(entry) {
|
|
816
|
+
return `/-/pieces/bundled/${pieceTarballName(entry.name, entry.version)}`;
|
|
817
|
+
}
|
|
818
|
+
/** One piece version: what provides it, and where its descriptor and bundle are. */
|
|
819
|
+
function pieceDetail(entry, origin) {
|
|
820
|
+
const descriptor = readDescriptor(entry);
|
|
821
|
+
if (!descriptor) return null;
|
|
822
|
+
return {
|
|
823
|
+
...descriptor,
|
|
824
|
+
name: entry.name,
|
|
825
|
+
version: entry.version,
|
|
826
|
+
package: entry.packageName,
|
|
827
|
+
packageVersion: entry.packageVersion,
|
|
828
|
+
descriptorUrl: `${origin}${cdnPath(entry, entry.descriptor)}`,
|
|
829
|
+
bundleUrl: `${origin}${pieceTarballPath(entry)}`
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
async function findPieceByTarball(extractor, cdnCachePath, filename, storagePath) {
|
|
833
|
+
if (!filename.endsWith(".tgz")) return void 0;
|
|
834
|
+
const stem = filename.slice(0, -4);
|
|
835
|
+
for (const name of pieceIndex(cdnCachePath, storagePath).keys()) {
|
|
836
|
+
const prefix = `${name.replace("/", "-")}-`;
|
|
837
|
+
if (!stem.startsWith(prefix)) continue;
|
|
838
|
+
const version = stem.slice(prefix.length);
|
|
839
|
+
if (!isExactVersion(version)) continue;
|
|
840
|
+
const found = await findPieceVersion(extractor, cdnCachePath, name, version, storagePath);
|
|
841
|
+
if (found.kind === "found") return found.entry;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function listFiles(dir, prefix = "") {
|
|
845
|
+
const files = [];
|
|
846
|
+
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
847
|
+
const rel = prefix ? `${prefix}/${item.name}` : item.name;
|
|
848
|
+
if (item.isDirectory()) files.push(...listFiles(path.join(dir, item.name), rel));
|
|
849
|
+
else if (item.isFile()) files.push(rel);
|
|
850
|
+
}
|
|
851
|
+
return files.sort();
|
|
852
|
+
}
|
|
853
|
+
const cuts = /* @__PURE__ */ new Map();
|
|
854
|
+
async function cutTarball(entry, pieceDir, target) {
|
|
855
|
+
const files = listFiles(pieceDir);
|
|
856
|
+
if (files.length === 0) return null;
|
|
857
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
858
|
+
const tmp = `${target}.tmp-${crypto.randomUUID()}`;
|
|
859
|
+
try {
|
|
860
|
+
await create({
|
|
861
|
+
file: tmp,
|
|
862
|
+
gzip: true,
|
|
863
|
+
cwd: pieceDir,
|
|
864
|
+
prefix: "package",
|
|
865
|
+
portable: true
|
|
866
|
+
}, files);
|
|
867
|
+
fs.renameSync(tmp, target);
|
|
868
|
+
} catch (error) {
|
|
869
|
+
fs.rmSync(tmp, { force: true });
|
|
870
|
+
console.error(`[registry] failed to pack piece ${entry.name}:`, error);
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
return target;
|
|
874
|
+
}
|
|
875
|
+
async function pieceTarball(entry) {
|
|
876
|
+
const pieceDir = insidePackage(entry, entry.bundle);
|
|
877
|
+
if (!pieceDir || !fs.existsSync(path.join(pieceDir, "package.json"))) return null;
|
|
878
|
+
const target = path.join(entry.packageDir, ".piece-bundles", pieceTarballName(entry.name, entry.version));
|
|
879
|
+
if (fs.existsSync(target)) return target;
|
|
880
|
+
const pending = cuts.get(target);
|
|
881
|
+
if (pending) return pending;
|
|
882
|
+
const started = cutTarball(entry, pieceDir, target).finally(() => {
|
|
883
|
+
cuts.delete(target);
|
|
884
|
+
});
|
|
885
|
+
cuts.set(target, started);
|
|
886
|
+
return started;
|
|
887
|
+
}
|
|
888
|
+
const MANIFEST_PATHS = [
|
|
889
|
+
"powerhouse.manifest.json",
|
|
890
|
+
"cdn/powerhouse.manifest.json",
|
|
891
|
+
"dist/powerhouse.manifest.json"
|
|
892
|
+
];
|
|
893
|
+
const MAX_TARBALL_BYTES = 134217728;
|
|
894
|
+
function stripRoot(entryPath) {
|
|
895
|
+
return entryPath.replace(/^[^/]+\//, "");
|
|
896
|
+
}
|
|
897
|
+
function manifestsInTar(tar) {
|
|
898
|
+
const found = /* @__PURE__ */ new Map();
|
|
899
|
+
list({
|
|
900
|
+
sync: true,
|
|
901
|
+
filter: (entryPath) => MANIFEST_PATHS.includes(stripRoot(entryPath)),
|
|
902
|
+
onReadEntry: (entry) => {
|
|
903
|
+
const chunks = [];
|
|
904
|
+
entry.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
905
|
+
entry.on("end", () => {
|
|
906
|
+
found.set(stripRoot(String(entry.path)), Buffer.concat(chunks).toString("utf-8"));
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
}).end(tar);
|
|
910
|
+
return found;
|
|
911
|
+
}
|
|
912
|
+
/** The piece names a published tarball claims; empty when it claims none. */
|
|
913
|
+
async function pieceNamesInTarball(tgz) {
|
|
914
|
+
let manifests;
|
|
915
|
+
try {
|
|
916
|
+
manifests = manifestsInTar(await gunzip$1(tgz, { maxOutputLength: MAX_TARBALL_BYTES }));
|
|
917
|
+
} catch {
|
|
918
|
+
return [];
|
|
919
|
+
}
|
|
920
|
+
for (const candidate of MANIFEST_PATHS) {
|
|
921
|
+
const raw = manifests.get(candidate);
|
|
922
|
+
if (raw === void 0) continue;
|
|
923
|
+
try {
|
|
924
|
+
return piecesOf(JSON.parse(raw)).map((piece) => piece.id).filter((id) => typeof id === "string" && id !== "");
|
|
925
|
+
} catch {
|
|
926
|
+
return [];
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return [];
|
|
930
|
+
}
|
|
931
|
+
//#endregion
|
|
517
932
|
//#region src/warmup.ts
|
|
518
933
|
const WARM_INTERVAL_MS = 3e4;
|
|
519
934
|
const WARM_CONCURRENCY = 8;
|
|
@@ -526,7 +941,7 @@ function createWarmer(config, cdn) {
|
|
|
526
941
|
let warmInFlight = false;
|
|
527
942
|
let lastWarmAt = 0;
|
|
528
943
|
return async function warm() {
|
|
529
|
-
if (warmInFlight) return;
|
|
944
|
+
if (warmInFlight || config.port === 0) return;
|
|
530
945
|
if (Date.now() - lastWarmAt < WARM_INTERVAL_MS) return;
|
|
531
946
|
warmInFlight = true;
|
|
532
947
|
try {
|
|
@@ -590,8 +1005,21 @@ const MIME_TYPES = {
|
|
|
590
1005
|
".html": "text/html",
|
|
591
1006
|
".svg": "image/svg+xml"
|
|
592
1007
|
};
|
|
1008
|
+
function localUrl(config) {
|
|
1009
|
+
return () => `http://localhost:${config.port}`;
|
|
1010
|
+
}
|
|
1011
|
+
function versionsRouteName(raw) {
|
|
1012
|
+
if (!raw.endsWith("/versions")) return null;
|
|
1013
|
+
const name = raw.slice(0, -9);
|
|
1014
|
+
const slashes = name.split("/").length - 1;
|
|
1015
|
+
return (name.startsWith("@") ? slashes === 1 : slashes === 0) && name !== "" ? name : null;
|
|
1016
|
+
}
|
|
593
1017
|
function getContentType(filePath) {
|
|
594
|
-
|
|
1018
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
1019
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
1020
|
+
}
|
|
1021
|
+
function originOf(req) {
|
|
1022
|
+
return `${req.get("x-forwarded-proto")?.split(",")[0].trim() || req.protocol}://${req.get("host") ?? ""}`;
|
|
595
1023
|
}
|
|
596
1024
|
function publisherFromRequest(req) {
|
|
597
1025
|
const name = req.remote_user?.name;
|
|
@@ -642,7 +1070,7 @@ function etagMatches(header, etag) {
|
|
|
642
1070
|
});
|
|
643
1071
|
}
|
|
644
1072
|
function createPowerhouseRouter(config, sse, webhooks, ownerStore) {
|
|
645
|
-
const cdn = new CdnCache(
|
|
1073
|
+
const cdn = new CdnCache(localUrl(config), config.cdnCachePath);
|
|
646
1074
|
const router = Router();
|
|
647
1075
|
const withOwners = async (pkgs) => {
|
|
648
1076
|
if (!ownerStore || pkgs.length === 0) return pkgs;
|
|
@@ -749,6 +1177,80 @@ function createPowerhouseRouter(config, sse, webhooks, ownerStore) {
|
|
|
749
1177
|
}
|
|
750
1178
|
res.json((await withOwners([pkg]))[0]);
|
|
751
1179
|
});
|
|
1180
|
+
router.get("/pieces", (req, res) => {
|
|
1181
|
+
warm();
|
|
1182
|
+
res.json(pieceCatalog(config.cdnCachePath, {
|
|
1183
|
+
storagePath: config.storagePath,
|
|
1184
|
+
suggestions: req.query.suggestionType === "ACTION_AND_TRIGGER"
|
|
1185
|
+
}));
|
|
1186
|
+
});
|
|
1187
|
+
router.get("/pieces/*", async (req, res) => {
|
|
1188
|
+
warm();
|
|
1189
|
+
const raw = req.params[0];
|
|
1190
|
+
const versionsOf = versionsRouteName(raw);
|
|
1191
|
+
if (versionsOf) {
|
|
1192
|
+
const versions = await pieceVersions(cdn, config.cdnCachePath, versionsOf, config.storagePath);
|
|
1193
|
+
if (!versions) {
|
|
1194
|
+
res.status(404).json({ error: `Piece not found: ${versionsOf}` });
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
res.json(versions.map(versionSummary));
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const name = raw;
|
|
1201
|
+
const version = typeof req.query.version === "string" ? req.query.version : void 0;
|
|
1202
|
+
if (version === void 0) {
|
|
1203
|
+
const entry = findPiece(config.cdnCachePath, name, config.storagePath);
|
|
1204
|
+
const detail = entry ? pieceDetail(entry, originOf(req)) : null;
|
|
1205
|
+
if (!detail) {
|
|
1206
|
+
res.status(404).json({ error: `Piece not found: ${name}` });
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
res.json(detail);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const found = await findPieceVersion(cdn, config.cdnCachePath, name, version, config.storagePath);
|
|
1213
|
+
if (found.kind === "unknown-piece") {
|
|
1214
|
+
res.status(404).json({ error: `Piece not found: ${name}` });
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (found.kind === "unknown-version") {
|
|
1218
|
+
res.status(404).json({
|
|
1219
|
+
error: `Piece version not found: ${name}@${version}`,
|
|
1220
|
+
available: found.available
|
|
1221
|
+
});
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const detail = pieceDetail(found.entry, originOf(req));
|
|
1225
|
+
if (!detail) {
|
|
1226
|
+
res.status(404).json({ error: `Piece not found: ${name}@${version}` });
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
res.json(detail);
|
|
1230
|
+
});
|
|
1231
|
+
router.get("/-/pieces/bundled/*", async (req, res) => {
|
|
1232
|
+
warm();
|
|
1233
|
+
const filename = req.params[0];
|
|
1234
|
+
const entry = await findPieceByTarball(cdn, config.cdnCachePath, filename, config.storagePath);
|
|
1235
|
+
const file = entry ? await pieceTarball(entry) : null;
|
|
1236
|
+
if (!entry || !file) {
|
|
1237
|
+
res.status(404).json({ error: `Piece bundle not found: ${filename}` });
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
1241
|
+
const etag = `"${entry.packageName}-${filename}"`;
|
|
1242
|
+
res.setHeader("ETag", etag);
|
|
1243
|
+
if (etagMatches(req.headers["if-none-match"], etag)) {
|
|
1244
|
+
res.status(304).end();
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
res.setHeader("Content-Type", "application/gzip");
|
|
1248
|
+
try {
|
|
1249
|
+
await pipeline(fs.createReadStream(file), res);
|
|
1250
|
+
} catch {
|
|
1251
|
+
res.destroy();
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
752
1254
|
router.get("/-/cdn/*", async (req, res) => {
|
|
753
1255
|
const fullPath = req.params[0];
|
|
754
1256
|
let packageSpec;
|
|
@@ -841,7 +1343,7 @@ function parseManifestRewrite(reqPath) {
|
|
|
841
1343
|
return { packageName: decodeURIComponent(beforeRev) };
|
|
842
1344
|
}
|
|
843
1345
|
function createUnpublishHook(config, notifications) {
|
|
844
|
-
const cdn = new CdnCache(
|
|
1346
|
+
const cdn = new CdnCache(localUrl(config), config.cdnCachePath);
|
|
845
1347
|
const handleManifestRewrite = (req, res) => {
|
|
846
1348
|
const rewrite = parseManifestRewrite(req.path);
|
|
847
1349
|
if (!rewrite) return;
|
|
@@ -850,6 +1352,7 @@ function createUnpublishHook(config, notifications) {
|
|
|
850
1352
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
851
1353
|
const publishedBy = publisherFromRequest(req);
|
|
852
1354
|
cdn.reconcileWithRegistry(rewrite.packageName).then((removed) => {
|
|
1355
|
+
if (removed.length > 0) invalidatePieceIndex();
|
|
853
1356
|
for (const version of removed) notifications.notifyUnpublish({
|
|
854
1357
|
packageName: rewrite.packageName,
|
|
855
1358
|
version,
|
|
@@ -882,6 +1385,7 @@ function createUnpublishHook(config, notifications) {
|
|
|
882
1385
|
if (res.statusCode >= 200 && res.statusCode < 300) try {
|
|
883
1386
|
if (parsed.version) cdn.invalidateVersion(parsed.packageName, parsed.version);
|
|
884
1387
|
else cdn.invalidate(parsed.packageName);
|
|
1388
|
+
invalidatePieceIndex();
|
|
885
1389
|
notifications.notifyUnpublish({
|
|
886
1390
|
packageName: parsed.packageName,
|
|
887
1391
|
version: parsed.version,
|
|
@@ -896,12 +1400,12 @@ function createUnpublishHook(config, notifications) {
|
|
|
896
1400
|
};
|
|
897
1401
|
}
|
|
898
1402
|
function createPublishHook(config, notifications) {
|
|
899
|
-
const cdn = new CdnCache(
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1403
|
+
const cdn = new CdnCache(localUrl(config), config.cdnCachePath);
|
|
1404
|
+
const parseBody = express.json({
|
|
1405
|
+
strict: false,
|
|
1406
|
+
limit: config.maxBodySize ?? "300mb"
|
|
1407
|
+
});
|
|
1408
|
+
const attachExtraction = (req, res) => {
|
|
905
1409
|
const originalEnd = res.end.bind(res);
|
|
906
1410
|
res.end = function(chunk, encoding, cb) {
|
|
907
1411
|
const urlPath = req.path.replace(/^\//, "");
|
|
@@ -917,6 +1421,7 @@ function createPublishHook(config, notifications) {
|
|
|
917
1421
|
if (versions.length > 1) console.warn(`[registry] Multiple versions published for ${packageName}: ${JSON.stringify(versions)}`);
|
|
918
1422
|
const publishedBy = publisherFromRequest(req);
|
|
919
1423
|
cdn.extractTarball(packageName, version).then(() => {
|
|
1424
|
+
invalidatePieceIndex();
|
|
920
1425
|
notifications.notifyPublish({
|
|
921
1426
|
packageName,
|
|
922
1427
|
version,
|
|
@@ -927,8 +1432,62 @@ function createPublishHook(config, notifications) {
|
|
|
927
1432
|
});
|
|
928
1433
|
return originalEnd(chunk, encoding, cb);
|
|
929
1434
|
};
|
|
930
|
-
next();
|
|
931
1435
|
};
|
|
1436
|
+
return (req, res, next) => {
|
|
1437
|
+
if (req.method !== "PUT" || req.path.includes("/-rev/")) {
|
|
1438
|
+
next();
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
const urlPath = req.path.replace(/^\//, "");
|
|
1442
|
+
if (!urlPath || urlPath.startsWith("-")) {
|
|
1443
|
+
attachExtraction(req, res);
|
|
1444
|
+
next();
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
parseBody(req, res, (parseError) => {
|
|
1448
|
+
if (parseError) {
|
|
1449
|
+
next();
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
pieceClaimConflict(config, decodeURIComponent(urlPath), req.body).then((conflict) => {
|
|
1453
|
+
if (conflict) {
|
|
1454
|
+
res.status(409).json({ error: conflict });
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
attachExtraction(req, res);
|
|
1458
|
+
next();
|
|
1459
|
+
}).catch((err) => {
|
|
1460
|
+
console.error("[registry] piece collision check failed:", err);
|
|
1461
|
+
attachExtraction(req, res);
|
|
1462
|
+
next();
|
|
1463
|
+
});
|
|
1464
|
+
});
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
function publishedTarball(body) {
|
|
1468
|
+
if (body === null || typeof body !== "object") return null;
|
|
1469
|
+
const attachments = body._attachments;
|
|
1470
|
+
if (!attachments || typeof attachments !== "object") return null;
|
|
1471
|
+
for (const [name, attachment] of Object.entries(attachments)) {
|
|
1472
|
+
if (!name.endsWith(".tgz")) continue;
|
|
1473
|
+
const data = attachment.data;
|
|
1474
|
+
if (typeof data === "string") return Buffer.from(data, "base64");
|
|
1475
|
+
}
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
async function pieceClaimConflict(config, packageName, body) {
|
|
1479
|
+
const tarball = publishedTarball(body);
|
|
1480
|
+
if (!tarball) return null;
|
|
1481
|
+
const names = await pieceNamesInTarball(tarball);
|
|
1482
|
+
if (names.length === 0) return null;
|
|
1483
|
+
const reserved = names.filter((name) => name.startsWith(RESERVED_PIECE_SCOPE));
|
|
1484
|
+
if (reserved.length > 0) return `Piece names in the ${RESERVED_PIECE_SCOPE} scope belong to Activepieces; ${packageName} cannot claim ${reserved.map((n) => `"${n}"`).join(", ")}. Rename the piece into your own scope.`;
|
|
1485
|
+
const index = pieceIndex(config.cdnCachePath, config.storagePath);
|
|
1486
|
+
for (const name of names) {
|
|
1487
|
+
const owner = index.get(name);
|
|
1488
|
+
if (owner && owner.packageName !== packageName) return `Piece "${name}" is already published by ${owner.packageName}; ${packageName} cannot claim it.`;
|
|
1489
|
+
}
|
|
1490
|
+
return null;
|
|
932
1491
|
}
|
|
933
1492
|
//#endregion
|
|
934
1493
|
//#region src/notifications/manager.ts
|
|
@@ -1187,7 +1746,8 @@ async function runRegistry(args) {
|
|
|
1187
1746
|
if (config.databaseUrl || config.authStore) console.log("[registry] Postgres-backed auth plugin active (persistent accounts + package ownership)");
|
|
1188
1747
|
await mkdir(storagePath, { recursive: true });
|
|
1189
1748
|
await mkdir(cdnCachePath, { recursive: true });
|
|
1190
|
-
const
|
|
1749
|
+
const verdaccioConfig = buildVerdaccioConfig(config);
|
|
1750
|
+
const verdaccioServer = await runServer(verdaccioConfig);
|
|
1191
1751
|
if (authStoreToken && !wasStoreLoaded(authStoreToken)) {
|
|
1192
1752
|
verdaccioServer.close();
|
|
1193
1753
|
throw new Error("registry-auth plugin failed to load despite a configured database/auth store; refusing to start without auth and package-ownership enforcement.");
|
|
@@ -1203,7 +1763,9 @@ async function runRegistry(args) {
|
|
|
1203
1763
|
app.use(createPublishHook(config, notifications));
|
|
1204
1764
|
app.use(createUnpublishHook(config, notifications));
|
|
1205
1765
|
app.use((req, res) => verdaccioHandler(req, res));
|
|
1206
|
-
|
|
1766
|
+
const server = app.listen(port, () => {
|
|
1767
|
+
config.port = server.address().port;
|
|
1768
|
+
const port = config.port;
|
|
1207
1769
|
console.log(`Powerhouse Registry running on http://localhost:${port}`);
|
|
1208
1770
|
console.log(` CDN: http://localhost:${port}/-/cdn/`);
|
|
1209
1771
|
console.log(` Packages: http://localhost:${port}/packages`);
|
|
@@ -1213,6 +1775,7 @@ async function runRegistry(args) {
|
|
|
1213
1775
|
if (config.s3) console.log(` S3: ${config.s3.endpoint}/${config.s3.bucket}`);
|
|
1214
1776
|
if (config.renown) console.log(` Renown auth: ${config.renown.publicUrl}`);
|
|
1215
1777
|
});
|
|
1778
|
+
return server;
|
|
1216
1779
|
}
|
|
1217
1780
|
//#endregion
|
|
1218
1781
|
//#region cli.ts
|
|
@@ -1362,7 +1925,8 @@ const registryCommand = command({
|
|
|
1362
1925
|
}
|
|
1363
1926
|
}
|
|
1364
1927
|
});
|
|
1365
|
-
|
|
1928
|
+
const registryCli = binary(registryCommand);
|
|
1929
|
+
await run(registryCli, process.argv);
|
|
1366
1930
|
//#endregion
|
|
1367
1931
|
export { registryCommand };
|
|
1368
1932
|
|