@powerhousedao/registry 6.2.3-dev.36 → 6.2.3-dev.38

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 CHANGED
@@ -22,6 +22,7 @@ export declare const registryCommand: Partial<import("cmd-ts/dist/cjs/argparser.
22
22
  workers: number;
23
23
  uplink: string | undefined;
24
24
  uplinkMaxage: string | undefined;
25
+ trustProxy: string | undefined;
25
26
  webEnabled: boolean;
26
27
  publicUrl: string | undefined;
27
28
  authRenown: boolean;
@@ -51,6 +52,7 @@ export declare const registryCommand: Partial<import("cmd-ts/dist/cjs/argparser.
51
52
  workers: number;
52
53
  uplink: string | undefined;
53
54
  uplinkMaxage: string | undefined;
55
+ trustProxy: string | undefined;
54
56
  webEnabled: boolean;
55
57
  publicUrl: string | undefined;
56
58
  authRenown: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.mts","names":[],"sources":["../cli.ts"],"mappings":";qBA2Ia,iBAAe,+CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAkI1B,6CAAA,iDAAA,mDAAA;qBAEW,eAAa,+CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyCxB,6CAAA,iDAAA,mDAAA;qBAEW,gBAAc,+CAAA;;;;;;;;;;;;IAkBzB,6CAAA,iDAAA,mDAAA;qBAEW,6BAA2B,+CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAatC,6CAAA,iDAAA,mDAAA"}
1
+ {"version":3,"file":"cli.d.mts","names":[],"sources":["../cli.ts"],"mappings":";qBA2Ia,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
@@ -1919,13 +1919,19 @@ function createS3ArtifactStore(config) {
1919
1919
  },
1920
1920
  async putFile(key, file, contentType) {
1921
1921
  const { size } = await fs.promises.stat(file);
1922
- await s3.send(new PutObjectCommand({
1923
- Bucket: config.bucket,
1924
- Key: root + key,
1925
- Body: fs.createReadStream(file),
1926
- ContentLength: size,
1927
- ContentType: contentType
1928
- }));
1922
+ const body = fs.createReadStream(file);
1923
+ body.on("error", () => void 0);
1924
+ try {
1925
+ await s3.send(new PutObjectCommand({
1926
+ Bucket: config.bucket,
1927
+ Key: root + key,
1928
+ Body: body,
1929
+ ContentLength: size,
1930
+ ContentType: contentType
1931
+ }));
1932
+ } finally {
1933
+ body.destroy();
1934
+ }
1929
1935
  },
1930
1936
  async get(key) {
1931
1937
  try {
@@ -2826,9 +2832,16 @@ function inside(root, relative) {
2826
2832
  }
2827
2833
  async function inBatches(items, size, fn) {
2828
2834
  let cursor = 0;
2829
- await Promise.all(Array.from({ length: Math.min(size, items.length) }, async () => {
2830
- while (cursor < items.length) await fn(items[cursor++]);
2831
- }));
2835
+ let failed = false;
2836
+ const rejected = (await Promise.allSettled(Array.from({ length: Math.min(size, items.length) }, async () => {
2837
+ while (!failed && cursor < items.length) try {
2838
+ await fn(items[cursor++]);
2839
+ } catch (err) {
2840
+ failed = true;
2841
+ throw err;
2842
+ }
2843
+ }))).find((lane) => lane.status === "rejected");
2844
+ if (rejected) throw rejected.reason;
2832
2845
  }
2833
2846
  async function packDir(dir) {
2834
2847
  const files = listFiles(dir);
@@ -2933,6 +2946,12 @@ async function indexedRevision(db, pkg) {
2933
2946
  }
2934
2947
  return (await db.query("SELECT rev FROM verdaccio_manifests WHERE name = $1", [pkg])).rows[0]?.rev ?? null;
2935
2948
  }
2949
+ async function publishedHere(db, pkg) {
2950
+ if (!(await db.query("SELECT to_regclass('verdaccio_packages') IS NOT NULL AS exists")).rows[0]?.exists) return null;
2951
+ return (await db.query(`SELECT EXISTS (SELECT 1 FROM verdaccio_packages WHERE name = $1)
2952
+ OR EXISTS (SELECT 1 FROM registry_package_owners WHERE package_name = $1)
2953
+ AS published`, [pkg])).rows[0]?.published === true;
2954
+ }
2936
2955
  async function upsertPackage(tx, pkg, packument, local, manifestRev) {
2937
2956
  const versions = Object.keys(packument.versions ?? {}).sort(compareSemver);
2938
2957
  const times = {};
@@ -3017,7 +3036,7 @@ function publisherOf$1(job) {
3017
3036
  async function processVersion(ctx, job, finish) {
3018
3037
  const pkg = job.package;
3019
3038
  const version = job.version;
3020
- const local = job.payload.local === true;
3039
+ const local = await publishedHere(ctx.db, pkg) ?? job.payload.local === true;
3021
3040
  const manifestRev = await indexedRevision(ctx.db, pkg);
3022
3041
  const packument = await fetchPackument(ctx.registryUrl, pkg);
3023
3042
  if (!packument?.versions?.[version]) {
@@ -3102,15 +3121,22 @@ async function syncPackage(ctx, job, finish) {
3102
3121
  const known = await ctx.db.query("SELECT version FROM registry_versions WHERE package = $1", [pkg]);
3103
3122
  const listed = new Set(Object.keys(packument?.versions ?? {}));
3104
3123
  const removed = known.rows.map((r) => r.version).filter((v) => !listed.has(v));
3124
+ const published = await publishedHere(ctx.db, pkg);
3125
+ const local = published ?? job.payload.local === true;
3126
+ if (!packument && published) throw new Error(`metadata for published ${pkg} returned 404`);
3105
3127
  await ctx.db.transaction(async (tx) => {
3106
3128
  await removeVersionRows(tx, pkg, removed);
3107
3129
  if (!packument) await tx.query("DELETE FROM registry_packages WHERE name = $1", [pkg]);
3108
3130
  else {
3109
- await upsertPackage(tx, pkg, packument, job.payload.local === true, manifestRev);
3131
+ await upsertPackage(tx, pkg, packument, local, manifestRev);
3110
3132
  await recomputeLatest(tx, pkg);
3111
- if ((await tx.query("SELECT local FROM registry_packages WHERE name = $1", [pkg])).rows[0]?.local) {
3133
+ if (local) {
3112
3134
  const have = new Set(known.rows.map((r) => r.version));
3113
- for (const version of listed) if (!have.has(version)) await enqueue(tx, "process", pkg, version, { local: true }, job.priority);
3135
+ const tagged = new Set(Object.values(packument["dist-tags"] ?? {}));
3136
+ for (const version of tagged) {
3137
+ if (!listed.has(version)) continue;
3138
+ if (!have.has(version)) await enqueue(tx, "process", pkg, version, { local: true }, job.priority);
3139
+ }
3114
3140
  }
3115
3141
  }
3116
3142
  await finish(tx);
@@ -4761,7 +4787,10 @@ function buildVerdaccioConfig(config) {
4761
4787
  primary_color: "#38C780",
4762
4788
  darkMode: true
4763
4789
  },
4764
- server: { keepAliveTimeout: 60 },
4790
+ server: {
4791
+ keepAliveTimeout: 60,
4792
+ ...config.trustProxy !== void 0 && { trustProxy: config.trustProxy }
4793
+ },
4765
4794
  log: {
4766
4795
  type: "stdout",
4767
4796
  format: "pretty",
@@ -4799,8 +4828,10 @@ async function enqueueSweep(db) {
4799
4828
  return names.rows.length;
4800
4829
  }
4801
4830
  async function hasManifestIndex(db) {
4802
- return (await db.query("SELECT to_regclass('verdaccio_manifests') IS NOT NULL AS exists")).rows[0]?.exists === true;
4831
+ return (await db.query(`SELECT to_regclass('verdaccio_manifests') IS NOT NULL
4832
+ AND to_regclass('verdaccio_packages') IS NOT NULL AS exists`)).rows[0]?.exists === true;
4803
4833
  }
4834
+ const PUBLISHED = "EXISTS (SELECT 1 FROM verdaccio_packages l WHERE l.name = m.name)";
4804
4835
  const UNPUBLISHED = `
4805
4836
  SELECT p.name FROM registry_packages p
4806
4837
  WHERE p.local AND p.updated_at < now() - interval '30 seconds'
@@ -4809,7 +4840,7 @@ const QUICK_CHANGED = `
4809
4840
  SELECT m.name
4810
4841
  FROM verdaccio_manifests m
4811
4842
  LEFT JOIN registry_packages p ON p.name = m.name
4812
- WHERE m.updated_at < now() - interval '30 seconds'
4843
+ WHERE m.updated_at < now() - interval '30 seconds' AND ${PUBLISHED}
4813
4844
  AND (p.name IS NULL OR p.manifest_rev IS DISTINCT FROM m.rev)
4814
4845
  UNION ${UNPUBLISHED}
4815
4846
  LIMIT 1000`;
@@ -4822,11 +4853,11 @@ const QUICK_MISSING = `
4822
4853
  AND j.version = r.version)
4823
4854
  LIMIT 1000`;
4824
4855
  const FULL_MISSING = `
4825
- SELECT m.name, v.version
4856
+ SELECT DISTINCT m.name, v.version
4826
4857
  FROM verdaccio_manifests m
4827
- CROSS JOIN LATERAL jsonb_array_elements_text(m.versions) AS v(version)
4858
+ CROSS JOIN LATERAL jsonb_each_text(m.dist_tags) AS v(tag, version)
4828
4859
  LEFT JOIN registry_versions r ON r.package = m.name AND r.version = v.version
4829
- WHERE m.updated_at < now() - interval '30 seconds'
4860
+ WHERE m.updated_at < now() - interval '30 seconds' AND ${PUBLISHED}
4830
4861
  AND (r.package IS NULL
4831
4862
  OR (r.status = 'pending' AND r.updated_at < now() - interval '5 minutes'
4832
4863
  AND NOT EXISTS (SELECT 1 FROM registry_jobs j
@@ -4837,8 +4868,8 @@ const FULL_CHANGED = `
4837
4868
  SELECT m.name
4838
4869
  FROM verdaccio_manifests m
4839
4870
  LEFT JOIN registry_packages p ON p.name = m.name
4840
- WHERE m.updated_at < now() - interval '30 seconds'
4841
- AND (p.name IS NULL OR p.dist_tags <> m.dist_tags
4871
+ WHERE m.updated_at < now() - interval '30 seconds' AND ${PUBLISHED}
4872
+ AND (p.name IS NULL OR NOT p.local OR p.dist_tags <> m.dist_tags
4842
4873
  OR EXISTS (SELECT 1 FROM registry_versions r
4843
4874
  WHERE r.package = m.name AND NOT m.versions ? r.version))
4844
4875
  UNION ${UNPUBLISHED}
@@ -4853,12 +4884,32 @@ async function reconcile(db, options = {}) {
4853
4884
  if (queued > 0) await wakeWorkers(db);
4854
4885
  return queued;
4855
4886
  }
4887
+ async function pruneCachedPackages(q, published) {
4888
+ if (!(await q.query("SELECT to_regclass('registry_jobs') IS NOT NULL AS exists")).rows[0]?.exists) return [];
4889
+ const pruned = (await q.query(`DELETE FROM verdaccio_packages p
4890
+ WHERE p.name <> ALL($1::text[])
4891
+ AND NOT EXISTS (SELECT 1 FROM registry_package_owners o
4892
+ WHERE o.package_name = p.name)
4893
+ AND NOT EXISTS (SELECT 1 FROM registry_packages r
4894
+ WHERE r.name = p.name AND r.listed_manifest IS NOT NULL)
4895
+ RETURNING p.name`, [published])).rows.map((row) => row.name);
4896
+ if (pruned.length) {
4897
+ await q.query("DELETE FROM registry_jobs WHERE package = ANY($1) AND locked_by IS NULL", [pruned]);
4898
+ await q.query("DELETE FROM registry_versions WHERE package = ANY($1)", [pruned]);
4899
+ await q.query("DELETE FROM registry_packages WHERE name = ANY($1)", [pruned]);
4900
+ }
4901
+ return pruned;
4902
+ }
4856
4903
  function publisherOf(job) {
4857
4904
  const by = job.payload.publishedBy;
4858
4905
  return by?.address ? by : void 0;
4859
4906
  }
4860
4907
  async function runJob(ctx, job, options) {
4861
4908
  const finish = (tx) => complete(tx, job);
4909
+ if (job.priority === 10 && await publishedHere(ctx.db, job.package) === false) {
4910
+ await ctx.db.transaction(finish);
4911
+ return;
4912
+ }
4862
4913
  if (job.kind === "process") {
4863
4914
  if (await processVersion(ctx, job, finish) && job.payload.notify === true) await options.webhooks?.notifyPublish({
4864
4915
  packageName: job.package,
@@ -4942,7 +4993,7 @@ async function startWorker(ctx, options) {
4942
4993
  };
4943
4994
  const loops = Array.from({ length: Math.max(1, options.concurrency) }, () => loop());
4944
4995
  let sweeper;
4945
- let lastFull = 0;
4996
+ let lastFull = Date.now() - SWEEP_INTERVAL_MS + RECONCILE_INTERVAL_MS;
4946
4997
  const sweep = async () => {
4947
4998
  try {
4948
4999
  const full = Date.now() - lastFull >= SWEEP_INTERVAL_MS;
@@ -5057,8 +5108,11 @@ async function seedVerdaccioSecret(databaseUrl, secret) {
5057
5108
  const MIB = 1048576;
5058
5109
  const DRAIN_DELAY_MS = 5e3;
5059
5110
  const DRAIN_TIMEOUT_MS = 2e4;
5111
+ function parseTrustProxy(value) {
5112
+ return /^\d+$/.test(value) ? Number(value) : value;
5113
+ }
5060
5114
  async function runRegistry(args) {
5061
- const { port, storageDir, cdnCacheDir, uplink, uplinkMaxage, webEnabled, webhooks, publicUrl, authRenown, renownUrl, verdaccioSecret, localPackages, databaseUrl, pluginsDir, authStore } = args;
5115
+ const { port, storageDir, cdnCacheDir, uplink, uplinkMaxage, trustProxy, webEnabled, webhooks, publicUrl, authRenown, renownUrl, verdaccioSecret, localPackages, databaseUrl, pluginsDir, authStore } = args;
5062
5116
  const storagePath = await resolveDir(storageDir);
5063
5117
  const cdnCachePath = await resolveDir(cdnCacheDir);
5064
5118
  const s3 = s3From(args);
@@ -5084,6 +5138,7 @@ async function runRegistry(args) {
5084
5138
  uplink,
5085
5139
  uplinkMaxage,
5086
5140
  webEnabled,
5141
+ ...trustProxy ? { trustProxy: parseTrustProxy(trustProxy) } : {},
5087
5142
  ...localPackagePatterns?.length ? { localPackagePatterns } : {},
5088
5143
  ...renownEnabled && publicUrl ? { renown: {
5089
5144
  publicUrl,
@@ -5271,27 +5326,6 @@ async function runMigrate(databaseUrl, options) {
5271
5326
  await db.close();
5272
5327
  }
5273
5328
  }
5274
- async function storedPackageNames(s3, config) {
5275
- const prefix = config.keyPrefix ?? "";
5276
- const names = /* @__PURE__ */ new Set();
5277
- let token;
5278
- do {
5279
- const page = await s3.send(new ListObjectsV2Command({
5280
- Bucket: config.bucket,
5281
- Prefix: prefix,
5282
- ContinuationToken: token
5283
- }));
5284
- for (const { Key } of page.Contents ?? []) {
5285
- const rel = Key?.slice(prefix.length) ?? "";
5286
- if (!rel.endsWith("/package.json") || rel.startsWith("artifacts/")) continue;
5287
- const name = rel.slice(0, -13);
5288
- const depth = name.split("/").length;
5289
- if (name.startsWith("@") ? depth === 2 : depth === 1) names.add(name);
5290
- }
5291
- token = page.IsTruncated ? page.NextContinuationToken : void 0;
5292
- } while (token);
5293
- return names;
5294
- }
5295
5329
  async function readStoredManifest(s3, config, name) {
5296
5330
  try {
5297
5331
  const res = await s3.send(new GetObjectCommand({
@@ -5327,9 +5361,9 @@ async function runImportVerdaccioState(args) {
5327
5361
  const name = err.name;
5328
5362
  if (name !== "NoSuchKey" && name !== "NotFound") throw err;
5329
5363
  }
5330
- const names = await storedPackageNames(s3, s3Config);
5331
- for (const name of legacy.list ?? []) names.add(name);
5364
+ const names = new Set(legacy.list ?? []);
5332
5365
  const pool = new pg.Pool({ connectionString: args.databaseUrl });
5366
+ let pruned = [];
5333
5367
  try {
5334
5368
  const store = new S3DatabasePostgres(pool, pluginLogger);
5335
5369
  await store.init();
@@ -5338,6 +5372,7 @@ async function runImportVerdaccioState(args) {
5338
5372
  if (manifest) await store.record(name, manifest);
5339
5373
  }
5340
5374
  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]);
5341
5376
  for (const token of legacy.tokens ?? []) await pool.query(`INSERT INTO verdaccio_tokens ("user", key, token) VALUES ($1, $2, $3)
5342
5377
  ON CONFLICT ("user", key) DO NOTHING`, [
5343
5378
  token.user,
@@ -5349,7 +5384,7 @@ async function runImportVerdaccioState(args) {
5349
5384
  } finally {
5350
5385
  await pool.end();
5351
5386
  }
5352
- console.log(`[registry] imported ${names.size} package name(s), ${legacy.tokens?.length ?? 0} token(s)${legacy.secret ? " and the secret" : ""}`);
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)`);
5353
5388
  }
5354
5389
  //#endregion
5355
5390
  //#region cli.ts
@@ -5500,6 +5535,13 @@ const registryCommand = command({
5500
5535
  defaultValue: () => process.env.PH_REGISTRY_UPLINK_MAXAGE,
5501
5536
  defaultValueIsSerializable: true
5502
5537
  }),
5538
+ trustProxy: option({
5539
+ long: "trust-proxy",
5540
+ type: optional(string),
5541
+ description: "Express `trust proxy` (e.g. 2 proxy hops), so rate limits key on client IPs; env PH_REGISTRY_TRUST_PROXY",
5542
+ defaultValue: () => process.env.PH_REGISTRY_TRUST_PROXY,
5543
+ defaultValueIsSerializable: true
5544
+ }),
5503
5545
  webEnabled: flag({
5504
5546
  long: "web-enabled",
5505
5547
  defaultValue: () => process.env.REGISTRY_WEB !== "false",
@@ -5546,6 +5588,7 @@ const registryCommand = command({
5546
5588
  databaseUrl: redact(args.databaseUrl),
5547
5589
  listenDatabaseUrl: redact(args.listenDatabaseUrl),
5548
5590
  verdaccioSecret: redact(args.verdaccioSecret),
5591
+ s3AccessKeyId: redact(args.s3AccessKeyId),
5549
5592
  s3SecretAccessKey: redact(args.s3SecretAccessKey)
5550
5593
  });
5551
5594
  try {