@adonis-agora/filter 1.0.0-rc.1 → 1.0.0-rc.3

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/commands/agora_doctor.d.ts +23 -0
  3. package/dist/commands/agora_doctor.d.ts.map +1 -0
  4. package/dist/commands/agora_doctor.js +110 -0
  5. package/dist/commands/agora_doctor.js.map +1 -0
  6. package/dist/commands/filter_doctor.d.ts +8 -0
  7. package/dist/commands/filter_doctor.d.ts.map +1 -0
  8. package/dist/commands/filter_doctor.js +8 -0
  9. package/dist/commands/filter_doctor.js.map +1 -0
  10. package/dist/commands/main.d.ts +6 -4
  11. package/dist/commands/main.d.ts.map +1 -1
  12. package/dist/commands/main.js +6 -3
  13. package/dist/commands/main.js.map +1 -1
  14. package/dist/providers/filter_provider.d.ts +1 -1
  15. package/dist/providers/filter_provider.d.ts.map +1 -1
  16. package/dist/providers/filter_provider.js +4 -1
  17. package/dist/providers/filter_provider.js.map +1 -1
  18. package/dist/src/doctor/agora_doctor.d.ts +116 -0
  19. package/dist/src/doctor/agora_doctor.d.ts.map +1 -0
  20. package/dist/src/doctor/agora_doctor.js +685 -0
  21. package/dist/src/doctor/agora_doctor.js.map +1 -0
  22. package/dist/src/doctor/checks.d.ts +21 -0
  23. package/dist/src/doctor/checks.d.ts.map +1 -0
  24. package/dist/src/doctor/checks.js +207 -0
  25. package/dist/src/doctor/checks.js.map +1 -0
  26. package/dist/src/doctor/index.d.ts +5 -0
  27. package/dist/src/doctor/index.d.ts.map +1 -0
  28. package/dist/src/doctor/index.js +5 -0
  29. package/dist/src/doctor/index.js.map +1 -0
  30. package/dist/src/generate_client.d.ts +10 -0
  31. package/dist/src/generate_client.d.ts.map +1 -1
  32. package/dist/src/generate_client.js +33 -4
  33. package/dist/src/generate_client.js.map +1 -1
  34. package/dist/src/index.d.ts +1 -1
  35. package/dist/src/index.js +1 -1
  36. package/package.json +8 -2
