@powerhousedao/registry 6.2.3-dev.39 → 6.2.3-dev.40
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.d.mts +6 -0
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +186 -33
- package/dist/cli.mjs.map +1 -1
- package/dist/plugins/verdaccio-registry-auth.js +6 -0
- package/package.json +3 -3
package/dist/cli.d.mts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const registryCommand: Partial<import("cmd-ts/dist/cjs/argparser.
|
|
|
10
10
|
s3SecretAccessKey: string | undefined;
|
|
11
11
|
s3KeyPrefix: string | undefined;
|
|
12
12
|
s3ForcePathStyle: boolean;
|
|
13
|
+
s3MaxWritesPerSecond: number | undefined;
|
|
13
14
|
databaseUrl: string | undefined;
|
|
14
15
|
listenDatabaseUrl: string | undefined;
|
|
15
16
|
webhooks: string | undefined;
|
|
@@ -40,6 +41,7 @@ export declare const registryCommand: Partial<import("cmd-ts/dist/cjs/argparser.
|
|
|
40
41
|
s3SecretAccessKey: string | undefined;
|
|
41
42
|
s3KeyPrefix: string | undefined;
|
|
42
43
|
s3ForcePathStyle: boolean;
|
|
44
|
+
s3MaxWritesPerSecond: number | undefined;
|
|
43
45
|
databaseUrl: string | undefined;
|
|
44
46
|
listenDatabaseUrl: string | undefined;
|
|
45
47
|
webhooks: string | undefined;
|
|
@@ -73,6 +75,7 @@ export declare const workerCommand: Partial<import("cmd-ts/dist/cjs/argparser.js
|
|
|
73
75
|
s3SecretAccessKey: string | undefined;
|
|
74
76
|
s3KeyPrefix: string | undefined;
|
|
75
77
|
s3ForcePathStyle: boolean;
|
|
78
|
+
s3MaxWritesPerSecond: number | undefined;
|
|
76
79
|
databaseUrl: string | undefined;
|
|
77
80
|
listenDatabaseUrl: string | undefined;
|
|
78
81
|
webhooks: string | undefined;
|
|
@@ -92,6 +95,7 @@ export declare const workerCommand: Partial<import("cmd-ts/dist/cjs/argparser.js
|
|
|
92
95
|
s3SecretAccessKey: string | undefined;
|
|
93
96
|
s3KeyPrefix: string | undefined;
|
|
94
97
|
s3ForcePathStyle: boolean;
|
|
98
|
+
s3MaxWritesPerSecond: number | undefined;
|
|
95
99
|
databaseUrl: string | undefined;
|
|
96
100
|
listenDatabaseUrl: string | undefined;
|
|
97
101
|
webhooks: string | undefined;
|
|
@@ -127,6 +131,7 @@ export declare const importVerdaccioStateCommand: Partial<import("cmd-ts/dist/cj
|
|
|
127
131
|
s3SecretAccessKey: string | undefined;
|
|
128
132
|
s3KeyPrefix: string | undefined;
|
|
129
133
|
s3ForcePathStyle: boolean;
|
|
134
|
+
s3MaxWritesPerSecond: number | undefined;
|
|
130
135
|
databaseUrl: string | undefined;
|
|
131
136
|
listenDatabaseUrl: string | undefined;
|
|
132
137
|
webhooks: string | undefined;
|
|
@@ -141,6 +146,7 @@ export declare const importVerdaccioStateCommand: Partial<import("cmd-ts/dist/cj
|
|
|
141
146
|
s3SecretAccessKey: string | undefined;
|
|
142
147
|
s3KeyPrefix: string | undefined;
|
|
143
148
|
s3ForcePathStyle: boolean;
|
|
149
|
+
s3MaxWritesPerSecond: number | undefined;
|
|
144
150
|
databaseUrl: string | undefined;
|
|
145
151
|
listenDatabaseUrl: string | undefined;
|
|
146
152
|
webhooks: string | undefined;
|
package/dist/cli.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.mts","names":[],"sources":["../cli.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.mts","names":[],"sources":["../cli.ts"],"mappings":";qBAkJa,iBAAe,+CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2I1B,6CAAA,iDAAA,mDAAA;qBAEW,eAAa,+CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyCxB,6CAAA,iDAAA,mDAAA;qBAEW,gBAAc,+CAAA;;;;;;;;;;;;IAkBzB,6CAAA,iDAAA,mDAAA;qBAEW,6BAA2B,+CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAatC,6CAAA,iDAAA,mDAAA"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1882,9 +1882,27 @@ async function forgetWith(query, name) {
|
|
|
1882
1882
|
await query("DELETE FROM verdaccio_manifests WHERE name = $1", [name]);
|
|
1883
1883
|
}
|
|
1884
1884
|
//#endregion
|
|
1885
|
+
//#region src/pace.ts
|
|
1886
|
+
/** Spaces calls to at most `perSecond`, in the order they ask. */
|
|
1887
|
+
function pacer(perSecond) {
|
|
1888
|
+
const gap = 1e3 / perSecond;
|
|
1889
|
+
let next = 0;
|
|
1890
|
+
return () => {
|
|
1891
|
+
const now = Date.now();
|
|
1892
|
+
const at = Math.max(now, next);
|
|
1893
|
+
next = at + gap;
|
|
1894
|
+
return at > now ? new Promise((resolve) => setTimeout(resolve, at - now)) : Promise.resolve();
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
//#endregion
|
|
1885
1898
|
//#region src/artifacts.ts
|
|
1899
|
+
const BUFFERED_UPLOAD_BYTES = 4194304;
|
|
1900
|
+
const DEFAULT_WRITES_PER_SECOND = 150;
|
|
1886
1901
|
function createS3ArtifactStore(config) {
|
|
1902
|
+
const paceWrite = pacer(config.maxWritesPerSecond ?? DEFAULT_WRITES_PER_SECOND);
|
|
1887
1903
|
const s3 = new S3Client({
|
|
1904
|
+
retryMode: "adaptive",
|
|
1905
|
+
maxAttempts: 5,
|
|
1888
1906
|
endpoint: config.endpoint,
|
|
1889
1907
|
region: config.region,
|
|
1890
1908
|
forcePathStyle: config.s3ForcePathStyle ?? true,
|
|
@@ -1910,6 +1928,7 @@ function createS3ArtifactStore(config) {
|
|
|
1910
1928
|
const root = `${config.keyPrefix ?? ""}artifacts/`;
|
|
1911
1929
|
return {
|
|
1912
1930
|
async put(key, body, contentType) {
|
|
1931
|
+
await paceWrite();
|
|
1913
1932
|
await s3.send(new PutObjectCommand({
|
|
1914
1933
|
Bucket: config.bucket,
|
|
1915
1934
|
Key: root + key,
|
|
@@ -1919,9 +1938,22 @@ function createS3ArtifactStore(config) {
|
|
|
1919
1938
|
},
|
|
1920
1939
|
async putFile(key, file, contentType) {
|
|
1921
1940
|
const { size } = await fs.promises.stat(file);
|
|
1941
|
+
if (size <= BUFFERED_UPLOAD_BYTES) {
|
|
1942
|
+
const body = await fs.promises.readFile(file);
|
|
1943
|
+
await paceWrite();
|
|
1944
|
+
await s3.send(new PutObjectCommand({
|
|
1945
|
+
Bucket: config.bucket,
|
|
1946
|
+
Key: root + key,
|
|
1947
|
+
Body: body,
|
|
1948
|
+
ContentLength: size,
|
|
1949
|
+
ContentType: contentType
|
|
1950
|
+
}));
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1922
1953
|
const body = fs.createReadStream(file);
|
|
1923
1954
|
body.on("error", () => void 0);
|
|
1924
1955
|
try {
|
|
1956
|
+
await paceWrite();
|
|
1925
1957
|
await s3.send(new PutObjectCommand({
|
|
1926
1958
|
Bucket: config.bucket,
|
|
1927
1959
|
Key: root + key,
|
|
@@ -1959,10 +1991,13 @@ function createS3ArtifactStore(config) {
|
|
|
1959
1991
|
ContinuationToken: token
|
|
1960
1992
|
}));
|
|
1961
1993
|
const keys = (listed.Contents ?? []).flatMap((o) => o.Key ? [{ Key: o.Key }] : []);
|
|
1962
|
-
if (keys.length > 0)
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1994
|
+
if (keys.length > 0) {
|
|
1995
|
+
await paceWrite();
|
|
1996
|
+
await s3.send(new DeleteObjectsCommand({
|
|
1997
|
+
Bucket: config.bucket,
|
|
1998
|
+
Delete: { Objects: keys }
|
|
1999
|
+
}));
|
|
2000
|
+
}
|
|
1966
2001
|
token = listed.IsTruncated ? listed.NextContinuationToken : void 0;
|
|
1967
2002
|
} while (token);
|
|
1968
2003
|
}
|
|
@@ -2406,6 +2441,12 @@ const MIGRATIONS = [
|
|
|
2406
2441
|
name: "reconcile by revision",
|
|
2407
2442
|
transaction: false,
|
|
2408
2443
|
statements: ["ALTER TABLE registry_packages ADD COLUMN IF NOT EXISTS manifest_rev text", ...indexConcurrently("registry_versions_pending", "ON registry_versions (updated_at) WHERE status = 'pending'")]
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
id: 7,
|
|
2447
|
+
name: "permanent version failures",
|
|
2448
|
+
transaction: false,
|
|
2449
|
+
statements: ["ALTER TABLE registry_versions ADD COLUMN IF NOT EXISTS permanent boolean NOT NULL DEFAULT false"]
|
|
2409
2450
|
}
|
|
2410
2451
|
];
|
|
2411
2452
|
const MIGRATIONS_TABLE = `CREATE TABLE IF NOT EXISTS registry_migrations (
|
|
@@ -3036,9 +3077,11 @@ function publisherOf$1(job) {
|
|
|
3036
3077
|
async function processVersion(ctx, job, finish) {
|
|
3037
3078
|
const pkg = job.package;
|
|
3038
3079
|
const version = job.version;
|
|
3039
|
-
const
|
|
3080
|
+
const published = await publishedHere(ctx.db, pkg);
|
|
3081
|
+
const local = published ?? job.payload.local === true;
|
|
3040
3082
|
const manifestRev = await indexedRevision(ctx.db, pkg);
|
|
3041
3083
|
const packument = await fetchPackument(ctx.registryUrl, pkg);
|
|
3084
|
+
if (!packument && published) throw new Error(`metadata for published ${pkg} returned 404`);
|
|
3042
3085
|
if (!packument?.versions?.[version]) {
|
|
3043
3086
|
await ctx.db.transaction(async (tx) => {
|
|
3044
3087
|
await removeVersionRows(tx, pkg, [version]);
|
|
@@ -3098,14 +3141,16 @@ async function processVersion(ctx, job, finish) {
|
|
|
3098
3141
|
return true;
|
|
3099
3142
|
}
|
|
3100
3143
|
/** Records a version that can't be processed. */
|
|
3101
|
-
async function failVersion(ctx, job, error) {
|
|
3102
|
-
await ctx.db.query(`INSERT INTO registry_versions (package, version, status, error)
|
|
3103
|
-
VALUES ($1, $2, 'failed', $3)
|
|
3144
|
+
async function failVersion(ctx, job, error, permanent = false) {
|
|
3145
|
+
await ctx.db.query(`INSERT INTO registry_versions (package, version, status, error, permanent)
|
|
3146
|
+
VALUES ($1, $2, 'failed', $3, $4)
|
|
3104
3147
|
ON CONFLICT (package, version) DO UPDATE SET
|
|
3105
|
-
status = 'failed', error = EXCLUDED.error,
|
|
3148
|
+
status = 'failed', error = EXCLUDED.error,
|
|
3149
|
+
permanent = EXCLUDED.permanent, updated_at = now()`, [
|
|
3106
3150
|
job.package,
|
|
3107
3151
|
job.version,
|
|
3108
|
-
error
|
|
3152
|
+
error,
|
|
3153
|
+
permanent
|
|
3109
3154
|
]);
|
|
3110
3155
|
await ctx.events.publish({
|
|
3111
3156
|
type: "version-failed",
|
|
@@ -3163,6 +3208,7 @@ const CACHE_TTL_MS = 3e4;
|
|
|
3163
3208
|
const POLL_MS = 1e3;
|
|
3164
3209
|
const ON_DEMAND_TIMEOUT_MS = 6e4;
|
|
3165
3210
|
const VERSION_CACHE_MAX = 5e3;
|
|
3211
|
+
const FAILED_RETRY_AFTER = "15 minutes";
|
|
3166
3212
|
function pieceColumns(table) {
|
|
3167
3213
|
return `${table}.name, ${table}.version, ${table}.package,
|
|
3168
3214
|
${table}.display_name AS "displayName", ${table}.description,
|
|
@@ -3330,8 +3376,14 @@ var Catalog = class {
|
|
|
3330
3376
|
const cached = this.#versions.get(key);
|
|
3331
3377
|
if (this.#fresh(cached) && cached.value?.status === "ready") return cached.value;
|
|
3332
3378
|
const row = (await this.#db.query(`SELECT package, version, status, manifest,
|
|
3333
|
-
package_json_version AS "packageJsonVersion", files, error
|
|
3334
|
-
|
|
3379
|
+
package_json_version AS "packageJsonVersion", files, error,
|
|
3380
|
+
(status = 'failed' AND NOT permanent
|
|
3381
|
+
AND updated_at < now() - $3::interval) AS retryable
|
|
3382
|
+
FROM registry_versions WHERE package = $1 AND version = $2`, [
|
|
3383
|
+
pkg,
|
|
3384
|
+
version,
|
|
3385
|
+
FAILED_RETRY_AFTER
|
|
3386
|
+
])).rows[0] ?? null;
|
|
3335
3387
|
if (row?.status === "ready") {
|
|
3336
3388
|
this.#versions.delete(key);
|
|
3337
3389
|
this.#versions.set(key, {
|
|
@@ -3357,11 +3409,20 @@ var Catalog = class {
|
|
|
3357
3409
|
kind: "ready",
|
|
3358
3410
|
row
|
|
3359
3411
|
};
|
|
3360
|
-
if (row?.status === "failed") return {
|
|
3412
|
+
if (row?.status === "failed" && !row.retryable) return {
|
|
3361
3413
|
kind: "failed",
|
|
3362
3414
|
error: row.error ?? ""
|
|
3363
3415
|
};
|
|
3364
|
-
if (
|
|
3416
|
+
if (row?.status === "failed") {
|
|
3417
|
+
const local = (await this.packageRow(pkg))?.local === true;
|
|
3418
|
+
await this.#db.transaction(async (tx) => {
|
|
3419
|
+
if ((await tx.query(`UPDATE registry_versions SET status = 'pending', error = NULL, updated_at = now()
|
|
3420
|
+
WHERE package = $1 AND version = $2 AND status = 'failed'
|
|
3421
|
+
RETURNING version`, [pkg, version])).rows.length === 0) return;
|
|
3422
|
+
await enqueue(tx, "process", pkg, version, {}, local ? 0 : 5);
|
|
3423
|
+
});
|
|
3424
|
+
await wakeWorkers(this.#db);
|
|
3425
|
+
} else if (!row) {
|
|
3365
3426
|
if (!(await fetchPackument(this.#registryUrl(), pkg))?.versions?.[version]) return { kind: "missing" };
|
|
3366
3427
|
const local = (await this.packageRow(pkg))?.local === true;
|
|
3367
3428
|
await this.#db.transaction(async (tx) => {
|
|
@@ -3623,7 +3684,9 @@ const SECONDS_BUCKETS = [
|
|
|
3623
3684
|
1,
|
|
3624
3685
|
2.5,
|
|
3625
3686
|
5,
|
|
3626
|
-
10
|
|
3687
|
+
10,
|
|
3688
|
+
30,
|
|
3689
|
+
60
|
|
3627
3690
|
];
|
|
3628
3691
|
const JOB_BUCKETS = [
|
|
3629
3692
|
.05,
|
|
@@ -3637,9 +3700,12 @@ const JOB_BUCKETS = [
|
|
|
3637
3700
|
30,
|
|
3638
3701
|
60,
|
|
3639
3702
|
120,
|
|
3640
|
-
300
|
|
3703
|
+
300,
|
|
3704
|
+
600,
|
|
3705
|
+
1800
|
|
3641
3706
|
];
|
|
3642
3707
|
const QUEUE_CACHE_MS = 5e3;
|
|
3708
|
+
const CATALOG_CACHE_MS = 3e4;
|
|
3643
3709
|
/** Event-loop delay, memory and CPU of this process. */
|
|
3644
3710
|
function processMetrics(metrics) {
|
|
3645
3711
|
const loop = monitorEventLoopDelay({ resolution: 10 });
|
|
@@ -3734,9 +3800,65 @@ function databaseMetrics(metrics, db) {
|
|
|
3734
3800
|
metrics.gauge("registry_jobs_oldest_due_seconds", "Age of the oldest unclaimed due job, by priority", byPriority("oldest_due"));
|
|
3735
3801
|
metrics.gauge("registry_listen_connected", "1 while the LISTEN connection is up", () => [{ value: db.listening() ? 1 : 0 }]);
|
|
3736
3802
|
}
|
|
3803
|
+
/**
|
|
3804
|
+
* Package and version counts. They're the same from every process, so only
|
|
3805
|
+
* one that runs jobs registers them.
|
|
3806
|
+
*/
|
|
3807
|
+
function catalogMetrics(metrics, db) {
|
|
3808
|
+
let catalogCached;
|
|
3809
|
+
const catalog = () => {
|
|
3810
|
+
if (!catalogCached || Date.now() - catalogCached.at > CATALOG_CACHE_MS) {
|
|
3811
|
+
const row = db.query(`SELECT
|
|
3812
|
+
(SELECT count(*) FROM registry_packages WHERE local)::int AS local,
|
|
3813
|
+
(SELECT count(*) FROM registry_packages
|
|
3814
|
+
WHERE local AND listed_manifest IS NOT NULL)::int AS listed,
|
|
3815
|
+
count(*) FILTER (WHERE status = 'ready')::int AS ready,
|
|
3816
|
+
count(*) FILTER (WHERE status = 'pending')::int AS pending,
|
|
3817
|
+
count(*) FILTER (WHERE status = 'failed' AND NOT permanent)::int AS failed,
|
|
3818
|
+
count(*) FILTER (WHERE status = 'failed' AND permanent)::int AS failed_permanent
|
|
3819
|
+
FROM registry_versions`).then((r) => r.rows[0]);
|
|
3820
|
+
catalogCached = {
|
|
3821
|
+
at: Date.now(),
|
|
3822
|
+
row
|
|
3823
|
+
};
|
|
3824
|
+
row.catch(() => catalogCached = void 0);
|
|
3825
|
+
}
|
|
3826
|
+
return catalogCached.row;
|
|
3827
|
+
};
|
|
3828
|
+
metrics.gauge("registry_packages", "Packages published here (local), and those /packages lists (listed)", async () => {
|
|
3829
|
+
const row = await catalog();
|
|
3830
|
+
return ["local", "listed"].map((state) => ({
|
|
3831
|
+
labels: { state },
|
|
3832
|
+
value: row[state]
|
|
3833
|
+
}));
|
|
3834
|
+
});
|
|
3835
|
+
metrics.gauge("registry_versions", "Processed versions, by status; failed_permanent is never retried", async () => {
|
|
3836
|
+
const row = await catalog();
|
|
3837
|
+
return [
|
|
3838
|
+
"ready",
|
|
3839
|
+
"pending",
|
|
3840
|
+
"failed",
|
|
3841
|
+
"failed_permanent"
|
|
3842
|
+
].map((status) => ({
|
|
3843
|
+
labels: { status },
|
|
3844
|
+
value: row[status]
|
|
3845
|
+
}));
|
|
3846
|
+
});
|
|
3847
|
+
}
|
|
3848
|
+
/** A bounded label for why a job failed. */
|
|
3849
|
+
function errorReason(err, permanent = false) {
|
|
3850
|
+
const name = err.name ?? "";
|
|
3851
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3852
|
+
if (name === "SlowDown" || /reduce your request rate/i.test(message)) return "s3_throttled";
|
|
3853
|
+
if (permanent) return "permanent";
|
|
3854
|
+
if (/returned 404/.test(message)) return "not_found";
|
|
3855
|
+
if (name === "TimeoutError" || /timed? ?out/i.test(message)) return "timeout";
|
|
3856
|
+
return "other";
|
|
3857
|
+
}
|
|
3737
3858
|
function workerMetrics(metrics) {
|
|
3738
3859
|
return {
|
|
3739
3860
|
jobDuration: metrics.histogram("registry_job_duration_seconds", "Time to run a job, by kind and outcome (done, retry, failed)", JOB_BUCKETS),
|
|
3861
|
+
jobErrors: metrics.counter("registry_job_errors_total", "Job failures, by kind and reason (s3_throttled, not_found, timeout, permanent, other)"),
|
|
3740
3862
|
reconcileQueued: metrics.counter("registry_reconcile_queued_total", "Jobs reconcile queued, by pass (quick, full)"),
|
|
3741
3863
|
reconcileRuns: metrics.counter("registry_reconcile_runs_total", "Reconcile passes, by pass (quick, full)")
|
|
3742
3864
|
};
|
|
@@ -4859,6 +4981,11 @@ const FULL_MISSING = `
|
|
|
4859
4981
|
LEFT JOIN registry_versions r ON r.package = m.name AND r.version = v.version
|
|
4860
4982
|
WHERE m.updated_at < now() - interval '30 seconds' AND ${PUBLISHED}
|
|
4861
4983
|
AND (r.package IS NULL
|
|
4984
|
+
OR (r.status = 'failed' AND NOT r.permanent
|
|
4985
|
+
AND r.updated_at < now() - interval '1 hour'
|
|
4986
|
+
AND NOT EXISTS (SELECT 1 FROM registry_jobs j
|
|
4987
|
+
WHERE j.kind = 'process' AND j.package = m.name
|
|
4988
|
+
AND j.version = v.version))
|
|
4862
4989
|
OR (r.status = 'pending' AND r.updated_at < now() - interval '5 minutes'
|
|
4863
4990
|
AND NOT EXISTS (SELECT 1 FROM registry_jobs j
|
|
4864
4991
|
WHERE j.kind = 'process' AND j.package = m.name
|
|
@@ -4907,7 +5034,10 @@ function publisherOf(job) {
|
|
|
4907
5034
|
async function runJob(ctx, job, options) {
|
|
4908
5035
|
const finish = (tx) => complete(tx, job);
|
|
4909
5036
|
if (job.priority === 10 && await publishedHere(ctx.db, job.package) === false) {
|
|
4910
|
-
await ctx.db.transaction(
|
|
5037
|
+
await ctx.db.transaction(async (tx) => {
|
|
5038
|
+
await tx.query("UPDATE registry_packages SET local = false WHERE name = $1 AND local", [job.package]);
|
|
5039
|
+
await finish(tx);
|
|
5040
|
+
});
|
|
4911
5041
|
return;
|
|
4912
5042
|
}
|
|
4913
5043
|
if (job.kind === "process") {
|
|
@@ -4978,10 +5108,14 @@ async function startWorker(ctx, options) {
|
|
|
4978
5108
|
console.error(`[registry] ${job.kind} ${job.package}${job.version ? `@${job.version}` : ""} failed (attempt ${job.attempts}):`, message);
|
|
4979
5109
|
const finalAttempt = err instanceof PermanentError || job.attempts >= 5;
|
|
4980
5110
|
observe(finalAttempt ? "failed" : "retry");
|
|
5111
|
+
options.metrics?.jobErrors.inc({
|
|
5112
|
+
kind: job.kind,
|
|
5113
|
+
reason: errorReason(err, err instanceof PermanentError)
|
|
5114
|
+
});
|
|
4981
5115
|
try {
|
|
4982
5116
|
if (!finalAttempt) await retry(ctx.db, job, message);
|
|
4983
5117
|
else {
|
|
4984
|
-
if (job.kind === "process") await failVersion(ctx, job, message);
|
|
5118
|
+
if (job.kind === "process") await failVersion(ctx, job, message, err instanceof PermanentError);
|
|
4985
5119
|
await complete(ctx.db, job);
|
|
4986
5120
|
}
|
|
4987
5121
|
} catch (recordErr) {
|
|
@@ -5051,7 +5185,8 @@ function s3From(args) {
|
|
|
5051
5185
|
accessKeyId: args.s3AccessKeyId,
|
|
5052
5186
|
secretAccessKey: args.s3SecretAccessKey,
|
|
5053
5187
|
keyPrefix: args.s3KeyPrefix,
|
|
5054
|
-
s3ForcePathStyle: args.s3ForcePathStyle
|
|
5188
|
+
s3ForcePathStyle: args.s3ForcePathStyle,
|
|
5189
|
+
maxWritesPerSecond: args.s3MaxWritesPerSecond
|
|
5055
5190
|
};
|
|
5056
5191
|
}
|
|
5057
5192
|
function webhooksFrom(list) {
|
|
@@ -5244,12 +5379,15 @@ async function runRegistry(args) {
|
|
|
5244
5379
|
console.log(` Data: ${cdnCachePath}`);
|
|
5245
5380
|
if (config.s3) console.log(` S3: ${config.s3.endpoint}/${config.s3.bucket}`);
|
|
5246
5381
|
if (config.renown) console.log(` Renown auth: ${config.renown.publicUrl}`);
|
|
5247
|
-
if (workers > 0)
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5382
|
+
if (workers > 0) {
|
|
5383
|
+
catalogMetrics(metrics, runtime.db);
|
|
5384
|
+
worker = startWorker(processorContext(runtime, localUrl), {
|
|
5385
|
+
concurrency: workers,
|
|
5386
|
+
webhooks: runtime.webhooks,
|
|
5387
|
+
sweep: true,
|
|
5388
|
+
metrics: jobMetrics
|
|
5389
|
+
});
|
|
5390
|
+
}
|
|
5253
5391
|
});
|
|
5254
5392
|
server.keepAliveTimeout = 65e3;
|
|
5255
5393
|
server.headersTimeout = 66e3;
|
|
@@ -5290,6 +5428,7 @@ async function runWorker(args) {
|
|
|
5290
5428
|
if (args.metricsPort !== void 0) {
|
|
5291
5429
|
processMetrics(metrics);
|
|
5292
5430
|
databaseMetrics(metrics, runtime.db);
|
|
5431
|
+
catalogMetrics(metrics, runtime.db);
|
|
5293
5432
|
metricsServer = await serveMetrics(metrics, args.metricsPort);
|
|
5294
5433
|
}
|
|
5295
5434
|
const worker = await startWorker(processorContext(runtime, () => args.registryUrl.replace(/\/$/, "")), {
|
|
@@ -5333,8 +5472,10 @@ async function readStoredManifest(s3, config, name) {
|
|
|
5333
5472
|
Key: `${config.keyPrefix ?? ""}${name}/package.json`
|
|
5334
5473
|
}));
|
|
5335
5474
|
return JSON.parse(await res.Body?.transformToString() ?? "null");
|
|
5336
|
-
} catch {
|
|
5337
|
-
|
|
5475
|
+
} catch (err) {
|
|
5476
|
+
const name = err.name;
|
|
5477
|
+
if (name === "NoSuchKey" || name === "NotFound") return null;
|
|
5478
|
+
throw err;
|
|
5338
5479
|
}
|
|
5339
5480
|
}
|
|
5340
5481
|
/** Moves Verdaccio's S3-file state into the Postgres store the S3 plugin reads. */
|
|
@@ -5364,15 +5505,21 @@ async function runImportVerdaccioState(args) {
|
|
|
5364
5505
|
const names = new Set(legacy.list ?? []);
|
|
5365
5506
|
const pool = new pg.Pool({ connectionString: args.databaseUrl });
|
|
5366
5507
|
let pruned = [];
|
|
5508
|
+
let gone = 0;
|
|
5367
5509
|
try {
|
|
5368
5510
|
const store = new S3DatabasePostgres(pool, pluginLogger);
|
|
5369
5511
|
await store.init();
|
|
5512
|
+
const missing = [];
|
|
5370
5513
|
for (const name of names) {
|
|
5371
5514
|
const manifest = await readStoredManifest(s3, s3Config, name);
|
|
5372
5515
|
if (manifest) await store.record(name, manifest);
|
|
5516
|
+
else missing.push(name);
|
|
5373
5517
|
}
|
|
5518
|
+
for (const name of missing) names.delete(name);
|
|
5519
|
+
await pool.query("DELETE FROM verdaccio_packages WHERE name = ANY($1)", [missing]);
|
|
5520
|
+
gone = missing.length;
|
|
5374
5521
|
for (const name of names) await pool.query("INSERT INTO verdaccio_packages (name) VALUES ($1) ON CONFLICT (name) DO NOTHING", [name]);
|
|
5375
|
-
pruned = await pruneCachedPackages(pool, [...names]);
|
|
5522
|
+
if (legacy.list) pruned = await pruneCachedPackages(pool, [...names]);
|
|
5376
5523
|
for (const token of legacy.tokens ?? []) await pool.query(`INSERT INTO verdaccio_tokens ("user", key, token) VALUES ($1, $2, $3)
|
|
5377
5524
|
ON CONFLICT ("user", key) DO NOTHING`, [
|
|
5378
5525
|
token.user,
|
|
@@ -5384,10 +5531,14 @@ async function runImportVerdaccioState(args) {
|
|
|
5384
5531
|
} finally {
|
|
5385
5532
|
await pool.end();
|
|
5386
5533
|
}
|
|
5387
|
-
console.log(`[registry] imported ${names.size} package name(s), ${legacy.tokens?.length ?? 0} token(s)${legacy.secret ? " and the secret" : ""}; pruned ${pruned.length} cached package(s)`);
|
|
5534
|
+
console.log(`[registry] imported ${names.size} package name(s), ${legacy.tokens?.length ?? 0} token(s)${legacy.secret ? " and the secret" : ""}; pruned ${pruned.length} cached package(s), dropped ${gone} with no stored manifest`);
|
|
5388
5535
|
}
|
|
5389
5536
|
//#endregion
|
|
5390
5537
|
//#region cli.ts
|
|
5538
|
+
const positiveFromEnv = (name) => () => {
|
|
5539
|
+
const n = Number(process.env[name]);
|
|
5540
|
+
return Number.isInteger(n) && n > 0 ? n : void 0;
|
|
5541
|
+
};
|
|
5391
5542
|
const storageArgs = {
|
|
5392
5543
|
storageDir: option({
|
|
5393
5544
|
long: "storage-dir",
|
|
@@ -5443,6 +5594,12 @@ const storageArgs = {
|
|
|
5443
5594
|
defaultValue: () => process.env.S3_FORCE_PATH_STYLE !== "false",
|
|
5444
5595
|
defaultValueIsSerializable: true
|
|
5445
5596
|
}),
|
|
5597
|
+
s3MaxWritesPerSecond: option({
|
|
5598
|
+
long: "s3-max-writes-per-second",
|
|
5599
|
+
type: optional(number),
|
|
5600
|
+
description: "Artifact uploads per second (default 150); Hetzner throttles a bucket past 750 requests per second. Env PH_REGISTRY_S3_MAX_WRITES_PER_SECOND",
|
|
5601
|
+
defaultValue: positiveFromEnv("PH_REGISTRY_S3_MAX_WRITES_PER_SECOND")
|
|
5602
|
+
}),
|
|
5446
5603
|
databaseUrl: option({
|
|
5447
5604
|
long: "database-url",
|
|
5448
5605
|
type: optional(string),
|
|
@@ -5473,10 +5630,6 @@ const migrateOnBootArg = option({
|
|
|
5473
5630
|
}),
|
|
5474
5631
|
description: "Apply database migrations at startup (true/false). Defaults to true without --database-url, else false; env PH_REGISTRY_MIGRATE_ON_BOOT"
|
|
5475
5632
|
});
|
|
5476
|
-
const positiveFromEnv = (name) => () => {
|
|
5477
|
-
const n = Number(process.env[name]);
|
|
5478
|
-
return Number.isInteger(n) && n > 0 ? n : void 0;
|
|
5479
|
-
};
|
|
5480
5633
|
const dbPoolMaxArg = option({
|
|
5481
5634
|
long: "db-pool-max",
|
|
5482
5635
|
type: optional(number),
|