@powerhousedao/registry 6.2.3-dev.6 → 6.2.3-dev.60
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 +151 -26
- package/dist/cli.d.mts +122 -19
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +5304 -819
- package/dist/cli.mjs.map +1 -1
- package/dist/plugins/@powerhousedao/verdaccio-s3-storage/es5-CoIkS7gl.js +1723 -0
- package/dist/plugins/@powerhousedao/verdaccio-s3-storage/index.js +53926 -0
- package/dist/plugins/@powerhousedao/verdaccio-s3-storage/rolldown-runtime-ch10HqoO.js +71 -0
- package/dist/plugins/verdaccio-registry-auth.js +423 -30
- package/package.json +12 -12
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esmMin = (fn, res, err) => () => {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = fn(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
17
|
+
var __exportAll = (all, no_symbols) => {
|
|
18
|
+
let target = {};
|
|
19
|
+
for (var name in all) __defProp(target, name, {
|
|
20
|
+
get: all[name],
|
|
21
|
+
enumerable: true
|
|
22
|
+
});
|
|
23
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
24
|
+
return target;
|
|
25
|
+
};
|
|
26
|
+
var __copyProps = (to, from, except, desc) => {
|
|
27
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
28
|
+
key = keys[i];
|
|
29
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
30
|
+
get: ((k) => from[k]).bind(null, key),
|
|
31
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return to;
|
|
35
|
+
};
|
|
36
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
37
|
+
value: mod,
|
|
38
|
+
enumerable: true
|
|
39
|
+
}) : target, mod));
|
|
40
|
+
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
41
|
+
//#endregion
|
|
42
|
+
Object.defineProperty(exports, "__commonJSMin", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function() {
|
|
45
|
+
return __commonJSMin;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(exports, "__esmMin", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function() {
|
|
51
|
+
return __esmMin;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(exports, "__exportAll", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function() {
|
|
57
|
+
return __exportAll;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(exports, "__toCommonJS", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
get: function() {
|
|
63
|
+
return __toCommonJS;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
Object.defineProperty(exports, "__toESM", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
get: function() {
|
|
69
|
+
return __toESM;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
@@ -19,50 +19,440 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
19
19
|
}
|
|
20
20
|
return to;
|
|
21
21
|
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
23
23
|
value: mod,
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
27
|
let _verdaccio_core = require("@verdaccio/core");
|
|
28
28
|
let bcryptjs = require("bcryptjs");
|
|
29
|
-
bcryptjs = __toESM(bcryptjs);
|
|
29
|
+
bcryptjs = __toESM(bcryptjs, 1);
|
|
30
|
+
require("@electric-sql/pglite");
|
|
31
|
+
require("@electric-sql/pglite/contrib/pg_trgm");
|
|
30
32
|
let pg = require("pg");
|
|
33
|
+
pg = __toESM(pg, 1);
|
|
31
34
|
let _renown_sdk = require("@renown/sdk");
|
|
35
|
+
//#region src/db/migrations.ts
|
|
36
|
+
const MIGRATION_LOCK_ID = 7446517101;
|
|
37
|
+
function indexConcurrently(name, definition) {
|
|
38
|
+
return [async (q) => {
|
|
39
|
+
if ((await q.query("SELECT indisvalid AS valid FROM pg_index WHERE indexrelid = to_regclass($1)", [name])).rows[0]?.valid === false) await q.query(`DROP INDEX CONCURRENTLY IF EXISTS ${name}`);
|
|
40
|
+
}, `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${name} ${definition}`];
|
|
41
|
+
}
|
|
42
|
+
const MIGRATIONS = [
|
|
43
|
+
{
|
|
44
|
+
id: 1,
|
|
45
|
+
name: "registry state",
|
|
46
|
+
statements: [
|
|
47
|
+
`CREATE TABLE IF NOT EXISTS registry_users (
|
|
48
|
+
username text PRIMARY KEY,
|
|
49
|
+
password_hash text NOT NULL,
|
|
50
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
51
|
+
)`,
|
|
52
|
+
`CREATE TABLE IF NOT EXISTS registry_package_owners (
|
|
53
|
+
package_name text PRIMARY KEY,
|
|
54
|
+
owners text[] NOT NULL,
|
|
55
|
+
claimed_at timestamptz NOT NULL DEFAULT now()
|
|
56
|
+
)`,
|
|
57
|
+
`CREATE TABLE registry_packages (
|
|
58
|
+
name text PRIMARY KEY,
|
|
59
|
+
local boolean NOT NULL DEFAULT false,
|
|
60
|
+
dist_tags jsonb NOT NULL DEFAULT '{}',
|
|
61
|
+
versions jsonb NOT NULL DEFAULT '[]',
|
|
62
|
+
times jsonb NOT NULL DEFAULT '{}',
|
|
63
|
+
latest text,
|
|
64
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
65
|
+
)`,
|
|
66
|
+
`CREATE TABLE registry_versions (
|
|
67
|
+
package text NOT NULL,
|
|
68
|
+
version text NOT NULL,
|
|
69
|
+
status text NOT NULL CHECK (status IN ('pending', 'ready', 'failed')),
|
|
70
|
+
manifest jsonb,
|
|
71
|
+
package_json_version text,
|
|
72
|
+
files jsonb NOT NULL DEFAULT '[]',
|
|
73
|
+
error text,
|
|
74
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
75
|
+
PRIMARY KEY (package, version)
|
|
76
|
+
)`,
|
|
77
|
+
`CREATE TABLE registry_piece_owners (
|
|
78
|
+
name text PRIMARY KEY,
|
|
79
|
+
package text NOT NULL,
|
|
80
|
+
claimed_at timestamptz NOT NULL DEFAULT now()
|
|
81
|
+
)`,
|
|
82
|
+
`CREATE TABLE registry_pieces (
|
|
83
|
+
name text NOT NULL,
|
|
84
|
+
version text NOT NULL,
|
|
85
|
+
package text NOT NULL,
|
|
86
|
+
display_name text NOT NULL,
|
|
87
|
+
description text,
|
|
88
|
+
descriptor jsonb,
|
|
89
|
+
descriptor_path text NOT NULL,
|
|
90
|
+
bundle_file text NOT NULL,
|
|
91
|
+
bundle_key text,
|
|
92
|
+
published_at timestamptz,
|
|
93
|
+
is_latest boolean NOT NULL DEFAULT false,
|
|
94
|
+
PRIMARY KEY (name, version)
|
|
95
|
+
)`,
|
|
96
|
+
"CREATE INDEX registry_pieces_package ON registry_pieces (package, version)",
|
|
97
|
+
"CREATE INDEX registry_pieces_bundle ON registry_pieces (bundle_file)",
|
|
98
|
+
`CREATE TABLE registry_jobs (
|
|
99
|
+
id bigserial PRIMARY KEY,
|
|
100
|
+
kind text NOT NULL,
|
|
101
|
+
package text NOT NULL,
|
|
102
|
+
version text NOT NULL DEFAULT '',
|
|
103
|
+
payload jsonb NOT NULL DEFAULT '{}',
|
|
104
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
105
|
+
run_after timestamptz NOT NULL DEFAULT now(),
|
|
106
|
+
locked_by text,
|
|
107
|
+
locked_at timestamptz,
|
|
108
|
+
requeued boolean NOT NULL DEFAULT false,
|
|
109
|
+
last_error text,
|
|
110
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
111
|
+
UNIQUE (kind, package, version)
|
|
112
|
+
)`,
|
|
113
|
+
`CREATE TABLE registry_webhooks (
|
|
114
|
+
endpoint text PRIMARY KEY,
|
|
115
|
+
headers jsonb NOT NULL DEFAULT '{}',
|
|
116
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
117
|
+
)`
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 2,
|
|
122
|
+
name: "job priority",
|
|
123
|
+
statements: ["ALTER TABLE registry_jobs ADD COLUMN priority integer NOT NULL DEFAULT 0", "CREATE INDEX registry_jobs_due ON registry_jobs (priority, id)"]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 3,
|
|
127
|
+
name: "search",
|
|
128
|
+
statements: [
|
|
129
|
+
"CREATE EXTENSION IF NOT EXISTS pg_trgm",
|
|
130
|
+
`CREATE OR REPLACE FUNCTION registry_search_name(pkg text, m jsonb) RETURNS text
|
|
131
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
132
|
+
AS $$ SELECT lower(concat_ws(' ', pkg, m->>'name')) $$`,
|
|
133
|
+
`CREATE OR REPLACE FUNCTION registry_search_text(m jsonb) RETURNS text
|
|
134
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
135
|
+
AS $$ SELECT lower(concat_ws(' ', m->>'description',
|
|
136
|
+
(SELECT string_agg(x #>> '{}', ' ')
|
|
137
|
+
FROM jsonb_path_query(m, 'lax $.*[*].name') x),
|
|
138
|
+
(SELECT string_agg(x #>> '{}', ' ')
|
|
139
|
+
FROM jsonb_path_query(m, 'lax $.*[*].id') x))) $$`,
|
|
140
|
+
`CREATE OR REPLACE FUNCTION registry_search_doc(pkg text, m jsonb) RETURNS text
|
|
141
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
142
|
+
AS $$ SELECT concat_ws(' ', registry_search_name(pkg, m),
|
|
143
|
+
registry_search_text(m)) $$`,
|
|
144
|
+
`CREATE OR REPLACE FUNCTION registry_piece_search_name(
|
|
145
|
+
name text, display_name text, d jsonb) RETURNS text
|
|
146
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
147
|
+
AS $$ SELECT lower(concat_ws(' ', name, display_name,
|
|
148
|
+
d->>'displayName')) $$`,
|
|
149
|
+
`CREATE OR REPLACE FUNCTION registry_piece_search_doc(
|
|
150
|
+
name text, display_name text, description text, d jsonb) RETURNS text
|
|
151
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
152
|
+
AS $$ SELECT concat_ws(' ',
|
|
153
|
+
registry_piece_search_name(name, display_name, d),
|
|
154
|
+
lower(coalesce(d->>'description', description))) $$`,
|
|
155
|
+
`ALTER TABLE registry_packages
|
|
156
|
+
ADD COLUMN listed_manifest jsonb,
|
|
157
|
+
ADD COLUMN listed_package_json_version text,
|
|
158
|
+
ADD COLUMN search_doc text GENERATED ALWAYS AS
|
|
159
|
+
(registry_search_doc(name, listed_manifest)) STORED,
|
|
160
|
+
ADD COLUMN search_tsv tsvector GENERATED ALWAYS AS (
|
|
161
|
+
setweight(to_tsvector('english',
|
|
162
|
+
coalesce(registry_search_name(name, listed_manifest), '')), 'A') ||
|
|
163
|
+
setweight(to_tsvector('english',
|
|
164
|
+
coalesce(registry_search_text(listed_manifest), '')), 'B')) STORED`,
|
|
165
|
+
`UPDATE registry_versions SET manifest = NULL
|
|
166
|
+
WHERE jsonb_typeof(manifest) = 'null'`,
|
|
167
|
+
`UPDATE registry_pieces SET descriptor = NULL
|
|
168
|
+
WHERE jsonb_typeof(descriptor) = 'null'`,
|
|
169
|
+
`UPDATE registry_packages p
|
|
170
|
+
SET listed_manifest = v.manifest,
|
|
171
|
+
listed_package_json_version = v.package_json_version
|
|
172
|
+
FROM registry_versions v
|
|
173
|
+
WHERE v.package = p.name AND v.version = p.latest
|
|
174
|
+
AND v.status = 'ready' AND v.manifest IS NOT NULL`,
|
|
175
|
+
`ALTER TABLE registry_pieces
|
|
176
|
+
ADD COLUMN search_doc text GENERATED ALWAYS AS
|
|
177
|
+
(registry_piece_search_doc(name, display_name, description, descriptor)) STORED,
|
|
178
|
+
ADD COLUMN search_tsv tsvector GENERATED ALWAYS AS (
|
|
179
|
+
setweight(to_tsvector('english',
|
|
180
|
+
coalesce(registry_piece_search_name(name, display_name, descriptor), '')), 'A') ||
|
|
181
|
+
setweight(to_tsvector('english',
|
|
182
|
+
coalesce(descriptor->>'description', description, '')), 'B')) STORED`
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 4,
|
|
187
|
+
name: "write ordering",
|
|
188
|
+
statements: [
|
|
189
|
+
"ALTER TABLE registry_packages ADD COLUMN modified timestamptz",
|
|
190
|
+
`CREATE TABLE registry_unpublished (
|
|
191
|
+
package text NOT NULL,
|
|
192
|
+
version text NOT NULL,
|
|
193
|
+
unpublished_at timestamptz NOT NULL DEFAULT now(),
|
|
194
|
+
PRIMARY KEY (package, version)
|
|
195
|
+
)`,
|
|
196
|
+
`ALTER TABLE registry_jobs SET (autovacuum_vacuum_scale_factor = 0,
|
|
197
|
+
autovacuum_vacuum_threshold = 500)`
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 5,
|
|
202
|
+
name: "search indexes",
|
|
203
|
+
transaction: false,
|
|
204
|
+
statements: [
|
|
205
|
+
...indexConcurrently("registry_packages_search_trgm", `ON registry_packages USING gin (search_doc gin_trgm_ops)
|
|
206
|
+
WHERE local AND listed_manifest IS NOT NULL`),
|
|
207
|
+
...indexConcurrently("registry_packages_search_tsv", `ON registry_packages USING gin (search_tsv)
|
|
208
|
+
WHERE local AND listed_manifest IS NOT NULL`),
|
|
209
|
+
...indexConcurrently("registry_pieces_search_trgm", "ON registry_pieces USING gin (search_doc gin_trgm_ops) WHERE is_latest"),
|
|
210
|
+
...indexConcurrently("registry_pieces_search_tsv", "ON registry_pieces USING gin (search_tsv) WHERE is_latest"),
|
|
211
|
+
...indexConcurrently("registry_pieces_latest", "ON registry_pieces (package) WHERE is_latest"),
|
|
212
|
+
"DROP INDEX CONCURRENTLY IF EXISTS registry_pieces_bundle"
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 6,
|
|
217
|
+
name: "reconcile by revision",
|
|
218
|
+
transaction: false,
|
|
219
|
+
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'")]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 7,
|
|
223
|
+
name: "permanent version failures",
|
|
224
|
+
transaction: false,
|
|
225
|
+
statements: ["ALTER TABLE registry_versions ADD COLUMN IF NOT EXISTS permanent boolean NOT NULL DEFAULT false"]
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
const MIGRATIONS_TABLE = `CREATE TABLE IF NOT EXISTS registry_migrations (
|
|
229
|
+
id integer PRIMARY KEY,
|
|
230
|
+
name text NOT NULL,
|
|
231
|
+
applied_at timestamptz NOT NULL DEFAULT now()
|
|
232
|
+
)`;
|
|
233
|
+
const LOCK_TIMEOUT = "5s";
|
|
234
|
+
async function appliedIds(q) {
|
|
235
|
+
if (!(await q.query("SELECT to_regclass('registry_migrations') IS NOT NULL AS exists")).rows[0]?.exists) return /* @__PURE__ */ new Set();
|
|
236
|
+
const applied = await q.query("SELECT id FROM registry_migrations");
|
|
237
|
+
return new Set(applied.rows.map((row) => row.id));
|
|
238
|
+
}
|
|
239
|
+
async function runSteps(q, migration, embedded) {
|
|
240
|
+
for (const step of migration.statements) if (typeof step === "function") await step(q);
|
|
241
|
+
else await q.query(embedded ? step.replace(/ CONCURRENTLY/g, "") : step);
|
|
242
|
+
await q.query("INSERT INTO registry_migrations (id, name) VALUES ($1, $2)", [migration.id, migration.name]);
|
|
243
|
+
}
|
|
244
|
+
const LOCK_POLL_MS = 500;
|
|
245
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
246
|
+
async function applyInTransaction(db, migration) {
|
|
247
|
+
for (;;) {
|
|
248
|
+
if (await db.transaction(async (tx) => {
|
|
249
|
+
if (!(await tx.query("SELECT pg_try_advisory_xact_lock($1) AS ok", [MIGRATION_LOCK_ID])).rows[0]?.ok) return false;
|
|
250
|
+
await tx.query(`SET LOCAL lock_timeout = '${LOCK_TIMEOUT}'`);
|
|
251
|
+
await tx.query(MIGRATIONS_TABLE);
|
|
252
|
+
if (!(await appliedIds(tx)).has(migration.id)) await runSteps(tx, migration, db.embedded);
|
|
253
|
+
return true;
|
|
254
|
+
})) return;
|
|
255
|
+
await sleep(LOCK_POLL_MS);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function applyOnline(db, migration) {
|
|
259
|
+
await db.session(async (s) => {
|
|
260
|
+
for (;;) {
|
|
261
|
+
if ((await s.query("SELECT pg_try_advisory_lock($1) AS ok", [MIGRATION_LOCK_ID])).rows[0]?.ok) break;
|
|
262
|
+
await sleep(LOCK_POLL_MS);
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
await s.query(`SET lock_timeout = '${LOCK_TIMEOUT}'`);
|
|
266
|
+
await s.query(MIGRATIONS_TABLE);
|
|
267
|
+
if ((await appliedIds(s)).has(migration.id)) return;
|
|
268
|
+
await runSteps(s, migration, false);
|
|
269
|
+
} finally {
|
|
270
|
+
await s.query("SELECT pg_advisory_unlock($1)", [MIGRATION_LOCK_ID]);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/** Applies pending migrations; any number of processes may run it at once. */
|
|
275
|
+
async function migrate(db) {
|
|
276
|
+
const applied = await appliedIds(db);
|
|
277
|
+
for (const migration of MIGRATIONS) {
|
|
278
|
+
if (applied.has(migration.id)) continue;
|
|
279
|
+
if (migration.transaction === false && !db.embedded) await applyOnline(db, migration);
|
|
280
|
+
else await applyInTransaction(db, migration);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** Migrations this build needs that the database hasn't applied. */
|
|
284
|
+
async function pendingMigrations(db) {
|
|
285
|
+
const applied = await appliedIds(db);
|
|
286
|
+
return MIGRATIONS.filter((m) => !applied.has(m.id)).map(({ id, name }) => ({
|
|
287
|
+
id,
|
|
288
|
+
name
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
async function checkSchema(db) {
|
|
292
|
+
const pending = await pendingMigrations(db);
|
|
293
|
+
if (pending.length === 0) return;
|
|
294
|
+
const list = pending.map((m) => `${m.id} (${m.name})`).join(", ");
|
|
295
|
+
throw new Error(`the registry database is missing migration(s) ${list}; run \`ph-registry migrate\` first, or set PH_REGISTRY_MIGRATE_ON_BOOT=true`);
|
|
296
|
+
}
|
|
297
|
+
/** Whether to migrate at boot: the flag, else the env, else only for PGlite. */
|
|
298
|
+
function migrateOnBoot(explicit, databaseUrl) {
|
|
299
|
+
if (explicit !== void 0) return explicit;
|
|
300
|
+
const env = process.env.PH_REGISTRY_MIGRATE_ON_BOOT;
|
|
301
|
+
if (env === "true" || env === "false") return env === "true";
|
|
302
|
+
return !databaseUrl;
|
|
303
|
+
}
|
|
304
|
+
/** Migrates, or checks the schema is current and refuses to start if not. */
|
|
305
|
+
function prepareSchema(db, migrateNow) {
|
|
306
|
+
return migrateNow ? migrate(db) : checkSchema(db);
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/db/database.ts
|
|
310
|
+
const LISTENER_CHECK_MS = 3e4;
|
|
311
|
+
function createPostgresDatabase(connectionString, listenConnectionString = connectionString, poolMax = 10) {
|
|
312
|
+
const pool = new pg.default.Pool({
|
|
313
|
+
connectionString,
|
|
314
|
+
connectionTimeoutMillis: 1e4,
|
|
315
|
+
max: poolMax
|
|
316
|
+
});
|
|
317
|
+
pool.on("error", (err) => {
|
|
318
|
+
console.error("[registry] postgres pool error:", err.message);
|
|
319
|
+
});
|
|
320
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
321
|
+
let listener;
|
|
322
|
+
let connected = false;
|
|
323
|
+
let everConnected = false;
|
|
324
|
+
let closed = false;
|
|
325
|
+
const reconnectHandlers = /* @__PURE__ */ new Set();
|
|
326
|
+
const dropListener = (client, reason) => {
|
|
327
|
+
console.error("[registry] postgres listener lost:", reason);
|
|
328
|
+
connected = false;
|
|
329
|
+
listener = void 0;
|
|
330
|
+
client.end().catch(() => void 0);
|
|
331
|
+
if (!closed) setTimeout(() => void reconnect(), 1e3);
|
|
332
|
+
};
|
|
333
|
+
const connectListener = async () => {
|
|
334
|
+
const client = new pg.default.Client({
|
|
335
|
+
connectionString: listenConnectionString,
|
|
336
|
+
keepAlive: true
|
|
337
|
+
});
|
|
338
|
+
client.on("notification", (msg) => {
|
|
339
|
+
for (const fn of listeners.get(msg.channel) ?? []) fn(msg.payload ?? "");
|
|
340
|
+
});
|
|
341
|
+
const timers = {};
|
|
342
|
+
client.on("error", (err) => {
|
|
343
|
+
clearInterval(timers.check);
|
|
344
|
+
dropListener(client, err.message);
|
|
345
|
+
});
|
|
346
|
+
await client.connect();
|
|
347
|
+
for (const channel of listeners.keys()) await client.query(`LISTEN ${pg.default.escapeIdentifier(channel)}`);
|
|
348
|
+
const check = setInterval(() => {
|
|
349
|
+
const timeout = setTimeout(() => {
|
|
350
|
+
clearInterval(check);
|
|
351
|
+
dropListener(client, "health check timed out");
|
|
352
|
+
}, 1e4);
|
|
353
|
+
client.query("SELECT 1").catch(() => void 0).finally(() => clearTimeout(timeout));
|
|
354
|
+
}, LISTENER_CHECK_MS);
|
|
355
|
+
check.unref();
|
|
356
|
+
timers.check = check;
|
|
357
|
+
client.on("end", () => clearInterval(check));
|
|
358
|
+
connected = true;
|
|
359
|
+
if (everConnected) for (const fn of reconnectHandlers) fn();
|
|
360
|
+
everConnected = true;
|
|
361
|
+
return client;
|
|
362
|
+
};
|
|
363
|
+
const reconnect = async () => {
|
|
364
|
+
if (closed || listener || listeners.size === 0) return;
|
|
365
|
+
listener = connectListener();
|
|
366
|
+
await listener.catch(() => {
|
|
367
|
+
listener = void 0;
|
|
368
|
+
setTimeout(() => void reconnect(), 1e3);
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
return {
|
|
372
|
+
async query(text, params) {
|
|
373
|
+
return pool.query(text, params);
|
|
374
|
+
},
|
|
375
|
+
async transaction(fn) {
|
|
376
|
+
const client = await pool.connect();
|
|
377
|
+
try {
|
|
378
|
+
await client.query("BEGIN");
|
|
379
|
+
const result = await fn({ query: (text, params) => client.query(text, params) });
|
|
380
|
+
await client.query("COMMIT");
|
|
381
|
+
return result;
|
|
382
|
+
} catch (err) {
|
|
383
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
384
|
+
throw err;
|
|
385
|
+
} finally {
|
|
386
|
+
client.release();
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
async session(fn) {
|
|
390
|
+
const client = new pg.default.Client({
|
|
391
|
+
connectionString: listenConnectionString,
|
|
392
|
+
connectionTimeoutMillis: 1e4
|
|
393
|
+
});
|
|
394
|
+
client.on("error", (err) => {
|
|
395
|
+
console.error("[registry] postgres session error:", err.message);
|
|
396
|
+
});
|
|
397
|
+
await client.connect();
|
|
398
|
+
try {
|
|
399
|
+
return await fn({ query: (text, params) => client.query(text, params) });
|
|
400
|
+
} finally {
|
|
401
|
+
await client.end().catch(() => void 0);
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
embedded: false,
|
|
405
|
+
async listen(channel, onMessage) {
|
|
406
|
+
let set = listeners.get(channel);
|
|
407
|
+
const isNew = !set;
|
|
408
|
+
if (!set) {
|
|
409
|
+
set = /* @__PURE__ */ new Set();
|
|
410
|
+
listeners.set(channel, set);
|
|
411
|
+
}
|
|
412
|
+
set.add(onMessage);
|
|
413
|
+
listener ??= connectListener();
|
|
414
|
+
const client = await listener;
|
|
415
|
+
if (isNew) await client.query(`LISTEN ${pg.default.escapeIdentifier(channel)}`);
|
|
416
|
+
return () => {
|
|
417
|
+
set.delete(onMessage);
|
|
418
|
+
return Promise.resolve();
|
|
419
|
+
};
|
|
420
|
+
},
|
|
421
|
+
async notify(channel, payload) {
|
|
422
|
+
await pool.query("SELECT pg_notify($1, $2)", [channel, payload]);
|
|
423
|
+
},
|
|
424
|
+
listening: () => connected || listeners.size === 0,
|
|
425
|
+
onReconnect: (fn) => {
|
|
426
|
+
reconnectHandlers.add(fn);
|
|
427
|
+
},
|
|
428
|
+
async close() {
|
|
429
|
+
closed = true;
|
|
430
|
+
await (await listener?.catch(() => void 0))?.end().catch(() => void 0);
|
|
431
|
+
await pool.end();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
32
436
|
//#region src/auth/pg-store.ts
|
|
33
|
-
/**
|
|
437
|
+
/** A database over a Postgres connection string. */
|
|
34
438
|
function createPgPool(databaseUrl) {
|
|
35
|
-
return
|
|
439
|
+
return createPostgresDatabase(databaseUrl);
|
|
36
440
|
}
|
|
37
441
|
/**
|
|
38
|
-
*
|
|
39
|
-
* - registry_users(username PK, password_hash, created_at)
|
|
40
|
-
* - registry_package_owners(package_name PK, owners text[], claimed_at)
|
|
442
|
+
* Database-backed AuthStore over `registry_users` and `registry_package_owners`.
|
|
41
443
|
*
|
|
42
444
|
* Ownership claim is race-free: `INSERT ... ON CONFLICT DO NOTHING` means the
|
|
43
445
|
* first publisher wins atomically; the follow-up read returns the actual
|
|
44
446
|
* owners so a losing racer is denied.
|
|
45
|
-
*
|
|
46
|
-
* Takes an already-built `pg.Pool` (tests inject a pg-mem pool cast to Pool).
|
|
47
447
|
*/
|
|
48
|
-
function createPgStore(pool) {
|
|
448
|
+
function createPgStore(pool, options = {}) {
|
|
49
449
|
let initialized = null;
|
|
50
450
|
return {
|
|
51
451
|
init() {
|
|
52
|
-
initialized ??= (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
password_hash text NOT NULL,
|
|
57
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
58
|
-
)`);
|
|
59
|
-
await pool.query(`
|
|
60
|
-
CREATE TABLE IF NOT EXISTS registry_package_owners (
|
|
61
|
-
package_name text PRIMARY KEY,
|
|
62
|
-
owners text[] NOT NULL,
|
|
63
|
-
claimed_at timestamptz NOT NULL DEFAULT now()
|
|
64
|
-
)`);
|
|
65
|
-
})();
|
|
452
|
+
initialized ??= prepareSchema(pool, options.migrate ?? true).catch((err) => {
|
|
453
|
+
initialized = null;
|
|
454
|
+
throw err;
|
|
455
|
+
});
|
|
66
456
|
return initialized;
|
|
67
457
|
},
|
|
68
458
|
async getUser(username) {
|
|
@@ -96,7 +486,7 @@ function createPgStore(pool) {
|
|
|
96
486
|
return (await pool.query("SELECT owners FROM registry_package_owners WHERE package_name = $1", [pkg])).rows[0]?.owners ?? [];
|
|
97
487
|
},
|
|
98
488
|
close() {
|
|
99
|
-
return pool.
|
|
489
|
+
return pool.close();
|
|
100
490
|
}
|
|
101
491
|
};
|
|
102
492
|
}
|
|
@@ -194,9 +584,11 @@ function createRegistryAuthPlugin(store, renownVerifier) {
|
|
|
194
584
|
}).catch((err) => cb(internal(err)));
|
|
195
585
|
},
|
|
196
586
|
adduser(user, password, cb) {
|
|
197
|
-
ready.then(() => store.createUser(user, bcryptjs.default.hashSync(password, BCRYPT_ROUNDS))).then((created) => {
|
|
198
|
-
if (
|
|
199
|
-
|
|
587
|
+
ready.then(() => store.createUser(user, bcryptjs.default.hashSync(password, BCRYPT_ROUNDS))).then(async (created) => {
|
|
588
|
+
if (created) return cb(null, true);
|
|
589
|
+
const rec = await store.getUser(user);
|
|
590
|
+
if (rec && bcryptjs.default.compareSync(password, rec.passwordHash)) return cb(null, true);
|
|
591
|
+
return cb(_verdaccio_core.errorUtils.getConflict("username already registered"));
|
|
200
592
|
}).catch((err) => cb(internal(err)));
|
|
201
593
|
},
|
|
202
594
|
allow_access(_user, _pkg, cb) {
|
|
@@ -228,9 +620,10 @@ function createRegistryAuthPlugin(store, renownVerifier) {
|
|
|
228
620
|
/** Verdaccio plugin entry. The loader calls this factory (or `new`s the
|
|
229
621
|
* default export — both return the plugin object). */
|
|
230
622
|
function registryAuthPlugin(config) {
|
|
231
|
-
const
|
|
623
|
+
const databaseUrl = () => config.databaseUrl ?? (() => {
|
|
232
624
|
throw new Error("registry-auth plugin requires a databaseUrl (or a store token)");
|
|
233
|
-
})()
|
|
625
|
+
})();
|
|
626
|
+
const plugin = createRegistryAuthPlugin((config.storeToken ? takeAuthStore(config.storeToken) : void 0) ?? createPgStore(createPgPool(databaseUrl()), { migrate: migrateOnBoot(void 0, config.databaseUrl) }), config.publicUrl ? createRenownVerifier({
|
|
234
627
|
publicUrl: config.publicUrl,
|
|
235
628
|
renownUrl: config.renownUrl
|
|
236
629
|
}) : void 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/registry",
|
|
3
|
-
"version": "6.2.3-dev.
|
|
3
|
+
"version": "6.2.3-dev.60",
|
|
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,29 +29,29 @@
|
|
|
29
29
|
"author": "",
|
|
30
30
|
"license": "ISC",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@
|
|
33
|
-
"@
|
|
34
|
-
"@
|
|
35
|
-
"@
|
|
32
|
+
"@aws-sdk/client-s3": "3.1087.0",
|
|
33
|
+
"@electric-sql/pglite": "0.3.15",
|
|
34
|
+
"@powerhousedao/shared": "6.2.3-dev.60",
|
|
35
|
+
"@renown/sdk": "6.2.3-dev.60",
|
|
36
|
+
"@verdaccio/core": "9.0.0-next-9.31",
|
|
36
37
|
"bcryptjs": "^2.4.3",
|
|
37
38
|
"cmd-ts": "0.15.0",
|
|
38
39
|
"express": "^4.22.1",
|
|
39
40
|
"find-up": "^8.0.0",
|
|
40
41
|
"pg": "8.18.0",
|
|
41
42
|
"tar": "^7.5.11",
|
|
42
|
-
"verdaccio": "
|
|
43
|
-
"verdaccio-aws-s3-storage": "^10.4.0"
|
|
43
|
+
"verdaccio": "7.0.0-next-7.27"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"
|
|
46
|
+
"@powerhousedao/reactor-workflow": "6.2.3-dev.60",
|
|
47
|
+
"@powerhousedao/verdaccio-s3-storage": "13.0.0",
|
|
47
48
|
"@types/bcryptjs": "^2.4.6",
|
|
48
49
|
"@types/express": "^4.17.21",
|
|
49
50
|
"@types/node": "25.2.3",
|
|
50
51
|
"@types/pg": "8.16.0",
|
|
51
|
-
"@
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"vitest": "4.1.1"
|
|
52
|
+
"@verdaccio/local-storage": "14.0.0-next-9.31",
|
|
53
|
+
"tsdown": "0.23.0",
|
|
54
|
+
"vitest": "5.0.3"
|
|
55
55
|
},
|
|
56
56
|
"homepage": "https://academy.vetra.io",
|
|
57
57
|
"scripts": {
|