@powerhousedao/registry 6.2.3-dev.16 → 6.2.3-dev.18

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/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
@@ -448,63 +450,65 @@ function getDocumentTypesFromManifest(manifest) {
448
450
  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
451
  return documentTypes;
450
452
  }
451
- function scanPackages(cdnCachePath, storagePath) {
453
+ function readPackageDir(cdnCachePath, dirName, storagePath) {
454
+ const pkgDir = path.join(cdnCachePath, dirName);
455
+ const manifestDir = getLatestVersionDir(pkgDir) ?? pkgDir;
456
+ const manifest = readManifest(manifestDir);
457
+ const name = manifest?.name || dirName;
458
+ const { distTags, versions, locallyPublished } = readPackageMetadata(storagePath, name);
459
+ return {
460
+ dirName,
461
+ manifestDir,
462
+ manifest,
463
+ name,
464
+ version: readPackageJsonVersion(manifestDir),
465
+ distTags,
466
+ versions,
467
+ locallyPublished
468
+ };
469
+ }
470
+ function* scanPackageDirs(cdnCachePath, storagePath) {
452
471
  const absDir = path.resolve(cdnCachePath);
453
- const packages = [];
454
472
  let entries;
455
473
  try {
456
474
  entries = fs.readdirSync(absDir, { withFileTypes: true });
457
475
  } catch {
458
- return packages;
476
+ return;
459
477
  }
460
478
  for (const entry of entries) {
461
479
  if (!entry.isDirectory()) continue;
462
- if (entry.name.startsWith("@")) {
463
- const scopeDir = path.join(absDir, entry.name);
464
- let scopedEntries;
465
- try {
466
- scopedEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
467
- } catch (error) {
468
- console.log(error);
469
- continue;
470
- }
471
- for (const scopedEntry of scopedEntries) {
472
- if (!scopedEntry.isDirectory()) continue;
473
- const dirName = `${entry.name}/${scopedEntry.name}`;
474
- const pkgDir = path.join(scopeDir, scopedEntry.name);
475
- const manifestDir = getLatestVersionDir(pkgDir) ?? pkgDir;
476
- const manifest = readManifest(manifestDir);
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
- });
480
+ if (!entry.name.startsWith("@")) {
481
+ yield readPackageDir(absDir, entry.name, storagePath);
482
+ continue;
483
+ }
484
+ const scopeDir = path.join(absDir, entry.name);
485
+ let scopedEntries;
486
+ try {
487
+ scopedEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
488
+ } catch (error) {
489
+ console.log(error);
490
+ continue;
491
+ }
492
+ for (const scopedEntry of scopedEntries) {
493
+ if (!scopedEntry.isDirectory()) continue;
494
+ yield readPackageDir(absDir, `${entry.name}/${scopedEntry.name}`, storagePath);
506
495
  }
507
496
  }
497
+ }
498
+ function scanPackages(cdnCachePath, storagePath) {
499
+ const packages = [];
500
+ for (const pkg of scanPackageDirs(cdnCachePath, storagePath)) {
501
+ if (pkg.locallyPublished === false) continue;
502
+ packages.push({
503
+ name: pkg.name,
504
+ path: `/-/cdn/${pkg.dirName}`,
505
+ manifest: pkg.manifest,
506
+ documentTypes: getDocumentTypesFromManifest(pkg.manifest),
507
+ version: pkg.version,
508
+ distTags: pkg.distTags,
509
+ versions: pkg.versions
510
+ });
511
+ }
508
512
  return packages;
509
513
  }
510
514
  function findPackagesByDocumentType(packagesDir, documentType) {
@@ -514,6 +518,250 @@ function findPackagesByDocumentType(packagesDir, documentType) {
514
518
  });
515
519
  }
516
520
  //#endregion
