@powerhousedao/registry 6.2.3-dev.30 → 6.2.3-dev.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/cli.mjs +279 -66
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -32,6 +32,36 @@ If the package is not published locally, Verdaccio transparently proxies metadat
|
|
|
32
32
|
|
|
33
33
|
Responses carry caching headers keyed on the request shape. Version-pinned requests (`<pkg>@1.2.3/...`) are served `Cache-Control: public, max-age=31536000, immutable`; moving requests (a dist-tag like `@dev`/`@latest`, or untagged) get `public, max-age=60, must-revalidate`. A version-derived weak `ETag` is sent, and a matching `If-None-Match` returns `304`. When the upstream metadata lookup fails (as opposed to a genuine not-found), the endpoint serves the latest cached version if present, otherwise responds `503` rather than a cacheable `404`.
|
|
34
34
|
|
|
35
|
+
### Pieces
|
|
36
|
+
|
|
37
|
+
A reactor's workflow runtime reads pieces from these endpoints, ahead of the
|
|
38
|
+
Activepieces catalogue.
|
|
39
|
+
|
|
40
|
+
#### `GET /pieces`
|
|
41
|
+
|
|
42
|
+
The catalog of pieces that published packages ship, in the shape of
|
|
43
|
+
cloud.activepieces.com's list endpoint. `?suggestionType=ACTION_AND_TRIGGER`
|
|
44
|
+
adds suggested actions and triggers.
|
|
45
|
+
|
|
46
|
+
#### `GET /pieces/<name>` and `GET /pieces/<name>?version=<v>`
|
|
47
|
+
|
|
48
|
+
One piece's detail: the latest version, or the one named. An unknown version
|
|
49
|
+
returns `404` with `available`, the versions the registry has.
|
|
50
|
+
|
|
51
|
+
#### `GET /pieces/<name>/versions`
|
|
52
|
+
|
|
53
|
+
Every version of the piece: `[{ version, packageVersion, publishedAt }]`. A
|
|
54
|
+
piece's version is always the version of the package that ships it.
|
|
55
|
+
|
|
56
|
+
#### `GET /-/pieces/bundled/<file>.tgz`
|
|
57
|
+
|
|
58
|
+
The piece directory as an npm-shaped tarball, the file a reactor's piece worker
|
|
59
|
+
downloads. The file name is the piece name with `/` replaced by `-`, then
|
|
60
|
+
`-<version>`, as cdn.activepieces.com names its own. It is served immutable.
|
|
61
|
+
|
|
62
|
+
Piece names under `@activepieces/` are reserved: a package that claims one is
|
|
63
|
+
refused.
|
|
64
|
+
|
|
35
65
|
### Publish Notifications
|
|
36
66
|
|
|
37
67
|
When a package is published, the registry can notify subscribers in real time via Server-Sent Events (SSE) and webhooks.
|
package/dist/cli.mjs
CHANGED
|
@@ -108,27 +108,48 @@ function wasStoreLoaded(token) {
|
|
|
108
108
|
}
|
|
109
109
|
//#endregion
|
|
110
110
|
//#region src/semver.ts
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
function splitVersion(version) {
|
|
112
|
+
const withoutBuild = version.split("+", 1)[0];
|
|
113
|
+
const dash = withoutBuild.indexOf("-");
|
|
114
|
+
const core = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
|
|
115
|
+
const pre = dash === -1 ? "" : withoutBuild.slice(dash + 1);
|
|
116
|
+
return {
|
|
117
|
+
core: core.split(".").map(Number),
|
|
118
|
+
pre: pre ? pre.split(".") : []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const NUMERIC = /^\d+$/;
|
|
122
|
+
function comparePrerelease(a, b) {
|
|
123
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
124
|
+
if (i >= a.length) return -1;
|
|
125
|
+
if (i >= b.length) return 1;
|
|
126
|
+
const x = a[i];
|
|
127
|
+
const y = b[i];
|
|
128
|
+
const xNum = NUMERIC.test(x);
|
|
129
|
+
const yNum = NUMERIC.test(y);
|
|
130
|
+
if (xNum && yNum) {
|
|
131
|
+
const diff = Number(x) - Number(y);
|
|
132
|
+
if (diff !== 0) return diff;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (xNum) return -1;
|
|
136
|
+
if (yNum) return 1;
|
|
137
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
/** Semver sort order: negative if a < b, positive if a > b, 0 if equal. */
|
|
118
142
|
function compareSemver(a, b) {
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const na = partsA[i] ?? 0;
|
|
125
|
-
const nb = partsB[i] ?? 0;
|
|
143
|
+
const va = splitVersion(a);
|
|
144
|
+
const vb = splitVersion(b);
|
|
145
|
+
for (let i = 0; i < Math.max(va.core.length, vb.core.length); i++) {
|
|
146
|
+
const na = va.core[i] ?? 0;
|
|
147
|
+
const nb = vb.core[i] ?? 0;
|
|
126
148
|
if (na !== nb) return na - nb;
|
|
127
149
|
}
|
|
128
|
-
if (
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
return 0;
|
|
150
|
+
if (va.pre.length === 0 && vb.pre.length > 0) return 1;
|
|
151
|
+
if (va.pre.length > 0 && vb.pre.length === 0) return -1;
|
|
152
|
+
return comparePrerelease(va.pre, vb.pre);
|
|
132
153
|
}
|
|
133
154
|
//#endregion
|
|
134
155
|
//#region src/cdn.ts
|
|
@@ -167,12 +188,15 @@ function isExactVersion(tag) {
|
|
|
167
188
|
return !!tag && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(tag);
|
|
168
189
|
}
|
|
169
190
|
var CdnCache = class {
|
|
170
|
-
registryUrl;
|
|
171
191
|
cdnCachePath;
|
|
172
192
|
#extractionLocks = /* @__PURE__ */ new Map();
|
|
193
|
+
#registryUrl;
|
|
173
194
|
constructor(registryUrl, cdnCachePath) {
|
|
174
|
-
this.registryUrl = registryUrl;
|
|
175
195
|
this.cdnCachePath = cdnCachePath;
|
|
196
|
+
this.#registryUrl = registryUrl;
|
|
197
|
+
}
|
|
198
|
+
get registryUrl() {
|
|
199
|
+
return typeof this.#registryUrl === "function" ? this.#registryUrl() : this.#registryUrl;
|
|
176
200
|
}
|
|
177
201
|
async getFileByVersion(packageName, version, filePath) {
|
|
178
202
|
const versionDir = path.join(this.cdnCachePath, packageName, version);
|
|
@@ -358,9 +382,15 @@ function readPackageMetadata(storagePath, packageName) {
|
|
|
358
382
|
const distTags = parsed["dist-tags"];
|
|
359
383
|
const versions = (parsed.versions ? Object.keys(parsed.versions) : []).slice().sort(compareSemver);
|
|
360
384
|
const locallyPublished = !!parsed._attachments && Object.keys(parsed._attachments).length > 0;
|
|
385
|
+
const time = {};
|
|
386
|
+
for (const version of versions) {
|
|
387
|
+
const at = parsed.time?.[version];
|
|
388
|
+
if (typeof at === "string") time[version] = at;
|
|
389
|
+
}
|
|
361
390
|
return {
|
|
362
391
|
distTags: distTags && Object.keys(distTags).length > 0 ? distTags : void 0,
|
|
363
392
|
versions: versions.length > 0 ? versions : void 0,
|
|
393
|
+
time,
|
|
364
394
|
locallyPublished
|
|
365
395
|
};
|
|
366
396
|
} catch {
|
|
@@ -396,16 +426,19 @@ function readPackageJsonVersion(dir) {
|
|
|
396
426
|
return;
|
|
397
427
|
}
|
|
398
428
|
}
|
|
399
|
-
|
|
429
|
+
/** Extracted version directories of a package, oldest first. */
|
|
430
|
+
function listVersionDirs(pkgDir) {
|
|
400
431
|
let entries;
|
|
401
432
|
try {
|
|
402
433
|
entries = fs.readdirSync(pkgDir, { withFileTypes: true });
|
|
403
434
|
} catch {
|
|
404
|
-
return
|
|
435
|
+
return [];
|
|
405
436
|
}
|
|
406
|
-
|
|
437
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort(compareSemver);
|
|
438
|
+
}
|
|
439
|
+
function getLatestVersionDir(pkgDir) {
|
|
440
|
+
const versions = listVersionDirs(pkgDir);
|
|
407
441
|
if (versions.length === 0) return null;
|
|
408
|
-
versions.sort(compareSemver);
|
|
409
442
|
return path.join(pkgDir, versions[versions.length - 1]);
|
|
410
443
|
}
|
|
411
444
|
function loadPackage(cdnCachePath, name, version, storagePath) {
|
|
@@ -454,18 +487,23 @@ function getDocumentTypesFromManifest(manifest) {
|
|
|
454
487
|
}
|
|
455
488
|
function readPackageDir(cdnCachePath, dirName, storagePath) {
|
|
456
489
|
const pkgDir = path.join(cdnCachePath, dirName);
|
|
457
|
-
const
|
|
490
|
+
const versionDirs = listVersionDirs(pkgDir);
|
|
491
|
+
const latest = versionDirs.at(-1);
|
|
492
|
+
const manifestDir = latest ? path.join(pkgDir, latest) : pkgDir;
|
|
458
493
|
const manifest = readManifest(manifestDir);
|
|
459
494
|
const name = manifest?.name || dirName;
|
|
460
|
-
const { distTags, versions, locallyPublished } = readPackageMetadata(storagePath, name);
|
|
495
|
+
const { distTags, versions, time, locallyPublished } = readPackageMetadata(storagePath, name);
|
|
461
496
|
return {
|
|
462
497
|
dirName,
|
|
498
|
+
packageDir: pkgDir,
|
|
463
499
|
manifestDir,
|
|
500
|
+
versionDirs,
|
|
464
501
|
manifest,
|
|
465
502
|
name,
|
|
466
503
|
version: readPackageJsonVersion(manifestDir),
|
|
467
504
|
distTags,
|
|
468
505
|
versions,
|
|
506
|
+
time,
|
|
469
507
|
locallyPublished
|
|
470
508
|
};
|
|
471
509
|
}
|
|
@@ -522,12 +560,17 @@ function findPackagesByDocumentType(packagesDir, documentType) {
|
|
|
522
560
|
//#endregion
|
|
523
561
|
//#region src/pieces.ts
|
|
524
562
|
const gunzip$1 = promisify(gunzip);
|
|
563
|
+
/** Piece names in this scope belong to Activepieces; no package may claim one. */
|
|
564
|
+
const RESERVED_PIECE_SCOPE = "@activepieces/";
|
|
525
565
|
const INDEX_TTL_MS = 3e4;
|
|
526
566
|
const indexes = /* @__PURE__ */ new Map();
|
|
567
|
+
const versionPieces = /* @__PURE__ */ new Map();
|
|
527
568
|
const descriptors = /* @__PURE__ */ new Map();
|
|
569
|
+
const materializing = /* @__PURE__ */ new Map();
|
|
528
570
|
/** Drop the cached index and descriptors — a publish or unpublish moved them. */
|
|
529
571
|
function invalidatePieceIndex() {
|
|
530
572
|
indexes.clear();
|
|
573
|
+
versionPieces.clear();
|
|
531
574
|
descriptors.clear();
|
|
532
575
|
}
|
|
533
576
|
function indexKey(cdnCachePath, storagePath) {
|
|
@@ -536,34 +579,77 @@ function indexKey(cdnCachePath, storagePath) {
|
|
|
536
579
|
function piecesOf(manifest) {
|
|
537
580
|
return manifest?.pieces ?? [];
|
|
538
581
|
}
|
|
539
|
-
function
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
582
|
+
function piecesAt(versionDir) {
|
|
583
|
+
const hit = versionPieces.get(versionDir);
|
|
584
|
+
if (hit) return hit;
|
|
585
|
+
if (!fs.existsSync(path.join(versionDir, "package.json"))) return [];
|
|
586
|
+
const manifest = readManifest(versionDir);
|
|
587
|
+
if (!manifest) return [];
|
|
588
|
+
const pieces = piecesOf(manifest);
|
|
589
|
+
versionPieces.set(versionDir, pieces);
|
|
590
|
+
return pieces;
|
|
591
|
+
}
|
|
592
|
+
function toIndexEntry(piece, packageName, packageVersion, packageDir, publishedAt) {
|
|
593
|
+
const { id, bundle } = piece;
|
|
594
|
+
if (!id || !bundle) return null;
|
|
542
595
|
return {
|
|
543
596
|
name: id,
|
|
544
597
|
displayName: piece.name || id,
|
|
545
598
|
...piece.description ? { description: piece.description } : {},
|
|
546
|
-
version,
|
|
599
|
+
version: packageVersion,
|
|
547
600
|
packageName,
|
|
548
601
|
packageVersion,
|
|
549
602
|
packageDir,
|
|
550
603
|
bundle,
|
|
551
|
-
descriptor: piece.descriptor ?? `${bundle}/descriptor.json
|
|
604
|
+
descriptor: piece.descriptor ?? `${bundle}/descriptor.json`,
|
|
605
|
+
...publishedAt ? { publishedAt } : {}
|
|
552
606
|
};
|
|
553
607
|
}
|
|
608
|
+
function indexVersion(index, pkg, version) {
|
|
609
|
+
const versionDir = path.join(pkg.dir, version);
|
|
610
|
+
const held = [];
|
|
611
|
+
for (const piece of piecesAt(versionDir)) {
|
|
612
|
+
const entry = toIndexEntry(piece, pkg.name, version, versionDir, pkg.publishedAt[version]);
|
|
613
|
+
if (!entry) continue;
|
|
614
|
+
let line = index.get(entry.name);
|
|
615
|
+
if (line && line.packageName !== pkg.name) {
|
|
616
|
+
console.warn(`[registry] piece "${entry.name}" is claimed by both ${line.packageName} and ${pkg.name}; keeping ${line.packageName}`);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (!line) {
|
|
620
|
+
line = {
|
|
621
|
+
packageName: pkg.name,
|
|
622
|
+
versions: /* @__PURE__ */ new Map(),
|
|
623
|
+
pending: /* @__PURE__ */ new Set(),
|
|
624
|
+
publishedAt: pkg.publishedAt
|
|
625
|
+
};
|
|
626
|
+
index.set(entry.name, line);
|
|
627
|
+
}
|
|
628
|
+
line.versions.set(version, entry);
|
|
629
|
+
held.push(entry.name);
|
|
630
|
+
}
|
|
631
|
+
return held;
|
|
632
|
+
}
|
|
554
633
|
function buildPieceIndex(cdnCachePath, storagePath) {
|
|
555
634
|
const index = /* @__PURE__ */ new Map();
|
|
556
|
-
for (const
|
|
557
|
-
if (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
index
|
|
635
|
+
for (const scanned of scanPackageDirs(cdnCachePath, storagePath)) {
|
|
636
|
+
if (scanned.locallyPublished === false) continue;
|
|
637
|
+
const pkg = {
|
|
638
|
+
name: scanned.name,
|
|
639
|
+
dir: scanned.packageDir,
|
|
640
|
+
publishedAt: scanned.time ?? {}
|
|
641
|
+
};
|
|
642
|
+
const names = /* @__PURE__ */ new Set();
|
|
643
|
+
const extracted = /* @__PURE__ */ new Set();
|
|
644
|
+
for (const version of scanned.versionDirs) {
|
|
645
|
+
const held = indexVersion(index, pkg, version);
|
|
646
|
+
if (fs.existsSync(path.join(pkg.dir, version, "package.json"))) extracted.add(version);
|
|
647
|
+
for (const name of held) names.add(name);
|
|
648
|
+
}
|
|
649
|
+
const pending = (scanned.versions ?? []).filter((v) => !extracted.has(v));
|
|
650
|
+
for (const name of names) {
|
|
651
|
+
const line = index.get(name);
|
|
652
|
+
for (const version of pending) line?.pending.add(version);
|
|
567
653
|
}
|
|
568
654
|
}
|
|
569
655
|
return index;
|
|
@@ -580,8 +666,77 @@ function pieceIndex(cdnCachePath, storagePath) {
|
|
|
580
666
|
});
|
|
581
667
|
return index;
|
|
582
668
|
}
|
|
669
|
+
/** The highest extracted version of a piece. */
|
|
670
|
+
function latestOf(line) {
|
|
671
|
+
let latest;
|
|
672
|
+
for (const entry of line.versions.values()) if (!latest || compareSemver(entry.version, latest.version) > 0) latest = entry;
|
|
673
|
+
return latest;
|
|
674
|
+
}
|
|
675
|
+
/** The latest version of a piece. */
|
|
583
676
|
function findPiece(cdnCachePath, name, storagePath) {
|
|
584
|
-
|
|
677
|
+
const line = pieceIndex(cdnCachePath, storagePath).get(name);
|
|
678
|
+
return line ? latestOf(line) : void 0;
|
|
679
|
+
}
|
|
680
|
+
async function materialize(extractor, cdnCachePath, index, line, version) {
|
|
681
|
+
const key = `${line.packageName}@${version}`;
|
|
682
|
+
const running = materializing.get(key);
|
|
683
|
+
if (running) return running;
|
|
684
|
+
const started = (async () => {
|
|
685
|
+
try {
|
|
686
|
+
await extractor.getFileByVersion(line.packageName, version, "package.json");
|
|
687
|
+
} catch (error) {
|
|
688
|
+
console.error(`[registry] failed to extract ${key}:`, error);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const dir = path.join(cdnCachePath, line.packageName);
|
|
692
|
+
if (!fs.existsSync(path.join(dir, version, "package.json"))) return;
|
|
693
|
+
indexVersion(index, {
|
|
694
|
+
name: line.packageName,
|
|
695
|
+
dir,
|
|
696
|
+
publishedAt: line.publishedAt
|
|
697
|
+
}, version);
|
|
698
|
+
for (const other of index.values()) if (other.packageName === line.packageName) other.pending.delete(version);
|
|
699
|
+
})().finally(() => materializing.delete(key));
|
|
700
|
+
materializing.set(key, started);
|
|
701
|
+
return started;
|
|
702
|
+
}
|
|
703
|
+
async function materializeAll(extractor, cdnCachePath, index, line) {
|
|
704
|
+
await Promise.all([...line.pending].map((version) => materialize(extractor, cdnCachePath, index, line, version)));
|
|
705
|
+
}
|
|
706
|
+
function newestFirst(line) {
|
|
707
|
+
return [...line.versions.values()].sort((a, b) => compareSemver(b.version, a.version));
|
|
708
|
+
}
|
|
709
|
+
/** Every version of a piece, newest first; undefined for an unknown name. */
|
|
710
|
+
async function pieceVersions(extractor, cdnCachePath, name, storagePath) {
|
|
711
|
+
const index = pieceIndex(cdnCachePath, storagePath);
|
|
712
|
+
const line = index.get(name);
|
|
713
|
+
if (!line) return void 0;
|
|
714
|
+
await materializeAll(extractor, cdnCachePath, index, line);
|
|
715
|
+
return newestFirst(line);
|
|
716
|
+
}
|
|
717
|
+
function versionSummary(entry) {
|
|
718
|
+
return {
|
|
719
|
+
version: entry.version,
|
|
720
|
+
packageVersion: entry.packageVersion,
|
|
721
|
+
publishedAt: entry.publishedAt ?? null
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
/** One exact version of a piece, extracting its package version if needed. */
|
|
725
|
+
async function findPieceVersion(extractor, cdnCachePath, name, version, storagePath) {
|
|
726
|
+
const index = pieceIndex(cdnCachePath, storagePath);
|
|
727
|
+
const line = index.get(name);
|
|
728
|
+
if (!line) return { kind: "unknown-piece" };
|
|
729
|
+
if (!line.versions.has(version) && line.pending.has(version)) await materialize(extractor, cdnCachePath, index, line, version);
|
|
730
|
+
const entry = line.versions.get(version);
|
|
731
|
+
if (entry) return {
|
|
732
|
+
kind: "found",
|
|
733
|
+
entry
|
|
734
|
+
};
|
|
735
|
+
await materializeAll(extractor, cdnCachePath, index, line);
|
|
736
|
+
return {
|
|
737
|
+
kind: "unknown-version",
|
|
738
|
+
available: newestFirst(line).map((e) => e.version)
|
|
739
|
+
};
|
|
585
740
|
}
|
|
586
741
|
function insidePackage(entry, relative) {
|
|
587
742
|
const root = path.resolve(entry.packageDir);
|
|
@@ -638,19 +793,20 @@ function catalogEntry(entry, descriptor, suggestions) {
|
|
|
638
793
|
} : {}
|
|
639
794
|
};
|
|
640
795
|
}
|
|
641
|
-
/** The catalog, sorted by display name
|
|
796
|
+
/** The catalog of latest versions, sorted by display name. */
|
|
642
797
|
function pieceCatalog(cdnCachePath, options = {}) {
|
|
643
798
|
const entries = [];
|
|
644
|
-
for (const
|
|
645
|
-
const
|
|
646
|
-
|
|
799
|
+
for (const line of pieceIndex(cdnCachePath, options.storagePath).values()) {
|
|
800
|
+
const entry = latestOf(line);
|
|
801
|
+
const descriptor = entry ? readDescriptor(entry) : null;
|
|
802
|
+
if (!entry || !descriptor) continue;
|
|
647
803
|
entries.push(catalogEntry(entry, descriptor, options.suggestions === true));
|
|
648
804
|
}
|
|
649
805
|
return entries.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
650
806
|
}
|
|
651
|
-
/** The CDN path of a file inside the package that ships the piece. */
|
|
807
|
+
/** The CDN path of a file inside the package version that ships the piece. */
|
|
652
808
|
function cdnPath(entry, relative) {
|
|
653
|
-
return `/-/cdn/${entry.
|
|
809
|
+
return `/-/cdn/${entry.packageName}@${entry.packageVersion}/${relative}`;
|
|
654
810
|
}
|
|
655
811
|
/** The tarball filename cdn.activepieces.com would use for the same piece. */
|
|
656
812
|
function pieceTarballName(name, version) {
|
|
@@ -659,7 +815,7 @@ function pieceTarballName(name, version) {
|
|
|
659
815
|
function pieceTarballPath(entry) {
|
|
660
816
|
return `/-/pieces/bundled/${pieceTarballName(entry.name, entry.version)}`;
|
|
661
817
|
}
|
|
662
|
-
/** One piece: what provides it, and where its descriptor and bundle are. */
|
|
818
|
+
/** One piece version: what provides it, and where its descriptor and bundle are. */
|
|
663
819
|
function pieceDetail(entry, origin) {
|
|
664
820
|
const descriptor = readDescriptor(entry);
|
|
665
821
|
if (!descriptor) return null;
|
|
@@ -673,8 +829,17 @@ function pieceDetail(entry, origin) {
|
|
|
673
829
|
bundleUrl: `${origin}${pieceTarballPath(entry)}`
|
|
674
830
|
};
|
|
675
831
|
}
|
|
676
|
-
function findPieceByTarball(cdnCachePath, filename, storagePath) {
|
|
677
|
-
|
|
832
|
+
async function findPieceByTarball(extractor, cdnCachePath, filename, storagePath) {
|
|
833
|
+
if (!filename.endsWith(".tgz")) return void 0;
|
|
834
|
+
const stem = filename.slice(0, -4);
|
|
835
|
+
for (const name of pieceIndex(cdnCachePath, storagePath).keys()) {
|
|
836
|
+
const prefix = `${name.replace("/", "-")}-`;
|
|
837
|
+
if (!stem.startsWith(prefix)) continue;
|
|
838
|
+
const version = stem.slice(prefix.length);
|
|
839
|
+
if (!isExactVersion(version)) continue;
|
|
840
|
+
const found = await findPieceVersion(extractor, cdnCachePath, name, version, storagePath);
|
|
841
|
+
if (found.kind === "found") return found.entry;
|
|
842
|
+
}
|
|
678
843
|
}
|
|
679
844
|
function listFiles(dir, prefix = "") {
|
|
680
845
|
const files = [];
|
|
@@ -776,7 +941,7 @@ function createWarmer(config, cdn) {
|
|
|
776
941
|
let warmInFlight = false;
|
|
777
942
|
let lastWarmAt = 0;
|
|
778
943
|
return async function warm() {
|
|
779
|
-
if (warmInFlight) return;
|
|
944
|
+
if (warmInFlight || config.port === 0) return;
|
|
780
945
|
if (Date.now() - lastWarmAt < WARM_INTERVAL_MS) return;
|
|
781
946
|
warmInFlight = true;
|
|
782
947
|
try {
|
|
@@ -840,6 +1005,15 @@ const MIME_TYPES = {
|
|
|
840
1005
|
".html": "text/html",
|
|
841
1006
|
".svg": "image/svg+xml"
|
|
842
1007
|
};
|
|
1008
|
+
function localUrl(config) {
|
|
1009
|
+
return () => `http://localhost:${config.port}`;
|
|
1010
|
+
}
|
|
1011
|
+
function versionsRouteName(raw) {
|
|
1012
|
+
if (!raw.endsWith("/versions")) return null;
|
|
1013
|
+
const name = raw.slice(0, -9);
|
|
1014
|
+
const slashes = name.split("/").length - 1;
|
|
1015
|
+
return (name.startsWith("@") ? slashes === 1 : slashes === 0) && name !== "" ? name : null;
|
|
1016
|
+
}
|
|
843
1017
|
function getContentType(filePath) {
|
|
844
1018
|
const ext = path.extname(filePath).toLowerCase();
|
|
845
1019
|
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
@@ -896,7 +1070,7 @@ function etagMatches(header, etag) {
|
|
|
896
1070
|
});
|
|
897
1071
|
}
|
|
898
1072
|
function createPowerhouseRouter(config, sse, webhooks, ownerStore) {
|
|
899
|
-
const cdn = new CdnCache(
|
|
1073
|
+
const cdn = new CdnCache(localUrl(config), config.cdnCachePath);
|
|
900
1074
|
const router = Router();
|
|
901
1075
|
const withOwners = async (pkgs) => {
|
|
902
1076
|
if (!ownerStore || pkgs.length === 0) return pkgs;
|
|
@@ -1010,21 +1184,54 @@ function createPowerhouseRouter(config, sse, webhooks, ownerStore) {
|
|
|
1010
1184
|
suggestions: req.query.suggestionType === "ACTION_AND_TRIGGER"
|
|
1011
1185
|
}));
|
|
1012
1186
|
});
|
|
1013
|
-
router.get("/pieces/*", (req, res) => {
|
|
1187
|
+
router.get("/pieces/*", async (req, res) => {
|
|
1014
1188
|
warm();
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1189
|
+
const raw = req.params[0];
|
|
1190
|
+
const versionsOf = versionsRouteName(raw);
|
|
1191
|
+
if (versionsOf) {
|
|
1192
|
+
const versions = await pieceVersions(cdn, config.cdnCachePath, versionsOf, config.storagePath);
|
|
1193
|
+
if (!versions) {
|
|
1194
|
+
res.status(404).json({ error: `Piece not found: ${versionsOf}` });
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
res.json(versions.map(versionSummary));
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const name = raw;
|
|
1201
|
+
const version = typeof req.query.version === "string" ? req.query.version : void 0;
|
|
1202
|
+
if (version === void 0) {
|
|
1203
|
+
const entry = findPiece(config.cdnCachePath, name, config.storagePath);
|
|
1204
|
+
const detail = entry ? pieceDetail(entry, originOf(req)) : null;
|
|
1205
|
+
if (!detail) {
|
|
1206
|
+
res.status(404).json({ error: `Piece not found: ${name}` });
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
res.json(detail);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const found = await findPieceVersion(cdn, config.cdnCachePath, name, version, config.storagePath);
|
|
1213
|
+
if (found.kind === "unknown-piece") {
|
|
1019
1214
|
res.status(404).json({ error: `Piece not found: ${name}` });
|
|
1020
1215
|
return;
|
|
1021
1216
|
}
|
|
1217
|
+
if (found.kind === "unknown-version") {
|
|
1218
|
+
res.status(404).json({
|
|
1219
|
+
error: `Piece version not found: ${name}@${version}`,
|
|
1220
|
+
available: found.available
|
|
1221
|
+
});
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const detail = pieceDetail(found.entry, originOf(req));
|
|
1225
|
+
if (!detail) {
|
|
1226
|
+
res.status(404).json({ error: `Piece not found: ${name}@${version}` });
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1022
1229
|
res.json(detail);
|
|
1023
1230
|
});
|
|
1024
1231
|
router.get("/-/pieces/bundled/*", async (req, res) => {
|
|
1025
1232
|
warm();
|
|
1026
1233
|
const filename = req.params[0];
|
|
1027
|
-
const entry = findPieceByTarball(config.cdnCachePath, filename, config.storagePath);
|
|
1234
|
+
const entry = await findPieceByTarball(cdn, config.cdnCachePath, filename, config.storagePath);
|
|
1028
1235
|
const file = entry ? await pieceTarball(entry) : null;
|
|
1029
1236
|
if (!entry || !file) {
|
|
1030
1237
|
res.status(404).json({ error: `Piece bundle not found: ${filename}` });
|
|
@@ -1136,7 +1343,7 @@ function parseManifestRewrite(reqPath) {
|
|
|
1136
1343
|
return { packageName: decodeURIComponent(beforeRev) };
|
|
1137
1344
|
}
|
|
1138
1345
|
function createUnpublishHook(config, notifications) {
|
|
1139
|
-
const cdn = new CdnCache(
|
|
1346
|
+
const cdn = new CdnCache(localUrl(config), config.cdnCachePath);
|
|
1140
1347
|
const handleManifestRewrite = (req, res) => {
|
|
1141
1348
|
const rewrite = parseManifestRewrite(req.path);
|
|
1142
1349
|
if (!rewrite) return;
|
|
@@ -1193,7 +1400,7 @@ function createUnpublishHook(config, notifications) {
|
|
|
1193
1400
|
};
|
|
1194
1401
|
}
|
|
1195
1402
|
function createPublishHook(config, notifications) {
|
|
1196
|
-
const cdn = new CdnCache(
|
|
1403
|
+
const cdn = new CdnCache(localUrl(config), config.cdnCachePath);
|
|
1197
1404
|
const parseBody = express.json({
|
|
1198
1405
|
strict: false,
|
|
1199
1406
|
limit: config.maxBodySize ?? "300mb"
|
|
@@ -1269,11 +1476,14 @@ function publishedTarball(body) {
|
|
|
1269
1476
|
return null;
|
|
1270
1477
|
}
|
|
1271
1478
|
async function pieceClaimConflict(config, packageName, body) {
|
|
1272
|
-
const index = pieceIndex(config.cdnCachePath, config.storagePath);
|
|
1273
|
-
if (index.size === 0) return null;
|
|
1274
1479
|
const tarball = publishedTarball(body);
|
|
1275
1480
|
if (!tarball) return null;
|
|
1276
|
-
|
|
1481
|
+
const names = await pieceNamesInTarball(tarball);
|
|
1482
|
+
if (names.length === 0) return null;
|
|
1483
|
+
const reserved = names.filter((name) => name.startsWith(RESERVED_PIECE_SCOPE));
|
|
1484
|
+
if (reserved.length > 0) return `Piece names in the ${RESERVED_PIECE_SCOPE} scope belong to Activepieces; ${packageName} cannot claim ${reserved.map((n) => `"${n}"`).join(", ")}. Rename the piece into your own scope.`;
|
|
1485
|
+
const index = pieceIndex(config.cdnCachePath, config.storagePath);
|
|
1486
|
+
for (const name of names) {
|
|
1277
1487
|
const owner = index.get(name);
|
|
1278
1488
|
if (owner && owner.packageName !== packageName) return `Piece "${name}" is already published by ${owner.packageName}; ${packageName} cannot claim it.`;
|
|
1279
1489
|
}
|
|
@@ -1553,7 +1763,9 @@ async function runRegistry(args) {
|
|
|
1553
1763
|
app.use(createPublishHook(config, notifications));
|
|
1554
1764
|
app.use(createUnpublishHook(config, notifications));
|
|
1555
1765
|
app.use((req, res) => verdaccioHandler(req, res));
|
|
1556
|
-
|
|
1766
|
+
const server = app.listen(port, () => {
|
|
1767
|
+
config.port = server.address().port;
|
|
1768
|
+
const port = config.port;
|
|
1557
1769
|
console.log(`Powerhouse Registry running on http://localhost:${port}`);
|
|
1558
1770
|
console.log(` CDN: http://localhost:${port}/-/cdn/`);
|
|
1559
1771
|
console.log(` Packages: http://localhost:${port}/packages`);
|
|
@@ -1563,6 +1775,7 @@ async function runRegistry(args) {
|
|
|
1563
1775
|
if (config.s3) console.log(` S3: ${config.s3.endpoint}/${config.s3.bucket}`);
|
|
1564
1776
|
if (config.renown) console.log(` Renown auth: ${config.renown.publicUrl}`);
|
|
1565
1777
|
});
|
|
1778
|
+
return server;
|
|
1566
1779
|
}
|
|
1567
1780
|
//#endregion
|
|
1568
1781
|
//#region cli.ts
|