@@ -0,0 +1,685 @@
1
+ // agora-doctor runner v1 — keep identical across libs
2
+ // Revision 1.1 (see the canonical README for the changelog).
3
+ // This file is copied byte for byte into every @adonis-agora lib (src/doctor/agora_doctor.ts); each
4
+ // repo's CI checks its sha256 against the pinned canonical one. Never edit a lib's copy: change the
5
+ // canonical copy, then re-copy it into every lib.
6
+ import { access, readFile, realpath } from 'node:fs/promises';
7
+ import { dirname, join } from 'node:path';
8
+ import { pathToFileURL } from 'node:url';
9
+ /** The container binding the registry lives at. */
10
+ export const AGORA_DOCTOR_BINDING = 'agora.doctor';
11
+ /** A doctor problem that stops the run itself (exit 2). */
12
+ export class AgoraDoctorUsageError extends Error {
13
+ }
14
+ /**
15
+ * Add a lib's checks to the app's doctor registry, creating it when absent. Call it from a provider's
16
+ * `boot()` (Adonis does not await `register()`, so an async registration there can be lost). A
17
+ * registry of another version is left alone; a duplicate id is skipped.
18
+ */
19
+ export async function registerDoctorChecks(container, checks) {
20
+ // A container without value bindings (a test double) has nowhere to keep them.
21
+ if (typeof container.hasBinding !== 'function' || typeof container.bindValue !== 'function')
22
+ return;
23
+ if (!container.hasBinding(AGORA_DOCTOR_BINDING)) {
24
+ const fresh = { version: 1, checks: [] };
25
+ container.bindValue(AGORA_DOCTOR_BINDING, fresh);
26
+ }
27
+ const registry = (await container.make(AGORA_DOCTOR_BINDING));
28
+ if (registry?.version !== 1 || !Array.isArray(registry.checks))
29
+ return;
30
+ for (const check of checks) {
31
+ if (registry.checks.some((existing) => existing.id === check.id))
32
+ continue;
33
+ registry.checks.push(check);
34
+ }
35
+ }
36
+ /** The registered checks; throws {@link AgoraDoctorUsageError} on a registry of another version. */
37
+ export async function readDoctorChecks(container) {
38
+ if (!container.hasBinding(AGORA_DOCTOR_BINDING))
39
+ return [];
40
+ const registry = (await container.make(AGORA_DOCTOR_BINDING));
41
+ if (registry?.version !== 1 || !Array.isArray(registry.checks)) {
42
+ throw new AgoraDoctorUsageError(`the doctor registry has version ${String(registry?.version)}; this command reads version 1. Upgrade the @adonis-agora libs together.`);
43
+ }
44
+ return registry.checks;
45
+ }
46
+ /** The app's `@adonis-agora/*` dependencies that publish a `./doctor` entry, sorted by name. */
47
+ async function doctorPackages(app) {
48
+ const manifest = await readJson(app.makePath('package.json'));
49
+ const names = Object.keys({
50
+ ...manifest?.dependencies,
51
+ ...manifest?.devDependencies,
52
+ }).filter((name) => name.startsWith('@adonis-agora/'));
53
+ const found = [];
54
+ for (const name of names.sort()) {
55
+ const dir = await packageDir(name, app.makePath());
56
+ if (dir === null)
57
+ continue;
58
+ const pkg = await readJson(join(dir, 'package.json'));
59
+ const entry = exportTarget(pkg?.exports, './doctor');
60
+ if (entry !== null)
61
+ found.push({ name, dir, entry });
62
+ }
63
+ return found;
64
+ }
65
+ /** `@adonis-agora/durable` → `durable`. */
66
+ function libOfPackage(name) {
67
+ return name.slice('@adonis-agora/'.length);
68
+ }
69
+ /**
70
+ * The checks of every installed `@adonis-agora/*` package that publishes a `./doctor` entry
71
+ * (`export const agoraDoctorChecks`), imported from the app root without booting providers. A
72
+ * package whose entry fails to load is reported as an error (`<lib>.doctor-entry`), never skipped.
73
+ */
74
+ export async function discoverDoctorChecks(app) {
75
+ const checks = [];
76
+ for (const { name, dir, entry } of await doctorPackages(app)) {
77
+ let problem = null;
78
+ try {
79
+ const mod = (await import(pathToFileURL(join(dir, entry)).href));
80
+ if (Array.isArray(mod.agoraDoctorChecks)) {
81
+ for (const check of mod.agoraDoctorChecks) {
82
+ if (!checks.some((c) => c.id === check.id))
83
+ checks.push(check);
84
+ }
85
+ }
86
+ else {
87
+ problem = 'it does not export `agoraDoctorChecks`';
88
+ }
89
+ }
90
+ catch (error) {
91
+ problem = `it failed to load: ${error instanceof Error ? error.message : String(error)}`;
92
+ }
93
+ if (problem !== null) {
94
+ const message = `The ./doctor entry of ${name}: ${problem}.`;
95
+ checks.push({
96
+ id: `${libOfPackage(name)}.doctor-entry`,
97
+ lib: libOfPackage(name),
98
+ package: name,
99
+ title: 'Doctor checks load',
100
+ run: () => ({
101
+ status: 'error',
102
+ message,
103
+ fix: `Reinstall ${name}, or run without --no-boot to see the boot error.`,
104
+ }),
105
+ });
106
+ }
107
+ }
108
+ return checks;
109
+ }
110
+ /**
111
+ * One `<lib>.registered` warning per installed package that has doctor checks but registered none:
112
+ * its provider is missing from `adonisrc.ts`.
113
+ */
114
+ async function unregisteredChecks(app, registeredPackages) {
115
+ return (await doctorPackages(app))
116
+ .filter(({ name }) => !registeredPackages.has(name))
117
+ .map(({ name }) => ({
118
+ id: `${libOfPackage(name)}.registered`,
119
+ lib: libOfPackage(name),
120
+ package: name,
121
+ title: 'Provider registered',
122
+ run: () => ({
123
+ status: 'warn',
124
+ message: `${name} is installed but its provider is not registered, so none of its checks ran.`,
125
+ fix: `Run \`node ace configure ${name}\`, or add its provider to adonisrc.ts.`,
126
+ }),
127
+ }));
128
+ }
129
+ function exportTarget(exportsField, subpath) {
130
+ if (typeof exportsField !== 'object' || exportsField === null)
131
+ return null;
132
+ const target = exportsField[subpath];
133
+ if (typeof target === 'string')
134
+ return target;
135
+ if (typeof target === 'object' && target !== null) {
136
+ const t = target;
137
+ for (const key of ['import', 'default'])
138
+ if (typeof t[key] === 'string')
139
+ return t[key];
140
+ }
141
+ return null;
142
+ }
143
+ async function readJson(path) {
144
+ try {
145
+ return JSON.parse(await readFile(path, 'utf8'));
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ /** The directory of `name` as Node would find it from `from` (walking up `node_modules`), real path. */
152
+ export async function packageDir(name, from) {
153
+ let dir = from;
154
+ for (;;) {
155
+ const candidate = join(dir, 'node_modules', name);
156
+ if ((await readJson(join(candidate, 'package.json'))) !== null) {
157
+ try {
158
+ return await realpath(candidate);
159
+ }
160
+ catch {
161
+ return candidate;
162
+ }
163
+ }
164
+ const parent = dirname(dir);
165
+ if (parent === dir)
166
+ return null;
167
+ dir = parent;
168
+ }
169
+ }
170
+ /** The installed version of `name` resolved from `from`, or `null` when it is not installed. */
171
+ export async function installedVersion(name, from) {
172
+ const dir = await packageDir(name, from);
173
+ if (dir === null)
174
+ return null;
175
+ const pkg = await readJson(join(dir, 'package.json'));
176
+ return typeof pkg?.version === 'string' ? pkg.version : null;
177
+ }
178
+ const PARTIAL = /^v?(\d+|x|X|\*)(?:\.(\d+|x|X|\*))?(?:\.(\d+|x|X|\*))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
179
+ function parsePartial(text) {
180
+ const m = PARTIAL.exec(text.trim());
181
+ if (!m)
182
+ return null;
183
+ const part = (s) => s === undefined || s === 'x' || s === 'X' || s === '*' ? null : Number(s);
184
+ const major = part(m[1]);
185
+ const minor = major === null ? null : part(m[2]);
186
+ const patch = minor === null ? null : part(m[3]);
187
+ return { major, minor, patch, pre: patch !== null && m[4] ? m[4].split('.') : [] };
188
+ }
189
+ function parseVersion(text) {
190
+ const p = parsePartial(text);
191
+ if (!p || p.major === null)
192
+ return null;
193
+ return { nums: [p.major, p.minor ?? 0, p.patch ?? 0], pre: p.pre };
194
+ }
195
+ const v = (major, minor, patch, pre = []) => ({
196
+ nums: [major, minor, patch],
197
+ pre,
198
+ });
199
+ /** The lowest version above every `major.minor.patch` release: `x.y.z-0`. */
200
+ const floor = (major, minor, patch) => v(major, minor, patch, ['0']);
201
+ function compare(a, b) {
202
+ for (const i of [0, 1, 2]) {
203
+ const d = a.nums[i] - b.nums[i];
204
+ if (d !== 0)
205
+ return d;
206
+ }
207
+ if (a.pre.length === 0 || b.pre.length === 0)
208
+ return b.pre.length - a.pre.length;
209
+ for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i += 1) {
210
+ const x = a.pre[i];
211
+ const y = b.pre[i];
212
+ if (x === undefined)
213
+ return -1;
214
+ if (y === undefined)
215
+ return 1;
216
+ const xNum = /^\d+$/.test(x);
217
+ const yNum = /^\d+$/.test(y);
218
+ if (xNum && yNum) {
219
+ if (Number(x) !== Number(y))
220
+ return Number(x) - Number(y);
221
+ }
222
+ else if (xNum !== yNum) {
223
+ return xNum ? -1 : 1;
224
+ }
225
+ else if (x !== y) {
226
+ return x < y ? -1 : 1;
227
+ }
228
+ }
229
+ return 0;
230
+ }
231
+ const ANY = [];
232
+ const NONE = [{ op: '<', v: v(0, 0, 0, ['0']) }];
233
+ /** The bounds of one comparator (`^1.2`, `>=1`, `1.x`, …); `null` when it does not parse. */
234
+ function boundsOf(comparator) {
235
+ const m = /^(~>|\^|~|>=|<=|>|<|=)?\s*(.*)$/.exec(comparator.trim());
236
+ if (!m)
237
+ return null;
238
+ const op = m[1] ?? '';
239
+ const rest = m[2] ?? '';
240
+ if (rest === '')
241
+ return op === '' ? ANY : null;
242
+ const p = parsePartial(rest);
243
+ if (!p)
244
+ return null;
245
+ const { major: M, minor: m2, patch: P, pre } = p;
246
+ if (M === null)
247
+ return op === '<' || op === '>' ? NONE : ANY;
248
+ // A partial lower bound starts at the first prerelease (`>=1` is `>=1.0.0-0`).
249
+ const low = P === null ? floor(M, m2 ?? 0, 0) : v(M, m2 ?? 0, P, pre);
250
+ if (op === '~' || op === '~>') {
251
+ if (m2 === null)
252
+ return [
253
+ { op: '>=', v: low },
254
+ { op: '<', v: floor(M + 1, 0, 0) },
255
+ ];
256
+ return [
257
+ { op: '>=', v: low },
258
+ { op: '<', v: floor(M, m2 + 1, 0) },
259
+ ];
260
+ }
261
+ if (op === '^') {
262
+ if (m2 === null)
263
+ return [
264
+ { op: '>=', v: low },
265
+ { op: '<', v: floor(M + 1, 0, 0) },
266
+ ];
267
+ if (M > 0)
268
+ return [
269
+ { op: '>=', v: low },
270
+ { op: '<', v: floor(M + 1, 0, 0) },
271
+ ];
272
+ if (P === null || m2 > 0)
273
+ return [
274
+ { op: '>=', v: low },
275
+ { op: '<', v: floor(0, m2 + 1, 0) },
276
+ ];
277
+ return [
278
+ { op: '>=', v: low },
279
+ { op: '<', v: floor(0, 0, P + 1) },
280
+ ];
281
+ }
282
+ if (op === '>') {
283
+ if (m2 === null)
284
+ return [{ op: '>=', v: floor(M + 1, 0, 0) }];
285
+ if (P === null)
286
+ return [{ op: '>=', v: floor(M, m2 + 1, 0) }];
287
+ return [{ op: '>', v: low }];
288
+ }
289
+ if (op === '>=')
290
+ return [{ op: '>=', v: low }];
291
+ if (op === '<')
292
+ return [{ op: '<', v: m2 === null || P === null ? floor(M, m2 ?? 0, 0) : low }];
293
+ if (op === '<=') {
294
+ if (m2 === null)
295
+ return [{ op: '<', v: floor(M + 1, 0, 0) }];
296
+ if (P === null)
297
+ return [{ op: '<', v: floor(M, m2 + 1, 0) }];
298
+ return [{ op: '<=', v: low }];
299
+ }
300
+ // bare or `=`
301
+ if (m2 === null)
302
+ return [
303
+ { op: '>=', v: low },
304
+ { op: '<', v: floor(M + 1, 0, 0) },
305
+ ];
306
+ if (P === null)
307
+ return [
308
+ { op: '>=', v: low },
309
+ { op: '<', v: floor(M, m2 + 1, 0) },
310
+ ];
311
+ return [{ op: '=', v: low }];
312
+ }
313
+ /** The bounds of a hyphen range `a - b`; `null` when either side does not parse. */
314
+ function hyphenBounds(from, to) {
315
+ const a = parsePartial(from);
316
+ const b = parsePartial(to);
317
+ if (!a || !b)
318
+ return null;
319
+ const bounds = [];
320
+ if (a.major !== null) {
321
+ // node-semver starts a hyphen range at the first prerelease unless `from` names one.
322
+ const low = a.patch === null || a.pre.length === 0
323
+ ? floor(a.major, a.minor ?? 0, a.patch ?? 0)
324
+ : v(a.major, a.minor ?? 0, a.patch, a.pre);
325
+ bounds.push({ op: '>=', v: low });
326
+ }
327
+ if (b.major !== null) {
328
+ if (b.minor === null)
329
+ bounds.push({ op: '<', v: floor(b.major + 1, 0, 0) });
330
+ else if (b.patch === null)
331
+ bounds.push({ op: '<', v: floor(b.major, b.minor + 1, 0) });
332
+ else
333
+ bounds.push({ op: '<=', v: v(b.major, b.minor, b.patch, b.pre) });
334
+ }
335
+ return bounds;
336
+ }
337
+ function within(version, bound) {
338
+ const cmp = compare(version, bound.v);
339
+ if (bound.op === '>=')
340
+ return cmp >= 0;
341
+ if (bound.op === '>')
342
+ return cmp > 0;
343
+ if (bound.op === '<')
344
+ return cmp < 0;
345
+ if (bound.op === '<=')
346
+ return cmp <= 0;
347
+ return cmp === 0;
348
+ }
349
+ /**
350
+ * Whether `version` satisfies `range`, with node-semver's grammar and prereleases included (like
351
+ * `semver.satisfies(version, range, { includePrerelease: true })`). An unparsable range is `false`.
352
+ */
353
+ export function satisfies(version, range) {
354
+ const parsed = parseVersion(version);
355
+ if (!parsed)
356
+ return false;
357
+ return range.split('||').some((set) => {
358
+ const text = set.trim().replace(/(~>|\^|~|>=|<=|>|<|=)\s+/g, '$1');
359
+ const hyphen = /^(\S+)\s+-\s+(\S+)$/.exec(text);
360
+ let bounds = [];
361
+ if (hyphen)
362
+ bounds = hyphenBounds(hyphen[1] ?? '', hyphen[2] ?? '');
363
+ else {
364
+ for (const comparator of text === '' ? [''] : text.split(/\s+/)) {
365
+ const more = boundsOf(comparator);
366
+ if (more === null)
367
+ return false;
368
+ bounds.push(...more);
369
+ }
370
+ }
371
+ return bounds?.every((bound) => within(parsed, bound)) === true;
372
+ });
373
+ }
374
+ // ── built-in checks, one set per lib ──────────────────────────────────────────────────────────────
375
+ function packageOf(check) {
376
+ return check.package ?? `@adonis-agora/${check.lib}`;
377
+ }
378
+ /**
379
+ * Agora packages that keep their own version line on purpose (not the 1.x train), so they never
380
+ * count as mixing. A package can also say so itself: `"agora": { "versioning": "independent" }`.
381
+ */
382
+ const INDEPENDENT_VERSIONING = new Set(['@adonis-agora/cms', '@adonis-agora/authkit-poppy']);
383
+ function isIndependent(pkg) {
384
+ const agora = pkg?.agora;
385
+ return (typeof agora === 'object' &&
386
+ agora !== null &&
387
+ agora.versioning === 'independent');
388
+ }
389
+ /** Whether the app (or a workspace above it) was installed by pnpm: a `node_modules/.pnpm` exists. */
390
+ async function isPnpmInstall(from) {
391
+ let dir = from;
392
+ for (;;) {
393
+ try {
394
+ await access(join(dir, 'node_modules', '.pnpm'));
395
+ return true;
396
+ }
397
+ catch {
398
+ const parent = dirname(dir);
399
+ if (parent === dir)
400
+ return false;
401
+ dir = parent;
402
+ }
403
+ }
404
+ }
405
+ function builtinChecks(lib, pkgName) {
406
+ return [
407
+ {
408
+ id: `${lib}.registered`,
409
+ lib,
410
+ title: 'Provider registered',
411
+ run: () => ({ status: 'ok', message: `${pkgName} is registered in this app.` }),
412
+ },
413
+ {
414
+ id: `${lib}.peers`,
415
+ lib,
416
+ title: 'Peer dependencies',
417
+ run: async (app) => {
418
+ const dir = await packageDir(pkgName, app.makePath());
419
+ if (dir === null) {
420
+ return { status: 'skip', message: `${pkgName} is not resolvable from the app root.` };
421
+ }
422
+ const pkg = await readJson(join(dir, 'package.json'));
423
+ const peers = (pkg?.peerDependencies ?? {});
424
+ const meta = (pkg?.peerDependenciesMeta ?? {});
425
+ const results = [];
426
+ for (const [peer, range] of Object.entries(peers)) {
427
+ const optional = meta[peer]?.optional === true;
428
+ const version = await installedVersion(peer, dir);
429
+ if (version === null) {
430
+ results.push(optional
431
+ ? { status: 'skip', message: `optional peer ${peer} is not installed.` }
432
+ : {
433
+ status: 'error',
434
+ message: `required peer ${peer} (${range}) is not installed.`,
435
+ fix: `Install it: npm install ${peer}`,
436
+ });
437
+ }
438
+ else if (!satisfies(version, range)) {
439
+ results.push({
440
+ status: 'error',
441
+ message: `${peer} ${version} is outside the supported range ${range}.`,
442
+ fix: `Install a version of ${peer} matching ${range}.`,
443
+ });
444
+ }
445
+ }
446
+ if (!results.some((r) => r.status !== 'skip')) {
447
+ results.push({ status: 'ok', message: 'Every installed peer is in range.' });
448
+ }
449
+ return results;
450
+ },
451
+ },
452
+ {
453
+ id: `${lib}.agora-siblings`,
454
+ lib,
455
+ title: 'Agora versions line up',
456
+ run: async (app) => {
457
+ if (!(await isPnpmInstall(app.makePath()))) {
458
+ return {
459
+ status: 'skip',
460
+ message: 'Only checked on pnpm installs (no node_modules/.pnpm found).',
461
+ };
462
+ }
463
+ const own = await installedVersion(pkgName, app.makePath());
464
+ if (own === null)
465
+ return { status: 'skip', message: `${pkgName} is not installed.` };
466
+ const manifest = await readJson(app.makePath('package.json'));
467
+ const names = Object.keys({
468
+ ...manifest?.dependencies,
469
+ ...manifest?.devDependencies,
470
+ }).filter((name) => name.startsWith('@adonis-agora/') && name !== pkgName);
471
+ const ownMajor = parseVersion(own)?.nums[0] ?? 0;
472
+ const off = [];
473
+ for (const name of names) {
474
+ if (INDEPENDENT_VERSIONING.has(name))
475
+ continue;
476
+ const dir = await packageDir(name, app.makePath());
477
+ if (dir === null)
478
+ continue;
479
+ const pkg = await readJson(join(dir, 'package.json'));
480
+ const version = typeof pkg?.version === 'string' ? pkg.version : null;
481
+ if (version === null || isIndependent(pkg))
482
+ continue;
483
+ const major = parseVersion(version)?.nums[0] ?? 0;
484
+ if ((ownMajor === 0) !== (major === 0))
485
+ off.push(`${name} ${version}`);
486
+ }
487
+ if (off.length === 0)
488
+ return { status: 'ok', message: `${pkgName} ${own}.` };
489
+ return {
490
+ status: 'warn',
491
+ message: `${pkgName} ${own} is installed next to ${off.join(', ')}: 0.x and 1.x Agora libs are not meant to be mixed.`,
492
+ fix: 'Upgrade every @adonis-agora package to the same release line.',
493
+ };
494
+ },
495
+ },
496
+ ];
497
+ }
498
+ // ── running ───────────────────────────────────────────────────────────────────────────────────────
499
+ async function withTimeout(work, ms) {
500
+ let timer;
501
+ try {
502
+ return await Promise.race([
503
+ work(),
504
+ new Promise((_, reject) => {
505
+ timer = setTimeout(() => reject(new Error(`timed out after ${ms / 1000}s`)), ms);
506
+ }),
507
+ ]);
508
+ }
509
+ finally {
510
+ if (timer !== undefined)
511
+ clearTimeout(timer);
512
+ }
513
+ }
514
+ async function runOne(app, check, options) {
515
+ const started = performance.now();
516
+ const ms = () => Math.round(performance.now() - started);
517
+ const budget = check.cost === 'deep' ? (options.deepTimeoutMs ?? 15_000) : (options.timeoutMs ?? 5_000);
518
+ try {
519
+ const out = await withTimeout(async () => check.run(app), budget);
520
+ const list = (Array.isArray(out) ? out : [out]).filter((r) => typeof r === 'object' && r !== null);
521
+ return list.map((result) => ({
522
+ id: check.id,
523
+ lib: check.lib,
524
+ title: check.title,
525
+ status: ['ok', 'warn', 'error', 'skip'].includes(result.status)
526
+ ? result.status
527
+ : 'error',
528
+ message: String(result.message ?? ''),
529
+ ...(result.fix ? { fix: result.fix } : {}),
530
+ ...(result.docs ? { docs: result.docs } : {}),
531
+ ms: ms(),
532
+ }));
533
+ }
534
+ catch (error) {
535
+ return [
536
+ {
537
+ id: check.id,
538
+ lib: check.lib,
539
+ title: check.title,
540
+ status: 'error',
541
+ message: error instanceof Error ? error.message : String(error),
542
+ ms: ms(),
543
+ },
544
+ ];
545
+ }
546
+ }
547
+ /**
548
+ * The environment shown in the report, from the app itself: `production` when `app.inProduction`,
549
+ * `test` when `app.inTest`, else the app's node environment, and `development` when that is unset.
550
+ */
551
+ export function environmentOf(app) {
552
+ if (app.inProduction)
553
+ return 'production';
554
+ if (app.inTest)
555
+ return 'test';
556
+ const env = app.nodeEnvironment;
557
+ return typeof env === 'string' && env !== '' && env !== 'unknown' ? env : 'development';
558
+ }
559
+ /** Run the checks and build the report. Throws {@link AgoraDoctorUsageError} for an unknown `--only`. */
560
+ export async function runAgoraDoctor(app, registered, options = {}) {
561
+ const libNames = [...new Set(registered.map((check) => check.lib))].sort();
562
+ const packages = new Map();
563
+ for (const check of registered)
564
+ if (!packages.has(check.lib))
565
+ packages.set(check.lib, packageOf(check));
566
+ const all = [];
567
+ for (const lib of libNames) {
568
+ all.push(...builtinChecks(lib, packages.get(lib)));
569
+ all.push(...registered.filter((check) => check.lib === lib));
570
+ }
571
+ for (const check of await unregisteredChecks(app, new Set(packages.values()))) {
572
+ if (!packages.has(check.lib))
573
+ packages.set(check.lib, packageOf(check));
574
+ all.push(check);
575
+ }
576
+ all.sort((a, b) => (a.lib < b.lib ? -1 : a.lib > b.lib ? 1 : 0));
577
+ if (options.only !== undefined && !all.some((check) => check.id === options.only)) {
578
+ throw new AgoraDoctorUsageError(`no check has the id "${options.only}".`);
579
+ }
580
+ const known = [...new Set(all.map((check) => check.lib))];
581
+ const unknownLibs = (options.libs ?? []).filter((lib) => !known.includes(lib));
582
+ if (unknownLibs.length > 0) {
583
+ throw new AgoraDoctorUsageError(`no installed lib is named ${unknownLibs.map((lib) => `"${lib}"`).join(', ')} (known: ${known.join(', ') || 'none'}).`);
584
+ }
585
+ const chosen = all.filter((check) => (options.only === undefined || check.id === options.only) &&
586
+ (options.libs === undefined || options.libs.length === 0 || options.libs.includes(check.lib)));
587
+ if (chosen.length === 0 && options.only !== undefined && (options.libs ?? []).length > 0) {
588
+ throw new AgoraDoctorUsageError(`--only=${String(options.only)} is not a check of ${(options.libs ?? []).join(', ')}: nothing to run.`);
589
+ }
590
+ // Deep checks that do not run still show up, as skipped with the reason.
591
+ const notRun = (check) => {
592
+ if (check.cost !== 'deep')
593
+ return null;
594
+ if (options.noBoot === true)
595
+ return 'Deep check: not run with --no-boot (it needs the booted app).';
596
+ if (options.deep === true || options.only === check.id)
597
+ return null;
598
+ return 'Deep check: run with --deep.';
599
+ };
600
+ const selected = chosen.map((check) => {
601
+ const reason = notRun(check);
602
+ if (reason === null)
603
+ return check;
604
+ return { ...check, cost: 'cheap', run: () => ({ status: 'skip', message: reason }) };
605
+ });
606
+ const byLib = new Map();
607
+ for (const check of selected)
608
+ byLib.set(check.lib, [...(byLib.get(check.lib) ?? []), check]);
609
+ const queue = [...byLib.keys()];
610
+ const perLib = new Map();
611
+ const worker = async () => {
612
+ for (let lib = queue.shift(); lib !== undefined; lib = queue.shift()) {
613
+ const entries = [];
614
+ for (const check of byLib.get(lib) ?? [])
615
+ entries.push(...(await runOne(app, check, options)));
616
+ perLib.set(lib, entries);
617
+ }
618
+ };
619
+ await Promise.all(Array.from({ length: Math.min(4, queue.length) }, worker));
620
+ const results = [...byLib.keys()].flatMap((lib) => perLib.get(lib) ?? []);
621
+ const summary = { ok: 0, warn: 0, error: 0, skip: 0 };
622
+ for (const r of results)
623
+ summary[r.status] += 1;
624
+ const libs = await Promise.all([...byLib.keys()].map(async (lib) => {
625
+ const pkg = packages.get(lib);
626
+ return { lib, package: pkg, version: await installedVersion(pkg, app.makePath()) };
627
+ }));
628
+ const failed = summary.error > 0 || (options.strict === true && summary.warn > 0);
629
+ return {
630
+ report: {
631
+ version: 1,
632
+ env: environmentOf(app),
633
+ summary,
634
+ libs,
635
+ results,
636
+ },
637
+ exitCode: failed ? 1 : 0,
638
+ };
639
+ }
640
+ const plain = {
641
+ green: (t) => t,
642
+ yellow: (t) => t,
643
+ red: (t) => t,
644
+ dim: (t) => t,
645
+ bold: (t) => t,
646
+ };
647
+ /** The human report. `fancy` = colors and symbols (a TTY without `NO_COLOR`). */
648
+ export function renderAgoraDoctor(report, options = {}) {
649
+ const p = options.fancy ? (options.paint ?? plain) : plain;
650
+ const symbol = (status) => {
651
+ if (!options.fancy)
652
+ return status;
653
+ if (status === 'ok')
654
+ return p.green('✔');
655
+ if (status === 'warn')
656
+ return p.yellow('!');
657
+ if (status === 'error')
658
+ return p.red('✖');
659
+ return p.dim('-');
660
+ };
661
+ const total = report.results.length;
662
+ const lines = [
663
+ p.bold(`@adonis-agora doctor · ${report.env} · ${report.libs.length} ${report.libs.length === 1 ? 'lib' : 'libs'} · ${total} checks`),
664
+ ];
665
+ for (const lib of report.libs) {
666
+ const shown = report.results.filter((r) => r.lib === lib.lib && (r.status !== 'skip' || options.verbose));
667
+ if (shown.length === 0)
668
+ continue;
669
+ lines.push('', p.bold(`${lib.lib}${lib.version ? ` ${lib.version}` : ''}`));
670
+ for (const r of shown) {
671
+ lines.push(` ${symbol(r.status)} ${r.title}`);
672
+ if (r.status !== 'ok' || options.verbose)
673
+ lines.push(` ${r.message}`);
674
+ if (r.fix && r.status !== 'ok')
675
+ lines.push(` ${p.dim(`fix: ${r.fix}`)}`);
676
+ if (r.docs && r.status !== 'ok')
677
+ lines.push(` ${p.dim(r.docs)}`);
678
+ }
679
+ }
680
+ const { ok, warn, error, skip } = report.summary;
681
+ const hidden = skip > 0 && !options.verbose ? ` (skipped ${skip}, run with --verbose)` : '';
682
+ lines.push('', `${total} checks: ${ok} ok · ${warn} warn · ${error} error${hidden}`);
683
+ return `${lines.join('\n')}\n`;
684
+ }
685
+ //# sourceMappingURL=agora_doctor.js.map