521
+ //#region src/pieces.ts
522
+ const gunzip$1 = promisify(gunzip);
523
+ const INDEX_TTL_MS = 3e4;
524
+ const indexes = /* @__PURE__ */ new Map();
525
+ const descriptors = /* @__PURE__ */ new Map();
526
+ /** Drop the cached index and descriptors — a publish or unpublish moved them. */
527
+ function invalidatePieceIndex() {
528
+ indexes.clear();
529
+ descriptors.clear();
530
+ }
531
+ function indexKey(cdnCachePath, storagePath) {
532
+ return `${path.resolve(cdnCachePath)}\0${storagePath ?? ""}`;
533
+ }
534
+ function piecesOf(manifest) {
535
+ return manifest?.pieces ?? [];
536
+ }
537
+ function toIndexEntry(piece, packageName, packageVersion, packageDir) {
538
+ const { id, version, bundle } = piece;
539
+ if (!id || !version || !bundle) return null;
540
+ return {
541
+ name: id,
542
+ displayName: piece.name || id,
543
+ ...piece.description ? { description: piece.description } : {},
544
+ version,
545
+ packageName,
546
+ packageVersion,
547
+ packageDir,
548
+ bundle,
549
+ descriptor: piece.descriptor ?? `${bundle}/descriptor.json`
550
+ };
551
+ }
552
+ function buildPieceIndex(cdnCachePath, storagePath) {
553
+ const index = /* @__PURE__ */ new Map();
554
+ for (const pkg of scanPackageDirs(cdnCachePath, storagePath)) {
555
+ if (pkg.locallyPublished === false) continue;
556
+ for (const piece of piecesOf(pkg.manifest)) {
557
+ const entry = toIndexEntry(piece, pkg.name, pkg.version, pkg.manifestDir);
558
+ if (!entry) continue;
559
+ const claimed = index.get(entry.name);
560
+ if (claimed && claimed.packageName !== entry.packageName) {
561
+ console.warn(`[registry] piece "${entry.name}" is claimed by both ${claimed.packageName} and ${entry.packageName}; keeping ${claimed.packageName}`);
562
+ continue;
563
+ }
564
+ index.set(entry.name, entry);
565
+ }
566
+ }
567
+ return index;
568
+ }
569
+ /** The piece index, rebuilt only when the cached one has gone stale. */
570
+ function pieceIndex(cdnCachePath, storagePath) {
571
+ const key = indexKey(cdnCachePath, storagePath);
572
+ const cached = indexes.get(key);
573
+ if (cached && Date.now() - cached.builtAt < INDEX_TTL_MS) return cached.index;
574
+ const index = buildPieceIndex(cdnCachePath, storagePath);
575
+ indexes.set(key, {
576
+ index,
577
+ builtAt: Date.now()
578
+ });
579
+ return index;
580
+ }
581
+ function findPiece(cdnCachePath, name, storagePath) {
582
+ return pieceIndex(cdnCachePath, storagePath).get(name);
583
+ }
584
+ function insidePackage(entry, relative) {
585
+ const root = path.resolve(entry.packageDir);
586
+ const resolved = path.resolve(root, relative);
587
+ if (resolved !== root && !resolved.startsWith(root + path.sep)) return null;
588
+ return resolved;
589
+ }
590
+ function readDescriptor(entry) {
591
+ const file = insidePackage(entry, entry.descriptor);
592
+ if (!file) return null;
593
+ const hit = descriptors.get(file);
594
+ if (hit !== void 0) return hit;
595
+ let parsed;
596
+ try {
597
+ parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
598
+ } catch {
599
+ parsed = null;
600
+ }
601
+ descriptors.set(file, parsed);
602
+ return parsed;
603
+ }
604
+ const count = (record) => record ? Object.keys(record).length : 0;
605
+ function suggested(blocks) {
606
+ return Object.entries(blocks ?? {}).map(([name, block]) => ({
607
+ name,
608
+ displayName: block.displayName ?? name,
609
+ description: block.description ?? ""
610
+ }));
611
+ }
612
+ function catalogEntry(entry, descriptor, suggestions) {
613
+ return {
614
+ name: entry.name,
615
+ displayName: descriptor.displayName || entry.displayName,
616
+ description: descriptor.description ?? entry.description ?? "",
617
+ logoUrl: descriptor.logoUrl ?? "",
618
+ version: entry.version,
619
+ authors: descriptor.authors ?? [],
620
+ categories: descriptor.categories ?? [],
621
+ auth: descriptor.auth ?? null,
622
+ actions: count(descriptor.actions),
623
+ triggers: count(descriptor.triggers),
624
+ ...descriptor.minimumSupportedRelease ? { minimumSupportedRelease: descriptor.minimumSupportedRelease } : {},
625
+ ...descriptor.maximumSupportedRelease ? { maximumSupportedRelease: descriptor.maximumSupportedRelease } : {},
626
+ package: entry.packageName,
627
+ packageVersion: entry.packageVersion,
628
+ ...suggestions ? {
629
+ suggestedActions: suggested(descriptor.actions),
630
+ suggestedTriggers: Object.entries(descriptor.triggers ?? {}).map(([name, block]) => ({
631
+ name,
632
+ displayName: block.displayName ?? name,
633
+ description: block.description ?? "",
634
+ type: block.type ?? ""
635
+ }))
636
+ } : {}
637
+ };
638
+ }
639
+ /** The catalog, sorted by display name the way the editor renders it. */
640
+ function pieceCatalog(cdnCachePath, options = {}) {
641
+ const entries = [];
642
+ for (const entry of pieceIndex(cdnCachePath, options.storagePath).values()) {
643
+ const descriptor = readDescriptor(entry);
644
+ if (!descriptor) continue;
645
+ entries.push(catalogEntry(entry, descriptor, options.suggestions === true));
646
+ }
647
+ return entries.sort((a, b) => a.displayName.localeCompare(b.displayName));
648
+ }
649
+ /** The CDN path of a file inside the package that ships the piece. */
650
+ function cdnPath(entry, relative) {
651
+ return `/-/cdn/${entry.packageVersion ? `${entry.packageName}@${entry.packageVersion}` : entry.packageName}/${relative}`;
652
+ }
653
+ /** The tarball filename cdn.activepieces.com would use for the same piece. */
654
+ function pieceTarballName(name, version) {
655
+ return `${name.replace("/", "-")}-${version}.tgz`;
656
+ }
657
+ function pieceTarballPath(entry) {
658
+ return `/-/pieces/bundled/${pieceTarballName(entry.name, entry.version)}`;
659
+ }
660
+ /** One piece: what provides it, and where its descriptor and bundle are. */
661
+ function pieceDetail(entry, origin) {
662
+ const descriptor = readDescriptor(entry);
663
+ if (!descriptor) return null;
664
+ return {
665
+ ...descriptor,
666
+ name: entry.name,
667
+ version: entry.version,
668
+ package: entry.packageName,
669
+ packageVersion: entry.packageVersion,
670
+ descriptorUrl: `${origin}${cdnPath(entry, entry.descriptor)}`,
671
+ bundleUrl: `${origin}${pieceTarballPath(entry)}`
672
+ };
673
+ }
674
+ function findPieceByTarball(cdnCachePath, filename, storagePath) {
675
+ for (const entry of pieceIndex(cdnCachePath, storagePath).values()) if (pieceTarballName(entry.name, entry.version) === filename) return entry;
676
+ }
677
+ function listFiles(dir, prefix = "") {
678
+ const files = [];
679
+ for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
680
+ const rel = prefix ? `${prefix}/${item.name}` : item.name;
681
+ if (item.isDirectory()) files.push(...listFiles(path.join(dir, item.name), rel));
682
+ else if (item.isFile()) files.push(rel);
683
+ }
684
+ return files.sort();
685
+ }
686
+ const cuts = /* @__PURE__ */ new Map();
687
+ async function cutTarball(entry, pieceDir, target) {
688
+ const files = listFiles(pieceDir);
689
+ if (files.length === 0) return null;
690
+ fs.mkdirSync(path.dirname(target), { recursive: true });
691
+ const tmp = `${target}.tmp-${crypto.randomUUID()}`;
692
+ try {
693
+ await create({
694
+ file: tmp,
695
+ gzip: true,
696
+ cwd: pieceDir,
697
+ prefix: "package",
698
+ portable: true
699
+ }, files);
700
+ fs.renameSync(tmp, target);
701
+ } catch (error) {
702
+ fs.rmSync(tmp, { force: true });
703
+ console.error(`[registry] failed to pack piece ${entry.name}:`, error);
704
+ return null;
705
+ }
706
+ return target;
707
+ }
708
+ async function pieceTarball(entry) {
709
+ const pieceDir = insidePackage(entry, entry.bundle);
710
+ if (!pieceDir || !fs.existsSync(path.join(pieceDir, "package.json"))) return null;
711
+ const target = path.join(entry.packageDir, ".piece-bundles", pieceTarballName(entry.name, entry.version));
712
+ if (fs.existsSync(target)) return target;
713
+ const pending = cuts.get(target);
714
+ if (pending) return pending;
715
+ const started = cutTarball(entry, pieceDir, target).finally(() => {
716
+ cuts.delete(target);
717
+ });
718
+ cuts.set(target, started);
719
+ return started;
720
+ }
721
+ const MANIFEST_PATHS = [
722
+ "powerhouse.manifest.json",
723
+ "cdn/powerhouse.manifest.json",
724
+ "dist/powerhouse.manifest.json"
725
+ ];
726
+ const MAX_TARBALL_BYTES = 128 * 1024 * 1024;
727
+ function stripRoot(entryPath) {
728
+ return entryPath.replace(/^[^/]+\//, "");
729
+ }
730
+ function manifestsInTar(tar) {
731
+ const found = /* @__PURE__ */ new Map();
732
+ list({
733
+ sync: true,
734
+ filter: (entryPath) => MANIFEST_PATHS.includes(stripRoot(entryPath)),
735
+ onReadEntry: (entry) => {
736
+ const chunks = [];
737
+ entry.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
738
+ entry.on("end", () => {
739
+ found.set(stripRoot(String(entry.path)), Buffer.concat(chunks).toString("utf-8"));
740
+ });
741
+ }
742
+ }).end(tar);
743
+ return found;
744
+ }
745
+ /** The piece names a published tarball claims; empty when it claims none. */
746
+ async function pieceNamesInTarball(tgz) {
747
+ let manifests;
748
+ try {
749
+ manifests = manifestsInTar(await gunzip$1(tgz, { maxOutputLength: MAX_TARBALL_BYTES }));
750
+ } catch {
751
+ return [];
752
+ }
753
+ for (const candidate of MANIFEST_PATHS) {
754
+ const raw = manifests.get(candidate);
755
+ if (raw === void 0) continue;
756
+ try {
757
+ return piecesOf(JSON.parse(raw)).map((piece) => piece.id).filter((id) => typeof id === "string" && id !== "");
758
+ } catch {
759
+ return [];
760
+ }
761
+ }
762
+ return [];
763
+ }
764
+ //#endregion
517
765
  //#region src/warmup.ts
518
766
  const WARM_INTERVAL_MS = 3e4;
519
767
  const WARM_CONCURRENCY = 8;
@@ -593,6 +841,9 @@ const MIME_TYPES = {
593
841
  function getContentType(filePath) {
594
842
  return MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
595
843
  }
844
+ function originOf(req) {
845
+ return `${req.get("x-forwarded-proto")?.split(",")[0].trim() || req.protocol}://${req.get("host") ?? ""}`;
846
+ }
596
847
  function publisherFromRequest(req) {
597
848
  const name = req.remote_user?.name;
598
849
  if (!name) return void 0;
@@ -749,6 +1000,47 @@ function createPowerhouseRouter(config, sse, webhooks, ownerStore) {
749
1000
  }
750
1001
  res.json((await withOwners([pkg]))[0]);
751
1002
  });
1003
+ router.get("/pieces", (req, res) => {
1004
+ warm();
1005
+ res.json(pieceCatalog(config.cdnCachePath, {
1006
+ storagePath: config.storagePath,
1007
+ suggestions: req.query.suggestionType === "ACTION_AND_TRIGGER"
1008
+ }));
1009
+ });
1010
+ router.get("/pieces/*", (req, res) => {
1011
+ warm();
1012
+ const name = req.params[0];
1013
+ const entry = findPiece(config.cdnCachePath, name, config.storagePath);
1014
+ const detail = entry ? pieceDetail(entry, originOf(req)) : null;
1015
+ if (!detail) {
1016
+ res.status(404).json({ error: `Piece not found: ${name}` });
1017
+ return;
1018
+ }
1019
+ res.json(detail);
1020
+ });
1021
+ router.get("/-/pieces/bundled/*", async (req, res) => {
1022
+ warm();
1023
+ const filename = req.params[0];
1024
+ const entry = findPieceByTarball(config.cdnCachePath, filename, config.storagePath);
1025
+ const file = entry ? await pieceTarball(entry) : null;
1026
+ if (!entry || !file) {
1027
+ res.status(404).json({ error: `Piece bundle not found: ${filename}` });
1028
+ return;
1029
+ }
1030
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
1031
+ const etag = `"${entry.packageName}-${filename}"`;
1032
+ res.setHeader("ETag", etag);
1033
+ if (etagMatches(req.headers["if-none-match"], etag)) {
1034
+ res.status(304).end();
1035
+ return;
1036
+ }
1037
+ res.setHeader("Content-Type", "application/gzip");
1038
+ try {
1039
+ await pipeline(fs.createReadStream(file), res);
1040
+ } catch {
1041
+ res.destroy();
1042
+ }
1043
+ });
752
1044
  router.get("/-/cdn/*", async (req, res) => {
753
1045
  const fullPath = req.params[0];
754
1046
  let packageSpec;
@@ -850,6 +1142,7 @@ function createUnpublishHook(config, notifications) {
850
1142
  if (res.statusCode >= 200 && res.statusCode < 300) {
851
1143
  const publishedBy = publisherFromRequest(req);
852
1144
  cdn.reconcileWithRegistry(rewrite.packageName).then((removed) => {
1145
+ if (removed.length > 0) invalidatePieceIndex();
853
1146
  for (const version of removed) notifications.notifyUnpublish({
854
1147
  packageName: rewrite.packageName,
855
1148
  version,
@@ -882,6 +1175,7 @@ function createUnpublishHook(config, notifications) {
882
1175
  if (res.statusCode >= 200 && res.statusCode < 300) try {
883
1176
  if (parsed.version) cdn.invalidateVersion(parsed.packageName, parsed.version);
884
1177
  else cdn.invalidate(parsed.packageName);
1178
+ invalidatePieceIndex();
885
1179
  notifications.notifyUnpublish({
886
1180
  packageName: parsed.packageName,
887
1181
  version: parsed.version,
@@ -897,11 +1191,11 @@ function createUnpublishHook(config, notifications) {
897
1191
  }
898
1192
  function createPublishHook(config, notifications) {
899
1193
  const cdn = new CdnCache(`http://localhost:${config.port}`, config.cdnCachePath);
900
- return (req, res, next) => {
901
- if (req.method !== "PUT" || req.path.includes("/-rev/")) {
902
- next();
903
- return;
904
- }
1194
+ const parseBody = express.json({
1195
+ strict: false,
1196
+ limit: config.maxBodySize ?? "300mb"
1197
+ });
1198
+ const attachExtraction = (req, res) => {
905
1199
  const originalEnd = res.end.bind(res);
906
1200
  res.end = function(chunk, encoding, cb) {
907
1201
  const urlPath = req.path.replace(/^\//, "");
@@ -917,6 +1211,7 @@ function createPublishHook(config, notifications) {
917
1211
  if (versions.length > 1) console.warn(`[registry] Multiple versions published for ${packageName}: ${JSON.stringify(versions)}`);
918
1212
  const publishedBy = publisherFromRequest(req);
919
1213
  cdn.extractTarball(packageName, version).then(() => {
1214
+ invalidatePieceIndex();
920
1215
  notifications.notifyPublish({
921
1216
  packageName,
922
1217
  version,
@@ -927,8 +1222,59 @@ function createPublishHook(config, notifications) {
927
1222
  });
928
1223
  return originalEnd(chunk, encoding, cb);
929
1224
  };
930
- next();
931
1225
  };
1226
+ return (req, res, next) => {
1227
+ if (req.method !== "PUT" || req.path.includes("/-rev/")) {
1228
+ next();
1229
+ return;
1230
+ }
1231
+ const urlPath = req.path.replace(/^\//, "");
1232
+ if (!urlPath || urlPath.startsWith("-")) {
1233
+ attachExtraction(req, res);
1234
+ next();
1235
+ return;
1236
+ }
1237
+ parseBody(req, res, (parseError) => {
1238
+ if (parseError) {
1239
+ next();
1240
+ return;
1241
+ }
1242
+ pieceClaimConflict(config, decodeURIComponent(urlPath), req.body).then((conflict) => {
1243
+ if (conflict) {
1244
+ res.status(409).json({ error: conflict });
1245
+ return;
1246
+ }
1247
+ attachExtraction(req, res);
1248
+ next();
1249
+ }).catch((err) => {
1250
+ console.error("[registry] piece collision check failed:", err);
1251
+ attachExtraction(req, res);
1252
+ next();
1253
+ });
1254
+ });
1255
+ };
1256
+ }
1257
+ function publishedTarball(body) {
1258
+ if (body === null || typeof body !== "object") return null;
1259
+ const attachments = body._attachments;
1260
+ if (!attachments || typeof attachments !== "object") return null;
1261
+ for (const [name, attachment] of Object.entries(attachments)) {
1262
+ if (!name.endsWith(".tgz")) continue;
1263
+ const data = attachment.data;
1264
+ if (typeof data === "string") return Buffer.from(data, "base64");
1265
+ }
1266
+ return null;
1267
+ }
1268
+ async function pieceClaimConflict(config, packageName, body) {
1269
+ const index = pieceIndex(config.cdnCachePath, config.storagePath);
1270
+ if (index.size === 0) return null;
1271
+ const tarball = publishedTarball(body);
1272
+ if (!tarball) return null;
1273
+ for (const name of await pieceNamesInTarball(tarball)) {
1274
+ const owner = index.get(name);
1275
+ if (owner && owner.packageName !== packageName) return `Piece "${name}" is already published by ${owner.packageName}; ${packageName} cannot claim it.`;
1276
+ }
1277
+ return null;
932
1278
  }
933
1279
  //#endregion
934
1280
  //#region src/notifications/manager.ts
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["#resolveFile","#extractWithLock","#extractionLocks","#channels","#clients","#broadcast","#storagePath","#predefined","#dynamic","#load","#save","#post","#filePath"],"sources":["../src/constants.ts","../src/auth/pg-store.ts","../src/auth/store-handoff.ts","../src/semver.ts","../src/cdn.ts","../src/packages.ts","../src/warmup.ts","../src/middleware.ts","../src/notifications/manager.ts","../src/notifications/sse.ts","../src/notifications/webhook.ts","../src/verdaccio-config.ts","../src/run.ts","../cli.ts"],"sourcesContent":["export const DEFAULT_PORT = 8080;\nexport const DEFAULT_STORAGE_DIR_NAME = \"./storage\" as const;\nexport const DEFAULT_REGISTRY_CDN_CACHE_DIR_NAME = \"./cdn-cache\" as const;\n","import { Pool } from \"pg\";\nimport type { AuthStore, UserRecord } from \"./auth-store.js\";\n\n/** Build a real Postgres pool from a connection string. */\nexport function createPgPool(databaseUrl: string): Pool {\n return new Pool({ connectionString: databaseUrl });\n}\n\n/**\n * Postgres-backed AuthStore. Two small tables:\n * - registry_users(username PK, password_hash, created_at)\n * - registry_package_owners(package_name PK, owners text[], claimed_at)\n *\n * Ownership claim is race-free: `INSERT ... ON CONFLICT DO NOTHING` means the\n * first publisher wins atomically; the follow-up read returns the actual\n * owners so a losing racer is denied.\n *\n * Takes an already-built `pg.Pool` (tests inject a pg-mem pool cast to Pool).\n */\nexport function createPgStore(pool: Pool): AuthStore {\n let initialized: Promise<void> | null = null;\n\n return {\n init(): Promise<void> {\n initialized ??= (async () => {\n await pool.query(`\n CREATE TABLE IF NOT EXISTS registry_users (\n username text PRIMARY KEY,\n password_hash text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n )`);\n await pool.query(`\n CREATE TABLE IF NOT EXISTS registry_package_owners (\n package_name text PRIMARY KEY,\n owners text[] NOT NULL,\n claimed_at timestamptz NOT NULL DEFAULT now()\n )`);\n })();\n return initialized;\n },\n\n async getUser(username: string): Promise<UserRecord | null> {\n const res = await pool.query<{ password_hash: string }>(\n \"SELECT password_hash FROM registry_users WHERE username = $1\",\n [username],\n );\n const row = res.rows[0] as { password_hash: string } | undefined;\n return row ? { passwordHash: row.password_hash } : null;\n },\n\n async createUser(username: string, passwordHash: string): Promise<boolean> {\n // Atomic: the PRIMARY KEY enforces uniqueness. A duplicate raises a\n // unique-violation (SQLSTATE 23505) — catch it as \"already registered\".\n try {\n await pool.query(\n \"INSERT INTO registry_users (username, password_hash) VALUES ($1, $2)\",\n [username, passwordHash],\n );\n return true;\n } catch (err) {\n if ((err as { code?: string }).code === \"23505\") return false;\n throw err;\n }\n },\n\n async getOwners(pkg: string): Promise<string[] | null> {\n const res = await pool.query<{ owners: string[] }>(\n \"SELECT owners FROM registry_package_owners WHERE package_name = $1\",\n [pkg],\n );\n return res.rows[0]?.owners ?? null;\n },\n\n async getOwnersFor(pkgs: string[]): Promise<Record<string, string[]>> {\n if (pkgs.length === 0) return {};\n // IN (...) with per-value placeholders — portable across pg and pg-mem,\n // unlike `= ANY($1)` array binding.\n const placeholders = pkgs.map((_, i) => `$${i + 1}`).join(\",\");\n const res = await pool.query<{ package_name: string; owners: string[] }>(\n `SELECT package_name, owners FROM registry_package_owners WHERE package_name IN (${placeholders})`,\n pkgs,\n );\n const out: Record<string, string[]> = {};\n for (const row of res.rows) out[row.package_name] = row.owners;\n return out;\n },\n\n async claimOwner(pkg: string, username: string): Promise<string[]> {\n // First publisher wins atomically; then read the actual owners.\n await pool.query(\n `INSERT INTO registry_package_owners (package_name, owners)\n VALUES ($1, ARRAY[$2]::text[])\n ON CONFLICT (package_name) DO NOTHING`,\n [pkg, username],\n );\n const res = await pool.query<{ owners: string[] }>(\n \"SELECT owners FROM registry_package_owners WHERE package_name = $1\",\n [pkg],\n );\n return res.rows[0]?.owners ?? [];\n },\n\n close(): Promise<void> {\n return pool.end();\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { AuthStore } from \"./auth-store.js\";\n\n// Carry a live AuthStore to the plugin as a token (verdaccio merges the app\n// config's `store` block into plugin configs), tracking whether it loaded.\nconst REGISTRY_KEY = Symbol.for(\"@powerhousedao/registry:auth-store-handoff\");\n\ninterface Entry {\n store: AuthStore;\n loaded: boolean;\n}\n\nfunction registry(): Map<string, Entry> {\n const g = globalThis as { [REGISTRY_KEY]?: Map<string, Entry> };\n return (g[REGISTRY_KEY] ??= new Map<string, Entry>());\n}\n\n/** Stash a store instance and return a token to carry through plugin config. */\nexport function stashAuthStore(store: AuthStore): string {\n const token = randomUUID();\n registry().set(token, { store, loaded: false });\n return token;\n}\n\n/** Resolve a stashed store by token (undefined if unknown). */\nexport function takeAuthStore(token: string): AuthStore | undefined {\n return registry().get(token)?.store;\n}\n\n/** Record that the plugin fully constructed with this token's store. */\nexport function markStoreLoaded(token: string): void {\n const entry = registry().get(token);\n if (entry) entry.loaded = true;\n}\n\n/** True once the plugin has loaded the store for this token. */\nexport function wasStoreLoaded(token: string): boolean {\n return registry().get(token)?.loaded ?? false;\n}\n","/**\n * Compare two semver version strings for sorting.\n * Returns negative if a < b, positive if a > b, 0 if equal.\n *\n * Handles numeric component comparison (so \"1.0.10\" > \"1.0.9\")\n * and prerelease ordering (release > prerelease).\n */\nexport function compareSemver(a: string, b: string): number {\n const [coreA, preA] = a.split(\"-\", 2);\n const [coreB, preB] = b.split(\"-\", 2);\n\n const partsA = coreA.split(\".\").map(Number);\n const partsB = coreB.split(\".\").map(Number);\n\n for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {\n const na = partsA[i] ?? 0;\n const nb = partsB[i] ?? 0;\n if (na !== nb) return na - nb;\n }\n\n // Equal core versions — release (no prerelease) sorts after prerelease\n if (!preA && preB) return 1;\n if (preA && !preB) return -1;\n if (preA && preB) return preA < preB ? -1 : preA > preB ? 1 : 0;\n\n return 0;\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { extract } from \"tar\";\nimport { compareSemver } from \"./semver.js\";\n\n/**\n * Parse a package specifier into name and version/tag.\n * Supports:\n * \"@scope/pkg\" -> { name: \"@scope/pkg\", tag: undefined }\n * \"@scope/pkg@dev\" -> { name: \"@scope/pkg\", tag: \"dev\" }\n * \"@scope/pkg@1.0.0\" -> { name: \"@scope/pkg\", tag: \"1.0.0\" }\n * \"pkg@latest\" -> { name: \"pkg\", tag: \"latest\" }\n */\nexport function parsePackageSpec(spec: string): {\n name: string;\n tag: string | undefined;\n} {\n // For scoped packages (@scope/name@tag), split on the last @\n // For unscoped packages (name@tag), split on the first @\n if (spec.startsWith(\"@\")) {\n // Scoped: find the @ after the scope/name portion\n const lastAt = spec.lastIndexOf(\"@\");\n if (lastAt > 0 && lastAt !== spec.indexOf(\"@\")) {\n return { name: spec.slice(0, lastAt), tag: spec.slice(lastAt + 1) };\n }\n return { name: spec, tag: undefined };\n }\n const atIndex = spec.indexOf(\"@\");\n if (atIndex > 0) {\n return { name: spec.slice(0, atIndex), tag: spec.slice(atIndex + 1) };\n }\n return { name: spec, tag: undefined };\n}\n\n/** True when tag is a concrete semver, false for dist-tag names or undefined. */\nexport function isExactVersion(tag?: string): boolean {\n // Strict semver charset: the version flows into the ETag header, so\n // arbitrary characters after the prerelease/build separator must not match.\n return (\n !!tag &&\n /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/.test(tag)\n );\n}\n\nexport class CdnCache {\n #extractionLocks = new Map<string, Promise<void>>();\n\n constructor(\n private registryUrl: string,\n private cdnCachePath: string,\n ) {}\n\n async getFileByVersion(\n packageName: string,\n version: string,\n filePath: string,\n ): Promise<string | null> {\n const versionDir = path.join(this.cdnCachePath, packageName, version);\n\n // Check all possible paths before attempting extraction\n const resolved = this.#resolveFile(versionDir, filePath);\n if (resolved) return resolved;\n\n // File not found in any location — extract tarball and try again\n await this.#extractWithLock(packageName, version);\n\n return this.#resolveFile(versionDir, filePath);\n }\n\n #resolveFile(versionDir: string, filePath: string): string | null {\n // Check direct path first, then fall back to cdn/ and dist/cdn/ subdirectories\n // (npm tarballs contain files under dist/, bun bundles go to cdn/)\n const candidates = [\n path.join(versionDir, filePath),\n path.join(versionDir, \"cdn\", filePath),\n path.join(versionDir, \"dist\", \"cdn\", filePath),\n path.join(versionDir, \"dist\", filePath),\n ];\n\n for (const candidate of candidates) {\n if (this.isSafePath(candidate) && fs.existsSync(candidate))\n return candidate;\n }\n\n return null;\n }\n\n async #extractWithLock(packageName: string, version: string): Promise<void> {\n const key = `${packageName}@${version}`;\n const existing = this.#extractionLocks.get(key);\n if (existing) return existing;\n\n const promise = this.extractTarball(packageName, version).finally(() => {\n this.#extractionLocks.delete(key);\n });\n this.#extractionLocks.set(key, promise);\n return promise;\n }\n\n getLatestCachedVersion(packageName: string): string | null {\n const pkgDir = path.join(this.cdnCachePath, packageName);\n try {\n const entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n const versions = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n if (versions.length === 0) return null;\n versions.sort(compareSemver);\n return versions[versions.length - 1];\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve a version for a package. If tag is a semver version that exists\n * in the registry, return it directly. If tag is a dist-tag name (e.g.\n * \"dev\", \"latest\"), resolve it to the concrete version. If no tag is\n * provided, prefer \"latest\", then fall back to any available dist-tag.\n * Returns null only for genuine not-found (404, or absent tag/version).\n * Throws on network errors and non-OK responses other than 404.\n */\n async resolveVersion(\n packageName: string,\n tag?: string,\n ): Promise<string | null> {\n const url = `${this.registryUrl}/${encodeURIComponent(packageName)}`;\n const res = await fetch(url, {\n headers: { Accept: \"application/json\" },\n });\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `Upstream metadata for ${packageName} returned ${res.status}`,\n );\n }\n const metadata = (await res.json()) as Record<string, unknown>;\n const distTags = metadata[\"dist-tags\"] as\n | Record<string, string>\n | undefined;\n const versions = metadata[\"versions\"] as\n | Record<string, unknown>\n | undefined;\n\n if (tag) {\n // If the tag matches an exact version in the registry, use it directly\n if (versions && tag in versions) return tag;\n // Otherwise treat it as a dist-tag name\n if (distTags && tag in distTags) return distTags[tag];\n // Tag not found\n return null;\n }\n\n if (!distTags) return null;\n // No tag specified: prefer \"latest\", fall back to any available tag\n return distTags.latest ?? Object.values(distTags)[0] ?? null;\n }\n\n async extractTarball(packageName: string, version: string): Promise<void> {\n const destDir = path.join(this.cdnCachePath, packageName, version);\n\n // Idempotence guard. extractTarball is hot-path-called from\n // warmCdnCacheFromVerdaccio on every /packages request (which the\n // deployment's readiness probe hits every 5s). Without this skip the\n // tarball is re-fetched (multi-MB over S3) and re-extracted on each\n // call, pinning CPU at multi-vCPU per pod and triggering HPA spirals.\n // We treat the presence of package.json as the marker for \"already\n // extracted\" — it's the first file npm tarballs put under the version\n // directory and removing it (e.g. by invalidate*) requires the rest to\n // go too.\n if (fs.existsSync(path.join(destDir, \"package.json\"))) return;\n\n const shortName = packageName.startsWith(\"@\")\n ? packageName.split(\"/\")[1]\n : packageName;\n const tarballUrl = `${this.registryUrl}/${encodeURIComponent(packageName)}/-/${shortName}-${version}.tgz`;\n\n let res: Response;\n try {\n res = await fetch(tarballUrl);\n if (!res.ok || !res.body) return;\n } catch {\n return;\n }\n\n fs.mkdirSync(destDir, { recursive: true });\n\n const tmpFile = path.join(\n destDir,\n `.tmp-tarball-${crypto.randomUUID()}.tgz`,\n );\n try {\n const fileStream = fs.createWriteStream(tmpFile);\n await pipeline(Readable.fromWeb(res.body as never), fileStream);\n await extract({ file: tmpFile, cwd: destDir, strip: 1 });\n } finally {\n fs.rmSync(tmpFile, { force: true });\n }\n }\n\n invalidate(packageName: string): void {\n const cacheDir = path.join(this.cdnCachePath, packageName);\n if (!this.isSafePath(cacheDir)) return;\n fs.rmSync(cacheDir, { recursive: true, force: true });\n }\n\n invalidateVersion(packageName: string, version: string): void {\n const versionDir = path.join(this.cdnCachePath, packageName, version);\n if (!this.isSafePath(versionDir)) return;\n fs.rmSync(versionDir, { recursive: true, force: true });\n // If the package dir is now empty, remove it too so the scanner doesn't\n // keep returning a ghost entry with no versions.\n const pkgDir = path.join(this.cdnCachePath, packageName);\n try {\n if (fs.readdirSync(pkgDir).length === 0) {\n fs.rmdirSync(pkgDir);\n }\n } catch {\n // ignore — dir may not exist\n }\n }\n\n // Reconcile the cache against the registry's current version set; returns\n // removed versions. Empty/failed listings are a no-op (never mass-purge).\n async reconcileWithRegistry(packageName: string): Promise<string[]> {\n const url = `${this.registryUrl}/${encodeURIComponent(packageName)}`;\n let versions: string[];\n try {\n const res = await fetch(url, { headers: { Accept: \"application/json\" } });\n if (!res.ok) return [];\n const meta = (await res.json()) as { versions?: Record<string, unknown> };\n versions = Object.keys(meta.versions ?? {});\n } catch {\n return [];\n }\n if (versions.length === 0) return [];\n return this.reconcileVersions(packageName, versions);\n }\n\n // Drop cached version dirs absent from `keepVersions`; returns the removed\n // ones. Mirrors single-version unpublish (a manifest rewrite, no DELETE).\n reconcileVersions(\n packageName: string,\n keepVersions: Iterable<string>,\n ): string[] {\n const pkgDir = path.join(this.cdnCachePath, packageName);\n const keep = new Set(keepVersions);\n const removed: string[] = [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n } catch {\n return removed;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || keep.has(entry.name)) continue;\n this.invalidateVersion(packageName, entry.name);\n removed.push(entry.name);\n }\n return removed;\n }\n\n /** Remove all cached version directories except the specified one. */\n pruneOldVersions(packageName: string, keepVersion: string): void {\n const pkgDir = path.join(this.cdnCachePath, packageName);\n try {\n const entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name !== keepVersion) {\n const dir = path.join(pkgDir, entry.name);\n if (this.isSafePath(dir)) {\n fs.rmSync(dir, { recursive: true, force: true });\n }\n }\n }\n } catch {\n // ignore — directory may not exist yet\n }\n }\n\n private isSafePath(filePath: string): boolean {\n const resolved = path.resolve(filePath);\n const cacheRoot = path.resolve(this.cdnCachePath);\n return resolved.startsWith(cacheRoot + path.sep) || resolved === cacheRoot;\n }\n}\n","import type { Manifest } from \"@powerhousedao/shared\";\nimport { slimManifest } from \"@powerhousedao/shared/registry\";\nimport type { PackageListItem } from \"@powerhousedao/shared/registry\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { compareSemver } from \"./semver.js\";\nimport type { PackageInfo } from \"./types.js\";\n\n/**\n * Read dist-tags, the full version list, and the local-publish flag for a\n * package from verdaccio's on-disk storage (`{storagePath}/{name}/package.json`).\n *\n * `locallyPublished` is tri-state:\n * - `true` → storage metadata has `_attachments` (tarball uploaded here).\n * - `false` → storage metadata exists but `_attachments` is empty (proxy\n * from the npm uplink only; no local publish at this registry).\n * - `undefined` → metadata file wasn't readable. Happens with non-filesystem\n * backends (S3, etc.) or if verdaccio stores metadata elsewhere.\n * Callers should treat this as \"unknown\" and default to including\n * the package, to avoid filtering the whole /packages list to an\n * empty array on deployments where we can't observe _attachments.\n */\nfunction readPackageMetadata(\n storagePath: string | undefined,\n packageName: string,\n): {\n distTags?: Record<string, string>;\n versions?: string[];\n locallyPublished: boolean | undefined;\n} {\n if (!storagePath) return { locallyPublished: undefined };\n try {\n const metadataPath = path.join(storagePath, packageName, \"package.json\");\n const raw = fs.readFileSync(metadataPath, \"utf-8\");\n const parsed = JSON.parse(raw) as {\n \"dist-tags\"?: Record<string, string>;\n versions?: Record<string, unknown>;\n _attachments?: Record<string, unknown>;\n };\n const distTags = parsed[\"dist-tags\"];\n const rawVersions = parsed.versions ? Object.keys(parsed.versions) : [];\n const versions = rawVersions.slice().sort(compareSemver);\n const locallyPublished =\n !!parsed._attachments && Object.keys(parsed._attachments).length > 0;\n return {\n distTags:\n distTags && Object.keys(distTags).length > 0 ? distTags : undefined,\n versions: versions.length > 0 ? versions : undefined,\n locallyPublished,\n };\n } catch {\n return { locallyPublished: undefined };\n }\n}\n\n/**\n * Locally-published check for a package, from verdaccio storage metadata.\n * Returns the same tri-state as `readPackageMetadata.locallyPublished`:\n * `true` (has `_attachments`), `false` (proxy-only), `undefined` (unreadable).\n */\nexport function isLocallyPublished(\n storagePath: string | undefined,\n packageName: string,\n): boolean | undefined {\n return readPackageMetadata(storagePath, packageName).locallyPublished;\n}\n\nfunction readManifest(dir: string): Manifest | null {\n const candidates = [\n path.join(dir, \"powerhouse.manifest.json\"),\n path.join(dir, \"cdn\", \"powerhouse.manifest.json\"),\n path.join(dir, \"dist\", \"powerhouse.manifest.json\"),\n ];\n for (const manifestPath of candidates) {\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n // Manifests are publisher-supplied JSON; slim to the known summary\n // fields so one oversized publish can't bloat every /packages\n // listing (a single 7.8 MB `features` blob once pushed the response\n // past clients' localStorage quota). The raw file stays available\n // through the CDN path.\n return slimManifest(JSON.parse(raw) as Manifest);\n } catch {\n // try next candidate\n }\n }\n return null;\n}\n\nfunction readPackageJsonVersion(dir: string): string | undefined {\n try {\n const raw = fs.readFileSync(path.join(dir, \"package.json\"), \"utf-8\");\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" ? pkg.version : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction getLatestVersionDir(pkgDir: string): string | null {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n } catch {\n return null;\n }\n const versions = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n if (versions.length === 0) return null;\n versions.sort(compareSemver);\n return path.join(pkgDir, versions[versions.length - 1]);\n}\n\nexport function loadPackage(\n cdnCachePath: string,\n name: string,\n version?: string,\n storagePath?: string,\n): PackageInfo | null {\n const pkgDir = path.join(cdnCachePath, name);\n const versionDir = version\n ? path.join(pkgDir, version)\n : getLatestVersionDir(pkgDir);\n const manifestDir = versionDir ?? pkgDir;\n const manifest = readManifest(manifestDir);\n\n if (!manifest) {\n return null;\n }\n const resolvedName = manifest.name || name;\n // Include dist-tags/versions so the paginated UI can lazy-load version\n // metadata for a single package (the list DTO omits them). Falls back to\n // undefined when storage metadata isn't available.\n const { distTags, versions } = readPackageMetadata(storagePath, resolvedName);\n return {\n name: resolvedName,\n path: `/-/cdn/${name}`,\n manifest,\n documentTypes: getDocumentTypesFromManifest(manifest),\n version: readPackageJsonVersion(manifestDir),\n distTags,\n versions,\n };\n}\n\n/**\n * Project a full {@link PackageInfo} down to the trimmed shape the paginated\n * package-listing UI renders per row. Version metadata and documentTypes are\n * intentionally dropped — the client fetches them on demand.\n */\nexport function toPackageListItem(pkg: PackageInfo): PackageListItem {\n const publisher = pkg.manifest?.publisher;\n return {\n name: pkg.name,\n path: pkg.path,\n version: pkg.version,\n description: pkg.manifest?.description ?? undefined,\n category: pkg.manifest?.category ?? undefined,\n publisher: publisher\n ? { name: publisher.name, url: publisher.url }\n : undefined,\n };\n}\n\nfunction getDocumentTypesFromManifest(manifest: Manifest | undefined | null) {\n if (!manifest) return [];\n\n const documentTypes: string[] = [];\n const { apps, documentModels, editors, subgraphs } = manifest;\n\n if (apps?.length) {\n documentTypes.push(\"powerhouse/document-drive\");\n }\n documentTypes.push(\n ...(documentModels ?? []).map((dm) => dm.id),\n ...(editors ?? [])\n .flatMap((e) => e.documentTypes)\n .filter((dt) => dt !== undefined),\n ...(subgraphs ?? [])\n .flatMap((e) => e.documentTypes)\n .filter((dt) => dt !== undefined),\n );\n\n return documentTypes;\n}\n\nexport function scanPackages(\n cdnCachePath: string,\n storagePath?: string,\n): PackageInfo[] {\n const absDir = path.resolve(cdnCachePath);\n const packages: PackageInfo[] = [];\n\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(absDir, { withFileTypes: true });\n } catch {\n return packages;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n if (entry.name.startsWith(\"@\")) {\n const scopeDir = path.join(absDir, entry.name);\n let scopedEntries: fs.Dirent[];\n try {\n scopedEntries = fs.readdirSync(scopeDir, { withFileTypes: true });\n } catch (error) {\n console.log(error);\n continue;\n }\n for (const scopedEntry of scopedEntries) {\n if (!scopedEntry.isDirectory()) continue;\n const dirName = `${entry.name}/${scopedEntry.name}`;\n const pkgDir = path.join(scopeDir, scopedEntry.name);\n const versionDir = getLatestVersionDir(pkgDir);\n const manifestDir = versionDir ?? pkgDir;\n const manifest = readManifest(manifestDir);\n // `||` (not `??`): slimManifest normalizes a missing manifest name to\n // \"\" — fall back to the directory name in that case too.\n const name = manifest?.name || dirName;\n const { distTags, versions, locallyPublished } = readPackageMetadata(\n storagePath,\n name,\n );\n // Drop npm-uplink passthroughs from the default listing. Only\n // skip when we can affirmatively tell the package is a proxy\n // (no `_attachments` in filesystem-backed storage). When the flag\n // is `undefined` (no storagePath, or non-filesystem backend where\n // we can't read verdaccio's metadata) we include the entry — the\n // alternative would be filtering everything to `[]` on S3 deploys.\n if (locallyPublished === false) continue;\n packages.push({\n name,\n path: `/-/cdn/${dirName}`,\n manifest,\n documentTypes: getDocumentTypesFromManifest(manifest),\n version: readPackageJsonVersion(manifestDir),\n distTags,\n versions,\n });\n }\n } else {\n const pkgDir = path.join(absDir, entry.name);\n const versionDir = getLatestVersionDir(pkgDir);\n const manifestDir = versionDir ?? pkgDir;\n const manifest = readManifest(manifestDir);\n const name = manifest?.name || entry.name;\n const { distTags, versions, locallyPublished } = readPackageMetadata(\n storagePath,\n name,\n );\n if (locallyPublished === false) continue;\n packages.push({\n name,\n path: `/-/cdn/${entry.name}`,\n manifest,\n documentTypes: getDocumentTypesFromManifest(manifest),\n version: readPackageJsonVersion(manifestDir),\n distTags,\n versions,\n });\n }\n }\n\n return packages;\n}\n\nexport function findPackagesByDocumentType(\n packagesDir: string,\n documentType: string,\n): PackageInfo[] {\n const allPackages = scanPackages(packagesDir);\n\n return allPackages.filter((pkg) => {\n if (!pkg.manifest?.documentModels) {\n return false;\n }\n return pkg.manifest.documentModels.some((dm) => dm.id === documentType);\n });\n}\n","import type { CdnCache } from \"./cdn.js\";\nimport { isLocallyPublished } from \"./packages.js\";\nimport type { RegistryConfig } from \"./types.js\";\n\n// The verdaccio listing already yields local-only, latest-per-name entries;\n// we re-filter by `_attachments` and dedupe as a guard against backend changes.\nconst WARM_INTERVAL_MS = 30_000;\nconst WARM_CONCURRENCY = 8;\n\ninterface VerdaccioPackage {\n name: string;\n version?: string;\n}\n\n/**\n * Build a throttled warmer that extracts locally-published package tarballs\n * into the CDN cache. 30s minimum interval plus an in-flight guard prevent\n * redundant fan-out from readiness-probe traffic across pods.\n */\nexport function createWarmer(\n config: RegistryConfig,\n cdn: CdnCache,\n): () => Promise<void> {\n let warmInFlight = false;\n let lastWarmAt = 0;\n\n return async function warm(): Promise<void> {\n if (warmInFlight) return;\n if (Date.now() - lastWarmAt < WARM_INTERVAL_MS) return;\n warmInFlight = true;\n try {\n const r = await fetch(\n `http://localhost:${config.port}/-/verdaccio/data/packages`,\n );\n if (!r.ok) {\n console.error(\n `[registry] verdaccio package listing returned ${r.status}`,\n );\n return;\n }\n const listed = (await r.json()) as VerdaccioPackage[];\n\n // Latest version per name (the listing yields one entry per package;\n // dedupe defensively), scoped to locally-published packages only.\n const latestByName = new Map<string, string>();\n for (const pkg of listed) {\n if (!pkg.version) continue;\n if (isLocallyPublished(config.storagePath, pkg.name) === false)\n continue;\n latestByName.set(pkg.name, pkg.version);\n }\n\n const targets = [...latestByName.entries()];\n let cursor = 0;\n const workers = Array.from({ length: WARM_CONCURRENCY }).map(async () => {\n while (cursor < targets.length) {\n const [name, version] = targets[cursor++];\n try {\n await cdn.extractTarball(name, version);\n } catch (err) {\n console.error(\n `[registry] failed to warm cache for ${name}@${version}:`,\n err,\n );\n }\n }\n });\n await Promise.all(workers);\n console.log(`[registry] /packages warm-up done (${targets.length} pkgs)`);\n // Throttle only after a successful cycle so failures (e.g. registry\n // not listening yet during startup) retry on the next call.\n lastWarmAt = Date.now();\n } catch (err) {\n console.error(\"[registry] /packages warm-up failed:\", err);\n } finally {\n warmInFlight = false;\n }\n };\n}\n","import express, {\n Router,\n type NextFunction,\n type Request,\n type Response,\n} from \"express\";\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { AuthStore } from \"./auth/auth-store.js\";\nimport { CdnCache, isExactVersion, parsePackageSpec } from \"./cdn.js\";\nimport type { PackageInfo } from \"./types.js\";\nimport type { SSEChannel } from \"./notifications/sse.js\";\nimport type { NotificationChannel } from \"./notifications/types.js\";\nimport type { WebhookChannel } from \"./notifications/webhook.js\";\nimport {\n findPackagesByDocumentType,\n loadPackage,\n scanPackages,\n toPackageListItem,\n} from \"./packages.js\";\nimport type { RegistryConfig } from \"./types.js\";\nimport { createWarmer } from \"./warmup.js\";\n\n// Paginated /packages mode: default and max page sizes.\nconst DEFAULT_PAGE_SIZE = 30;\nconst MAX_PAGE_SIZE = 50;\n\n/** Parse+clamp a `limit` query param to 1..MAX_PAGE_SIZE (default 30). */\nfunction clampPageSize(raw: unknown): number {\n const n = Number.parseInt(String(raw), 10);\n if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE;\n return Math.min(n, MAX_PAGE_SIZE);\n}\n\n/** Parse a non-negative `offset` query param (default 0). */\nfunction parseOffset(raw: unknown): number {\n const n = Number.parseInt(String(raw), 10);\n if (!Number.isFinite(n) || n < 0) return 0;\n return n;\n}\n\nconst MIME_TYPES: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".wasm\": \"application/wasm\",\n \".map\": \"application/json\",\n \".html\": \"text/html\",\n \".svg\": \"image/svg+xml\",\n};\n\nfunction getContentType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase();\n return MIME_TYPES[ext] ?? \"application/octet-stream\";\n}\n\n// Publisher identity from verdaccio's remote_user (the renown middleware sets\n// its name to the owner pkh DID). undefined when anonymous.\nfunction publisherFromRequest(\n req: Request,\n): { address: string; did?: string } | undefined {\n const name = (req as { remote_user?: { name?: string } }).remote_user?.name;\n if (!name) return undefined;\n if (name.startsWith(\"did:pkh:\")) {\n const address = (name.split(\":\").pop() ?? name).toLowerCase();\n return { address, did: name };\n }\n return { address: name };\n}\n\ntype VersionResolution =\n | { kind: \"ok\"; version: string }\n | { kind: \"not-found\" }\n | { kind: \"upstream-error\" };\n\n/**\n * Resolve a package version. Exact versions skip the network call. Upstream\n * errors fall back to the latest cached version; genuine not-found falls back\n * too, then reports not-found.\n */\nasync function resolvePackageVersion(\n cdn: CdnCache,\n packageName: string,\n tag: string | undefined,\n): Promise<VersionResolution> {\n if (tag && isExactVersion(tag)) return { kind: \"ok\", version: tag };\n\n try {\n const resolved =\n (await cdn.resolveVersion(packageName, tag)) ??\n cdn.getLatestCachedVersion(packageName);\n if (!resolved) return { kind: \"not-found\" };\n return { kind: \"ok\", version: resolved };\n } catch {\n const cached = cdn.getLatestCachedVersion(packageName);\n if (!cached) return { kind: \"upstream-error\" };\n return { kind: \"ok\", version: cached };\n }\n}\n\n/** Strip the weak-validator prefix: weak comparison is correct for GET/HEAD. */\nfunction opaqueTag(tag: string): string {\n return tag.startsWith(\"W/\") ? tag.slice(2) : tag;\n}\n\n/** RFC 9110 If-None-Match: a comma-separated list of entity-tags or \"*\". */\nfunction etagMatches(\n header: string | string[] | undefined,\n etag: string,\n): boolean {\n if (!header) return false;\n const target = opaqueTag(etag);\n const value = Array.isArray(header) ? header.join(\",\") : header;\n return value.split(\",\").some((candidate) => {\n const tag = candidate.trim();\n return tag === \"*\" || opaqueTag(tag) === target;\n });\n}\n\nexport function createPowerhouseRouter(\n config: RegistryConfig,\n sse: SSEChannel,\n webhooks: WebhookChannel,\n ownerStore?: AuthStore,\n): Router {\n const cdn = new CdnCache(\n `http://localhost:${config.port}`,\n config.cdnCachePath,\n );\n const router = Router();\n\n // Attach package owners from the auth store. Best-effort: a missing store or\n // DB error degrades to no `owners` (the /packages route feeds readiness).\n const withOwners = async (pkgs: PackageInfo[]): Promise<PackageInfo[]> => {\n if (!ownerStore || pkgs.length === 0) return pkgs;\n try {\n await ownerStore.init();\n const map = await ownerStore.getOwnersFor(pkgs.map((p) => p.name));\n return pkgs.map((p) =>\n p.name in map ? { ...p, owners: map[p.name] } : p,\n );\n } catch (err) {\n console.error(\"[registry] owner lookup failed:\", err);\n return pkgs;\n }\n };\n\n // CORS on every response\n router.use((_req: Request, res: Response, next: NextFunction) => {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n next();\n });\n\n // SSE endpoint for publish notifications\n router.get(\"/-/events\", (_req: Request, res: Response) => {\n sse.addClient(res);\n });\n\n // Webhook management\n router.get(\"/-/webhooks\", (_req: Request, res: Response) => {\n res.json(webhooks.getWebhooks());\n });\n\n router.post(\"/-/webhooks\", express.json(), (req: Request, res: Response) => {\n const { endpoint, headers } = req.body as {\n endpoint?: string;\n headers?: Record<string, string>;\n };\n if (!endpoint) {\n res.status(400).json({ error: \"Missing required field: endpoint\" });\n return;\n }\n webhooks.addWebhook({ endpoint, headers });\n res.status(201).json({ endpoint, headers });\n });\n\n router.delete(\n \"/-/webhooks\",\n express.json(),\n (req: Request, res: Response) => {\n const { endpoint } = req.body as { endpoint?: string };\n if (!endpoint) {\n res.status(400).json({ error: \"Missing required field: endpoint\" });\n return;\n }\n const removed = webhooks.removeWebhook(endpoint);\n if (!removed) {\n res.status(404).json({ error: \"Webhook not found\" });\n return;\n }\n res.status(204).end();\n },\n );\n\n const warm = createWarmer(config, cdn);\n\n // Kick off an initial warm so /packages is useful soon after pod start\n // even if no clients hit it. Fire-and-forget — must not block the listener.\n void warm();\n\n // Package listing API.\n // Returns whatever's currently in the local cdn-cache (instant response —\n // important: this endpoint is wired to the deployment's readiness probe,\n // so it must not synchronously fetch or extract). Each call also nudges\n // a background warm-up so newly-published packages appear in the listing\n // without operator intervention.\n router.get(\"/packages\", async (req: Request, res: Response) => {\n void warm();\n const packages = scanPackages(config.cdnCachePath, config.storagePath);\n\n // Legacy mode 1 (unchanged): filter by document type, return the full\n // PackageInfo array. Used by MissingPackageModal / package discovery.\n const documentType = req.query.documentType as string | undefined;\n if (documentType) {\n const filtered = packages.filter((pkg) =>\n pkg.manifest?.documentModels?.some((m) => m.id === documentType),\n );\n res.json(await withOwners(filtered));\n return;\n }\n\n // Legacy mode 2 (unchanged): no `limit` → full PackageInfo array in\n // directory order. Keeps every existing consumer working.\n if (req.query.limit === undefined) {\n res.json(await withOwners(packages));\n return;\n }\n\n // Paginated mode: `?limit=&offset=&search=` → trimmed-item envelope.\n // Sort by name so offset paging is stable (scanPackages is dir order).\n const limit = clampPageSize(req.query.limit);\n const offset = parseOffset(req.query.offset);\n const search =\n typeof req.query.search === \"string\"\n ? req.query.search.trim().toLowerCase()\n : \"\";\n\n // Match on package name only (not description).\n const filtered = search\n ? packages.filter((pkg) => pkg.name.toLowerCase().includes(search))\n : packages;\n const sorted = filtered\n .slice()\n .sort((a, b) => a.name.localeCompare(b.name));\n\n const total = sorted.length;\n const items = sorted.slice(offset, offset + limit).map(toPackageListItem);\n res.json({\n items,\n total,\n limit,\n offset,\n hasMore: offset + limit < total,\n });\n });\n\n // Find packages by document type - returns array of package names\n router.get(\"/packages/by-document-type\", (req: Request, res: Response) => {\n const documentType = req.query.type;\n\n if (typeof documentType !== \"string\" || !documentType) {\n res.status(400).json({ error: \"Missing required query parameter: type\" });\n return;\n }\n\n const packages = findPackagesByDocumentType(\n config.cdnCachePath,\n documentType,\n );\n const packageNames = packages.map((pkg) => pkg.name);\n res.json(packageNames);\n });\n\n // Single package info\n router.get(\"/packages/*\", async (req: Request, res: Response) => {\n const raw = (req.params as Record<string, string>)[0];\n const { name, tag } = parsePackageSpec(raw);\n const resolution = await resolvePackageVersion(cdn, name, tag);\n if (resolution.kind === \"upstream-error\") {\n res.status(503).send(\"Upstream registry unavailable\");\n return;\n }\n const version = resolution.kind === \"ok\" ? resolution.version : undefined;\n const pkg = loadPackage(\n config.cdnCachePath,\n name,\n version,\n config.storagePath,\n );\n if (!pkg) {\n res.status(404).send(\"Package not found\");\n return;\n }\n res.json((await withOwners([pkg]))[0]);\n });\n\n // CDN file serving\n router.get(\"/-/cdn/*\", async (req: Request, res: Response) => {\n const fullPath = (req.params as Record<string, string>)[0];\n\n // Parse scoped or unscoped package specifier from the path\n let packageSpec: string;\n let filePath: string;\n\n if (fullPath.startsWith(\"@\")) {\n // Scoped: @scope/pkg@1.0.0/file.js -> packageSpec = @scope/pkg@1.0.0, filePath = file.js\n const segments = fullPath.split(\"/\");\n if (segments.length < 2) {\n res.status(400).send(\"Invalid package path\");\n return;\n }\n packageSpec = `${segments[0]}/${segments[1]}`;\n filePath = segments.slice(2).join(\"/\") || \"index.js\";\n } else {\n // Unscoped: pkg@1.0.0/file.js -> packageSpec = pkg@1.0.0, filePath = file.js\n const segments = fullPath.split(\"/\");\n packageSpec = segments[0];\n filePath = segments.slice(1).join(\"/\") || \"index.js\";\n }\n\n const { name: packageName, tag } = parsePackageSpec(packageSpec);\n const pinned = isExactVersion(tag);\n const resolution = await resolvePackageVersion(cdn, packageName, tag);\n if (resolution.kind === \"upstream-error\") {\n res.status(503).send(\"Upstream registry unavailable\");\n return;\n }\n if (resolution.kind === \"not-found\") {\n res.status(404).send(\"File not found\");\n return;\n }\n const version = resolution.version;\n\n const resolved = await cdn.getFileByVersion(packageName, version, filePath);\n if (!resolved) {\n // Pinned requests skip the metadata lookup above, so a miss here may be\n // an upstream failure rather than a genuine 404 — probe to distinguish,\n // otherwise the CDN would cache a 404 while upstream is merely down.\n if (pinned) {\n try {\n await cdn.resolveVersion(packageName, tag);\n } catch {\n res.status(503).send(\"Upstream registry unavailable\");\n return;\n }\n }\n res.status(404).send(\"File not found\");\n return;\n }\n\n // Cache based on the request shape: pinned requests are immutable, moving\n // ones (dist-tag / untagged) must revalidate frequently.\n res.setHeader(\n \"Cache-Control\",\n pinned\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=60, must-revalidate\",\n );\n\n // Hash the file path: it comes from the URL and may contain characters\n // that are invalid in header values.\n const fileHash = crypto\n .createHash(\"sha1\")\n .update(filePath)\n .digest(\"hex\")\n .slice(0, 16);\n const etag = `W/\"${version}-${fileHash}\"`;\n res.setHeader(\"ETag\", etag);\n if (etagMatches(req.headers[\"if-none-match\"], etag)) {\n res.status(304).end();\n return;\n }\n\n res.setHeader(\"Content-Type\", getContentType(filePath));\n try {\n await pipeline(fs.createReadStream(resolved), res);\n } catch {\n // Stream failure (I/O error, client abort) after headers may already\n // be sent — destroy the socket so the request doesn't hang.\n res.destroy();\n }\n });\n\n return router;\n}\n\n/**\n * Parse verdaccio's unpublish URL shape:\n * DELETE /<pkg>/-rev/<rev> → full package\n * DELETE /<pkg>/-/<tarball-name>/-rev/<rev> → single version\n * where <pkg> may be scoped (@scope%2Fname, encoded) or unscoped, and the\n * tarball name is `<short-name>-<version>.tgz`.\n */\nexport function parseUnpublishRequest(\n reqPath: string,\n): { packageName: string; version: string | null } | null {\n const revIdx = reqPath.indexOf(\"/-rev/\");\n if (revIdx <= 0) return null;\n const beforeRev = reqPath.slice(1, revIdx); // strip leading slash\n\n const tarballMarker = \"/-/\";\n const tarballIdx = beforeRev.indexOf(tarballMarker);\n if (tarballIdx === -1) {\n // Full package: beforeRev is the package name (possibly URL-encoded scope)\n const packageName = decodeURIComponent(beforeRev);\n return { packageName, version: null };\n }\n\n const packageName = decodeURIComponent(beforeRev.slice(0, tarballIdx));\n const tarballName = beforeRev.slice(tarballIdx + tarballMarker.length);\n if (!tarballName.endsWith(\".tgz\")) return null;\n const shortName = packageName.startsWith(\"@\")\n ? packageName.split(\"/\")[1]\n : packageName;\n const prefix = `${shortName}-`;\n if (!tarballName.startsWith(prefix)) return null;\n const version = tarballName.slice(prefix.length, -\".tgz\".length);\n if (!version) return null;\n return { packageName, version };\n}\n\n// PUT /<pkg>/-rev/<rev> is npm's manifest rewrite (single-version unpublish,\n// deprecate). Exclude the tarball-DELETE shape, which also carries /-rev/.\nexport function parseManifestRewrite(\n reqPath: string,\n): { packageName: string } | null {\n const revIdx = reqPath.indexOf(\"/-rev/\");\n if (revIdx <= 0) return null;\n const beforeRev = reqPath.slice(1, revIdx);\n if (beforeRev.includes(\"/-/\")) return null;\n return { packageName: decodeURIComponent(beforeRev) };\n}\n\nexport function createUnpublishHook(\n config: RegistryConfig,\n notifications: NotificationChannel,\n) {\n const cdn = new CdnCache(\n `http://localhost:${config.port}`,\n config.cdnCachePath,\n );\n\n // Reconcile the CDN cache after verdaccio rewrites a manifest to drop a\n // version. Re-fetch survivors — req.body isn't reliable on this route.\n const handleManifestRewrite = (req: Request, res: Response) => {\n const rewrite = parseManifestRewrite(req.path);\n if (!rewrite) return;\n\n const originalEnd = res.end.bind(res);\n res.end = function (\n this: Response,\n chunk?: unknown,\n encoding?: unknown,\n cb?: () => void,\n ) {\n if (res.statusCode >= 200 && res.statusCode < 300) {\n const publishedBy = publisherFromRequest(req);\n cdn\n .reconcileWithRegistry(rewrite.packageName)\n .then((removed) => {\n for (const version of removed) {\n notifications.notifyUnpublish({\n packageName: rewrite.packageName,\n version,\n publishedBy,\n });\n }\n })\n .catch((err) => {\n console.error(\n `[registry] CDN reconcile failed for ${rewrite.packageName}:`,\n err,\n );\n });\n }\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n };\n };\n\n return (req: Request, res: Response, next: NextFunction) => {\n if (req.method === \"PUT\") {\n handleManifestRewrite(req, res);\n next();\n return;\n }\n if (req.method !== \"DELETE\") {\n next();\n return;\n }\n\n const parsed = parseUnpublishRequest(req.path);\n if (!parsed) {\n next();\n return;\n }\n\n const originalEnd = res.end.bind(res);\n res.end = function (\n this: Response,\n chunk?: unknown,\n encoding?: unknown,\n cb?: () => void,\n ) {\n if (res.statusCode >= 200 && res.statusCode < 300) {\n try {\n if (parsed.version) {\n cdn.invalidateVersion(parsed.packageName, parsed.version);\n } else {\n cdn.invalidate(parsed.packageName);\n }\n notifications.notifyUnpublish({\n packageName: parsed.packageName,\n version: parsed.version,\n publishedBy: publisherFromRequest(req),\n });\n } catch (err) {\n console.error(\n `[registry] CDN purge failed for ${parsed.packageName}${parsed.version ? `@${parsed.version}` : \"\"}:`,\n err,\n );\n }\n }\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n };\n\n next();\n };\n}\n\nexport function createPublishHook(\n config: RegistryConfig,\n notifications: NotificationChannel,\n) {\n const cdn = new CdnCache(\n `http://localhost:${config.port}`,\n config.cdnCachePath,\n );\n\n return (req: Request, res: Response, next: NextFunction) => {\n // Only intercept PUT requests to npm publish endpoints.\n // Skip PUTs to `/<pkg>/-rev/<rev>` — those are npm's manifest-rewrite\n // step during single-version unpublish, not a new publish.\n if (req.method !== \"PUT\" || req.path.includes(\"/-rev/\")) {\n next();\n return;\n }\n\n const originalEnd = res.end.bind(res);\n res.end = function (\n this: Response,\n chunk?: unknown,\n encoding?: unknown,\n cb?: () => void,\n ) {\n const urlPath = req.path.replace(/^\\//, \"\");\n if (\n res.statusCode < 200 ||\n res.statusCode >= 300 ||\n !urlPath ||\n urlPath.startsWith(\"-\")\n ) {\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n }\n const packageName = decodeURIComponent(urlPath);\n const versionsObj = (req.body as { versions: Record<string, unknown> })\n .versions;\n const versions = Object.keys(versionsObj);\n const version = versions.at(0);\n if (!version) {\n console.error(`[registry] No version found for ${packageName}`);\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n }\n if (versions.length > 1) {\n console.warn(\n `[registry] Multiple versions published for ${packageName}: ${JSON.stringify(versions)}`,\n );\n }\n\n const publishedBy = publisherFromRequest(req);\n cdn\n .extractTarball(packageName, version)\n .then(() => {\n notifications.notifyPublish({ packageName, version, publishedBy });\n })\n .catch((err) => {\n console.error(\n `[registry] Failed to extract ${packageName} to CDN cache:`,\n err,\n );\n });\n\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n };\n\n next();\n };\n}\n","import type {\n NotificationChannel,\n PublishEvent,\n UnpublishEvent,\n} from \"./types.js\";\n\nexport class NotificationManager implements NotificationChannel {\n #channels: NotificationChannel[];\n\n constructor(channels: NotificationChannel[]) {\n this.#channels = channels;\n }\n\n notifyPublish(event: PublishEvent): void {\n for (const channel of this.#channels) {\n channel.notifyPublish(event);\n }\n }\n\n notifyUnpublish(event: UnpublishEvent): void {\n for (const channel of this.#channels) {\n channel.notifyUnpublish(event);\n }\n }\n}\n","import type { Response } from \"express\";\nimport type {\n NotificationChannel,\n PublishEvent,\n UnpublishEvent,\n} from \"./types.js\";\n\nexport class SSEChannel implements NotificationChannel {\n #clients = new Set<Response>();\n\n addClient(res: Response): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.write(\"event: connected\\ndata: {}\\n\\n\");\n\n this.#clients.add(res);\n res.on(\"close\", () => {\n this.#clients.delete(res);\n });\n }\n\n notifyPublish(event: PublishEvent): void {\n this.#broadcast(\"publish\", event);\n }\n\n notifyUnpublish(event: UnpublishEvent): void {\n this.#broadcast(\"unpublish\", event);\n }\n\n #broadcast(eventName: string, event: PublishEvent | UnpublishEvent): void {\n const payload = `event: ${eventName}\\ndata: ${JSON.stringify(event)}\\n\\n`;\n for (const client of this.#clients) {\n try {\n client.write(payload);\n } catch (err) {\n console.error(\"[registry] SSE client write failed:\", err);\n this.#clients.delete(client);\n }\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { NotifyConfig, WebhookConfig } from \"../types.js\";\nimport type {\n NotificationChannel,\n PublishEvent,\n UnpublishEvent,\n} from \"./types.js\";\n\nconst WEBHOOKS_FILE = \"webhooks.json\";\n\nexport class WebhookChannel implements NotificationChannel {\n #predefined: WebhookConfig[];\n #dynamic: WebhookConfig[];\n #storagePath: string;\n\n constructor(storagePath: string, config?: NotifyConfig) {\n this.#storagePath = storagePath;\n this.#predefined = config?.webhooks ?? [];\n this.#dynamic = this.#load();\n }\n\n getWebhooks(): WebhookConfig[] {\n return [...this.#predefined, ...this.#dynamic];\n }\n\n addWebhook(webhook: WebhookConfig): void {\n const exists = this.getWebhooks().some(\n (w) => w.endpoint === webhook.endpoint,\n );\n if (exists) return;\n this.#dynamic.push(webhook);\n this.#save();\n }\n\n removeWebhook(endpoint: string): boolean {\n const before = this.#dynamic.length;\n this.#dynamic = this.#dynamic.filter((w) => w.endpoint !== endpoint);\n if (this.#dynamic.length === before) return false;\n this.#save();\n return true;\n }\n\n notifyPublish(event: PublishEvent): void {\n this.#post({ type: \"publish\", ...event });\n }\n\n notifyUnpublish(event: UnpublishEvent): void {\n this.#post({ type: \"unpublish\", ...event });\n }\n\n #post(body: Record<string, unknown>): void {\n for (const webhook of this.getWebhooks()) {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...webhook.headers,\n };\n\n fetch(webhook.endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n }).catch((err: unknown) => {\n console.error(`[registry] Webhook to ${webhook.endpoint} failed:`, err);\n });\n }\n }\n\n #filePath(): string {\n return path.join(this.#storagePath, WEBHOOKS_FILE);\n }\n\n #load(): WebhookConfig[] {\n try {\n const raw = fs.readFileSync(this.#filePath(), \"utf-8\");\n return JSON.parse(raw) as WebhookConfig[];\n } catch {\n return [];\n }\n }\n\n #save(): void {\n fs.mkdirSync(this.#storagePath, { recursive: true });\n fs.writeFileSync(this.#filePath(), JSON.stringify(this.#dynamic, null, 2));\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AuthStore } from \"./auth/auth-store.js\";\nimport { stashAuthStore } from \"./auth/store-handoff.js\";\nimport type { RegistryConfig } from \"./types.js\";\n\nexport function buildVerdaccioConfig(config: RegistryConfig) {\n const htpasswdPath = path.join(config.storagePath, \"htpasswd\");\n\n const uplinkUrl = config.uplink ?? \"https://registry.npmjs.org/\";\n\n // With a database configured, use the Postgres-backed auth plugin\n // (persistent accounts + npm-style package ownership). Verdaccio loads it\n // via require() from the dist/plugins dir. Without a DB (local dev / tests),\n // keep the built-in htpasswd path.\n const usePgAuth = Boolean(config.databaseUrl || config.authStore);\n // Prefer a runtime-provided token (so the launcher owns it for load\n // detection); otherwise stash the injected store here (direct callers/tests).\n const storeToken =\n config.authStoreToken ??\n (config.authStore\n ? stashAuthStore(config.authStore as AuthStore)\n : undefined);\n const pluginsDir =\n config.pluginsDir ??\n path.join(path.dirname(fileURLToPath(import.meta.url)), \"plugins\");\n const auth = usePgAuth\n ? {\n \"registry-auth\": {\n ...(config.databaseUrl ? { databaseUrl: config.databaseUrl } : {}),\n ...(storeToken ? { storeToken } : {}),\n ...(config.renown\n ? {\n publicUrl: config.renown.publicUrl,\n ...(config.renown.renownUrl\n ? { renownUrl: config.renown.renownUrl }\n : {}),\n }\n : {}),\n },\n }\n : { htpasswd: { file: htpasswdPath } };\n\n const base: Record<string, unknown> = {\n storage: config.storagePath,\n self_path: \"./\",\n // Top-level secret used by verdaccio to sign / verify its API JWTs.\n // The renown middleware mints a verdaccio-format JWT with the same\n // secret so verdaccio's apiJWTmiddleware accepts the swapped token.\n ...(config.verdaccioSecret ? { secret: config.verdaccioSecret } : {}),\n // Force JWT mode for the npm API. Without this verdaccio falls back to\n // its legacy aes-encrypted token format, which signPayload won't produce.\n security: {\n api: {\n jwt: {\n sign: { expiresIn: \"90d\" },\n verify: {},\n },\n },\n },\n auth,\n ...(usePgAuth ? { plugins: pluginsDir } : {}),\n uplinks: {\n npmjs: {\n url: uplinkUrl,\n // Defaults to verdaccio's own default of 2m. The previous 15m\n // hardcoded value made publish-to-install dev loops painful —\n // when a newly-published version landed on npmjs, our registry\n // kept handing out the pre-publish packument for up to 15min.\n // Operators that want heavier upstream caching for production\n // can opt in via --uplink-maxage / PH_REGISTRY_UPLINK_MAXAGE.\n maxage: config.uplinkMaxage ?? \"2m\",\n timeout: \"30s\",\n cache: true,\n },\n },\n // Verdaccio matches packages config top-to-bottom (first match wins),\n // so local-only globs must come first. We also skip emitting the\n // default proxied entries when a glob with the same key is in the\n // local-only list — otherwise the value would be overwritten but the\n // iteration position would still be the default's earlier slot.\n packages: (() => {\n const local = config.localPackagePatterns ?? [];\n const localSet = new Set(local);\n const access = {\n access: \"$all\",\n publish: \"$authenticated\",\n unpublish: \"$authenticated\",\n };\n const entries: [string, Record<string, unknown>][] = [];\n // Locals first — no proxy means verdaccio resolves them from local\n // storage only, so re-publishing a version that exists on npmjs\n // doesn't 409.\n for (const pattern of local) {\n entries.push([pattern, { ...access }]);\n }\n // Defaults follow, skipping any glob the caller already overrode.\n if (!localSet.has(\"@powerhousedao/*\")) {\n entries.push([\"@powerhousedao/*\", { ...access, proxy: \"npmjs\" }]);\n }\n if (!localSet.has(\"**\")) {\n entries.push([\"**\", { ...access, proxy: \"npmjs\" }]);\n }\n return Object.fromEntries(entries);\n })(),\n web: {\n enable: config.webEnabled !== false,\n title: \"Powerhouse Registry\",\n logo: \"https://raw.githubusercontent.com/powerhouse-inc/powerhouse/main/packages/registry/static/logo.svg\",\n favicon: \"/-/static/favicon.ico\",\n primary_color: \"#38C780\",\n darkMode: true,\n },\n server: {\n keepAliveTimeout: 60,\n },\n log: {\n type: \"stdout\",\n format: \"pretty\",\n level: \"warn\",\n },\n max_body_size: config.maxBodySize ?? \"300mb\",\n };\n\n if (config.s3) {\n base.store = {\n \"aws-s3-storage\": {\n bucket: config.s3.bucket,\n endpoint: config.s3.endpoint,\n region: config.s3.region,\n s3ForcePathStyle: config.s3.s3ForcePathStyle ?? true,\n ...(config.s3.keyPrefix && { keyPrefix: config.s3.keyPrefix }),\n ...(config.s3.accessKeyId && { accessKeyId: config.s3.accessKeyId }),\n ...(config.s3.secretAccessKey && {\n secretAccessKey: config.s3.secretAccessKey,\n }),\n },\n };\n }\n\n return base;\n}\n","import express from \"express\";\nimport { findUp } from \"find-up\";\nimport { randomBytes } from \"node:crypto\";\nimport { mkdir } from \"node:fs/promises\";\nimport type { Server } from \"node:http\";\nimport path from \"node:path\";\nimport { runServer } from \"verdaccio\";\nimport type { AuthStore } from \"./auth/auth-store.js\";\nimport { createPgPool, createPgStore } from \"./auth/pg-store.js\";\nimport { stashAuthStore, wasStoreLoaded } from \"./auth/store-handoff.js\";\nimport {\n createPowerhouseRouter,\n createPublishHook,\n createUnpublishHook,\n} from \"./middleware.js\";\nimport { NotificationManager } from \"./notifications/manager.js\";\nimport { SSEChannel } from \"./notifications/sse.js\";\nimport { WebhookChannel } from \"./notifications/webhook.js\";\nimport type { RegistryCommandArgs, RegistryConfig } from \"./types.js\";\nimport { buildVerdaccioConfig } from \"./verdaccio-config.js\";\n\nasync function resolveDir(dir: string): Promise<string> {\n if (path.isAbsolute(dir)) {\n await mkdir(dir, { recursive: true });\n return dir;\n }\n const found = await findUp(dir, { type: \"directory\" });\n if (!found) {\n await mkdir(dir, { recursive: true });\n return dir;\n }\n return found;\n}\n\nexport async function runRegistry(args: RegistryCommandArgs) {\n const {\n port,\n storageDir,\n cdnCacheDir,\n uplink,\n uplinkMaxage,\n webEnabled,\n webhooks,\n s3AccessKeyId,\n s3Bucket,\n s3Endpoint,\n s3ForcePathStyle,\n s3KeyPrefix,\n s3Region,\n s3SecretAccessKey,\n publicUrl,\n authRenown,\n renownUrl,\n verdaccioSecret: verdaccioSecretArg,\n localPackages,\n databaseUrl,\n pluginsDir,\n authStore,\n } = args;\n const storagePath = await resolveDir(storageDir);\n const cdnCachePath = await resolveDir(cdnCacheDir);\n\n // Per-pod random verdaccio JWT secret. The verdaccio-format token we mint\n // in the renown middleware never leaves the pod (it's swapped into the\n // request before verdaccio sees it), so a per-pod secret is sufficient.\n // An override is exposed for tests / multi-pod behaviors that depend on\n // shared verdaccio JWTs.\n const verdaccioSecret = verdaccioSecretArg ?? randomBytes(32).toString(\"hex\");\n\n // Renown auth turns on when the operator both opts in (`--auth-renown`,\n // default true via the CLI flag) and has set --public-url for the audience\n // claim. Tests / programmatic users that don't pass either keep the legacy\n // unsigned/htpasswd path with no warning.\n const renownEnabled = authRenown === true && Boolean(publicUrl);\n if (authRenown === true && !publicUrl) {\n console.warn(\n \"[registry] auth-renown is enabled but --public-url / PH_REGISTRY_PUBLIC_URL is not set; Renown auth will be disabled.\",\n );\n }\n // Renown auth is served by the registry-auth plugin, which loads only with a\n // database (it also holds ownership). Without one, renown can't engage.\n if (renownEnabled && !databaseUrl && !authStore) {\n console.warn(\n \"[registry] Renown auth requires a database (--database-url) for the auth plugin; renown will be inactive.\",\n );\n }\n\n console.log({\n storagePath,\n cdnCachePath,\n });\n\n const webhookConfigs = webhooks\n ?.split(\",\")\n .map((url) => url.trim())\n .filter(Boolean)\n .map((endpoint) => ({ endpoint }));\n\n const localPackagePatterns = localPackages\n ?.split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n\n // One AuthStore, shared by the verdaccio auth plugin and the /packages owner\n // enrichment — a single Postgres pool per process (injected store wins).\n const sharedAuthStore: AuthStore | undefined =\n (authStore as AuthStore | undefined) ??\n (databaseUrl ? createPgStore(createPgPool(databaseUrl)) : undefined);\n // Token carries the store through verdaccio's plugin config; we later assert\n // the plugin loaded it, so a configured-but-broken auth setup fails to boot.\n const authStoreToken = sharedAuthStore\n ? stashAuthStore(sharedAuthStore)\n : undefined;\n\n const config: RegistryConfig = {\n port,\n storagePath,\n cdnCachePath,\n uplink,\n uplinkMaxage,\n webEnabled,\n verdaccioSecret,\n ...(localPackagePatterns?.length ? { localPackagePatterns } : {}),\n ...(renownEnabled && publicUrl\n ? { renown: { publicUrl, ...(renownUrl ? { renownUrl } : {}) } }\n : {}),\n ...(webhookConfigs?.length && {\n notify: { webhooks: webhookConfigs },\n }),\n ...(s3Bucket &&\n s3Endpoint &&\n s3Region && {\n s3: {\n bucket: s3Bucket,\n endpoint: s3Endpoint,\n region: s3Region,\n accessKeyId: s3AccessKeyId,\n secretAccessKey: s3SecretAccessKey,\n keyPrefix: s3KeyPrefix,\n s3ForcePathStyle,\n },\n }),\n ...(databaseUrl ? { databaseUrl } : {}),\n ...(pluginsDir ? { pluginsDir } : {}),\n ...(sharedAuthStore ? { authStore: sharedAuthStore } : {}),\n ...(authStoreToken ? { authStoreToken } : {}),\n };\n\n if (config.databaseUrl || config.authStore) {\n console.log(\n \"[registry] Postgres-backed auth plugin active (persistent accounts + package ownership)\",\n );\n }\n // Ensure directories exist (for relative paths resolved via findUp)\n await mkdir(storagePath, { recursive: true });\n await mkdir(cdnCachePath, { recursive: true });\n\n const verdaccioConfig = buildVerdaccioConfig(config);\n\n // verdaccio's runServer returns Promise<any> (upstream type limitation)\n const verdaccioServer = (await runServer(verdaccioConfig)) as Server;\n\n // Fail fast: a configured auth store that the plugin never loaded means\n // verdaccio silently fell back to no auth — refuse to run without ownership.\n if (authStoreToken && !wasStoreLoaded(authStoreToken)) {\n verdaccioServer.close();\n throw new Error(\n \"registry-auth plugin failed to load despite a configured database/auth store; refusing to start without auth and package-ownership enforcement.\",\n );\n }\n const verdaccioHandler = verdaccioServer.listeners(\"request\")[0] as (\n ...args: unknown[]\n ) => void;\n\n const app = express();\n\n const sseChannel = new SSEChannel();\n const webhookChannel = new WebhookChannel(config.storagePath, config.notify);\n const notifications = new NotificationManager([sseChannel, webhookChannel]);\n\n // Serve static assets (logo, etc.)\n const staticDir = await findUp(\"static\", { type: \"directory\" });\n if (staticDir) {\n app.use(\"/-/static\", express.static(staticDir));\n }\n\n // Our routes take priority over Verdaccio\n app.use(\n createPowerhouseRouter(config, sseChannel, webhookChannel, sharedAuthStore),\n );\n\n app.use(createPublishHook(config, notifications));\n app.use(createUnpublishHook(config, notifications));\n\n // Verdaccio handles everything else (npm protocol, web UI, auth)\n app.use((req, res) => verdaccioHandler(req, res));\n\n const server = app.listen(port, () => {\n console.log(`Powerhouse Registry running on http://localhost:${port}`);\n console.log(` CDN: http://localhost:${port}/-/cdn/`);\n console.log(` Packages: http://localhost:${port}/packages`);\n console.log(` npm: http://localhost:${port}/`);\n console.log(` Storage: ${storagePath}`);\n console.log(` CDN cache: ${cdnCachePath}`);\n if (config.s3) {\n console.log(` S3: ${config.s3.endpoint}/${config.s3.bucket}`);\n }\n if (config.renown) {\n console.log(` Renown auth: ${config.renown.publicUrl}`);\n }\n });\n\n return server;\n}\n","import {\n binary,\n command,\n flag,\n number,\n option,\n optional,\n run,\n string,\n} from \"cmd-ts\";\nimport {\n DEFAULT_PORT,\n DEFAULT_REGISTRY_CDN_CACHE_DIR_NAME,\n DEFAULT_STORAGE_DIR_NAME,\n} from \"./src/constants.js\";\nimport { runRegistry } from \"./src/run.js\";\n\nexport const registryCommand = command({\n name: \"Package registry\",\n args: {\n port: option({\n long: \"port\",\n type: number,\n defaultValue: () => Number(process.env.PORT) || DEFAULT_PORT,\n defaultValueIsSerializable: true,\n }),\n storageDir: option({\n long: \"storage-dir\",\n type: string,\n defaultValue: () =>\n process.env.REGISTRY_STORAGE || DEFAULT_STORAGE_DIR_NAME,\n defaultValueIsSerializable: true,\n }),\n cdnCacheDir: option({\n long: \"cdn-cache-dir\",\n type: string,\n defaultValue: () =>\n process.env.REGISTRY_CDN_CACHE || DEFAULT_REGISTRY_CDN_CACHE_DIR_NAME,\n defaultValueIsSerializable: true,\n }),\n uplink: option({\n long: \"uplink\",\n type: optional(string),\n defaultValue: () => process.env.REGISTRY_UPLINK,\n defaultValueIsSerializable: true,\n }),\n uplinkMaxage: option({\n long: \"uplink-maxage\",\n type: optional(string),\n description:\n \"How long verdaccio caches npmjs uplink metadata before refetching. \" +\n \"Accepts verdaccio time strings (e.g. '30s', '2m', '1h'). \" +\n \"Default '2m' matches verdaccio upstream — shortens the publish-to-\" +\n \"install propagation window in dev. Bump for production deployments \" +\n \"that want to reduce npmjs load.\",\n defaultValue: () => process.env.PH_REGISTRY_UPLINK_MAXAGE,\n defaultValueIsSerializable: true,\n }),\n s3Bucket: option({\n long: \"s3-bucket\",\n type: optional(string),\n defaultValue: () => process.env.S3_BUCKET,\n defaultValueIsSerializable: true,\n }),\n s3Endpoint: option({\n long: \"s3-endpoint\",\n type: optional(string),\n defaultValue: () => process.env.S3_ENDPOINT,\n defaultValueIsSerializable: true,\n }),\n s3Region: option({\n long: \"s3-region\",\n type: optional(string),\n defaultValue: () => process.env.S3_REGION,\n defaultValueIsSerializable: true,\n }),\n s3AccessKeyId: option({\n long: \"s3-access-key-id\",\n type: optional(string),\n defaultValue: () => process.env.S3_ACCESS_KEY_ID,\n defaultValueIsSerializable: true,\n }),\n s3SecretAccessKey: option({\n long: \"s3-secret-access-key\",\n type: optional(string),\n defaultValue: () => process.env.S3_SECRET_ACCESS_KEY,\n defaultValueIsSerializable: true,\n }),\n s3KeyPrefix: option({\n long: \"s3-key-prefix\",\n type: optional(string),\n defaultValue: () => process.env.S3_KEY_PREFIX,\n defaultValueIsSerializable: true,\n }),\n s3ForcePathStyle: flag({\n long: \"s3-force-path-style\",\n defaultValue: () => process.env.S3_FORCE_PATH_STYLE !== \"false\",\n defaultValueIsSerializable: true,\n }),\n webEnabled: flag({\n long: \"web-enabled\",\n defaultValue: () => process.env.REGISTRY_WEB !== \"false\",\n defaultValueIsSerializable: true,\n }),\n webhooks: option({\n long: \"webhook\",\n type: optional(string),\n description: \"Comma-separated webhook URLs to notify on publish\",\n defaultValue: () => process.env.REGISTRY_WEBHOOKS,\n defaultValueIsSerializable: true,\n }),\n publicUrl: option({\n long: \"public-url\",\n type: optional(string),\n description:\n \"Public origin of this registry (used as the JWT `aud` claim for Renown bearer tokens). Required when --auth-renown is true.\",\n defaultValue: () => process.env.PH_REGISTRY_PUBLIC_URL,\n defaultValueIsSerializable: true,\n }),\n authRenown: flag({\n long: \"auth-renown\",\n description:\n \"Verify Renown-signed bearer tokens in front of verdaccio (stateless). Disabled when --public-url is unset.\",\n defaultValue: () => process.env.PH_REGISTRY_AUTH_RENOWN === \"true\",\n defaultValueIsSerializable: true,\n }),\n renownUrl: option({\n long: \"renown-url\",\n type: optional(string),\n description:\n \"Renown service base URL for credential verification. Defaults to https://www.renown.id.\",\n defaultValue: () => process.env.PH_REGISTRY_RENOWN_URL,\n defaultValueIsSerializable: true,\n }),\n verdaccioSecret: option({\n long: \"verdaccio-secret\",\n type: optional(string),\n description:\n \"Override verdaccio's internal JWT signing secret. Default: random per pod (fine — the swapped JWT never leaves this process).\",\n defaultValue: () => process.env.PH_REGISTRY_VERDACCIO_SECRET,\n defaultValueIsSerializable: true,\n }),\n localPackages: option({\n long: \"local-packages\",\n type: optional(string),\n description:\n \"Comma-separated globs (e.g. '@powerhousedao/*,document-model,ph-cmd') served locally only — no npmjs uplink proxy. Lets you re-publish a workspace package whose version already exists on npmjs without bumping.\",\n defaultValue: () => process.env.PH_REGISTRY_LOCAL_PACKAGES,\n defaultValueIsSerializable: true,\n }),\n databaseUrl: option({\n long: \"database-url\",\n type: optional(string),\n description:\n \"Postgres connection string. When set, the registry uses the DB-backed auth plugin (persistent accounts + npm-style package ownership) instead of the built-in htpasswd.\",\n defaultValue: () =>\n process.env.PH_REGISTRY_DATABASE_URL ?? process.env.DATABASE_URL,\n defaultValueIsSerializable: true,\n }),\n },\n handler: async (args) => {\n // Redact secrets — this object otherwise leaks the DB password / signing\n // secret into logs.\n const redact = (v?: string) => (v ? \"[redacted]\" : undefined);\n console.log({\n ...args,\n databaseUrl: redact(args.databaseUrl),\n verdaccioSecret: redact(args.verdaccioSecret),\n s3SecretAccessKey: redact(args.s3SecretAccessKey),\n });\n\n try {\n await runRegistry(args);\n } catch (error) {\n console.error(\"Failed to start registry:\");\n console.error(error);\n process.exit(1);\n }\n },\n});\n\nconst registryCli = binary(registryCommand);\n\nawait run(registryCli, process.argv);\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["#resolveFile","#extractWithLock","#extractionLocks","gunzip","gunzipCb","#channels","#clients","#broadcast","#storagePath","#predefined","#dynamic","#load","#save","#post","#filePath"],"sources":["../src/constants.ts","../src/auth/pg-store.ts","../src/auth/store-handoff.ts","../src/semver.ts","../src/cdn.ts","../src/packages.ts","../src/pieces.ts","../src/warmup.ts","../src/middleware.ts","../src/notifications/manager.ts","../src/notifications/sse.ts","../src/notifications/webhook.ts","../src/verdaccio-config.ts","../src/run.ts","../cli.ts"],"sourcesContent":["export const DEFAULT_PORT = 8080;\nexport const DEFAULT_STORAGE_DIR_NAME = \"./storage\" as const;\nexport const DEFAULT_REGISTRY_CDN_CACHE_DIR_NAME = \"./cdn-cache\" as const;\n","import { Pool } from \"pg\";\nimport type { AuthStore, UserRecord } from \"./auth-store.js\";\n\n/** Build a real Postgres pool from a connection string. */\nexport function createPgPool(databaseUrl: string): Pool {\n return new Pool({ connectionString: databaseUrl });\n}\n\n/**\n * Postgres-backed AuthStore. Two small tables:\n * - registry_users(username PK, password_hash, created_at)\n * - registry_package_owners(package_name PK, owners text[], claimed_at)\n *\n * Ownership claim is race-free: `INSERT ... ON CONFLICT DO NOTHING` means the\n * first publisher wins atomically; the follow-up read returns the actual\n * owners so a losing racer is denied.\n *\n * Takes an already-built `pg.Pool` (tests inject a pg-mem pool cast to Pool).\n */\nexport function createPgStore(pool: Pool): AuthStore {\n let initialized: Promise<void> | null = null;\n\n return {\n init(): Promise<void> {\n initialized ??= (async () => {\n await pool.query(`\n CREATE TABLE IF NOT EXISTS registry_users (\n username text PRIMARY KEY,\n password_hash text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n )`);\n await pool.query(`\n CREATE TABLE IF NOT EXISTS registry_package_owners (\n package_name text PRIMARY KEY,\n owners text[] NOT NULL,\n claimed_at timestamptz NOT NULL DEFAULT now()\n )`);\n })();\n return initialized;\n },\n\n async getUser(username: string): Promise<UserRecord | null> {\n const res = await pool.query<{ password_hash: string }>(\n \"SELECT password_hash FROM registry_users WHERE username = $1\",\n [username],\n );\n const row = res.rows[0] as { password_hash: string } | undefined;\n return row ? { passwordHash: row.password_hash } : null;\n },\n\n async createUser(username: string, passwordHash: string): Promise<boolean> {\n // Atomic: the PRIMARY KEY enforces uniqueness. A duplicate raises a\n // unique-violation (SQLSTATE 23505) — catch it as \"already registered\".\n try {\n await pool.query(\n \"INSERT INTO registry_users (username, password_hash) VALUES ($1, $2)\",\n [username, passwordHash],\n );\n return true;\n } catch (err) {\n if ((err as { code?: string }).code === \"23505\") return false;\n throw err;\n }\n },\n\n async getOwners(pkg: string): Promise<string[] | null> {\n const res = await pool.query<{ owners: string[] }>(\n \"SELECT owners FROM registry_package_owners WHERE package_name = $1\",\n [pkg],\n );\n return res.rows[0]?.owners ?? null;\n },\n\n async getOwnersFor(pkgs: string[]): Promise<Record<string, string[]>> {\n if (pkgs.length === 0) return {};\n // IN (...) with per-value placeholders — portable across pg and pg-mem,\n // unlike `= ANY($1)` array binding.\n const placeholders = pkgs.map((_, i) => `$${i + 1}`).join(\",\");\n const res = await pool.query<{ package_name: string; owners: string[] }>(\n `SELECT package_name, owners FROM registry_package_owners WHERE package_name IN (${placeholders})`,\n pkgs,\n );\n const out: Record<string, string[]> = {};\n for (const row of res.rows) out[row.package_name] = row.owners;\n return out;\n },\n\n async claimOwner(pkg: string, username: string): Promise<string[]> {\n // First publisher wins atomically; then read the actual owners.\n await pool.query(\n `INSERT INTO registry_package_owners (package_name, owners)\n VALUES ($1, ARRAY[$2]::text[])\n ON CONFLICT (package_name) DO NOTHING`,\n [pkg, username],\n );\n const res = await pool.query<{ owners: string[] }>(\n \"SELECT owners FROM registry_package_owners WHERE package_name = $1\",\n [pkg],\n );\n return res.rows[0]?.owners ?? [];\n },\n\n close(): Promise<void> {\n return pool.end();\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { AuthStore } from \"./auth-store.js\";\n\n// Carry a live AuthStore to the plugin as a token (verdaccio merges the app\n// config's `store` block into plugin configs), tracking whether it loaded.\nconst REGISTRY_KEY = Symbol.for(\"@powerhousedao/registry:auth-store-handoff\");\n\ninterface Entry {\n store: AuthStore;\n loaded: boolean;\n}\n\nfunction registry(): Map<string, Entry> {\n const g = globalThis as { [REGISTRY_KEY]?: Map<string, Entry> };\n return (g[REGISTRY_KEY] ??= new Map<string, Entry>());\n}\n\n/** Stash a store instance and return a token to carry through plugin config. */\nexport function stashAuthStore(store: AuthStore): string {\n const token = randomUUID();\n registry().set(token, { store, loaded: false });\n return token;\n}\n\n/** Resolve a stashed store by token (undefined if unknown). */\nexport function takeAuthStore(token: string): AuthStore | undefined {\n return registry().get(token)?.store;\n}\n\n/** Record that the plugin fully constructed with this token's store. */\nexport function markStoreLoaded(token: string): void {\n const entry = registry().get(token);\n if (entry) entry.loaded = true;\n}\n\n/** True once the plugin has loaded the store for this token. */\nexport function wasStoreLoaded(token: string): boolean {\n return registry().get(token)?.loaded ?? false;\n}\n","/**\n * Compare two semver version strings for sorting.\n * Returns negative if a < b, positive if a > b, 0 if equal.\n *\n * Handles numeric component comparison (so \"1.0.10\" > \"1.0.9\")\n * and prerelease ordering (release > prerelease).\n */\nexport function compareSemver(a: string, b: string): number {\n const [coreA, preA] = a.split(\"-\", 2);\n const [coreB, preB] = b.split(\"-\", 2);\n\n const partsA = coreA.split(\".\").map(Number);\n const partsB = coreB.split(\".\").map(Number);\n\n for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {\n const na = partsA[i] ?? 0;\n const nb = partsB[i] ?? 0;\n if (na !== nb) return na - nb;\n }\n\n // Equal core versions — release (no prerelease) sorts after prerelease\n if (!preA && preB) return 1;\n if (preA && !preB) return -1;\n if (preA && preB) return preA < preB ? -1 : preA > preB ? 1 : 0;\n\n return 0;\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { extract } from \"tar\";\nimport { compareSemver } from \"./semver.js\";\n\n/**\n * Parse a package specifier into name and version/tag.\n * Supports:\n * \"@scope/pkg\" -> { name: \"@scope/pkg\", tag: undefined }\n * \"@scope/pkg@dev\" -> { name: \"@scope/pkg\", tag: \"dev\" }\n * \"@scope/pkg@1.0.0\" -> { name: \"@scope/pkg\", tag: \"1.0.0\" }\n * \"pkg@latest\" -> { name: \"pkg\", tag: \"latest\" }\n */\nexport function parsePackageSpec(spec: string): {\n name: string;\n tag: string | undefined;\n} {\n // For scoped packages (@scope/name@tag), split on the last @\n // For unscoped packages (name@tag), split on the first @\n if (spec.startsWith(\"@\")) {\n // Scoped: find the @ after the scope/name portion\n const lastAt = spec.lastIndexOf(\"@\");\n if (lastAt > 0 && lastAt !== spec.indexOf(\"@\")) {\n return { name: spec.slice(0, lastAt), tag: spec.slice(lastAt + 1) };\n }\n return { name: spec, tag: undefined };\n }\n const atIndex = spec.indexOf(\"@\");\n if (atIndex > 0) {\n return { name: spec.slice(0, atIndex), tag: spec.slice(atIndex + 1) };\n }\n return { name: spec, tag: undefined };\n}\n\n/** True when tag is a concrete semver, false for dist-tag names or undefined. */\nexport function isExactVersion(tag?: string): boolean {\n // Strict semver charset: the version flows into the ETag header, so\n // arbitrary characters after the prerelease/build separator must not match.\n return (\n !!tag &&\n /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/.test(tag)\n );\n}\n\nexport class CdnCache {\n #extractionLocks = new Map<string, Promise<void>>();\n\n constructor(\n private registryUrl: string,\n private cdnCachePath: string,\n ) {}\n\n async getFileByVersion(\n packageName: string,\n version: string,\n filePath: string,\n ): Promise<string | null> {\n const versionDir = path.join(this.cdnCachePath, packageName, version);\n\n // Check all possible paths before attempting extraction\n const resolved = this.#resolveFile(versionDir, filePath);\n if (resolved) return resolved;\n\n // File not found in any location — extract tarball and try again\n await this.#extractWithLock(packageName, version);\n\n return this.#resolveFile(versionDir, filePath);\n }\n\n #resolveFile(versionDir: string, filePath: string): string | null {\n // Check direct path first, then fall back to cdn/ and dist/cdn/ subdirectories\n // (npm tarballs contain files under dist/, bun bundles go to cdn/)\n const candidates = [\n path.join(versionDir, filePath),\n path.join(versionDir, \"cdn\", filePath),\n path.join(versionDir, \"dist\", \"cdn\", filePath),\n path.join(versionDir, \"dist\", filePath),\n ];\n\n for (const candidate of candidates) {\n if (this.isSafePath(candidate) && fs.existsSync(candidate))\n return candidate;\n }\n\n return null;\n }\n\n async #extractWithLock(packageName: string, version: string): Promise<void> {\n const key = `${packageName}@${version}`;\n const existing = this.#extractionLocks.get(key);\n if (existing) return existing;\n\n const promise = this.extractTarball(packageName, version).finally(() => {\n this.#extractionLocks.delete(key);\n });\n this.#extractionLocks.set(key, promise);\n return promise;\n }\n\n getLatestCachedVersion(packageName: string): string | null {\n const pkgDir = path.join(this.cdnCachePath, packageName);\n try {\n const entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n const versions = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n if (versions.length === 0) return null;\n versions.sort(compareSemver);\n return versions[versions.length - 1];\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve a version for a package. If tag is a semver version that exists\n * in the registry, return it directly. If tag is a dist-tag name (e.g.\n * \"dev\", \"latest\"), resolve it to the concrete version. If no tag is\n * provided, prefer \"latest\", then fall back to any available dist-tag.\n * Returns null only for genuine not-found (404, or absent tag/version).\n * Throws on network errors and non-OK responses other than 404.\n */\n async resolveVersion(\n packageName: string,\n tag?: string,\n ): Promise<string | null> {\n const url = `${this.registryUrl}/${encodeURIComponent(packageName)}`;\n const res = await fetch(url, {\n headers: { Accept: \"application/json\" },\n });\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `Upstream metadata for ${packageName} returned ${res.status}`,\n );\n }\n const metadata = (await res.json()) as Record<string, unknown>;\n const distTags = metadata[\"dist-tags\"] as\n | Record<string, string>\n | undefined;\n const versions = metadata[\"versions\"] as\n | Record<string, unknown>\n | undefined;\n\n if (tag) {\n // If the tag matches an exact version in the registry, use it directly\n if (versions && tag in versions) return tag;\n // Otherwise treat it as a dist-tag name\n if (distTags && tag in distTags) return distTags[tag];\n // Tag not found\n return null;\n }\n\n if (!distTags) return null;\n // No tag specified: prefer \"latest\", fall back to any available tag\n return distTags.latest ?? Object.values(distTags)[0] ?? null;\n }\n\n async extractTarball(packageName: string, version: string): Promise<void> {\n const destDir = path.join(this.cdnCachePath, packageName, version);\n\n // Idempotence guard. extractTarball is hot-path-called from\n // warmCdnCacheFromVerdaccio on every /packages request (which the\n // deployment's readiness probe hits every 5s). Without this skip the\n // tarball is re-fetched (multi-MB over S3) and re-extracted on each\n // call, pinning CPU at multi-vCPU per pod and triggering HPA spirals.\n // We treat the presence of package.json as the marker for \"already\n // extracted\" — it's the first file npm tarballs put under the version\n // directory and removing it (e.g. by invalidate*) requires the rest to\n // go too.\n if (fs.existsSync(path.join(destDir, \"package.json\"))) return;\n\n const shortName = packageName.startsWith(\"@\")\n ? packageName.split(\"/\")[1]\n : packageName;\n const tarballUrl = `${this.registryUrl}/${encodeURIComponent(packageName)}/-/${shortName}-${version}.tgz`;\n\n let res: Response;\n try {\n res = await fetch(tarballUrl);\n if (!res.ok || !res.body) return;\n } catch {\n return;\n }\n\n fs.mkdirSync(destDir, { recursive: true });\n\n const tmpFile = path.join(\n destDir,\n `.tmp-tarball-${crypto.randomUUID()}.tgz`,\n );\n try {\n const fileStream = fs.createWriteStream(tmpFile);\n await pipeline(Readable.fromWeb(res.body as never), fileStream);\n await extract({ file: tmpFile, cwd: destDir, strip: 1 });\n } finally {\n fs.rmSync(tmpFile, { force: true });\n }\n }\n\n invalidate(packageName: string): void {\n const cacheDir = path.join(this.cdnCachePath, packageName);\n if (!this.isSafePath(cacheDir)) return;\n fs.rmSync(cacheDir, { recursive: true, force: true });\n }\n\n invalidateVersion(packageName: string, version: string): void {\n const versionDir = path.join(this.cdnCachePath, packageName, version);\n if (!this.isSafePath(versionDir)) return;\n fs.rmSync(versionDir, { recursive: true, force: true });\n // If the package dir is now empty, remove it too so the scanner doesn't\n // keep returning a ghost entry with no versions.\n const pkgDir = path.join(this.cdnCachePath, packageName);\n try {\n if (fs.readdirSync(pkgDir).length === 0) {\n fs.rmdirSync(pkgDir);\n }\n } catch {\n // ignore — dir may not exist\n }\n }\n\n // Reconcile the cache against the registry's current version set; returns\n // removed versions. Empty/failed listings are a no-op (never mass-purge).\n async reconcileWithRegistry(packageName: string): Promise<string[]> {\n const url = `${this.registryUrl}/${encodeURIComponent(packageName)}`;\n let versions: string[];\n try {\n const res = await fetch(url, { headers: { Accept: \"application/json\" } });\n if (!res.ok) return [];\n const meta = (await res.json()) as { versions?: Record<string, unknown> };\n versions = Object.keys(meta.versions ?? {});\n } catch {\n return [];\n }\n if (versions.length === 0) return [];\n return this.reconcileVersions(packageName, versions);\n }\n\n // Drop cached version dirs absent from `keepVersions`; returns the removed\n // ones. Mirrors single-version unpublish (a manifest rewrite, no DELETE).\n reconcileVersions(\n packageName: string,\n keepVersions: Iterable<string>,\n ): string[] {\n const pkgDir = path.join(this.cdnCachePath, packageName);\n const keep = new Set(keepVersions);\n const removed: string[] = [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n } catch {\n return removed;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || keep.has(entry.name)) continue;\n this.invalidateVersion(packageName, entry.name);\n removed.push(entry.name);\n }\n return removed;\n }\n\n /** Remove all cached version directories except the specified one. */\n pruneOldVersions(packageName: string, keepVersion: string): void {\n const pkgDir = path.join(this.cdnCachePath, packageName);\n try {\n const entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name !== keepVersion) {\n const dir = path.join(pkgDir, entry.name);\n if (this.isSafePath(dir)) {\n fs.rmSync(dir, { recursive: true, force: true });\n }\n }\n }\n } catch {\n // ignore — directory may not exist yet\n }\n }\n\n private isSafePath(filePath: string): boolean {\n const resolved = path.resolve(filePath);\n const cacheRoot = path.resolve(this.cdnCachePath);\n return resolved.startsWith(cacheRoot + path.sep) || resolved === cacheRoot;\n }\n}\n","import type { Manifest } from \"@powerhousedao/shared\";\nimport { slimManifest } from \"@powerhousedao/shared/registry\";\nimport type { PackageListItem } from \"@powerhousedao/shared/registry\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { compareSemver } from \"./semver.js\";\nimport type { PackageInfo } from \"./types.js\";\n\n/**\n * Read dist-tags, the full version list, and the local-publish flag for a\n * package from verdaccio's on-disk storage (`{storagePath}/{name}/package.json`).\n *\n * `locallyPublished` is tri-state:\n * - `true` → storage metadata has `_attachments` (tarball uploaded here).\n * - `false` → storage metadata exists but `_attachments` is empty (proxy\n * from the npm uplink only; no local publish at this registry).\n * - `undefined` → metadata file wasn't readable. Happens with non-filesystem\n * backends (S3, etc.) or if verdaccio stores metadata elsewhere.\n * Callers should treat this as \"unknown\" and default to including\n * the package, to avoid filtering the whole /packages list to an\n * empty array on deployments where we can't observe _attachments.\n */\nfunction readPackageMetadata(\n storagePath: string | undefined,\n packageName: string,\n): {\n distTags?: Record<string, string>;\n versions?: string[];\n locallyPublished: boolean | undefined;\n} {\n if (!storagePath) return { locallyPublished: undefined };\n try {\n const metadataPath = path.join(storagePath, packageName, \"package.json\");\n const raw = fs.readFileSync(metadataPath, \"utf-8\");\n const parsed = JSON.parse(raw) as {\n \"dist-tags\"?: Record<string, string>;\n versions?: Record<string, unknown>;\n _attachments?: Record<string, unknown>;\n };\n const distTags = parsed[\"dist-tags\"];\n const rawVersions = parsed.versions ? Object.keys(parsed.versions) : [];\n const versions = rawVersions.slice().sort(compareSemver);\n const locallyPublished =\n !!parsed._attachments && Object.keys(parsed._attachments).length > 0;\n return {\n distTags:\n distTags && Object.keys(distTags).length > 0 ? distTags : undefined,\n versions: versions.length > 0 ? versions : undefined,\n locallyPublished,\n };\n } catch {\n return { locallyPublished: undefined };\n }\n}\n\n/**\n * Locally-published check for a package, from verdaccio storage metadata.\n * Returns the same tri-state as `readPackageMetadata.locallyPublished`:\n * `true` (has `_attachments`), `false` (proxy-only), `undefined` (unreadable).\n */\nexport function isLocallyPublished(\n storagePath: string | undefined,\n packageName: string,\n): boolean | undefined {\n return readPackageMetadata(storagePath, packageName).locallyPublished;\n}\n\nfunction readManifest(dir: string): Manifest | null {\n const candidates = [\n path.join(dir, \"powerhouse.manifest.json\"),\n path.join(dir, \"cdn\", \"powerhouse.manifest.json\"),\n path.join(dir, \"dist\", \"powerhouse.manifest.json\"),\n ];\n for (const manifestPath of candidates) {\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n // Manifests are publisher-supplied JSON; slim to the known summary\n // fields so one oversized publish can't bloat every /packages\n // listing (a single 7.8 MB `features` blob once pushed the response\n // past clients' localStorage quota). The raw file stays available\n // through the CDN path.\n return slimManifest(JSON.parse(raw) as Manifest);\n } catch {\n // try next candidate\n }\n }\n return null;\n}\n\nfunction readPackageJsonVersion(dir: string): string | undefined {\n try {\n const raw = fs.readFileSync(path.join(dir, \"package.json\"), \"utf-8\");\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" ? pkg.version : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction getLatestVersionDir(pkgDir: string): string | null {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(pkgDir, { withFileTypes: true });\n } catch {\n return null;\n }\n const versions = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n if (versions.length === 0) return null;\n versions.sort(compareSemver);\n return path.join(pkgDir, versions[versions.length - 1]);\n}\n\nexport function loadPackage(\n cdnCachePath: string,\n name: string,\n version?: string,\n storagePath?: string,\n): PackageInfo | null {\n const pkgDir = path.join(cdnCachePath, name);\n const versionDir = version\n ? path.join(pkgDir, version)\n : getLatestVersionDir(pkgDir);\n const manifestDir = versionDir ?? pkgDir;\n const manifest = readManifest(manifestDir);\n\n if (!manifest) {\n return null;\n }\n const resolvedName = manifest.name || name;\n // Include dist-tags/versions so the paginated UI can lazy-load version\n // metadata for a single package (the list DTO omits them). Falls back to\n // undefined when storage metadata isn't available.\n const { distTags, versions } = readPackageMetadata(storagePath, resolvedName);\n return {\n name: resolvedName,\n path: `/-/cdn/${name}`,\n manifest,\n documentTypes: getDocumentTypesFromManifest(manifest),\n version: readPackageJsonVersion(manifestDir),\n distTags,\n versions,\n };\n}\n\n/**\n * Project a full {@link PackageInfo} down to the trimmed shape the paginated\n * package-listing UI renders per row. Version metadata and documentTypes are\n * intentionally dropped — the client fetches them on demand.\n */\nexport function toPackageListItem(pkg: PackageInfo): PackageListItem {\n const publisher = pkg.manifest?.publisher;\n return {\n name: pkg.name,\n path: pkg.path,\n version: pkg.version,\n description: pkg.manifest?.description ?? undefined,\n category: pkg.manifest?.category ?? undefined,\n publisher: publisher\n ? { name: publisher.name, url: publisher.url }\n : undefined,\n };\n}\n\nfunction getDocumentTypesFromManifest(manifest: Manifest | undefined | null) {\n if (!manifest) return [];\n\n const documentTypes: string[] = [];\n const { apps, documentModels, editors, subgraphs } = manifest;\n\n if (apps?.length) {\n documentTypes.push(\"powerhouse/document-drive\");\n }\n documentTypes.push(\n ...(documentModels ?? []).map((dm) => dm.id),\n ...(editors ?? [])\n .flatMap((e) => e.documentTypes)\n .filter((dt) => dt !== undefined),\n ...(subgraphs ?? [])\n .flatMap((e) => e.documentTypes)\n .filter((dt) => dt !== undefined),\n );\n\n return documentTypes;\n}\n\n/** One cdn-cache package directory, as the scan below found it. */\nexport interface ScannedPackage {\n /** Directory under the cdn cache: `name` or `@scope/name`. */\n dirName: string;\n /** Absolute directory the manifest was read from (the version dir, if any). */\n manifestDir: string;\n manifest: Manifest | null;\n name: string;\n version?: string;\n distTags?: Record<string, string>;\n versions?: string[];\n locallyPublished: boolean | undefined;\n}\n\nfunction readPackageDir(\n cdnCachePath: string,\n dirName: string,\n storagePath: string | undefined,\n): ScannedPackage {\n const pkgDir = path.join(cdnCachePath, dirName);\n const versionDir = getLatestVersionDir(pkgDir);\n const manifestDir = versionDir ?? pkgDir;\n const manifest = readManifest(manifestDir);\n // `||` (not `??`): slimManifest normalizes a missing manifest name to\n // \"\" — fall back to the directory name in that case too.\n const name = manifest?.name || dirName;\n const { distTags, versions, locallyPublished } = readPackageMetadata(\n storagePath,\n name,\n );\n return {\n dirName,\n manifestDir,\n manifest,\n name,\n version: readPackageJsonVersion(manifestDir),\n distTags,\n versions,\n locallyPublished,\n };\n}\n\n// One walk of the cdn cache, yielding every package directory with its\n// manifest: the package list and the piece index are both built from it.\nexport function* scanPackageDirs(\n cdnCachePath: string,\n storagePath?: string,\n): Generator<ScannedPackage> {\n const absDir = path.resolve(cdnCachePath);\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(absDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n if (!entry.name.startsWith(\"@\")) {\n yield readPackageDir(absDir, entry.name, storagePath);\n continue;\n }\n const scopeDir = path.join(absDir, entry.name);\n let scopedEntries: fs.Dirent[];\n try {\n scopedEntries = fs.readdirSync(scopeDir, { withFileTypes: true });\n } catch (error) {\n console.log(error);\n continue;\n }\n for (const scopedEntry of scopedEntries) {\n if (!scopedEntry.isDirectory()) continue;\n yield readPackageDir(\n absDir,\n `${entry.name}/${scopedEntry.name}`,\n storagePath,\n );\n }\n }\n}\n\nexport function scanPackages(\n cdnCachePath: string,\n storagePath?: string,\n): PackageInfo[] {\n const packages: PackageInfo[] = [];\n for (const pkg of scanPackageDirs(cdnCachePath, storagePath)) {\n // Drop npm-uplink passthroughs, but only when storage metadata says so:\n // `undefined` (S3, no storagePath) would otherwise empty the listing.\n if (pkg.locallyPublished === false) continue;\n packages.push({\n name: pkg.name,\n path: `/-/cdn/${pkg.dirName}`,\n manifest: pkg.manifest,\n documentTypes: getDocumentTypesFromManifest(pkg.manifest),\n version: pkg.version,\n distTags: pkg.distTags,\n versions: pkg.versions,\n });\n }\n return packages;\n}\n\nexport function findPackagesByDocumentType(\n packagesDir: string,\n documentType: string,\n): PackageInfo[] {\n const allPackages = scanPackages(packagesDir);\n\n return allPackages.filter((pkg) => {\n if (!pkg.manifest?.documentModels) {\n return false;\n }\n return pkg.manifest.documentModels.some((dm) => dm.id === documentType);\n });\n}\n","// Pieces published inside reactor packages: indexed from the same manifests\n// the package list reads, and served on their own like an Activepieces bundle.\nimport type { Manifest, PieceModule } from \"@powerhousedao/shared\";\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { gunzip as gunzipCb } from \"node:zlib\";\nimport { create, list } from \"tar\";\nimport { scanPackageDirs } from \"./packages.js\";\n\nconst gunzip = promisify(gunzipCb);\n\n/** Where a piece lives: which package ships it, and where inside that package. */\nexport interface PieceIndexEntry {\n /** The piece name a block type refers to (the manifest entry's `id`). */\n name: string;\n displayName: string;\n description?: string;\n version: string;\n packageName: string;\n packageVersion?: string;\n /** Absolute package directory; `bundle` and `descriptor` resolve against it. */\n packageDir: string;\n bundle: string;\n descriptor: string;\n}\n\n/** A piece's own metadata, as `ph build` wrote it next to the bundle. */\ninterface PieceDescriptor {\n displayName?: string;\n description?: string;\n logoUrl?: string;\n authors?: string[];\n categories?: string[];\n auth?: unknown;\n minimumSupportedRelease?: string;\n maximumSupportedRelease?: string;\n actions?: Record<string, DescriptorBlock>;\n triggers?: Record<string, DescriptorBlock>;\n}\n\ninterface DescriptorBlock {\n displayName?: string;\n description?: string;\n /** Triggers only: POLLING | WEBHOOK | APP_WEBHOOK. */\n type?: string;\n}\n\ninterface SuggestedAction {\n name: string;\n displayName: string;\n description: string;\n}\n\ninterface SuggestedTrigger extends SuggestedAction {\n type: string;\n}\n\n// One catalog entry, in cloud.activepieces.com's list shape\n// (`PieceMetadataSummary`), plus the package that ships it.\nexport interface PieceCatalogEntry {\n name: string;\n displayName: string;\n description: string;\n logoUrl: string;\n version: string;\n authors: string[];\n categories: string[];\n auth: unknown;\n actions: number;\n triggers: number;\n minimumSupportedRelease?: string;\n maximumSupportedRelease?: string;\n package: string;\n packageVersion?: string;\n suggestedActions?: SuggestedAction[];\n suggestedTriggers?: SuggestedTrigger[];\n}\n\n// The index is rebuilt at most this often, and immediately after a publish or\n// unpublish invalidates it, so a lookup never rescans the manifests itself.\nconst INDEX_TTL_MS = 30_000;\n\ntype PieceIndex = Map<string, PieceIndexEntry>;\n\nconst indexes = new Map<string, { index: PieceIndex; builtAt: number }>();\n// A published (package, version) never changes its files, so a descriptor read\n// once stays good until the version directory itself goes away.\nconst descriptors = new Map<string, PieceDescriptor | null>();\n\n/** Drop the cached index and descriptors — a publish or unpublish moved them. */\nexport function invalidatePieceIndex(): void {\n indexes.clear();\n descriptors.clear();\n}\n\nfunction indexKey(cdnCachePath: string, storagePath?: string): string {\n return `${path.resolve(cdnCachePath)}\\0${storagePath ?? \"\"}`;\n}\n\nfunction piecesOf(manifest: Manifest | null): PieceModule[] {\n return manifest?.pieces ?? [];\n}\n\n// A piece is servable only once `ph build` has enriched its manifest entry:\n// without a version and a bundle path there is nothing to hand a reactor.\nfunction toIndexEntry(\n piece: PieceModule,\n packageName: string,\n packageVersion: string | undefined,\n packageDir: string,\n): PieceIndexEntry | null {\n const { id, version, bundle } = piece;\n if (!id || !version || !bundle) return null;\n return {\n name: id,\n displayName: piece.name || id,\n ...(piece.description ? { description: piece.description } : {}),\n version,\n packageName,\n packageVersion,\n packageDir,\n bundle,\n descriptor: piece.descriptor ?? `${bundle}/descriptor.json`,\n };\n}\n\nfunction buildPieceIndex(\n cdnCachePath: string,\n storagePath?: string,\n): PieceIndex {\n const index: PieceIndex = new Map();\n for (const pkg of scanPackageDirs(cdnCachePath, storagePath)) {\n if (pkg.locallyPublished === false) continue;\n for (const piece of piecesOf(pkg.manifest)) {\n const entry = toIndexEntry(piece, pkg.name, pkg.version, pkg.manifestDir);\n if (!entry) continue;\n const claimed = index.get(entry.name);\n if (claimed && claimed.packageName !== entry.packageName) {\n console.warn(\n `[registry] piece \"${entry.name}\" is claimed by both ${claimed.packageName} and ${entry.packageName}; keeping ${claimed.packageName}`,\n );\n continue;\n }\n index.set(entry.name, entry);\n }\n }\n return index;\n}\n\n/** The piece index, rebuilt only when the cached one has gone stale. */\nexport function pieceIndex(\n cdnCachePath: string,\n storagePath?: string,\n): PieceIndex {\n const key = indexKey(cdnCachePath, storagePath);\n const cached = indexes.get(key);\n if (cached && Date.now() - cached.builtAt < INDEX_TTL_MS) return cached.index;\n const index = buildPieceIndex(cdnCachePath, storagePath);\n indexes.set(key, { index, builtAt: Date.now() });\n return index;\n}\n\nexport function findPiece(\n cdnCachePath: string,\n name: string,\n storagePath?: string,\n): PieceIndexEntry | undefined {\n return pieceIndex(cdnCachePath, storagePath).get(name);\n}\n\n// Resolves a package-relative path and refuses one that climbs out: manifest\n// paths are publisher-supplied and land straight in a file read.\nfunction insidePackage(\n entry: PieceIndexEntry,\n relative: string,\n): string | null {\n const root = path.resolve(entry.packageDir);\n const resolved = path.resolve(root, relative);\n if (resolved !== root && !resolved.startsWith(root + path.sep)) return null;\n return resolved;\n}\n\nfunction readDescriptor(entry: PieceIndexEntry): PieceDescriptor | null {\n const file = insidePackage(entry, entry.descriptor);\n if (!file) return null;\n const hit = descriptors.get(file);\n if (hit !== undefined) return hit;\n let parsed: PieceDescriptor | null;\n try {\n parsed = JSON.parse(fs.readFileSync(file, \"utf-8\")) as PieceDescriptor;\n } catch {\n parsed = null;\n }\n descriptors.set(file, parsed);\n return parsed;\n}\n\nconst count = (record: Record<string, unknown> | undefined) =>\n record ? Object.keys(record).length : 0;\n\n// Only the fields the editor's block search reads. The cloud list types these\n// as whole actions but sends the summary, and props would dwarf the listing.\nfunction suggested(\n blocks: Record<string, DescriptorBlock> | undefined,\n): SuggestedAction[] {\n return Object.entries(blocks ?? {}).map(([name, block]) => ({\n name,\n displayName: block.displayName ?? name,\n description: block.description ?? \"\",\n }));\n}\n\nfunction catalogEntry(\n entry: PieceIndexEntry,\n descriptor: PieceDescriptor,\n suggestions: boolean,\n): PieceCatalogEntry {\n return {\n name: entry.name,\n displayName: descriptor.displayName || entry.displayName,\n description: descriptor.description ?? entry.description ?? \"\",\n logoUrl: descriptor.logoUrl ?? \"\",\n version: entry.version,\n authors: descriptor.authors ?? [],\n categories: descriptor.categories ?? [],\n auth: descriptor.auth ?? null,\n actions: count(descriptor.actions),\n triggers: count(descriptor.triggers),\n ...(descriptor.minimumSupportedRelease\n ? { minimumSupportedRelease: descriptor.minimumSupportedRelease }\n : {}),\n ...(descriptor.maximumSupportedRelease\n ? { maximumSupportedRelease: descriptor.maximumSupportedRelease }\n : {}),\n package: entry.packageName,\n packageVersion: entry.packageVersion,\n ...(suggestions\n ? {\n suggestedActions: suggested(descriptor.actions),\n suggestedTriggers: Object.entries(descriptor.triggers ?? {}).map(\n ([name, block]) => ({\n name,\n displayName: block.displayName ?? name,\n description: block.description ?? \"\",\n type: block.type ?? \"\",\n }),\n ),\n }\n : {}),\n };\n}\n\n/** The catalog, sorted by display name the way the editor renders it. */\nexport function pieceCatalog(\n cdnCachePath: string,\n options: { storagePath?: string; suggestions?: boolean } = {},\n): PieceCatalogEntry[] {\n const entries: PieceCatalogEntry[] = [];\n for (const entry of pieceIndex(cdnCachePath, options.storagePath).values()) {\n const descriptor = readDescriptor(entry);\n if (!descriptor) continue;\n entries.push(catalogEntry(entry, descriptor, options.suggestions === true));\n }\n return entries.sort((a, b) => a.displayName.localeCompare(b.displayName));\n}\n\n/** The CDN path of a file inside the package that ships the piece. */\nfunction cdnPath(entry: PieceIndexEntry, relative: string): string {\n const spec = entry.packageVersion\n ? `${entry.packageName}@${entry.packageVersion}`\n : entry.packageName;\n return `/-/cdn/${spec}/${relative}`;\n}\n\n/** The tarball filename cdn.activepieces.com would use for the same piece. */\nexport function pieceTarballName(name: string, version: string): string {\n return `${name.replace(\"/\", \"-\")}-${version}.tgz`;\n}\n\nexport function pieceTarballPath(entry: PieceIndexEntry): string {\n return `/-/pieces/bundled/${pieceTarballName(entry.name, entry.version)}`;\n}\n\n/** One piece: what provides it, and where its descriptor and bundle are. */\nexport function pieceDetail(\n entry: PieceIndexEntry,\n origin: string,\n): Record<string, unknown> | null {\n const descriptor = readDescriptor(entry);\n if (!descriptor) return null;\n // The descriptor verbatim, so `fetchPieceDetail` reads this endpoint exactly\n // as it reads the cloud one; the provider fields are additions.\n return {\n ...descriptor,\n name: entry.name,\n version: entry.version,\n package: entry.packageName,\n packageVersion: entry.packageVersion,\n descriptorUrl: `${origin}${cdnPath(entry, entry.descriptor)}`,\n bundleUrl: `${origin}${pieceTarballPath(entry)}`,\n };\n}\n\nexport function findPieceByTarball(\n cdnCachePath: string,\n filename: string,\n storagePath?: string,\n): PieceIndexEntry | undefined {\n for (const entry of pieceIndex(cdnCachePath, storagePath).values()) {\n if (pieceTarballName(entry.name, entry.version) === filename) return entry;\n }\n return undefined;\n}\n\nfunction listFiles(dir: string, prefix = \"\"): string[] {\n const files: string[] = [];\n for (const item of fs.readdirSync(dir, { withFileTypes: true })) {\n const rel = prefix ? `${prefix}/${item.name}` : item.name;\n if (item.isDirectory()) {\n files.push(...listFiles(path.join(dir, item.name), rel));\n } else if (item.isFile()) {\n files.push(rel);\n }\n }\n return files.sort();\n}\n\nconst cuts = new Map<string, Promise<string | null>>();\n\nasync function cutTarball(\n entry: PieceIndexEntry,\n pieceDir: string,\n target: string,\n): Promise<string | null> {\n const files = listFiles(pieceDir);\n if (files.length === 0) return null;\n fs.mkdirSync(path.dirname(target), { recursive: true });\n const tmp = `${target}.tmp-${crypto.randomUUID()}`;\n try {\n // `prefix` and `portable` give the npm-bundle layout the reactor's own\n // extractor strips a root segment from, byte-stable across build hosts.\n await create(\n {\n file: tmp,\n gzip: true,\n cwd: pieceDir,\n prefix: \"package\",\n portable: true,\n },\n files,\n );\n fs.renameSync(tmp, target);\n } catch (error) {\n fs.rmSync(tmp, { force: true });\n console.error(`[registry] failed to pack piece ${entry.name}:`, error);\n return null;\n }\n return target;\n}\n\n// The piece directory as a gzipped tarball, cut out of the extracted package\n// on first request and cached beside it. Null when it cannot be built.\nexport async function pieceTarball(\n entry: PieceIndexEntry,\n): Promise<string | null> {\n const pieceDir = insidePackage(entry, entry.bundle);\n if (!pieceDir || !fs.existsSync(path.join(pieceDir, \"package.json\"))) {\n return null;\n }\n const target = path.join(\n entry.packageDir,\n \".piece-bundles\",\n pieceTarballName(entry.name, entry.version),\n );\n if (fs.existsSync(target)) return target;\n\n const pending = cuts.get(target);\n if (pending) return pending;\n const started = cutTarball(entry, pieceDir, target).finally(() => {\n cuts.delete(target);\n });\n cuts.set(target, started);\n return started;\n}\n\n// Manifest locations inside a published tarball, in the order readManifest\n// resolves them, so the publish check sees the manifest the index will.\nconst MANIFEST_PATHS = [\n \"powerhouse.manifest.json\",\n \"cdn/powerhouse.manifest.json\",\n \"dist/powerhouse.manifest.json\",\n];\n\n// Bounds what a publish can inflate to before we look for its manifest; past\n// it the check gives up rather than the registry.\nconst MAX_TARBALL_BYTES = 128 * 1024 * 1024;\n\nfunction stripRoot(entryPath: string): string {\n return entryPath.replace(/^[^/]+\\//, \"\");\n}\n\nfunction manifestsInTar(tar: Buffer): Map<string, string> {\n const found = new Map<string, string>();\n const parser = list({\n sync: true,\n filter: (entryPath: string) =>\n MANIFEST_PATHS.includes(stripRoot(entryPath)),\n onReadEntry: (entry) => {\n const chunks: Buffer[] = [];\n entry.on(\"data\", (chunk: Buffer) => chunks.push(Buffer.from(chunk)));\n entry.on(\"end\", () => {\n found.set(\n stripRoot(String(entry.path)),\n Buffer.concat(chunks).toString(\"utf-8\"),\n );\n });\n },\n });\n parser.end(tar);\n return found;\n}\n\n/** The piece names a published tarball claims; empty when it claims none. */\nexport async function pieceNamesInTarball(tgz: Buffer): Promise<string[]> {\n let manifests: Map<string, string>;\n try {\n const tar = await gunzip(tgz, { maxOutputLength: MAX_TARBALL_BYTES });\n manifests = manifestsInTar(tar);\n } catch {\n return [];\n }\n for (const candidate of MANIFEST_PATHS) {\n const raw = manifests.get(candidate);\n if (raw === undefined) continue;\n try {\n const manifest = JSON.parse(raw) as Manifest;\n return piecesOf(manifest)\n .map((piece) => piece.id)\n .filter((id): id is string => typeof id === \"string\" && id !== \"\");\n } catch {\n return [];\n }\n }\n return [];\n}\n","import type { CdnCache } from \"./cdn.js\";\nimport { isLocallyPublished } from \"./packages.js\";\nimport type { RegistryConfig } from \"./types.js\";\n\n// The verdaccio listing already yields local-only, latest-per-name entries;\n// we re-filter by `_attachments` and dedupe as a guard against backend changes.\nconst WARM_INTERVAL_MS = 30_000;\nconst WARM_CONCURRENCY = 8;\n\ninterface VerdaccioPackage {\n name: string;\n version?: string;\n}\n\n/**\n * Build a throttled warmer that extracts locally-published package tarballs\n * into the CDN cache. 30s minimum interval plus an in-flight guard prevent\n * redundant fan-out from readiness-probe traffic across pods.\n */\nexport function createWarmer(\n config: RegistryConfig,\n cdn: CdnCache,\n): () => Promise<void> {\n let warmInFlight = false;\n let lastWarmAt = 0;\n\n return async function warm(): Promise<void> {\n if (warmInFlight) return;\n if (Date.now() - lastWarmAt < WARM_INTERVAL_MS) return;\n warmInFlight = true;\n try {\n const r = await fetch(\n `http://localhost:${config.port}/-/verdaccio/data/packages`,\n );\n if (!r.ok) {\n console.error(\n `[registry] verdaccio package listing returned ${r.status}`,\n );\n return;\n }\n const listed = (await r.json()) as VerdaccioPackage[];\n\n // Latest version per name (the listing yields one entry per package;\n // dedupe defensively), scoped to locally-published packages only.\n const latestByName = new Map<string, string>();\n for (const pkg of listed) {\n if (!pkg.version) continue;\n if (isLocallyPublished(config.storagePath, pkg.name) === false)\n continue;\n latestByName.set(pkg.name, pkg.version);\n }\n\n const targets = [...latestByName.entries()];\n let cursor = 0;\n const workers = Array.from({ length: WARM_CONCURRENCY }).map(async () => {\n while (cursor < targets.length) {\n const [name, version] = targets[cursor++];\n try {\n await cdn.extractTarball(name, version);\n } catch (err) {\n console.error(\n `[registry] failed to warm cache for ${name}@${version}:`,\n err,\n );\n }\n }\n });\n await Promise.all(workers);\n console.log(`[registry] /packages warm-up done (${targets.length} pkgs)`);\n // Throttle only after a successful cycle so failures (e.g. registry\n // not listening yet during startup) retry on the next call.\n lastWarmAt = Date.now();\n } catch (err) {\n console.error(\"[registry] /packages warm-up failed:\", err);\n } finally {\n warmInFlight = false;\n }\n };\n}\n","import express, {\n Router,\n type NextFunction,\n type Request,\n type Response,\n} from \"express\";\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { AuthStore } from \"./auth/auth-store.js\";\nimport { CdnCache, isExactVersion, parsePackageSpec } from \"./cdn.js\";\nimport type { PackageInfo } from \"./types.js\";\nimport type { SSEChannel } from \"./notifications/sse.js\";\nimport type { NotificationChannel } from \"./notifications/types.js\";\nimport type { WebhookChannel } from \"./notifications/webhook.js\";\nimport {\n findPackagesByDocumentType,\n loadPackage,\n scanPackages,\n toPackageListItem,\n} from \"./packages.js\";\nimport {\n findPiece,\n findPieceByTarball,\n invalidatePieceIndex,\n pieceCatalog,\n pieceDetail,\n pieceIndex,\n pieceNamesInTarball,\n pieceTarball,\n} from \"./pieces.js\";\nimport type { RegistryConfig } from \"./types.js\";\nimport { createWarmer } from \"./warmup.js\";\n\n// Paginated /packages mode: default and max page sizes.\nconst DEFAULT_PAGE_SIZE = 30;\nconst MAX_PAGE_SIZE = 50;\n\n/** Parse+clamp a `limit` query param to 1..MAX_PAGE_SIZE (default 30). */\nfunction clampPageSize(raw: unknown): number {\n const n = Number.parseInt(String(raw), 10);\n if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE;\n return Math.min(n, MAX_PAGE_SIZE);\n}\n\n/** Parse a non-negative `offset` query param (default 0). */\nfunction parseOffset(raw: unknown): number {\n const n = Number.parseInt(String(raw), 10);\n if (!Number.isFinite(n) || n < 0) return 0;\n return n;\n}\n\nconst MIME_TYPES: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".wasm\": \"application/wasm\",\n \".map\": \"application/json\",\n \".html\": \"text/html\",\n \".svg\": \"image/svg+xml\",\n};\n\nfunction getContentType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase();\n return MIME_TYPES[ext] ?? \"application/octet-stream\";\n}\n\n// Where a client reached us, for the absolute URLs the piece endpoints hand\n// out. `trust proxy` is off here, so the forwarded scheme is read directly.\nfunction originOf(req: Request): string {\n const forwarded = req.get(\"x-forwarded-proto\")?.split(\",\")[0].trim();\n return `${forwarded || req.protocol}://${req.get(\"host\") ?? \"\"}`;\n}\n\n// Publisher identity from verdaccio's remote_user (the renown middleware sets\n// its name to the owner pkh DID). undefined when anonymous.\nfunction publisherFromRequest(\n req: Request,\n): { address: string; did?: string } | undefined {\n const name = (req as { remote_user?: { name?: string } }).remote_user?.name;\n if (!name) return undefined;\n if (name.startsWith(\"did:pkh:\")) {\n const address = (name.split(\":\").pop() ?? name).toLowerCase();\n return { address, did: name };\n }\n return { address: name };\n}\n\ntype VersionResolution =\n | { kind: \"ok\"; version: string }\n | { kind: \"not-found\" }\n | { kind: \"upstream-error\" };\n\n/**\n * Resolve a package version. Exact versions skip the network call. Upstream\n * errors fall back to the latest cached version; genuine not-found falls back\n * too, then reports not-found.\n */\nasync function resolvePackageVersion(\n cdn: CdnCache,\n packageName: string,\n tag: string | undefined,\n): Promise<VersionResolution> {\n if (tag && isExactVersion(tag)) return { kind: \"ok\", version: tag };\n\n try {\n const resolved =\n (await cdn.resolveVersion(packageName, tag)) ??\n cdn.getLatestCachedVersion(packageName);\n if (!resolved) return { kind: \"not-found\" };\n return { kind: \"ok\", version: resolved };\n } catch {\n const cached = cdn.getLatestCachedVersion(packageName);\n if (!cached) return { kind: \"upstream-error\" };\n return { kind: \"ok\", version: cached };\n }\n}\n\n/** Strip the weak-validator prefix: weak comparison is correct for GET/HEAD. */\nfunction opaqueTag(tag: string): string {\n return tag.startsWith(\"W/\") ? tag.slice(2) : tag;\n}\n\n/** RFC 9110 If-None-Match: a comma-separated list of entity-tags or \"*\". */\nfunction etagMatches(\n header: string | string[] | undefined,\n etag: string,\n): boolean {\n if (!header) return false;\n const target = opaqueTag(etag);\n const value = Array.isArray(header) ? header.join(\",\") : header;\n return value.split(\",\").some((candidate) => {\n const tag = candidate.trim();\n return tag === \"*\" || opaqueTag(tag) === target;\n });\n}\n\nexport function createPowerhouseRouter(\n config: RegistryConfig,\n sse: SSEChannel,\n webhooks: WebhookChannel,\n ownerStore?: AuthStore,\n): Router {\n const cdn = new CdnCache(\n `http://localhost:${config.port}`,\n config.cdnCachePath,\n );\n const router = Router();\n\n // Attach package owners from the auth store. Best-effort: a missing store or\n // DB error degrades to no `owners` (the /packages route feeds readiness).\n const withOwners = async (pkgs: PackageInfo[]): Promise<PackageInfo[]> => {\n if (!ownerStore || pkgs.length === 0) return pkgs;\n try {\n await ownerStore.init();\n const map = await ownerStore.getOwnersFor(pkgs.map((p) => p.name));\n return pkgs.map((p) =>\n p.name in map ? { ...p, owners: map[p.name] } : p,\n );\n } catch (err) {\n console.error(\"[registry] owner lookup failed:\", err);\n return pkgs;\n }\n };\n\n // CORS on every response\n router.use((_req: Request, res: Response, next: NextFunction) => {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n next();\n });\n\n // SSE endpoint for publish notifications\n router.get(\"/-/events\", (_req: Request, res: Response) => {\n sse.addClient(res);\n });\n\n // Webhook management\n router.get(\"/-/webhooks\", (_req: Request, res: Response) => {\n res.json(webhooks.getWebhooks());\n });\n\n router.post(\"/-/webhooks\", express.json(), (req: Request, res: Response) => {\n const { endpoint, headers } = req.body as {\n endpoint?: string;\n headers?: Record<string, string>;\n };\n if (!endpoint) {\n res.status(400).json({ error: \"Missing required field: endpoint\" });\n return;\n }\n webhooks.addWebhook({ endpoint, headers });\n res.status(201).json({ endpoint, headers });\n });\n\n router.delete(\n \"/-/webhooks\",\n express.json(),\n (req: Request, res: Response) => {\n const { endpoint } = req.body as { endpoint?: string };\n if (!endpoint) {\n res.status(400).json({ error: \"Missing required field: endpoint\" });\n return;\n }\n const removed = webhooks.removeWebhook(endpoint);\n if (!removed) {\n res.status(404).json({ error: \"Webhook not found\" });\n return;\n }\n res.status(204).end();\n },\n );\n\n const warm = createWarmer(config, cdn);\n\n // Kick off an initial warm so /packages is useful soon after pod start\n // even if no clients hit it. Fire-and-forget — must not block the listener.\n void warm();\n\n // Package listing API.\n // Returns whatever's currently in the local cdn-cache (instant response —\n // important: this endpoint is wired to the deployment's readiness probe,\n // so it must not synchronously fetch or extract). Each call also nudges\n // a background warm-up so newly-published packages appear in the listing\n // without operator intervention.\n router.get(\"/packages\", async (req: Request, res: Response) => {\n void warm();\n const packages = scanPackages(config.cdnCachePath, config.storagePath);\n\n // Legacy mode 1 (unchanged): filter by document type, return the full\n // PackageInfo array. Used by MissingPackageModal / package discovery.\n const documentType = req.query.documentType as string | undefined;\n if (documentType) {\n const filtered = packages.filter((pkg) =>\n pkg.manifest?.documentModels?.some((m) => m.id === documentType),\n );\n res.json(await withOwners(filtered));\n return;\n }\n\n // Legacy mode 2 (unchanged): no `limit` → full PackageInfo array in\n // directory order. Keeps every existing consumer working.\n if (req.query.limit === undefined) {\n res.json(await withOwners(packages));\n return;\n }\n\n // Paginated mode: `?limit=&offset=&search=` → trimmed-item envelope.\n // Sort by name so offset paging is stable (scanPackages is dir order).\n const limit = clampPageSize(req.query.limit);\n const offset = parseOffset(req.query.offset);\n const search =\n typeof req.query.search === \"string\"\n ? req.query.search.trim().toLowerCase()\n : \"\";\n\n // Match on package name only (not description).\n const filtered = search\n ? packages.filter((pkg) => pkg.name.toLowerCase().includes(search))\n : packages;\n const sorted = filtered\n .slice()\n .sort((a, b) => a.name.localeCompare(b.name));\n\n const total = sorted.length;\n const items = sorted.slice(offset, offset + limit).map(toPackageListItem);\n res.json({\n items,\n total,\n limit,\n offset,\n hasMore: offset + limit < total,\n });\n });\n\n // Find packages by document type - returns array of package names\n router.get(\"/packages/by-document-type\", (req: Request, res: Response) => {\n const documentType = req.query.type;\n\n if (typeof documentType !== \"string\" || !documentType) {\n res.status(400).json({ error: \"Missing required query parameter: type\" });\n return;\n }\n\n const packages = findPackagesByDocumentType(\n config.cdnCachePath,\n documentType,\n );\n const packageNames = packages.map((pkg) => pkg.name);\n res.json(packageNames);\n });\n\n // Single package info\n router.get(\"/packages/*\", async (req: Request, res: Response) => {\n const raw = (req.params as Record<string, string>)[0];\n const { name, tag } = parsePackageSpec(raw);\n const resolution = await resolvePackageVersion(cdn, name, tag);\n if (resolution.kind === \"upstream-error\") {\n res.status(503).send(\"Upstream registry unavailable\");\n return;\n }\n const version = resolution.kind === \"ok\" ? resolution.version : undefined;\n const pkg = loadPackage(\n config.cdnCachePath,\n name,\n version,\n config.storagePath,\n );\n if (!pkg) {\n res.status(404).send(\"Package not found\");\n return;\n }\n res.json((await withOwners([pkg]))[0]);\n });\n\n // The piece catalog, in the shape cloud.activepieces.com's list endpoint\n // returns, so the engine consumes both through one code path.\n router.get(\"/pieces\", (req: Request, res: Response) => {\n void warm();\n res.json(\n pieceCatalog(config.cdnCachePath, {\n storagePath: config.storagePath,\n suggestions: req.query.suggestionType === \"ACTION_AND_TRIGGER\",\n }),\n );\n });\n\n // Wildcard, not `:name`: a scoped piece name carries a slash.\n router.get(\"/pieces/*\", (req: Request, res: Response) => {\n void warm();\n const name = (req.params as Record<string, string>)[0];\n const entry = findPiece(config.cdnCachePath, name, config.storagePath);\n const detail = entry ? pieceDetail(entry, originOf(req)) : null;\n if (!detail) {\n res.status(404).json({ error: `Piece not found: ${name}` });\n return;\n }\n res.json(detail);\n });\n\n // The piece directory as an npm-shaped tarball, the one thing a reactor's\n // piece worker downloads. Named as cdn.activepieces.com names its own.\n router.get(\"/-/pieces/bundled/*\", async (req: Request, res: Response) => {\n void warm();\n const filename = (req.params as Record<string, string>)[0];\n const entry = findPieceByTarball(\n config.cdnCachePath,\n filename,\n config.storagePath,\n );\n const file = entry ? await pieceTarball(entry) : null;\n if (!entry || !file) {\n res.status(404).json({ error: `Piece bundle not found: ${filename}` });\n return;\n }\n // A piece bundle is immutable per (name, version), like the npm tarball it\n // was cut from, so it can be cached for as long as a client likes.\n res.setHeader(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n const etag = `\"${entry.packageName}-${filename}\"`;\n res.setHeader(\"ETag\", etag);\n if (etagMatches(req.headers[\"if-none-match\"], etag)) {\n res.status(304).end();\n return;\n }\n res.setHeader(\"Content-Type\", \"application/gzip\");\n try {\n await pipeline(fs.createReadStream(file), res);\n } catch {\n res.destroy();\n }\n });\n\n // CDN file serving\n router.get(\"/-/cdn/*\", async (req: Request, res: Response) => {\n const fullPath = (req.params as Record<string, string>)[0];\n\n // Parse scoped or unscoped package specifier from the path\n let packageSpec: string;\n let filePath: string;\n\n if (fullPath.startsWith(\"@\")) {\n // Scoped: @scope/pkg@1.0.0/file.js -> packageSpec = @scope/pkg@1.0.0, filePath = file.js\n const segments = fullPath.split(\"/\");\n if (segments.length < 2) {\n res.status(400).send(\"Invalid package path\");\n return;\n }\n packageSpec = `${segments[0]}/${segments[1]}`;\n filePath = segments.slice(2).join(\"/\") || \"index.js\";\n } else {\n // Unscoped: pkg@1.0.0/file.js -> packageSpec = pkg@1.0.0, filePath = file.js\n const segments = fullPath.split(\"/\");\n packageSpec = segments[0];\n filePath = segments.slice(1).join(\"/\") || \"index.js\";\n }\n\n const { name: packageName, tag } = parsePackageSpec(packageSpec);\n const pinned = isExactVersion(tag);\n const resolution = await resolvePackageVersion(cdn, packageName, tag);\n if (resolution.kind === \"upstream-error\") {\n res.status(503).send(\"Upstream registry unavailable\");\n return;\n }\n if (resolution.kind === \"not-found\") {\n res.status(404).send(\"File not found\");\n return;\n }\n const version = resolution.version;\n\n const resolved = await cdn.getFileByVersion(packageName, version, filePath);\n if (!resolved) {\n // Pinned requests skip the metadata lookup above, so a miss here may be\n // an upstream failure rather than a genuine 404 — probe to distinguish,\n // otherwise the CDN would cache a 404 while upstream is merely down.\n if (pinned) {\n try {\n await cdn.resolveVersion(packageName, tag);\n } catch {\n res.status(503).send(\"Upstream registry unavailable\");\n return;\n }\n }\n res.status(404).send(\"File not found\");\n return;\n }\n\n // Cache based on the request shape: pinned requests are immutable, moving\n // ones (dist-tag / untagged) must revalidate frequently.\n res.setHeader(\n \"Cache-Control\",\n pinned\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=60, must-revalidate\",\n );\n\n // Hash the file path: it comes from the URL and may contain characters\n // that are invalid in header values.\n const fileHash = crypto\n .createHash(\"sha1\")\n .update(filePath)\n .digest(\"hex\")\n .slice(0, 16);\n const etag = `W/\"${version}-${fileHash}\"`;\n res.setHeader(\"ETag\", etag);\n if (etagMatches(req.headers[\"if-none-match\"], etag)) {\n res.status(304).end();\n return;\n }\n\n res.setHeader(\"Content-Type\", getContentType(filePath));\n try {\n await pipeline(fs.createReadStream(resolved), res);\n } catch {\n // Stream failure (I/O error, client abort) after headers may already\n // be sent — destroy the socket so the request doesn't hang.\n res.destroy();\n }\n });\n\n return router;\n}\n\n/**\n * Parse verdaccio's unpublish URL shape:\n * DELETE /<pkg>/-rev/<rev> → full package\n * DELETE /<pkg>/-/<tarball-name>/-rev/<rev> → single version\n * where <pkg> may be scoped (@scope%2Fname, encoded) or unscoped, and the\n * tarball name is `<short-name>-<version>.tgz`.\n */\nexport function parseUnpublishRequest(\n reqPath: string,\n): { packageName: string; version: string | null } | null {\n const revIdx = reqPath.indexOf(\"/-rev/\");\n if (revIdx <= 0) return null;\n const beforeRev = reqPath.slice(1, revIdx); // strip leading slash\n\n const tarballMarker = \"/-/\";\n const tarballIdx = beforeRev.indexOf(tarballMarker);\n if (tarballIdx === -1) {\n // Full package: beforeRev is the package name (possibly URL-encoded scope)\n const packageName = decodeURIComponent(beforeRev);\n return { packageName, version: null };\n }\n\n const packageName = decodeURIComponent(beforeRev.slice(0, tarballIdx));\n const tarballName = beforeRev.slice(tarballIdx + tarballMarker.length);\n if (!tarballName.endsWith(\".tgz\")) return null;\n const shortName = packageName.startsWith(\"@\")\n ? packageName.split(\"/\")[1]\n : packageName;\n const prefix = `${shortName}-`;\n if (!tarballName.startsWith(prefix)) return null;\n const version = tarballName.slice(prefix.length, -\".tgz\".length);\n if (!version) return null;\n return { packageName, version };\n}\n\n// PUT /<pkg>/-rev/<rev> is npm's manifest rewrite (single-version unpublish,\n// deprecate). Exclude the tarball-DELETE shape, which also carries /-rev/.\nexport function parseManifestRewrite(\n reqPath: string,\n): { packageName: string } | null {\n const revIdx = reqPath.indexOf(\"/-rev/\");\n if (revIdx <= 0) return null;\n const beforeRev = reqPath.slice(1, revIdx);\n if (beforeRev.includes(\"/-/\")) return null;\n return { packageName: decodeURIComponent(beforeRev) };\n}\n\nexport function createUnpublishHook(\n config: RegistryConfig,\n notifications: NotificationChannel,\n) {\n const cdn = new CdnCache(\n `http://localhost:${config.port}`,\n config.cdnCachePath,\n );\n\n // Reconcile the CDN cache after verdaccio rewrites a manifest to drop a\n // version. Re-fetch survivors — req.body isn't reliable on this route.\n const handleManifestRewrite = (req: Request, res: Response) => {\n const rewrite = parseManifestRewrite(req.path);\n if (!rewrite) return;\n\n const originalEnd = res.end.bind(res);\n res.end = function (\n this: Response,\n chunk?: unknown,\n encoding?: unknown,\n cb?: () => void,\n ) {\n if (res.statusCode >= 200 && res.statusCode < 300) {\n const publishedBy = publisherFromRequest(req);\n cdn\n .reconcileWithRegistry(rewrite.packageName)\n .then((removed) => {\n if (removed.length > 0) invalidatePieceIndex();\n for (const version of removed) {\n notifications.notifyUnpublish({\n packageName: rewrite.packageName,\n version,\n publishedBy,\n });\n }\n })\n .catch((err) => {\n console.error(\n `[registry] CDN reconcile failed for ${rewrite.packageName}:`,\n err,\n );\n });\n }\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n };\n };\n\n return (req: Request, res: Response, next: NextFunction) => {\n if (req.method === \"PUT\") {\n handleManifestRewrite(req, res);\n next();\n return;\n }\n if (req.method !== \"DELETE\") {\n next();\n return;\n }\n\n const parsed = parseUnpublishRequest(req.path);\n if (!parsed) {\n next();\n return;\n }\n\n const originalEnd = res.end.bind(res);\n res.end = function (\n this: Response,\n chunk?: unknown,\n encoding?: unknown,\n cb?: () => void,\n ) {\n if (res.statusCode >= 200 && res.statusCode < 300) {\n try {\n if (parsed.version) {\n cdn.invalidateVersion(parsed.packageName, parsed.version);\n } else {\n cdn.invalidate(parsed.packageName);\n }\n invalidatePieceIndex();\n notifications.notifyUnpublish({\n packageName: parsed.packageName,\n version: parsed.version,\n publishedBy: publisherFromRequest(req),\n });\n } catch (err) {\n console.error(\n `[registry] CDN purge failed for ${parsed.packageName}${parsed.version ? `@${parsed.version}` : \"\"}:`,\n err,\n );\n }\n }\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n };\n\n next();\n };\n}\n\nexport function createPublishHook(\n config: RegistryConfig,\n notifications: NotificationChannel,\n) {\n const cdn = new CdnCache(\n `http://localhost:${config.port}`,\n config.cdnCachePath,\n );\n\n // Publish bodies are read here, ahead of verdaccio: body-parser marks the\n // request parsed, so verdaccio reuses this one instead of a spent stream.\n const parseBody = express.json({\n strict: false,\n limit: config.maxBodySize ?? \"300mb\",\n });\n\n const attachExtraction = (req: Request, res: Response) => {\n const originalEnd = res.end.bind(res);\n res.end = function (\n this: Response,\n chunk?: unknown,\n encoding?: unknown,\n cb?: () => void,\n ) {\n const urlPath = req.path.replace(/^\\//, \"\");\n if (\n res.statusCode < 200 ||\n res.statusCode >= 300 ||\n !urlPath ||\n urlPath.startsWith(\"-\")\n ) {\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n }\n const packageName = decodeURIComponent(urlPath);\n const versionsObj = (req.body as { versions: Record<string, unknown> })\n .versions;\n const versions = Object.keys(versionsObj);\n const version = versions.at(0);\n if (!version) {\n console.error(`[registry] No version found for ${packageName}`);\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n }\n if (versions.length > 1) {\n console.warn(\n `[registry] Multiple versions published for ${packageName}: ${JSON.stringify(versions)}`,\n );\n }\n\n const publishedBy = publisherFromRequest(req);\n cdn\n .extractTarball(packageName, version)\n .then(() => {\n // The new version's pieces only exist once it is extracted, so the\n // index is dropped here rather than when the publish was accepted.\n invalidatePieceIndex();\n notifications.notifyPublish({ packageName, version, publishedBy });\n })\n .catch((err) => {\n console.error(\n `[registry] Failed to extract ${packageName} to CDN cache:`,\n err,\n );\n });\n\n return originalEnd(chunk, encoding as BufferEncoding, cb);\n };\n };\n\n return (req: Request, res: Response, next: NextFunction) => {\n // Only npm publish endpoints: a PUT to `/<pkg>/-rev/<rev>` is the\n // manifest rewrite of a single-version unpublish, not a new publish.\n if (req.method !== \"PUT\" || req.path.includes(\"/-rev/\")) {\n next();\n return;\n }\n const urlPath = req.path.replace(/^\\//, \"\");\n if (!urlPath || urlPath.startsWith(\"-\")) {\n attachExtraction(req, res);\n next();\n return;\n }\n\n parseBody(req, res, (parseError?: unknown) => {\n // A body verdaccio will reject anyway: let it own the error message.\n if (parseError) {\n next();\n return;\n }\n void pieceClaimConflict(config, decodeURIComponent(urlPath), req.body)\n .then((conflict) => {\n if (conflict) {\n res.status(409).json({ error: conflict });\n return;\n }\n attachExtraction(req, res);\n next();\n })\n .catch((err) => {\n console.error(\"[registry] piece collision check failed:\", err);\n attachExtraction(req, res);\n next();\n });\n });\n };\n}\n\n// The tarball a publish payload carries, if it carries one: npm sends a single\n// `_attachments` entry holding the base64 of the version being published.\nfunction publishedTarball(body: unknown): Buffer | null {\n if (body === null || typeof body !== \"object\") return null;\n const attachments = (body as { _attachments?: Record<string, unknown> })\n ._attachments;\n if (!attachments || typeof attachments !== \"object\") return null;\n for (const [name, attachment] of Object.entries(attachments)) {\n if (!name.endsWith(\".tgz\")) continue;\n const data = (attachment as { data?: unknown }).data;\n if (typeof data === \"string\") return Buffer.from(data, \"base64\");\n }\n return null;\n}\n\n// A piece name is one package's for good: two packages claiming it would make\n// a block type ambiguous, and a reactor would download whichever won a scan.\nasync function pieceClaimConflict(\n config: RegistryConfig,\n packageName: string,\n body: unknown,\n): Promise<string | null> {\n // Nothing to collide with, so a registry that serves no pieces never pays\n // for decompressing a publish to find out.\n const index = pieceIndex(config.cdnCachePath, config.storagePath);\n if (index.size === 0) return null;\n const tarball = publishedTarball(body);\n if (!tarball) return null;\n for (const name of await pieceNamesInTarball(tarball)) {\n const owner = index.get(name);\n if (owner && owner.packageName !== packageName) {\n return `Piece \"${name}\" is already published by ${owner.packageName}; ${packageName} cannot claim it.`;\n }\n }\n return null;\n}\n","import type {\n NotificationChannel,\n PublishEvent,\n UnpublishEvent,\n} from \"./types.js\";\n\nexport class NotificationManager implements NotificationChannel {\n #channels: NotificationChannel[];\n\n constructor(channels: NotificationChannel[]) {\n this.#channels = channels;\n }\n\n notifyPublish(event: PublishEvent): void {\n for (const channel of this.#channels) {\n channel.notifyPublish(event);\n }\n }\n\n notifyUnpublish(event: UnpublishEvent): void {\n for (const channel of this.#channels) {\n channel.notifyUnpublish(event);\n }\n }\n}\n","import type { Response } from \"express\";\nimport type {\n NotificationChannel,\n PublishEvent,\n UnpublishEvent,\n} from \"./types.js\";\n\nexport class SSEChannel implements NotificationChannel {\n #clients = new Set<Response>();\n\n addClient(res: Response): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.write(\"event: connected\\ndata: {}\\n\\n\");\n\n this.#clients.add(res);\n res.on(\"close\", () => {\n this.#clients.delete(res);\n });\n }\n\n notifyPublish(event: PublishEvent): void {\n this.#broadcast(\"publish\", event);\n }\n\n notifyUnpublish(event: UnpublishEvent): void {\n this.#broadcast(\"unpublish\", event);\n }\n\n #broadcast(eventName: string, event: PublishEvent | UnpublishEvent): void {\n const payload = `event: ${eventName}\\ndata: ${JSON.stringify(event)}\\n\\n`;\n for (const client of this.#clients) {\n try {\n client.write(payload);\n } catch (err) {\n console.error(\"[registry] SSE client write failed:\", err);\n this.#clients.delete(client);\n }\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { NotifyConfig, WebhookConfig } from \"../types.js\";\nimport type {\n NotificationChannel,\n PublishEvent,\n UnpublishEvent,\n} from \"./types.js\";\n\nconst WEBHOOKS_FILE = \"webhooks.json\";\n\nexport class WebhookChannel implements NotificationChannel {\n #predefined: WebhookConfig[];\n #dynamic: WebhookConfig[];\n #storagePath: string;\n\n constructor(storagePath: string, config?: NotifyConfig) {\n this.#storagePath = storagePath;\n this.#predefined = config?.webhooks ?? [];\n this.#dynamic = this.#load();\n }\n\n getWebhooks(): WebhookConfig[] {\n return [...this.#predefined, ...this.#dynamic];\n }\n\n addWebhook(webhook: WebhookConfig): void {\n const exists = this.getWebhooks().some(\n (w) => w.endpoint === webhook.endpoint,\n );\n if (exists) return;\n this.#dynamic.push(webhook);\n this.#save();\n }\n\n removeWebhook(endpoint: string): boolean {\n const before = this.#dynamic.length;\n this.#dynamic = this.#dynamic.filter((w) => w.endpoint !== endpoint);\n if (this.#dynamic.length === before) return false;\n this.#save();\n return true;\n }\n\n notifyPublish(event: PublishEvent): void {\n this.#post({ type: \"publish\", ...event });\n }\n\n notifyUnpublish(event: UnpublishEvent): void {\n this.#post({ type: \"unpublish\", ...event });\n }\n\n #post(body: Record<string, unknown>): void {\n for (const webhook of this.getWebhooks()) {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...webhook.headers,\n };\n\n fetch(webhook.endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n }).catch((err: unknown) => {\n console.error(`[registry] Webhook to ${webhook.endpoint} failed:`, err);\n });\n }\n }\n\n #filePath(): string {\n return path.join(this.#storagePath, WEBHOOKS_FILE);\n }\n\n #load(): WebhookConfig[] {\n try {\n const raw = fs.readFileSync(this.#filePath(), \"utf-8\");\n return JSON.parse(raw) as WebhookConfig[];\n } catch {\n return [];\n }\n }\n\n #save(): void {\n fs.mkdirSync(this.#storagePath, { recursive: true });\n fs.writeFileSync(this.#filePath(), JSON.stringify(this.#dynamic, null, 2));\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AuthStore } from \"./auth/auth-store.js\";\nimport { stashAuthStore } from \"./auth/store-handoff.js\";\nimport type { RegistryConfig } from \"./types.js\";\n\nexport function buildVerdaccioConfig(config: RegistryConfig) {\n const htpasswdPath = path.join(config.storagePath, \"htpasswd\");\n\n const uplinkUrl = config.uplink ?? \"https://registry.npmjs.org/\";\n\n // With a database configured, use the Postgres-backed auth plugin\n // (persistent accounts + npm-style package ownership). Verdaccio loads it\n // via require() from the dist/plugins dir. Without a DB (local dev / tests),\n // keep the built-in htpasswd path.\n const usePgAuth = Boolean(config.databaseUrl || config.authStore);\n // Prefer a runtime-provided token (so the launcher owns it for load\n // detection); otherwise stash the injected store here (direct callers/tests).\n const storeToken =\n config.authStoreToken ??\n (config.authStore\n ? stashAuthStore(config.authStore as AuthStore)\n : undefined);\n const pluginsDir =\n config.pluginsDir ??\n path.join(path.dirname(fileURLToPath(import.meta.url)), \"plugins\");\n const auth = usePgAuth\n ? {\n \"registry-auth\": {\n ...(config.databaseUrl ? { databaseUrl: config.databaseUrl } : {}),\n ...(storeToken ? { storeToken } : {}),\n ...(config.renown\n ? {\n publicUrl: config.renown.publicUrl,\n ...(config.renown.renownUrl\n ? { renownUrl: config.renown.renownUrl }\n : {}),\n }\n : {}),\n },\n }\n : { htpasswd: { file: htpasswdPath } };\n\n const base: Record<string, unknown> = {\n storage: config.storagePath,\n self_path: \"./\",\n // Top-level secret used by verdaccio to sign / verify its API JWTs.\n // The renown middleware mints a verdaccio-format JWT with the same\n // secret so verdaccio's apiJWTmiddleware accepts the swapped token.\n ...(config.verdaccioSecret ? { secret: config.verdaccioSecret } : {}),\n // Force JWT mode for the npm API. Without this verdaccio falls back to\n // its legacy aes-encrypted token format, which signPayload won't produce.\n security: {\n api: {\n jwt: {\n sign: { expiresIn: \"90d\" },\n verify: {},\n },\n },\n },\n auth,\n ...(usePgAuth ? { plugins: pluginsDir } : {}),\n uplinks: {\n npmjs: {\n url: uplinkUrl,\n // Defaults to verdaccio's own default of 2m. The previous 15m\n // hardcoded value made publish-to-install dev loops painful —\n // when a newly-published version landed on npmjs, our registry\n // kept handing out the pre-publish packument for up to 15min.\n // Operators that want heavier upstream caching for production\n // can opt in via --uplink-maxage / PH_REGISTRY_UPLINK_MAXAGE.\n maxage: config.uplinkMaxage ?? \"2m\",\n timeout: \"30s\",\n cache: true,\n },\n },\n // Verdaccio matches packages config top-to-bottom (first match wins),\n // so local-only globs must come first. We also skip emitting the\n // default proxied entries when a glob with the same key is in the\n // local-only list — otherwise the value would be overwritten but the\n // iteration position would still be the default's earlier slot.\n packages: (() => {\n const local = config.localPackagePatterns ?? [];\n const localSet = new Set(local);\n const access = {\n access: \"$all\",\n publish: \"$authenticated\",\n unpublish: \"$authenticated\",\n };\n const entries: [string, Record<string, unknown>][] = [];\n // Locals first — no proxy means verdaccio resolves them from local\n // storage only, so re-publishing a version that exists on npmjs\n // doesn't 409.\n for (const pattern of local) {\n entries.push([pattern, { ...access }]);\n }\n // Defaults follow, skipping any glob the caller already overrode.\n if (!localSet.has(\"@powerhousedao/*\")) {\n entries.push([\"@powerhousedao/*\", { ...access, proxy: \"npmjs\" }]);\n }\n if (!localSet.has(\"**\")) {\n entries.push([\"**\", { ...access, proxy: \"npmjs\" }]);\n }\n return Object.fromEntries(entries);\n })(),\n web: {\n enable: config.webEnabled !== false,\n title: \"Powerhouse Registry\",\n logo: \"https://raw.githubusercontent.com/powerhouse-inc/powerhouse/main/packages/registry/static/logo.svg\",\n favicon: \"/-/static/favicon.ico\",\n primary_color: \"#38C780\",\n darkMode: true,\n },\n server: {\n keepAliveTimeout: 60,\n },\n log: {\n type: \"stdout\",\n format: \"pretty\",\n level: \"warn\",\n },\n max_body_size: config.maxBodySize ?? \"300mb\",\n };\n\n if (config.s3) {\n base.store = {\n \"aws-s3-storage\": {\n bucket: config.s3.bucket,\n endpoint: config.s3.endpoint,\n region: config.s3.region,\n s3ForcePathStyle: config.s3.s3ForcePathStyle ?? true,\n ...(config.s3.keyPrefix && { keyPrefix: config.s3.keyPrefix }),\n ...(config.s3.accessKeyId && { accessKeyId: config.s3.accessKeyId }),\n ...(config.s3.secretAccessKey && {\n secretAccessKey: config.s3.secretAccessKey,\n }),\n },\n };\n }\n\n return base;\n}\n","import express from \"express\";\nimport { findUp } from \"find-up\";\nimport { randomBytes } from \"node:crypto\";\nimport { mkdir } from \"node:fs/promises\";\nimport type { Server } from \"node:http\";\nimport path from \"node:path\";\nimport { runServer } from \"verdaccio\";\nimport type { AuthStore } from \"./auth/auth-store.js\";\nimport { createPgPool, createPgStore } from \"./auth/pg-store.js\";\nimport { stashAuthStore, wasStoreLoaded } from \"./auth/store-handoff.js\";\nimport {\n createPowerhouseRouter,\n createPublishHook,\n createUnpublishHook,\n} from \"./middleware.js\";\nimport { NotificationManager } from \"./notifications/manager.js\";\nimport { SSEChannel } from \"./notifications/sse.js\";\nimport { WebhookChannel } from \"./notifications/webhook.js\";\nimport type { RegistryCommandArgs, RegistryConfig } from \"./types.js\";\nimport { buildVerdaccioConfig } from \"./verdaccio-config.js\";\n\nasync function resolveDir(dir: string): Promise<string> {\n if (path.isAbsolute(dir)) {\n await mkdir(dir, { recursive: true });\n return dir;\n }\n const found = await findUp(dir, { type: \"directory\" });\n if (!found) {\n await mkdir(dir, { recursive: true });\n return dir;\n }\n return found;\n}\n\nexport async function runRegistry(args: RegistryCommandArgs) {\n const {\n port,\n storageDir,\n cdnCacheDir,\n uplink,\n uplinkMaxage,\n webEnabled,\n webhooks,\n s3AccessKeyId,\n s3Bucket,\n s3Endpoint,\n s3ForcePathStyle,\n s3KeyPrefix,\n s3Region,\n s3SecretAccessKey,\n publicUrl,\n authRenown,\n renownUrl,\n verdaccioSecret: verdaccioSecretArg,\n localPackages,\n databaseUrl,\n pluginsDir,\n authStore,\n } = args;\n const storagePath = await resolveDir(storageDir);\n const cdnCachePath = await resolveDir(cdnCacheDir);\n\n // Per-pod random verdaccio JWT secret. The verdaccio-format token we mint\n // in the renown middleware never leaves the pod (it's swapped into the\n // request before verdaccio sees it), so a per-pod secret is sufficient.\n // An override is exposed for tests / multi-pod behaviors that depend on\n // shared verdaccio JWTs.\n const verdaccioSecret = verdaccioSecretArg ?? randomBytes(32).toString(\"hex\");\n\n // Renown auth turns on when the operator both opts in (`--auth-renown`,\n // default true via the CLI flag) and has set --public-url for the audience\n // claim. Tests / programmatic users that don't pass either keep the legacy\n // unsigned/htpasswd path with no warning.\n const renownEnabled = authRenown === true && Boolean(publicUrl);\n if (authRenown === true && !publicUrl) {\n console.warn(\n \"[registry] auth-renown is enabled but --public-url / PH_REGISTRY_PUBLIC_URL is not set; Renown auth will be disabled.\",\n );\n }\n // Renown auth is served by the registry-auth plugin, which loads only with a\n // database (it also holds ownership). Without one, renown can't engage.\n if (renownEnabled && !databaseUrl && !authStore) {\n console.warn(\n \"[registry] Renown auth requires a database (--database-url) for the auth plugin; renown will be inactive.\",\n );\n }\n\n console.log({\n storagePath,\n cdnCachePath,\n });\n\n const webhookConfigs = webhooks\n ?.split(\",\")\n .map((url) => url.trim())\n .filter(Boolean)\n .map((endpoint) => ({ endpoint }));\n\n const localPackagePatterns = localPackages\n ?.split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n\n // One AuthStore, shared by the verdaccio auth plugin and the /packages owner\n // enrichment — a single Postgres pool per process (injected store wins).\n const sharedAuthStore: AuthStore | undefined =\n (authStore as AuthStore | undefined) ??\n (databaseUrl ? createPgStore(createPgPool(databaseUrl)) : undefined);\n // Token carries the store through verdaccio's plugin config; we later assert\n // the plugin loaded it, so a configured-but-broken auth setup fails to boot.\n const authStoreToken = sharedAuthStore\n ? stashAuthStore(sharedAuthStore)\n : undefined;\n\n const config: RegistryConfig = {\n port,\n storagePath,\n cdnCachePath,\n uplink,\n uplinkMaxage,\n webEnabled,\n verdaccioSecret,\n ...(localPackagePatterns?.length ? { localPackagePatterns } : {}),\n ...(renownEnabled && publicUrl\n ? { renown: { publicUrl, ...(renownUrl ? { renownUrl } : {}) } }\n : {}),\n ...(webhookConfigs?.length && {\n notify: { webhooks: webhookConfigs },\n }),\n ...(s3Bucket &&\n s3Endpoint &&\n s3Region && {\n s3: {\n bucket: s3Bucket,\n endpoint: s3Endpoint,\n region: s3Region,\n accessKeyId: s3AccessKeyId,\n secretAccessKey: s3SecretAccessKey,\n keyPrefix: s3KeyPrefix,\n s3ForcePathStyle,\n },\n }),\n ...(databaseUrl ? { databaseUrl } : {}),\n ...(pluginsDir ? { pluginsDir } : {}),\n ...(sharedAuthStore ? { authStore: sharedAuthStore } : {}),\n ...(authStoreToken ? { authStoreToken } : {}),\n };\n\n if (config.databaseUrl || config.authStore) {\n console.log(\n \"[registry] Postgres-backed auth plugin active (persistent accounts + package ownership)\",\n );\n }\n // Ensure directories exist (for relative paths resolved via findUp)\n await mkdir(storagePath, { recursive: true });\n await mkdir(cdnCachePath, { recursive: true });\n\n const verdaccioConfig = buildVerdaccioConfig(config);\n\n // verdaccio's runServer returns Promise<any> (upstream type limitation)\n const verdaccioServer = (await runServer(verdaccioConfig)) as Server;\n\n // Fail fast: a configured auth store that the plugin never loaded means\n // verdaccio silently fell back to no auth — refuse to run without ownership.\n if (authStoreToken && !wasStoreLoaded(authStoreToken)) {\n verdaccioServer.close();\n throw new Error(\n \"registry-auth plugin failed to load despite a configured database/auth store; refusing to start without auth and package-ownership enforcement.\",\n );\n }\n const verdaccioHandler = verdaccioServer.listeners(\"request\")[0] as (\n ...args: unknown[]\n ) => void;\n\n const app = express();\n\n const sseChannel = new SSEChannel();\n const webhookChannel = new WebhookChannel(config.storagePath, config.notify);\n const notifications = new NotificationManager([sseChannel, webhookChannel]);\n\n // Serve static assets (logo, etc.)\n const staticDir = await findUp(\"static\", { type: \"directory\" });\n if (staticDir) {\n app.use(\"/-/static\", express.static(staticDir));\n }\n\n // Our routes take priority over Verdaccio\n app.use(\n createPowerhouseRouter(config, sseChannel, webhookChannel, sharedAuthStore),\n );\n\n app.use(createPublishHook(config, notifications));\n app.use(createUnpublishHook(config, notifications));\n\n // Verdaccio handles everything else (npm protocol, web UI, auth)\n app.use((req, res) => verdaccioHandler(req, res));\n\n const server = app.listen(port, () => {\n console.log(`Powerhouse Registry running on http://localhost:${port}`);\n console.log(` CDN: http://localhost:${port}/-/cdn/`);\n console.log(` Packages: http://localhost:${port}/packages`);\n console.log(` npm: http://localhost:${port}/`);\n console.log(` Storage: ${storagePath}`);\n console.log(` CDN cache: ${cdnCachePath}`);\n if (config.s3) {\n console.log(` S3: ${config.s3.endpoint}/${config.s3.bucket}`);\n }\n if (config.renown) {\n console.log(` Renown auth: ${config.renown.publicUrl}`);\n }\n });\n\n return server;\n}\n","import {\n binary,\n command,\n flag,\n number,\n option,\n optional,\n run,\n string,\n} from \"cmd-ts\";\nimport {\n DEFAULT_PORT,\n DEFAULT_REGISTRY_CDN_CACHE_DIR_NAME,\n DEFAULT_STORAGE_DIR_NAME,\n} from \"./src/constants.js\";\nimport { runRegistry } from \"./src/run.js\";\n\nexport const registryCommand = command({\n name: \"Package registry\",\n args: {\n port: option({\n long: \"port\",\n type: number,\n defaultValue: () => Number(process.env.PORT) || DEFAULT_PORT,\n defaultValueIsSerializable: true,\n }),\n storageDir: option({\n long: \"storage-dir\",\n type: string,\n defaultValue: () =>\n process.env.REGISTRY_STORAGE || DEFAULT_STORAGE_DIR_NAME,\n defaultValueIsSerializable: true,\n }),\n cdnCacheDir: option({\n long: \"cdn-cache-dir\",\n type: string,\n defaultValue: () =>\n process.env.REGISTRY_CDN_CACHE || DEFAULT_REGISTRY_CDN_CACHE_DIR_NAME,\n defaultValueIsSerializable: true,\n }),\n uplink: option({\n long: \"uplink\",\n type: optional(string),\n defaultValue: () => process.env.REGISTRY_UPLINK,\n defaultValueIsSerializable: true,\n }),\n uplinkMaxage: option({\n long: \"uplink-maxage\",\n type: optional(string),\n description:\n \"How long verdaccio caches npmjs uplink metadata before refetching. \" +\n \"Accepts verdaccio time strings (e.g. '30s', '2m', '1h'). \" +\n \"Default '2m' matches verdaccio upstream — shortens the publish-to-\" +\n \"install propagation window in dev. Bump for production deployments \" +\n \"that want to reduce npmjs load.\",\n defaultValue: () => process.env.PH_REGISTRY_UPLINK_MAXAGE,\n defaultValueIsSerializable: true,\n }),\n s3Bucket: option({\n long: \"s3-bucket\",\n type: optional(string),\n defaultValue: () => process.env.S3_BUCKET,\n defaultValueIsSerializable: true,\n }),\n s3Endpoint: option({\n long: \"s3-endpoint\",\n type: optional(string),\n defaultValue: () => process.env.S3_ENDPOINT,\n defaultValueIsSerializable: true,\n }),\n s3Region: option({\n long: \"s3-region\",\n type: optional(string),\n defaultValue: () => process.env.S3_REGION,\n defaultValueIsSerializable: true,\n }),\n s3AccessKeyId: option({\n long: \"s3-access-key-id\",\n type: optional(string),\n defaultValue: () => process.env.S3_ACCESS_KEY_ID,\n defaultValueIsSerializable: true,\n }),\n s3SecretAccessKey: option({\n long: \"s3-secret-access-key\",\n type: optional(string),\n defaultValue: () => process.env.S3_SECRET_ACCESS_KEY,\n defaultValueIsSerializable: true,\n }),\n s3KeyPrefix: option({\n long: \"s3-key-prefix\",\n type: optional(string),\n defaultValue: () => process.env.S3_KEY_PREFIX,\n defaultValueIsSerializable: true,\n }),\n s3ForcePathStyle: flag({\n long: \"s3-force-path-style\",\n defaultValue: () => process.env.S3_FORCE_PATH_STYLE !== \"false\",\n defaultValueIsSerializable: true,\n }),\n webEnabled: flag({\n long: \"web-enabled\",\n defaultValue: () => process.env.REGISTRY_WEB !== \"false\",\n defaultValueIsSerializable: true,\n }),\n webhooks: option({\n long: \"webhook\",\n type: optional(string),\n description: \"Comma-separated webhook URLs to notify on publish\",\n defaultValue: () => process.env.REGISTRY_WEBHOOKS,\n defaultValueIsSerializable: true,\n }),\n publicUrl: option({\n long: \"public-url\",\n type: optional(string),\n description:\n \"Public origin of this registry (used as the JWT `aud` claim for Renown bearer tokens). Required when --auth-renown is true.\",\n defaultValue: () => process.env.PH_REGISTRY_PUBLIC_URL,\n defaultValueIsSerializable: true,\n }),\n authRenown: flag({\n long: \"auth-renown\",\n description:\n \"Verify Renown-signed bearer tokens in front of verdaccio (stateless). Disabled when --public-url is unset.\",\n defaultValue: () => process.env.PH_REGISTRY_AUTH_RENOWN === \"true\",\n defaultValueIsSerializable: true,\n }),\n renownUrl: option({\n long: \"renown-url\",\n type: optional(string),\n description:\n \"Renown service base URL for credential verification. Defaults to https://www.renown.id.\",\n defaultValue: () => process.env.PH_REGISTRY_RENOWN_URL,\n defaultValueIsSerializable: true,\n }),\n verdaccioSecret: option({\n long: \"verdaccio-secret\",\n type: optional(string),\n description:\n \"Override verdaccio's internal JWT signing secret. Default: random per pod (fine — the swapped JWT never leaves this process).\",\n defaultValue: () => process.env.PH_REGISTRY_VERDACCIO_SECRET,\n defaultValueIsSerializable: true,\n }),\n localPackages: option({\n long: \"local-packages\",\n type: optional(string),\n description:\n \"Comma-separated globs (e.g. '@powerhousedao/*,document-model,ph-cmd') served locally only — no npmjs uplink proxy. Lets you re-publish a workspace package whose version already exists on npmjs without bumping.\",\n defaultValue: () => process.env.PH_REGISTRY_LOCAL_PACKAGES,\n defaultValueIsSerializable: true,\n }),\n databaseUrl: option({\n long: \"database-url\",\n type: optional(string),\n description:\n \"Postgres connection string. When set, the registry uses the DB-backed auth plugin (persistent accounts + npm-style package ownership) instead of the built-in htpasswd.\",\n defaultValue: () =>\n process.env.PH_REGISTRY_DATABASE_URL ?? process.env.DATABASE_URL,\n defaultValueIsSerializable: true,\n }),\n },\n handler: async (args) => {\n // Redact secrets — this object otherwise leaks the DB password / signing\n // secret into logs.\n const redact = (v?: string) => (v ? \"[redacted]\" : undefined);\n console.log({\n ...args,\n databaseUrl: redact(args.databaseUrl),\n verdaccioSecret: redact(args.verdaccioSecret),\n s3SecretAccessKey: redact(args.s3SecretAccessKey),\n });\n\n try {\n await runRegistry(args);\n } catch (error) {\n console.error(\"Failed to start registry:\");\n console.error(error);\n process.exit(1);\n }\n },\n});\n\nconst registryCli = binary(registryCommand);\n\nawait run(registryCli, process.argv);\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/registry",
3
- "version": "6.2.3-dev.16",
3
+ "version": "6.2.3-dev.18",
4
4
  "description": "Powerhouse package registry — resolves and manages document model package dependencies within the Powerhouse ecosystem.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "author": "",
30
30
  "license": "ISC",
31
31
  "dependencies": {
32
- "@powerhousedao/shared": "6.2.3-dev.16",
33
- "@renown/sdk": "6.2.3-dev.16",
32
+ "@powerhousedao/shared": "6.2.3-dev.18",
33
+ "@renown/sdk": "6.2.3-dev.18",
34
34
  "@verdaccio/core": "8.0.0-next-8.37",
35
35
  "@verdaccio/signature": "8.0.0-next-8.29",
36
36
  "bcryptjs": "^2.4.3",