youiyoux 0.1.10 → 0.1.13
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/chunks/{chunk-HDJ2TOWZ.js → chunk-5I2VHSEN.js} +1 -1
- package/dist/chunks/{chunk-T3KNPVQG.js → chunk-DX2NKY2M.js} +4 -4
- package/dist/chunks/{chunk-75DBKEAH.js → chunk-EV62TUDV.js} +1119 -619
- package/dist/chunks/{chunk-3RP664IP.js → chunk-N5Z5A637.js} +2 -2
- package/dist/chunks/{chunk-DWYDHVLZ.js → chunk-NF4OJYKC.js} +106 -66
- package/dist/chunks/{chunk-X2YYMHR4.js → chunk-PCLXMWZ2.js} +3 -3
- package/dist/chunks/{chunk-OCZJ3JGH.js → chunk-PWGZRP3C.js} +3 -3
- package/dist/chunks/{chunk-FLBRX6Y3.js → chunk-XOXF2ILO.js} +1 -1
- package/dist/chunks/{chunk-6GITMASZ.js → chunk-ZOQW6LNH.js} +56 -900
- package/dist/chunks/chunk-ZWVWENSB.js +899 -0
- package/dist/chunks/{src-XTNHB65M.js → src-4QOHR5GC.js} +3 -3
- package/dist/chunks/src-624TVB6T.js +55 -0
- package/dist/chunks/{src-DPFJ4S2N.js → src-E6ZPFKPR.js} +3 -2
- package/dist/chunks/{src-75ODGHE3.js → src-HVYMF4S2.js} +9 -7
- package/dist/chunks/{src-BYNWPNRT.js → src-IKDQ2BZO.js} +7 -1
- package/dist/chunks/{src-ZG7JU6YJ.js → src-PBM76LFQ.js} +2 -2
- package/dist/chunks/{src-6SNQVIZS.js → src-R7JGKOYI.js} +4 -4
- package/dist/chunks/{src-PURAHRCR.js → src-YC64NNQ3.js} +2 -2
- package/dist/hub/assets/{CandidatePanel-ZzbsyZo3.js → CandidatePanel-DfntneYT.js} +1 -1
- package/dist/hub/assets/{CatalogScreen-DDYlvOHy.js → CatalogScreen-kpFYZqX3.js} +1 -1
- package/dist/hub/assets/{ChangesScreen-C8_EyHgJ.js → ChangesScreen-Chu-Ui1B.js} +1 -1
- package/dist/hub/assets/{CheatSheet-DfcPiXdS.js → CheatSheet-B1dXTYTW.js} +1 -1
- package/dist/hub/assets/{CommandPalette-DfFkDTVg.js → CommandPalette-LBZHJ659.js} +1 -1
- package/dist/hub/assets/{DiscoverScreen-D6NTgEgk.js → DiscoverScreen-Yd3gVUG4.js} +2 -2
- package/dist/hub/assets/DraftEditor-QH9Sa_Uc.js +5 -0
- package/dist/hub/assets/{GapsScreen-CqedCj4z.js → GapsScreen-DPYGlNnA.js} +1 -1
- package/dist/hub/assets/{HomeScreen-CIj1QTkw.js → HomeScreen-BCPPk7mz.js} +1 -1
- package/dist/hub/assets/{InventoryScreen-CyvFXa2_.js → InventoryScreen-SleB733C.js} +1 -1
- package/dist/hub/assets/{LibraryScreen-C4WU0f42.js → LibraryScreen-BVXhOVPU.js} +1 -1
- package/dist/hub/assets/{Opened-CeRWkA7c.js → Opened-CJyMbxqV.js} +1 -1
- package/dist/hub/assets/OrgApp-Dxs_7CxZ.js +1 -0
- package/dist/hub/assets/{Preview-CgW-FMB7.js → Preview-DWFP0BIe.js} +1 -1
- package/dist/hub/assets/{RuleDetail-BDhZ1gCd.js → RuleDetail-C2fTlLJL.js} +3 -3
- package/dist/hub/assets/{RulesScreen-D3rSo_z2.js → RulesScreen-BABeAGJg.js} +3 -3
- package/dist/hub/assets/{ScreenImage-DLeA3nRh.js → ScreenImage-CyhKm_nj.js} +1 -1
- package/dist/hub/assets/ScreensScreen-8Ih8byGy.js +1 -0
- package/dist/hub/assets/{SearchField-DtEgq9-Q.js → SearchField-hF-6-YDM.js} +1 -1
- package/dist/hub/assets/{ServiceDown-Bn6AcA3J.js → ServiceDown-S4ya0Cob.js} +1 -1
- package/dist/hub/assets/{SpecimenCapture-p6YErF5y.js → SpecimenCapture-e_pgT4Sp.js} +1 -1
- package/dist/hub/assets/{Specimens-DuaE0WeO.js → Specimens-D_9hjlJH.js} +2 -2
- package/dist/hub/assets/{StackScreen-DHDulDng.js → StackScreen-J2FQilLW.js} +1 -1
- package/dist/hub/assets/{StubScreens-CmhVNX_Z.js → StubScreens-DAkMph3i.js} +1 -1
- package/dist/hub/assets/StyleScreen-C5WytSZU.js +1 -0
- package/dist/hub/assets/{Tabs-Cbh9BCKW.js → Tabs-DU-fFRDV.js} +1 -1
- package/dist/hub/assets/{TokenDetail-BICobLK1.js → TokenDetail-CDY1xxb9.js} +1 -1
- package/dist/hub/assets/{TokensScreen-CyTHn55q.js → TokensScreen-Sx4aICWk.js} +1 -1
- package/dist/hub/assets/{WorkspaceScreen-uPWqfklh.js → WorkspaceScreen-CzZ16fPZ.js} +1 -1
- package/dist/hub/assets/{client-BXHiuV_w.js → client-Bdgr12LP.js} +1 -1
- package/dist/hub/assets/{discover-CFhyKler.js → discover-va2KFuK_.js} +1 -1
- package/dist/hub/assets/{dist-DYNYuTjI.js → dist-C8tf4g8c.js} +1 -1
- package/dist/hub/assets/{dist-VmEpVQjP.js → dist-CC_p8DIj.js} +1 -1
- package/dist/hub/assets/{dist-C6iTWL0b.js → dist-YTWqg5sS.js} +1 -1
- package/dist/hub/assets/{family-tiles-B8_onXUi.js → family-tiles-qaK1-_c8.js} +1 -1
- package/dist/hub/assets/main-cvOFSjox.js +2 -0
- package/dist/hub/assets/main-pNY4iNRo.css +2 -0
- package/dist/hub/assets/{try-live-CX04d2L2.js → try-live-Iys3nCBG.js} +1 -1
- package/dist/hub/assets/when-COrv5lkZ.js +1 -0
- package/dist/hub/assets/{workflow-DbBdiOmA.js → workflow-C53UBmRZ.js} +1 -1
- package/dist/hub/index.html +2 -2
- package/dist/hub-previews.js +6 -5
- package/dist/hub-server.js +3 -3
- package/dist/hub-snapshot.js +1 -1
- package/dist/schema/youi.org.schema.json +438 -0
- package/dist/schema/youi.record.schema.json +338 -0
- package/dist/youi.js +7 -6
- package/dist/youiyoux.js +7 -6
- package/package.json +4 -4
- package/dist/hub/assets/DraftEditor-CUNGYbg0.js +0 -5
- package/dist/hub/assets/ScreensScreen-bGuFIyWj.js +0 -1
- package/dist/hub/assets/StyleScreen-LwTqfGID.js +0 -1
- package/dist/hub/assets/main-7snyIdoi.css +0 -2
- package/dist/hub/assets/main-CfbpuT4Y.js +0 -2
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
|
|
2
|
+
import {
|
|
3
|
+
INDEX_FILE,
|
|
4
|
+
defaultIndexPath,
|
|
5
|
+
nodeId,
|
|
6
|
+
openStore,
|
|
7
|
+
pageInteger,
|
|
8
|
+
searchTerms
|
|
9
|
+
} from "./chunk-ZWVWENSB.js";
|
|
2
10
|
import {
|
|
3
11
|
LIBRARY_SOURCE_KINDS,
|
|
4
12
|
LibraryEntry,
|
|
5
|
-
SHAPE_DIMENSIONS,
|
|
6
13
|
copyleft,
|
|
7
14
|
external_exports,
|
|
8
15
|
sha256,
|
|
9
|
-
shortHash,
|
|
10
16
|
writeFileAtomic
|
|
11
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-NF4OJYKC.js";
|
|
12
18
|
|
|
13
19
|
// ../discover/src/aliases.ts
|
|
14
20
|
var CONCEPTS = [
|
|
@@ -372,852 +378,8 @@ function npmEntry(object, concepts, searchUrl, fetchedAt) {
|
|
|
372
378
|
|
|
373
379
|
// ../discover/src/store.ts
|
|
374
380
|
import { readFile, readdir, rm, stat } from "node:fs/promises";
|
|
375
|
-
import { homedir as homedir2 } from "node:os";
|
|
376
|
-
import { join as join2 } from "node:path";
|
|
377
|
-
|
|
378
|
-
// ../store/src/driver.ts
|
|
379
|
-
function searchTerms(words2) {
|
|
380
|
-
const terms = words2.flatMap((w) => w.normalize("NFC").toLowerCase().split(/[^\p{L}\p{N}]+/u)).filter((w) => w.length > 0);
|
|
381
|
-
return [...new Set(terms)].slice(0, 64);
|
|
382
|
-
}
|
|
383
|
-
function numbered(sql) {
|
|
384
|
-
let n = 0;
|
|
385
|
-
return sql.replace(/\?/g, () => `$${++n}`);
|
|
386
|
-
}
|
|
387
|
-
var Lock = class {
|
|
388
|
-
tail = Promise.resolve();
|
|
389
|
-
run(fn) {
|
|
390
|
-
const next = this.tail.then(fn, fn);
|
|
391
|
-
this.tail = next.catch(() => {
|
|
392
|
-
});
|
|
393
|
-
return next;
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
function pageInteger(value, fallback, max = 500) {
|
|
397
|
-
if (value === void 0 || Number.isNaN(value)) return fallback;
|
|
398
|
-
const bounded = Math.max(0, Math.min(max, Math.floor(value)));
|
|
399
|
-
return Number.isSafeInteger(bounded) ? bounded : fallback;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// ../store/src/migrations.ts
|
|
403
|
-
var MIGRATIONS = [
|
|
404
|
-
{
|
|
405
|
-
id: 1,
|
|
406
|
-
name: "base",
|
|
407
|
-
statements: ({ json: json2 }) => [
|
|
408
|
-
`CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`,
|
|
409
|
-
// Discover listings: metadata only, never source. scope 'chosen' is the synced sources, 'all' what --all added.
|
|
410
|
-
`CREATE TABLE IF NOT EXISTS discover_items (
|
|
411
|
-
id TEXT PRIMARY KEY,
|
|
412
|
-
scope TEXT NOT NULL,
|
|
413
|
-
ref TEXT,
|
|
414
|
-
name TEXT NOT NULL,
|
|
415
|
-
title TEXT,
|
|
416
|
-
summary TEXT NOT NULL,
|
|
417
|
-
kind TEXT NOT NULL,
|
|
418
|
-
source_kind TEXT,
|
|
419
|
-
source_class TEXT NOT NULL,
|
|
420
|
-
registry TEXT,
|
|
421
|
-
licence TEXT NOT NULL,
|
|
422
|
-
style TEXT NOT NULL,
|
|
423
|
-
downloads INTEGER,
|
|
424
|
-
stars INTEGER,
|
|
425
|
-
archived INTEGER NOT NULL,
|
|
426
|
-
search_text TEXT NOT NULL,
|
|
427
|
-
entry ${json2} NOT NULL,
|
|
428
|
-
built_at TEXT NOT NULL
|
|
429
|
-
)`,
|
|
430
|
-
`CREATE INDEX IF NOT EXISTS discover_items_scope ON discover_items (scope, source_class)`,
|
|
431
|
-
`CREATE INDEX IF NOT EXISTS discover_items_ref ON discover_items (ref)`,
|
|
432
|
-
`CREATE TABLE IF NOT EXISTS discover_roles (item_id TEXT NOT NULL, role TEXT NOT NULL, PRIMARY KEY (item_id, role))`,
|
|
433
|
-
`CREATE INDEX IF NOT EXISTS discover_roles_role ON discover_roles (role)`,
|
|
434
|
-
`CREATE TABLE IF NOT EXISTS signals (key TEXT PRIMARY KEY, kind TEXT NOT NULL, data ${json2} NOT NULL, fetched_at TEXT)`,
|
|
435
|
-
`CREATE TABLE IF NOT EXISTS licences (item_id TEXT PRIMARY KEY, spdx TEXT NOT NULL, evidence_url TEXT, verified_at TEXT)`,
|
|
436
|
-
// Inventory history: one row per project per run.
|
|
437
|
-
`CREATE TABLE IF NOT EXISTS scans (
|
|
438
|
-
id TEXT PRIMARY KEY,
|
|
439
|
-
run TEXT NOT NULL,
|
|
440
|
-
source TEXT NOT NULL,
|
|
441
|
-
project TEXT NOT NULL,
|
|
442
|
-
root TEXT NOT NULL,
|
|
443
|
-
commit_sha TEXT,
|
|
444
|
-
scanned_at TEXT NOT NULL,
|
|
445
|
-
status TEXT NOT NULL,
|
|
446
|
-
files INTEGER NOT NULL,
|
|
447
|
-
looks INTEGER NOT NULL,
|
|
448
|
-
families INTEGER NOT NULL,
|
|
449
|
-
near_duplicates INTEGER NOT NULL,
|
|
450
|
-
roles ${json2} NOT NULL
|
|
451
|
-
)`,
|
|
452
|
-
`CREATE INDEX IF NOT EXISTS scans_project ON scans (project, scanned_at)`,
|
|
453
|
-
`CREATE INDEX IF NOT EXISTS scans_run ON scans (run)`,
|
|
454
|
-
`CREATE TABLE IF NOT EXISTS families (
|
|
455
|
-
run TEXT NOT NULL,
|
|
456
|
-
id TEXT NOT NULL,
|
|
457
|
-
role TEXT NOT NULL,
|
|
458
|
-
looks INTEGER NOT NULL,
|
|
459
|
-
near_duplicate INTEGER NOT NULL,
|
|
460
|
-
projects ${json2} NOT NULL,
|
|
461
|
-
kits ${json2} NOT NULL,
|
|
462
|
-
shape ${json2} NOT NULL,
|
|
463
|
-
PRIMARY KEY (run, id)
|
|
464
|
-
)`,
|
|
465
|
-
`CREATE TABLE IF NOT EXISTS looks (
|
|
466
|
-
run TEXT NOT NULL,
|
|
467
|
-
id TEXT NOT NULL,
|
|
468
|
-
project TEXT NOT NULL,
|
|
469
|
-
role TEXT NOT NULL,
|
|
470
|
-
file TEXT NOT NULL,
|
|
471
|
-
line INTEGER NOT NULL,
|
|
472
|
-
col INTEGER NOT NULL,
|
|
473
|
-
tag TEXT NOT NULL,
|
|
474
|
-
kit TEXT,
|
|
475
|
-
family TEXT,
|
|
476
|
-
routes ${json2} NOT NULL,
|
|
477
|
-
shape ${json2} NOT NULL,
|
|
478
|
-
PRIMARY KEY (run, id)
|
|
479
|
-
)`,
|
|
480
|
-
`CREATE INDEX IF NOT EXISTS looks_family ON looks (run, family)`,
|
|
481
|
-
// The graph: what uses what, read with recursive CTEs that are the same text on both engines.
|
|
482
|
-
`CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, kind TEXT NOT NULL, ref TEXT NOT NULL, source TEXT NOT NULL)`,
|
|
483
|
-
`CREATE INDEX IF NOT EXISTS nodes_ref ON nodes (kind, ref)`,
|
|
484
|
-
`CREATE TABLE IF NOT EXISTS edges ("from" TEXT NOT NULL, "to" TEXT NOT NULL, kind TEXT NOT NULL, source TEXT NOT NULL, PRIMARY KEY ("from", "to", kind))`,
|
|
485
|
-
`CREATE INDEX IF NOT EXISTS edges_to ON edges ("to", kind)`,
|
|
486
|
-
`CREATE INDEX IF NOT EXISTS edges_source ON edges (source)`
|
|
487
|
-
]
|
|
488
|
-
},
|
|
489
|
-
{ id: 2, name: "discover-search", statements: (dialect) => dialect.searchDdl },
|
|
490
|
-
{ id: 3, name: "project-graph", statements: () => [
|
|
491
|
-
`ALTER TABLE nodes ADD COLUMN project TEXT`,
|
|
492
|
-
`CREATE INDEX nodes_project ON nodes (project, kind, ref)`,
|
|
493
|
-
`CREATE INDEX looks_project_family ON looks (run, project, family)`,
|
|
494
|
-
// Old graph ids cannot be assigned safely to a project; rebuild restores this cache.
|
|
495
|
-
`DELETE FROM edges`,
|
|
496
|
-
`DELETE FROM nodes`
|
|
497
|
-
] }
|
|
498
|
-
];
|
|
499
|
-
var SCHEMA_VERSION = Math.max(...MIGRATIONS.map((m) => m.id));
|
|
500
|
-
|
|
501
|
-
// ../store/src/store.ts
|
|
502
|
-
var TABLES = ["meta", "discover_items", "discover_roles", "signals", "licences", "scans", "families", "looks", "nodes", "edges"];
|
|
503
|
-
var MAX_PARAMS = 3e4;
|
|
504
|
-
var MAX_DEPTH = 6;
|
|
505
|
-
var SOURCE_CLASSES = ["official", "community", "npm", "other"];
|
|
506
|
-
var json = (value) => JSON.stringify(value);
|
|
507
|
-
var parse = (value) => typeof value === "string" ? JSON.parse(value) : value;
|
|
508
|
-
var int = (value) => Number(value ?? 0);
|
|
509
|
-
var marks = (n) => Array.from({ length: n }, () => "?").join(", ");
|
|
510
|
-
var batches = (items) => Array.from({ length: Math.ceil(items.length / MAX_PARAMS) }, (_, i) => items.slice(i * MAX_PARAMS, (i + 1) * MAX_PARAMS));
|
|
511
|
-
var SqlStore = class {
|
|
512
|
-
db;
|
|
513
|
-
lock = new Lock();
|
|
514
|
-
constructor(adapter) {
|
|
515
|
-
this.db = adapter;
|
|
516
|
-
}
|
|
517
|
-
get engine() {
|
|
518
|
-
return this.db.engine;
|
|
519
|
-
}
|
|
520
|
-
get location() {
|
|
521
|
-
return this.db.location;
|
|
522
|
-
}
|
|
523
|
-
async checkSchema() {
|
|
524
|
-
try {
|
|
525
|
-
const rows = await this.db.all(`SELECT MAX(id) AS id FROM migrations`);
|
|
526
|
-
if (int(rows[0]?.id) !== SCHEMA_VERSION) throw new Error("schema mismatch");
|
|
527
|
-
} catch {
|
|
528
|
-
throw new Error("Index schema is outdated: run youiyoux index rebuild");
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
/** Apply pending migrations, each in its own transaction. Safe to run on every open. */
|
|
532
|
-
async migrate() {
|
|
533
|
-
return this.lock.run(async () => {
|
|
534
|
-
await this.db.exec(`CREATE TABLE IF NOT EXISTS migrations (id INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL)`);
|
|
535
|
-
const done = new Set((await this.db.all(`SELECT id FROM migrations`)).map((r) => int(r.id)));
|
|
536
|
-
const applied = [];
|
|
537
|
-
for (const migration of MIGRATIONS) {
|
|
538
|
-
if (done.has(migration.id)) continue;
|
|
539
|
-
await this.db.exec(this.db.begin);
|
|
540
|
-
try {
|
|
541
|
-
for (const statement of migration.statements(this.db)) await this.db.exec(statement);
|
|
542
|
-
await this.db.run(`INSERT INTO migrations (id, name, applied_at) VALUES (?, ?, ?)`, [migration.id, migration.name, (/* @__PURE__ */ new Date()).toISOString()]);
|
|
543
|
-
await this.db.exec("COMMIT");
|
|
544
|
-
} catch (error) {
|
|
545
|
-
await this.db.exec("ROLLBACK");
|
|
546
|
-
throw error;
|
|
547
|
-
}
|
|
548
|
-
applied.push(migration.id);
|
|
549
|
-
}
|
|
550
|
-
for (const statement of this.db.optional ?? []) await this.db.exec(statement).catch(() => {
|
|
551
|
-
});
|
|
552
|
-
return applied;
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
write(fn) {
|
|
556
|
-
return this.lock.run(async () => {
|
|
557
|
-
await this.db.exec(this.db.begin);
|
|
558
|
-
try {
|
|
559
|
-
const result = await fn();
|
|
560
|
-
await this.db.exec("COMMIT");
|
|
561
|
-
return result;
|
|
562
|
-
} catch (error) {
|
|
563
|
-
await this.db.exec("ROLLBACK").catch(() => {
|
|
564
|
-
});
|
|
565
|
-
throw error;
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
/** Rows in batches of one multi-row INSERT each. `columns` are SQL names (quote reserved ones). */
|
|
570
|
-
async insert(table, columns, rows, conflict) {
|
|
571
|
-
const size = Math.max(1, Math.floor(MAX_PARAMS / columns.length));
|
|
572
|
-
for (let i = 0; i < rows.length; i += size) {
|
|
573
|
-
const batch = rows.slice(i, i + size);
|
|
574
|
-
const values = batch.map(() => `(${marks(columns.length)})`).join(", ");
|
|
575
|
-
await this.db.run(`INSERT INTO ${table} (${columns.join(", ")}) VALUES ${values} ${conflict}`, batch.flat());
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
async close() {
|
|
579
|
-
await this.lock.run(() => this.db.close());
|
|
580
|
-
}
|
|
581
|
-
async reset() {
|
|
582
|
-
await this.write(async () => {
|
|
583
|
-
for (const table of TABLES) await this.db.run(`DELETE FROM ${table}`);
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
async stats() {
|
|
587
|
-
const tables = {};
|
|
588
|
-
for (const table of TABLES) tables[table] = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM ${table}`))[0]?.n);
|
|
589
|
-
const schema = int((await this.db.all(`SELECT MAX(id) AS id FROM migrations`))[0]?.id);
|
|
590
|
-
return { engine: this.engine, location: this.location, schema: schema || SCHEMA_VERSION, generation: await this.meta("generation"), tables };
|
|
591
|
-
}
|
|
592
|
-
async meta(key) {
|
|
593
|
-
const row = (await this.db.all(`SELECT value FROM meta WHERE key = ?`, [key]))[0];
|
|
594
|
-
return row ? row.value : null;
|
|
595
|
-
}
|
|
596
|
-
async setMeta(key, value) {
|
|
597
|
-
await this.write(() => this.db.run(`INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value`, [key, value]));
|
|
598
|
-
}
|
|
599
|
-
/* ------------------------------------------------------------------ Discover */
|
|
600
|
-
async putItems(items) {
|
|
601
|
-
const columns = ["id", "scope", "ref", "name", "title", "summary", "kind", "source_kind", "source_class", "registry", "licence", "style", "downloads", "stars", "archived", "search_text", "entry", "built_at"];
|
|
602
|
-
const update = columns.slice(1).map((c) => `${c} = excluded.${c}`).join(", ");
|
|
603
|
-
await this.insert("discover_items", columns, items.map((i) => [i.id, i.scope, i.ref, i.name, i.title, i.summary, i.kind, i.sourceKind, i.sourceClass, i.registry, i.licence, i.style, i.downloads, i.stars, i.archived ? 1 : 0, i.searchText, json(i.entry), i.builtAt]), `ON CONFLICT (id) DO UPDATE SET ${update}`);
|
|
604
|
-
const ids = items.map((i) => i.id);
|
|
605
|
-
for (const batch of batches(ids)) await this.db.run(`DELETE FROM discover_roles WHERE item_id IN (${marks(batch.length)})`, batch);
|
|
606
|
-
await this.insert("discover_roles", ["item_id", "role"], items.flatMap((i) => [...new Set(i.roles)].map((role) => [i.id, role])), "ON CONFLICT (item_id, role) DO NOTHING");
|
|
607
|
-
}
|
|
608
|
-
async replaceDiscoverItems(scope, items) {
|
|
609
|
-
await this.write(async () => {
|
|
610
|
-
await this.db.run(`DELETE FROM discover_roles WHERE item_id IN (SELECT id FROM discover_items WHERE scope = ?)`, [scope]);
|
|
611
|
-
await this.db.run(`DELETE FROM discover_items WHERE scope = ?`, [scope]);
|
|
612
|
-
await this.putItems(items.map((i) => ({ ...i, scope })));
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
async upsertDiscoverItems(items) {
|
|
616
|
-
await this.write(() => this.putItems(items));
|
|
617
|
-
}
|
|
618
|
-
/** WHERE conditions for a filter, on discover_items aliased `d`. */
|
|
619
|
-
filterSql(filter) {
|
|
620
|
-
const parts = [];
|
|
621
|
-
const params = [];
|
|
622
|
-
const any = (column, values, lower = false) => {
|
|
623
|
-
if (!values?.length) return;
|
|
624
|
-
parts.push(`${lower ? `LOWER(${column})` : column} IN (${marks(values.length)})`);
|
|
625
|
-
params.push(...values.map((v) => lower ? v.toLowerCase() : v));
|
|
626
|
-
};
|
|
627
|
-
any("d.scope", filter.scopes.length ? filter.scopes : ["chosen"]);
|
|
628
|
-
any("d.source_kind", filter.sourceKinds);
|
|
629
|
-
any("d.source_class", filter.sourceClasses);
|
|
630
|
-
any("d.registry", filter.registries, true);
|
|
631
|
-
any("d.licence", filter.licences, true);
|
|
632
|
-
any("d.kind", filter.kinds);
|
|
633
|
-
any("d.style", filter.styles);
|
|
634
|
-
if (filter.roles?.length) {
|
|
635
|
-
parts.push(`d.id IN (SELECT item_id FROM discover_roles WHERE role IN (${marks(filter.roles.length)}))`);
|
|
636
|
-
params.push(...filter.roles);
|
|
637
|
-
}
|
|
638
|
-
return { sql: parts.join(" AND "), params };
|
|
639
|
-
}
|
|
640
|
-
async matchDiscover(query) {
|
|
641
|
-
if (query.terms.length === 0) return [];
|
|
642
|
-
const filtered = [query.roles, query.sourceKinds, query.sourceClasses, query.registries, query.licences, query.kinds, query.styles].some((v) => v?.length);
|
|
643
|
-
if (!filtered && this.db.top) {
|
|
644
|
-
const rows = await this.db.top(query.terms, query.scopes.length ? query.scopes : ["chosen"], SOURCE_CLASSES, pageInteger(query.perClass, 400, 1e4));
|
|
645
|
-
return rows.map((r) => ({ id: r.id, sourceClass: r.source_class, entry: parse(r.entry) }));
|
|
646
|
-
}
|
|
647
|
-
const match = this.db.match(query.terms);
|
|
648
|
-
const filter = this.filterSql(query);
|
|
649
|
-
const ranked = await this.db.all(
|
|
650
|
-
`SELECT id, source_class, r FROM (
|
|
651
|
-
SELECT id, source_class, r, ROW_NUMBER() OVER (PARTITION BY source_class ORDER BY r, id) AS n
|
|
652
|
-
FROM (SELECT d.id, d.source_class, ${match.rank} AS r FROM ${match.from} WHERE ${match.where} AND ${filter.sql}) matched
|
|
653
|
-
) best WHERE n <= CAST(? AS INTEGER) ORDER BY r, id`,
|
|
654
|
-
[...match.rankParams, ...match.whereParams, ...filter.params, pageInteger(query.perClass, 400, 1e4)]
|
|
655
|
-
);
|
|
656
|
-
const entries = /* @__PURE__ */ new Map();
|
|
657
|
-
const ids = ranked.map((r) => r.id);
|
|
658
|
-
for (const batch of batches(ids)) {
|
|
659
|
-
for (const row of await this.db.all(`SELECT id, entry FROM discover_items WHERE id IN (${marks(batch.length)})`, batch)) entries.set(row.id, row.entry);
|
|
660
|
-
}
|
|
661
|
-
return ranked.map((r) => ({ id: r.id, sourceClass: r.source_class, entry: parse(entries.get(r.id)) }));
|
|
662
|
-
}
|
|
663
|
-
async listDiscover(filter, page = {}) {
|
|
664
|
-
const where = this.filterSql(filter);
|
|
665
|
-
const limit = pageInteger(page.limit, 20);
|
|
666
|
-
const offset = pageInteger(page.offset, 0, 2147483647);
|
|
667
|
-
const rows = await this.db.all(
|
|
668
|
-
`SELECT d.id, d.source_class, d.entry FROM discover_items d WHERE ${where.sql} ORDER BY CASE d.source_class WHEN 'official' THEN 0 ELSE 1 END, COALESCE(d.downloads, -1) DESC, d.id LIMIT ? OFFSET ?`,
|
|
669
|
-
[...where.params, limit, offset]
|
|
670
|
-
);
|
|
671
|
-
return { rows: rows.map((r) => ({ id: r.id, sourceClass: r.source_class, entry: parse(r.entry) })), total: await this.countDiscover(filter) };
|
|
672
|
-
}
|
|
673
|
-
async countDiscover(filter) {
|
|
674
|
-
const where = this.filterSql(filter);
|
|
675
|
-
return int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM discover_items d WHERE ${where.sql}`, where.params))[0]?.n);
|
|
676
|
-
}
|
|
677
|
-
async discoverFacets(filter) {
|
|
678
|
-
const where = this.filterSql(filter);
|
|
679
|
-
const group = async (column) => {
|
|
680
|
-
const rows = await this.db.all(`SELECT ${column} AS k, CAST(COUNT(*) AS INTEGER) AS n FROM discover_items d WHERE ${where.sql} GROUP BY ${column} ORDER BY n DESC, k`, where.params);
|
|
681
|
-
return Object.fromEntries(rows.map((r) => [r.k ?? "unknown", int(r.n)]));
|
|
682
|
-
};
|
|
683
|
-
const roles = await this.db.all(`SELECT r.role AS k, CAST(COUNT(*) AS INTEGER) AS n FROM discover_roles r JOIN discover_items d ON d.id = r.item_id WHERE ${where.sql} GROUP BY r.role ORDER BY n DESC, k`, where.params);
|
|
684
|
-
return { role: Object.fromEntries(roles.map((r) => [r.k, int(r.n)])), source: await group("COALESCE(d.registry, d.source_kind)"), licence: await group("d.licence"), style: await group("d.style") };
|
|
685
|
-
}
|
|
686
|
-
async getDiscoverItem(idOrRef, scopes) {
|
|
687
|
-
const scoped = scopes.length ? scopes : ["chosen"];
|
|
688
|
-
for (const column of ["id", "ref"]) {
|
|
689
|
-
const row = (await this.db.all(`SELECT id, source_class, entry FROM discover_items WHERE ${column} = ? AND scope IN (${marks(scoped.length)}) ORDER BY CASE scope WHEN 'chosen' THEN 0 ELSE 1 END, id LIMIT 1`, [idOrRef, ...scoped]))[0];
|
|
690
|
-
if (row) return { id: row.id, sourceClass: row.source_class, entry: parse(row.entry) };
|
|
691
|
-
}
|
|
692
|
-
return null;
|
|
693
|
-
}
|
|
694
|
-
async discoverRoles(itemIds) {
|
|
695
|
-
const out = {};
|
|
696
|
-
for (const batch of batches(itemIds)) {
|
|
697
|
-
for (const row of await this.db.all(`SELECT item_id, role FROM discover_roles WHERE item_id IN (${marks(batch.length)}) ORDER BY item_id, role`, batch)) (out[row.item_id] ??= []).push(row.role);
|
|
698
|
-
}
|
|
699
|
-
return out;
|
|
700
|
-
}
|
|
701
|
-
/* ------------------------------------------------------- signals and licences */
|
|
702
|
-
async replaceSignals(signals) {
|
|
703
|
-
await this.write(async () => {
|
|
704
|
-
await this.db.run(`DELETE FROM signals`);
|
|
705
|
-
await this.insert("signals", ["key", "kind", "data", "fetched_at"], signals.map((s) => [s.key, s.kind, json(s.data), s.fetchedAt]), "ON CONFLICT (key) DO UPDATE SET kind = excluded.kind, data = excluded.data, fetched_at = excluded.fetched_at");
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
async signal(key) {
|
|
709
|
-
const row = (await this.db.all(`SELECT key, kind, data, fetched_at FROM signals WHERE key = ?`, [key]))[0];
|
|
710
|
-
return row ? { key: row.key, kind: row.kind, data: parse(row.data), fetchedAt: row.fetched_at } : null;
|
|
711
|
-
}
|
|
712
|
-
async replaceLicences(licences) {
|
|
713
|
-
await this.write(async () => {
|
|
714
|
-
await this.db.run(`DELETE FROM licences`);
|
|
715
|
-
await this.insert("licences", ["item_id", "spdx", "evidence_url", "verified_at"], licences.map((l) => [l.itemId, l.spdx, l.evidenceUrl, l.verifiedAt]), "ON CONFLICT (item_id) DO UPDATE SET spdx = excluded.spdx, evidence_url = excluded.evidence_url, verified_at = excluded.verified_at");
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
async licence(itemId) {
|
|
719
|
-
const row = (await this.db.all(`SELECT item_id, spdx, evidence_url, verified_at FROM licences WHERE item_id = ?`, [itemId]))[0];
|
|
720
|
-
return row ? { itemId: row.item_id, spdx: row.spdx, evidenceUrl: row.evidence_url, verifiedAt: row.verified_at } : null;
|
|
721
|
-
}
|
|
722
|
-
/* ----------------------------------------------------------------- inventory */
|
|
723
|
-
async recordInventory(run) {
|
|
724
|
-
await this.write(async () => {
|
|
725
|
-
await this.insert(
|
|
726
|
-
"scans",
|
|
727
|
-
["id", "run", "source", "project", "root", "commit_sha", "scanned_at", "status", "files", "looks", "families", "near_duplicates", "roles"],
|
|
728
|
-
run.projects.map((p) => [`${run.run}:${p.project}`, run.run, run.source, p.project, p.root, p.commit, run.scannedAt, p.status, p.files, p.looks, p.families, p.nearDuplicates, json(p.roles)]),
|
|
729
|
-
"ON CONFLICT (id) DO UPDATE SET commit_sha = excluded.commit_sha, scanned_at = excluded.scanned_at, status = excluded.status, files = excluded.files, looks = excluded.looks, families = excluded.families, near_duplicates = excluded.near_duplicates, roles = excluded.roles"
|
|
730
|
-
);
|
|
731
|
-
await this.db.run(`DELETE FROM families WHERE run = ?`, [run.run]);
|
|
732
|
-
await this.insert("families", ["run", "id", "role", "looks", "near_duplicate", "projects", "kits", "shape"], run.families.map((f) => [run.run, f.id, f.role, f.looks, f.nearDuplicate ? 1 : 0, json(f.projects), json(f.kits), json(f.shape)]), "ON CONFLICT (run, id) DO NOTHING");
|
|
733
|
-
const newest = (await this.db.all(`SELECT MAX(scanned_at) AS scanned_at FROM scans WHERE source = ?`, [run.source]))[0]?.scanned_at;
|
|
734
|
-
if (newest && newest > run.scannedAt) return;
|
|
735
|
-
await this.db.run(`DELETE FROM looks WHERE run IN (SELECT DISTINCT run FROM scans WHERE source = ?)`, [run.source]);
|
|
736
|
-
await this.insert("looks", ["run", "id", "project", "role", "file", "line", "col", "tag", "kit", "family", "routes", "shape"], run.looks.map((l) => [run.run, l.id, l.project, l.role, l.file, l.line, l.column, l.tag, l.kit, l.family, json(l.routes), json(l.shape)]), "ON CONFLICT (run, id) DO NOTHING");
|
|
737
|
-
await this.putGraph(run.source, run.graph);
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
async history(filter = {}) {
|
|
741
|
-
const where = filter.project ? `WHERE project = ?` : "";
|
|
742
|
-
const rows = await this.db.all(`SELECT * FROM scans ${where} ORDER BY scanned_at DESC, project LIMIT ? OFFSET ?`, [...filter.project ? [filter.project] : [], pageInteger(filter.limit, 20), pageInteger(filter.offset, 0, 2147483647)]);
|
|
743
|
-
return rows.map((r) => ({ id: String(r.id), run: String(r.run), source: String(r.source), project: String(r.project), root: String(r.root), commit: r.commit_sha ?? null, scannedAt: String(r.scanned_at), status: String(r.status), files: int(r.files), looks: int(r.looks), families: int(r.families), nearDuplicates: int(r.near_duplicates), roles: parse(r.roles) }));
|
|
744
|
-
}
|
|
745
|
-
async latestRun(source) {
|
|
746
|
-
const row = (await this.db.all(`SELECT run, source, scanned_at FROM scans ${source ? "WHERE source = ?" : ""} ORDER BY scanned_at DESC, run DESC LIMIT 1`, source ? [source] : []))[0];
|
|
747
|
-
return row ? { run: row.run, source: row.source, scannedAt: row.scanned_at } : null;
|
|
748
|
-
}
|
|
749
|
-
async families(filter = {}) {
|
|
750
|
-
const run = filter.run ?? (await this.latestRun())?.run ?? null;
|
|
751
|
-
if (!run) return { run: null, total: 0, rows: [] };
|
|
752
|
-
const where = `run = ?${filter.role ? " AND role = ?" : ""}`;
|
|
753
|
-
const params = [run, ...filter.role ? [filter.role] : []];
|
|
754
|
-
const total = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM families WHERE ${where}`, params))[0]?.n);
|
|
755
|
-
const rows = await this.db.all(`SELECT * FROM families WHERE ${where} ORDER BY role, looks DESC, id LIMIT ? OFFSET ?`, [...params, pageInteger(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
|
|
756
|
-
return { run, total, rows: rows.map((r) => ({ id: String(r.id), role: String(r.role), looks: int(r.looks), nearDuplicate: int(r.near_duplicate) === 1, projects: parse(r.projects), kits: parse(r.kits), shape: parse(r.shape) })) };
|
|
757
|
-
}
|
|
758
|
-
async looks(filter) {
|
|
759
|
-
const run = filter.run ?? (await this.latestRun())?.run ?? null;
|
|
760
|
-
if (!run) return { run: null, total: 0, rows: [] };
|
|
761
|
-
const where = `run = ?${filter.family ? " AND family = ?" : ""}${filter.role ? " AND role = ?" : ""}`;
|
|
762
|
-
const params = [run, ...filter.family ? [filter.family] : [], ...filter.role ? [filter.role] : []];
|
|
763
|
-
const total = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM looks WHERE ${where}`, params))[0]?.n);
|
|
764
|
-
const rows = await this.db.all(`SELECT * FROM looks WHERE ${where} ORDER BY project, file, line, col, id LIMIT ? OFFSET ?`, [...params, pageInteger(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
|
|
765
|
-
return { run, total, rows: rows.map((r) => ({ id: String(r.id), project: String(r.project), role: String(r.role), file: String(r.file), line: int(r.line), column: int(r.col), tag: String(r.tag), kit: r.kit ?? null, family: r.family ?? null, routes: parse(r.routes), shape: parse(r.shape) })) };
|
|
766
|
-
}
|
|
767
|
-
/* --------------------------------------------------------------------- graph */
|
|
768
|
-
async putGraph(source, graph) {
|
|
769
|
-
await this.db.run(`DELETE FROM edges WHERE source = ?`, [source]);
|
|
770
|
-
await this.db.run(`DELETE FROM nodes WHERE source = ? AND id NOT IN (SELECT "from" FROM edges) AND id NOT IN (SELECT "to" FROM edges)`, [source]);
|
|
771
|
-
const nodes = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
772
|
-
await this.insert("nodes", ["id", "kind", "ref", "source", "project"], [...nodes.values()].map((n) => [n.id, n.kind, n.ref, source, n.project ?? null]), "ON CONFLICT (id) DO UPDATE SET kind = excluded.kind, ref = excluded.ref, project = excluded.project");
|
|
773
|
-
const edges = new Map(graph.edges.map((e) => [`${e.from}\0${e.to}\0${e.kind}`, e]));
|
|
774
|
-
await this.insert("edges", [`"from"`, `"to"`, "kind", "source"], [...edges.values()].map((e) => [e.from, e.to, e.kind, source]), `ON CONFLICT ("from", "to", kind) DO UPDATE SET source = excluded.source`);
|
|
775
|
-
}
|
|
776
|
-
async replaceGraph(source, graph) {
|
|
777
|
-
await this.write(() => this.putGraph(source, graph));
|
|
778
|
-
}
|
|
779
|
-
async reach(start, kinds, options = {}) {
|
|
780
|
-
if (start.length === 0 || kinds.length === 0) return [];
|
|
781
|
-
const [near, far] = options.reverse ? [`"to"`, `"from"`] : [`"from"`, `"to"`];
|
|
782
|
-
const depth = Math.max(0, Math.min(MAX_DEPTH, options.maxDepth ?? 3));
|
|
783
|
-
const rows = await this.db.all(
|
|
784
|
-
`WITH RECURSIVE reach(id, depth) AS (
|
|
785
|
-
SELECT id, 0 FROM nodes WHERE id IN (${marks(start.length)})
|
|
786
|
-
UNION
|
|
787
|
-
SELECT e.${far}, r.depth + 1 FROM edges e JOIN reach r ON e.${near} = r.id
|
|
788
|
-
WHERE e.kind IN (${marks(kinds.length)}) AND r.depth < CAST(? AS INTEGER)
|
|
789
|
-
)
|
|
790
|
-
SELECT n.id, n.kind, n.ref, MIN(r.depth) AS depth FROM reach r JOIN nodes n ON n.id = r.id
|
|
791
|
-
GROUP BY n.id, n.kind, n.ref ORDER BY MIN(r.depth), n.kind, n.ref`,
|
|
792
|
-
[...start, ...kinds, depth]
|
|
793
|
-
);
|
|
794
|
-
return rows.map((r) => ({ id: r.id, kind: r.kind, ref: r.ref, depth: int(r.depth) }));
|
|
795
|
-
}
|
|
796
|
-
async findNodes(kind, ref) {
|
|
797
|
-
return this.db.all(`SELECT id, kind, ref, source, project FROM nodes WHERE kind = ? AND (ref = ? OR id = ?) ORDER BY id`, [kind, ref, ref]);
|
|
798
|
-
}
|
|
799
|
-
async nodesOfKind(kind, source) {
|
|
800
|
-
return this.db.all(`SELECT id, kind, ref, source, project FROM nodes WHERE kind = ?${source ? " AND source = ?" : ""} ORDER BY id`, source ? [kind, source] : [kind]);
|
|
801
|
-
}
|
|
802
|
-
async resolveFamily(ref, project) {
|
|
803
|
-
const found = (await this.findNodes("family", ref)).filter((n) => project === void 0 || n.project === project);
|
|
804
|
-
return found.length > 1 ? { choices: found } : found[0] ?? null;
|
|
805
|
-
}
|
|
806
|
-
async resolveRule(ref, project) {
|
|
807
|
-
const escaped = ref.toLowerCase().replace(/[!%_]/g, "!$&");
|
|
808
|
-
const found = await this.db.all(
|
|
809
|
-
`SELECT id, kind, ref, source, project FROM nodes WHERE kind = 'rule' AND (LOWER(ref) = ? OR LOWER(id) = ? OR LOWER(ref) LIKE ? ESCAPE '!')${project === void 0 ? "" : " AND project = ?"} ORDER BY id`,
|
|
810
|
-
[ref.toLowerCase(), ref.toLowerCase(), `%/rule/${escaped}`, ...project === void 0 ? [] : [project]]
|
|
811
|
-
);
|
|
812
|
-
return found.length > 1 ? { choices: found } : found[0] ?? null;
|
|
813
|
-
}
|
|
814
|
-
async familyLooks(source, ids, project) {
|
|
815
|
-
const run = (await this.latestRun(source))?.run;
|
|
816
|
-
if (!run || ids.length === 0) return /* @__PURE__ */ new Map();
|
|
817
|
-
const rows = await this.db.all(`SELECT family AS id, COUNT(*) AS looks, MIN(role) AS role FROM looks WHERE run = ? AND family IN (${marks(ids.length)})${project == null ? "" : " AND project = ?"} GROUP BY family`, [run, ...ids, ...project == null ? [] : [project]]);
|
|
818
|
-
return new Map(rows.map((r) => [r.id, { looks: int(r.looks), role: r.role }]));
|
|
819
|
-
}
|
|
820
|
-
async whereUsed(family, options = {}) {
|
|
821
|
-
const node = await this.resolveFamily(family, options.project);
|
|
822
|
-
if (!node || "choices" in node) return node;
|
|
823
|
-
const used = await this.reach([node.id], ["used-in"], { maxDepth: 2 });
|
|
824
|
-
const near = await this.reach([node.id], ["near-duplicate-of"], { maxDepth: 1 });
|
|
825
|
-
const info = (await this.familyLooks(node.source, [node.ref], node.project)).get(node.ref);
|
|
826
|
-
return {
|
|
827
|
-
family: node.ref,
|
|
828
|
-
role: info?.role ?? null,
|
|
829
|
-
looks: info?.looks ?? 0,
|
|
830
|
-
files: used.filter((n) => n.kind === "file").map((n) => n.ref),
|
|
831
|
-
routes: used.filter((n) => n.kind === "route").map((n) => n.ref),
|
|
832
|
-
nearDuplicates: near.filter((n) => n.depth > 0 && n.kind === "family").map((n) => n.ref),
|
|
833
|
-
source: node.source
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
async swapImpact(candidate, family, options = {}) {
|
|
837
|
-
const node = await this.resolveFamily(family, options.project);
|
|
838
|
-
if (!node || "choices" in node) return node;
|
|
839
|
-
const cand = (await this.findNodes("candidate", candidate))[0];
|
|
840
|
-
const replaces = cand ? await this.reach([cand.id], ["could-replace"], { maxDepth: 1 }) : [];
|
|
841
|
-
const twins = (await this.reach([node.id], ["near-duplicate-of"], { maxDepth: 4 })).filter((n) => n.kind === "family");
|
|
842
|
-
const used = await this.reach(twins.map((t) => t.id), ["used-in"], { maxDepth: 2 });
|
|
843
|
-
const looks = await this.familyLooks(node.source, twins.map((t) => t.ref), node.project);
|
|
844
|
-
const others = await this.reach([node.id], ["could-replace"], { maxDepth: 1, reverse: true });
|
|
845
|
-
return {
|
|
846
|
-
candidate,
|
|
847
|
-
family: node.ref,
|
|
848
|
-
listed: replaces.some((n) => n.id === node.id),
|
|
849
|
-
families: twins.map((t) => ({ id: t.ref, looks: looks.get(t.ref)?.looks ?? 0, depth: t.depth })),
|
|
850
|
-
looks: twins.reduce((n, t) => n + (looks.get(t.ref)?.looks ?? 0), 0),
|
|
851
|
-
files: used.filter((n) => n.kind === "file").map((n) => n.ref),
|
|
852
|
-
routes: used.filter((n) => n.kind === "route").map((n) => n.ref),
|
|
853
|
-
otherCandidates: others.filter((n) => n.kind === "candidate" && n.ref !== candidate).map((n) => n.ref)
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
async ruleCovers(rule, options = {}) {
|
|
857
|
-
const node = await this.resolveRule(rule, options.project);
|
|
858
|
-
if (!node || "choices" in node) return node;
|
|
859
|
-
const reached = await this.reach([node.id], ["governs", "used-in"], { maxDepth: 3 });
|
|
860
|
-
return {
|
|
861
|
-
rule: node.ref,
|
|
862
|
-
components: reached.filter((n) => n.kind === "component").map((n) => n.ref),
|
|
863
|
-
files: reached.filter((n) => n.kind === "file").map((n) => n.ref),
|
|
864
|
-
routes: reached.filter((n) => n.kind === "route").map((n) => n.ref)
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
|
|
869
|
-
// ../store/src/sqlite.ts
|
|
870
|
-
import { mkdirSync, statSync } from "node:fs";
|
|
871
|
-
import { createRequire } from "node:module";
|
|
872
|
-
import { dirname } from "node:path";
|
|
873
|
-
var STATEMENT_CACHE = 200;
|
|
874
|
-
var sqlite = null;
|
|
875
|
-
function openDatabase(...args) {
|
|
876
|
-
sqlite ??= createRequire(import.meta.url)("node:sqlite");
|
|
877
|
-
return new sqlite.DatabaseSync(...args);
|
|
878
|
-
}
|
|
879
|
-
var words = (terms) => searchTerms(terms).map((t) => `"${t.replace(/"/g, "")}"`).join(" OR ");
|
|
880
|
-
function sqliteAdapter(path, options = {}) {
|
|
881
|
-
if (!options.readOnly && path !== ":memory:") mkdirSync(dirname(path), { recursive: true });
|
|
882
|
-
const db = openDatabase(path, { timeout: 5e3, readOnly: options.readOnly ?? false });
|
|
883
|
-
if (!options.readOnly) db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;");
|
|
884
|
-
const statements = /* @__PURE__ */ new Map();
|
|
885
|
-
const prepare = (sql) => {
|
|
886
|
-
let statement = statements.get(sql);
|
|
887
|
-
if (!statement) {
|
|
888
|
-
if (statements.size >= STATEMENT_CACHE) statements.delete(statements.keys().next().value);
|
|
889
|
-
statement = db.prepare(sql);
|
|
890
|
-
statements.set(sql, statement);
|
|
891
|
-
}
|
|
892
|
-
return statement;
|
|
893
|
-
};
|
|
894
|
-
const values = (params) => params;
|
|
895
|
-
return {
|
|
896
|
-
engine: "sqlite",
|
|
897
|
-
location: path,
|
|
898
|
-
json: "TEXT",
|
|
899
|
-
begin: "BEGIN IMMEDIATE",
|
|
900
|
-
searchDdl: [
|
|
901
|
-
// The listing's words, and its scope and source class as tag words (`schosen ccommunity`),
|
|
902
|
-
// so an unfiltered search ranks inside the index alone. Kept in step by rowid.
|
|
903
|
-
`CREATE VIRTUAL TABLE IF NOT EXISTS discover_fts USING fts5(search_text, tags, tokenize='porter unicode61')`,
|
|
904
|
-
`CREATE TRIGGER IF NOT EXISTS discover_items_ai AFTER INSERT ON discover_items BEGIN
|
|
905
|
-
INSERT INTO discover_fts (rowid, search_text, tags) VALUES (new.rowid, new.search_text, 's' || new.scope || ' c' || new.source_class);
|
|
906
|
-
END`,
|
|
907
|
-
`CREATE TRIGGER IF NOT EXISTS discover_items_ad AFTER DELETE ON discover_items BEGIN
|
|
908
|
-
DELETE FROM discover_fts WHERE rowid = old.rowid;
|
|
909
|
-
END`,
|
|
910
|
-
`CREATE TRIGGER IF NOT EXISTS discover_items_au AFTER UPDATE ON discover_items BEGIN
|
|
911
|
-
DELETE FROM discover_fts WHERE rowid = old.rowid;
|
|
912
|
-
INSERT INTO discover_fts (rowid, search_text, tags) VALUES (new.rowid, new.search_text, 's' || new.scope || ' c' || new.source_class);
|
|
913
|
-
END`
|
|
914
|
-
],
|
|
915
|
-
match: (terms) => ({
|
|
916
|
-
from: `discover_fts JOIN discover_items d ON d.rowid = discover_fts.rowid`,
|
|
917
|
-
where: searchTerms(terms).length ? `discover_fts MATCH ?` : `0 = 1 AND ? = ?`,
|
|
918
|
-
whereParams: searchTerms(terms).length ? [`search_text : (${words(terms)})`] : [0, 1],
|
|
919
|
-
rank: `bm25(discover_fts)`,
|
|
920
|
-
rankParams: []
|
|
921
|
-
}),
|
|
922
|
-
// One query per source class: the tag words narrow it, bm25 ranks only that class's matches, LIMIT keeps the best.
|
|
923
|
-
top: async (terms, scopes, classes, perClass) => {
|
|
924
|
-
if (!searchTerms(terms).length) return [];
|
|
925
|
-
const statement = prepare(`SELECT d.id, d.source_class, d.entry FROM (SELECT rowid AS rid, bm25(discover_fts) AS r FROM discover_fts WHERE discover_fts MATCH ? ORDER BY r LIMIT ?) m JOIN discover_items d ON d.rowid = m.rid ORDER BY m.r, d.id`);
|
|
926
|
-
const scope = words(scopes.map((s) => `s${s}`));
|
|
927
|
-
return classes.flatMap((c) => statement.all(`search_text : (${words(terms)}) AND tags : (${scope}) AND tags : ${words([`c${c}`])}`, perClass));
|
|
928
|
-
},
|
|
929
|
-
all: async (sql, params = []) => prepare(sql).all(...values(params)),
|
|
930
|
-
run: async (sql, params = []) => {
|
|
931
|
-
prepare(sql).run(...values(params));
|
|
932
|
-
},
|
|
933
|
-
exec: async (script) => db.exec(script),
|
|
934
|
-
close: async () => {
|
|
935
|
-
statements.clear();
|
|
936
|
-
if (db.isOpen) db.close();
|
|
937
|
-
}
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
async function openSqlite(path, options = {}) {
|
|
941
|
-
const connect = async () => {
|
|
942
|
-
const store = new SqlStore(sqliteAdapter(path, options));
|
|
943
|
-
try {
|
|
944
|
-
if (options.readOnly) await store.checkSchema();
|
|
945
|
-
else await store.migrate();
|
|
946
|
-
return store;
|
|
947
|
-
} catch (error) {
|
|
948
|
-
await store.close();
|
|
949
|
-
throw error;
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
const identity = () => {
|
|
953
|
-
const s = statSync(path);
|
|
954
|
-
return `${s.dev}:${s.ino}`;
|
|
955
|
-
};
|
|
956
|
-
let inode = null;
|
|
957
|
-
if (path !== ":memory:") {
|
|
958
|
-
try {
|
|
959
|
-
inode = identity();
|
|
960
|
-
} catch (error) {
|
|
961
|
-
if (error.code !== "ENOENT") throw error;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
let current = await connect();
|
|
965
|
-
if (path === ":memory:") return current;
|
|
966
|
-
inode ??= identity();
|
|
967
|
-
let generation = await current.meta("generation");
|
|
968
|
-
let checked = Date.now();
|
|
969
|
-
let closed = false;
|
|
970
|
-
const lock = new Lock();
|
|
971
|
-
return new Proxy(current, {
|
|
972
|
-
get(_target, key) {
|
|
973
|
-
const value = Reflect.get(current, key);
|
|
974
|
-
if (typeof value !== "function") return value;
|
|
975
|
-
return (...args) => lock.run(async () => {
|
|
976
|
-
if (key === "close") {
|
|
977
|
-
closed = true;
|
|
978
|
-
return current.close();
|
|
979
|
-
}
|
|
980
|
-
if (closed) throw new Error("Store is closed");
|
|
981
|
-
const next = identity();
|
|
982
|
-
const now = Date.now();
|
|
983
|
-
const nextGeneration = next === inode && now - checked >= 100 ? await current.meta("generation") : generation;
|
|
984
|
-
if (next !== inode || nextGeneration !== generation) {
|
|
985
|
-
await current.close();
|
|
986
|
-
const openingInode = identity();
|
|
987
|
-
current = await connect();
|
|
988
|
-
inode = openingInode;
|
|
989
|
-
generation = await current.meta("generation");
|
|
990
|
-
}
|
|
991
|
-
if (now - checked >= 100) checked = now;
|
|
992
|
-
return Reflect.apply(Reflect.get(current, key), current, args);
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
function checkpointSqlite(path) {
|
|
998
|
-
const db = openDatabase(path);
|
|
999
|
-
try {
|
|
1000
|
-
const result = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
1001
|
-
if (Number(result?.busy ?? 0)) throw new Error("Index checkpoint is busy; retry rebuild");
|
|
1002
|
-
} finally {
|
|
1003
|
-
db.close();
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// ../store/src/postgres.ts
|
|
1008
|
-
var TRIGRAM_TERMS = 8;
|
|
1009
|
-
function postgresAdapter(connection, location) {
|
|
1010
|
-
const adapter = {
|
|
1011
|
-
engine: "postgres",
|
|
1012
|
-
location,
|
|
1013
|
-
json: "JSONB",
|
|
1014
|
-
begin: "BEGIN",
|
|
1015
|
-
trigram: false,
|
|
1016
|
-
searchDdl: [
|
|
1017
|
-
// English stemming like FTS5's porter, but no stop-word list, so no asked-for word is ever dropped.
|
|
1018
|
-
`DO $$ BEGIN
|
|
1019
|
-
IF NOT EXISTS (SELECT 1 FROM pg_ts_dict WHERE dictname = 'youi_stem') THEN
|
|
1020
|
-
CREATE TEXT SEARCH DICTIONARY youi_stem (TEMPLATE = snowball, Language = english);
|
|
1021
|
-
END IF;
|
|
1022
|
-
IF NOT EXISTS (SELECT 1 FROM pg_ts_config WHERE cfgname = 'youi') THEN
|
|
1023
|
-
CREATE TEXT SEARCH CONFIGURATION youi (COPY = simple);
|
|
1024
|
-
ALTER TEXT SEARCH CONFIGURATION youi ALTER MAPPING FOR asciiword, word, numword, asciihword, hword, numhword, hword_asciipart, hword_part, hword_numpart WITH youi_stem;
|
|
1025
|
-
END IF;
|
|
1026
|
-
END $$`,
|
|
1027
|
-
`ALTER TABLE discover_items ADD COLUMN IF NOT EXISTS search_tsv tsvector GENERATED ALWAYS AS (to_tsvector('youi', search_text)) STORED`,
|
|
1028
|
-
`CREATE INDEX IF NOT EXISTS discover_items_search ON discover_items USING GIN (search_tsv)`
|
|
1029
|
-
],
|
|
1030
|
-
optional: [`CREATE EXTENSION IF NOT EXISTS pg_trgm`, `CREATE INDEX IF NOT EXISTS discover_items_name_trgm ON discover_items USING GIN (name gin_trgm_ops)`],
|
|
1031
|
-
match(terms) {
|
|
1032
|
-
terms = searchTerms(terms);
|
|
1033
|
-
const query = terms.join(" | ");
|
|
1034
|
-
const prefixes = adapter.trigram ? terms.slice(0, TRIGRAM_TERMS).map((t) => `${t}%`) : [];
|
|
1035
|
-
return {
|
|
1036
|
-
from: `discover_items d`,
|
|
1037
|
-
where: prefixes.length ? `(d.search_tsv @@ to_tsquery('youi', ?) OR ${prefixes.map(() => "d.name ILIKE ? ESCAPE '!'").join(" OR ")})` : `d.search_tsv @@ to_tsquery('youi', ?)`,
|
|
1038
|
-
whereParams: [query, ...prefixes],
|
|
1039
|
-
rank: `-ts_rank(d.search_tsv, to_tsquery('youi', ?))`,
|
|
1040
|
-
rankParams: [query]
|
|
1041
|
-
};
|
|
1042
|
-
},
|
|
1043
|
-
all: async (sql, params = []) => (await connection.query(numbered(sql), [...params])).rows,
|
|
1044
|
-
run: async (sql, params = []) => {
|
|
1045
|
-
await connection.query(numbered(sql), [...params]);
|
|
1046
|
-
},
|
|
1047
|
-
exec: (script) => connection.exec(script),
|
|
1048
|
-
close: () => connection.close()
|
|
1049
|
-
};
|
|
1050
|
-
return adapter;
|
|
1051
|
-
}
|
|
1052
|
-
async function openWith(connection, location, options = {}) {
|
|
1053
|
-
const adapter = postgresAdapter(connection, location);
|
|
1054
|
-
const store = new SqlStore(adapter);
|
|
1055
|
-
try {
|
|
1056
|
-
if (options.readOnly) {
|
|
1057
|
-
await connection.exec("SET default_transaction_read_only = on");
|
|
1058
|
-
await store.checkSchema();
|
|
1059
|
-
} else await store.migrate();
|
|
1060
|
-
} catch (error) {
|
|
1061
|
-
await store.close();
|
|
1062
|
-
throw error;
|
|
1063
|
-
}
|
|
1064
|
-
adapter.trigram = (await adapter.all(`SELECT 1 AS ok FROM pg_extension WHERE extname = 'pg_trgm'`)).length > 0;
|
|
1065
|
-
return store;
|
|
1066
|
-
}
|
|
1067
|
-
function redactUrl(url) {
|
|
1068
|
-
try {
|
|
1069
|
-
const parsed = new URL(url);
|
|
1070
|
-
if (parsed.password) parsed.password = "***";
|
|
1071
|
-
return parsed.toString();
|
|
1072
|
-
} catch {
|
|
1073
|
-
return "postgres://\u2026";
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
async function openPostgres(url, options = {}) {
|
|
1077
|
-
const { default: pg } = await import("pg");
|
|
1078
|
-
const client = new pg.Client({ connectionString: url });
|
|
1079
|
-
await client.connect();
|
|
1080
|
-
return openWith({ query: (sql, params) => client.query(sql, params), exec: async (script) => void await client.query(script), close: () => client.end() }, redactUrl(url), options);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// ../store/src/open.ts
|
|
1084
381
|
import { homedir } from "node:os";
|
|
1085
|
-
import {
|
|
1086
|
-
var INDEX_FILE = "index.db";
|
|
1087
|
-
function defaultIndexPath(env = process.env) {
|
|
1088
|
-
if (env.YOUI_INDEX) return env.YOUI_INDEX;
|
|
1089
|
-
return join(env.XDG_CACHE_HOME || join(homedir(), ".cache"), "youiyoux", INDEX_FILE);
|
|
1090
|
-
}
|
|
1091
|
-
function indexHome(path, env = process.env) {
|
|
1092
|
-
return path === ":memory:" ? dirname2(defaultIndexPath(env)) : dirname2(path);
|
|
1093
|
-
}
|
|
1094
|
-
function storeLocation(options = {}) {
|
|
1095
|
-
const env = options.env ?? process.env;
|
|
1096
|
-
return env.DATABASE_URL ? redactUrl(env.DATABASE_URL) : options.path ?? defaultIndexPath(env);
|
|
1097
|
-
}
|
|
1098
|
-
async function openStore(options = {}) {
|
|
1099
|
-
const env = options.env ?? process.env;
|
|
1100
|
-
if (env.DATABASE_URL) return openPostgres(env.DATABASE_URL, options);
|
|
1101
|
-
return openSqlite(options.path ?? defaultIndexPath(env), options);
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// ../store/src/derive.ts
|
|
1105
|
-
var nodeId = {
|
|
1106
|
-
family: (inventory, id, project = "") => `family:${shortHash(inventory)}/${encodeURIComponent(project)}/${id}`,
|
|
1107
|
-
file: (project, file) => `file:${project}:${file}`,
|
|
1108
|
-
route: (project, route) => `route:${project}:${route}`,
|
|
1109
|
-
project: (id) => `project:${id}`,
|
|
1110
|
-
library: (name) => `library:${name}`,
|
|
1111
|
-
candidate: (id) => `candidate:${id}`,
|
|
1112
|
-
rule: (id, project = "") => `rule:${encodeURIComponent(project)}:${id}`,
|
|
1113
|
-
component: (id, project = "") => `component:${encodeURIComponent(project)}:${id}`
|
|
1114
|
-
};
|
|
1115
|
-
var GraphBuilder = class {
|
|
1116
|
-
nodes = /* @__PURE__ */ new Map();
|
|
1117
|
-
edges = /* @__PURE__ */ new Map();
|
|
1118
|
-
node(node) {
|
|
1119
|
-
if (!this.nodes.has(node.id)) this.nodes.set(node.id, node);
|
|
1120
|
-
return node.id;
|
|
1121
|
-
}
|
|
1122
|
-
edge(from, to, kind) {
|
|
1123
|
-
if (from !== to) this.edges.set(`${from}\0${to}\0${kind}`, { from, to, kind });
|
|
1124
|
-
}
|
|
1125
|
-
build() {
|
|
1126
|
-
return { nodes: [...this.nodes.values()], edges: [...this.edges.values()] };
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
var TOLERANCE_KEY = { height: "height", paddingX: "padding", paddingY: "padding", radius: "radius", fontSize: "fontSize", fontWeight: "fontWeight" };
|
|
1130
|
-
function nearDuplicatePairs(families, tolerance) {
|
|
1131
|
-
const pairs = [];
|
|
1132
|
-
for (let i = 0; i < families.length; i++) {
|
|
1133
|
-
for (let j = i + 1; j < families.length; j++) {
|
|
1134
|
-
const a = families[i];
|
|
1135
|
-
const b = families[j];
|
|
1136
|
-
if (a.role !== b.role) continue;
|
|
1137
|
-
if (a.kits.length > 0 && b.kits.length > 0 && a.kits.every((k) => b.kits.includes(k)) && b.kits.every((k) => a.kits.includes(k))) continue;
|
|
1138
|
-
let compared = 0;
|
|
1139
|
-
let close = true;
|
|
1140
|
-
for (const dimension of SHAPE_DIMENSIONS) {
|
|
1141
|
-
const x = a.shape[dimension];
|
|
1142
|
-
const y = b.shape[dimension];
|
|
1143
|
-
if (x === null || y === null) continue;
|
|
1144
|
-
compared++;
|
|
1145
|
-
if (Math.abs(x - y) > 2 * tolerance[TOLERANCE_KEY[dimension]]) close = false;
|
|
1146
|
-
}
|
|
1147
|
-
if (close && compared >= 2) pairs.push([a.id, b.id]);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return pairs;
|
|
1151
|
-
}
|
|
1152
|
-
function inventoryRun(report, source) {
|
|
1153
|
-
const run = `${report.generatedAt}#${shortHash(`${source}\0${report.generatedAt}`).slice(0, 8)}`;
|
|
1154
|
-
const g = new GraphBuilder();
|
|
1155
|
-
const familyNode = (id, project) => g.node({ id: nodeId.family(source, id, project), kind: "family", ref: id, project });
|
|
1156
|
-
for (const family of report.families) for (const project of family.projects) familyNode(family.id, project);
|
|
1157
|
-
const uses = report.looks.filter((l) => !l.story);
|
|
1158
|
-
for (const look of uses) {
|
|
1159
|
-
if (!look.family) continue;
|
|
1160
|
-
const file = g.node({ id: nodeId.file(look.project, look.file), kind: "file", ref: `${look.project}:${look.file}` });
|
|
1161
|
-
g.edge(familyNode(look.family, look.project), file, "used-in");
|
|
1162
|
-
for (const route of look.routes) g.edge(file, g.node({ id: nodeId.route(look.project, route), kind: "route", ref: `${look.project}:${route}` }), "used-in");
|
|
1163
|
-
}
|
|
1164
|
-
for (const [a, b] of nearDuplicatePairs(report.families, report.tolerance)) {
|
|
1165
|
-
const projects = report.families.find((f) => f.id === a).projects.filter((p) => report.families.find((f) => f.id === b).projects.includes(p));
|
|
1166
|
-
for (const project of projects) {
|
|
1167
|
-
g.edge(familyNode(a, project), familyNode(b, project), "near-duplicate-of");
|
|
1168
|
-
g.edge(familyNode(b, project), familyNode(a, project), "near-duplicate-of");
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
for (const primitive of report.primitives) g.edge(g.node({ id: nodeId.project(primitive.project), kind: "project", ref: primitive.project }), g.node({ id: nodeId.library(primitive.package), kind: "library", ref: primitive.package }), "uses-library");
|
|
1172
|
-
for (const kit of report.kits) g.edge(g.node({ id: nodeId.project(kit.project), kind: "project", ref: kit.project }), g.node({ id: nodeId.library(kit.id), kind: "library", ref: kit.id }), "uses-library");
|
|
1173
|
-
const roles = (project) => {
|
|
1174
|
-
const counts = {};
|
|
1175
|
-
for (const look of uses) if (look.project === project) counts[look.role] = (counts[look.role] ?? 0) + 1;
|
|
1176
|
-
return counts;
|
|
1177
|
-
};
|
|
1178
|
-
return {
|
|
1179
|
-
run,
|
|
1180
|
-
source,
|
|
1181
|
-
scannedAt: report.generatedAt,
|
|
1182
|
-
projects: report.projects.map((p) => {
|
|
1183
|
-
const families = report.families.filter((f) => f.projects.includes(p.id));
|
|
1184
|
-
return { project: p.id, root: p.root, commit: p.commit, status: p.status, files: p.files, looks: uses.filter((l) => l.project === p.id).length, families: families.length, nearDuplicates: families.filter((f) => f.nearDuplicate).length, roles: roles(p.id) };
|
|
1185
|
-
}),
|
|
1186
|
-
families: report.families.map((f) => ({ id: f.id, role: f.role, looks: f.looks, nearDuplicate: f.nearDuplicate, projects: f.projects, kits: f.kits, shape: f.shape })),
|
|
1187
|
-
looks: uses.map((l) => ({ id: l.id, project: l.project, role: l.role, file: l.file, line: l.line, column: l.column, tag: l.tag, kit: l.kit, family: l.family, routes: l.routes, shape: l.shape })),
|
|
1188
|
-
graph: g.build()
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
function strings(value) {
|
|
1192
|
-
if (typeof value === "string") return [value];
|
|
1193
|
-
if (Array.isArray(value)) return value.flatMap(strings);
|
|
1194
|
-
if (value && typeof value === "object") return Object.values(value).flatMap(strings);
|
|
1195
|
-
return [];
|
|
1196
|
-
}
|
|
1197
|
-
function governedComponents(rule, components) {
|
|
1198
|
-
if (rule.severity === "off") return [];
|
|
1199
|
-
if (rule.check.engine === "youi" && (rule.check.id === "prop-values" || rule.check.id === "lookalike-component")) return components.map((c) => c.id);
|
|
1200
|
-
const named = new Set(rule.check.engine === "youi" ? strings(rule.check.options) : []);
|
|
1201
|
-
const words2 = `${rule.title} ${rule.statement} ${rule.agent}`;
|
|
1202
|
-
return components.filter((c) => named.has(c.id) || new RegExp(`(?:^|[^A-Za-z0-9])<?${c.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:$|[^A-Za-z0-9])`).test(words2)).map((c) => c.id);
|
|
1203
|
-
}
|
|
1204
|
-
function projectGraph(input) {
|
|
1205
|
-
const g = new GraphBuilder();
|
|
1206
|
-
const names = new Map(input.components.map((c) => [c.id, c.name]));
|
|
1207
|
-
const component = (id) => g.node({ id: nodeId.component(id, input.project), kind: "component", ref: names.get(id) ?? id, project: input.project });
|
|
1208
|
-
for (const rule of input.rules) {
|
|
1209
|
-
const node = g.node({ id: nodeId.rule(rule.id, input.project), kind: "rule", ref: rule.id, project: input.project });
|
|
1210
|
-
for (const id of governedComponents(rule, input.components)) g.edge(node, component(id), "governs");
|
|
1211
|
-
}
|
|
1212
|
-
for (const site of input.usage?.usages ?? []) {
|
|
1213
|
-
const file = g.node({ id: nodeId.file(input.project, site.file), kind: "file", ref: `${input.project}:${site.file}` });
|
|
1214
|
-
g.edge(component(site.element), file, "used-in");
|
|
1215
|
-
for (const route of site.routes) g.edge(file, g.node({ id: nodeId.route(input.project, route), kind: "route", ref: `${input.project}:${route}` }), "used-in");
|
|
1216
|
-
}
|
|
1217
|
-
return g.build();
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
// ../discover/src/store.ts
|
|
382
|
+
import { join } from "node:path";
|
|
1221
383
|
var INDEX_FILE2 = "index.json";
|
|
1222
384
|
var ALL_INDEX_FILE = "index-all.json";
|
|
1223
385
|
var HEALTH_FILE = "health.json";
|
|
@@ -1227,12 +389,12 @@ var BODIES_DIR = "bodies";
|
|
|
1227
389
|
function discoverCacheDir(explicit, env = process.env) {
|
|
1228
390
|
if (explicit) return explicit;
|
|
1229
391
|
if (env.YOUI_DISCOVER_CACHE) return env.YOUI_DISCOVER_CACHE;
|
|
1230
|
-
return
|
|
392
|
+
return join(env.XDG_CACHE_HOME || join(homedir(), ".cache"), "youiyoux", "discover");
|
|
1231
393
|
}
|
|
1232
394
|
function discoverIndexPath(explicit, env = process.env) {
|
|
1233
|
-
if (explicit) return
|
|
395
|
+
if (explicit) return join(explicit, INDEX_FILE);
|
|
1234
396
|
if (env.YOUI_INDEX) return env.YOUI_INDEX;
|
|
1235
|
-
if (env.YOUI_DISCOVER_CACHE) return
|
|
397
|
+
if (env.YOUI_DISCOVER_CACHE) return join(env.YOUI_DISCOVER_CACHE, INDEX_FILE);
|
|
1236
398
|
return defaultIndexPath(env);
|
|
1237
399
|
}
|
|
1238
400
|
var Iso = external_exports.string().datetime({ offset: true });
|
|
@@ -1279,7 +441,7 @@ var MissingIndexError = class extends Error {
|
|
|
1279
441
|
}
|
|
1280
442
|
};
|
|
1281
443
|
async function readIndex(cacheDir) {
|
|
1282
|
-
const raw = await readJson(
|
|
444
|
+
const raw = await readJson(join(cacheDir, INDEX_FILE2));
|
|
1283
445
|
if (raw === null) return null;
|
|
1284
446
|
const modern = raw.format === 2;
|
|
1285
447
|
const index = DiscoverIndex.parse(modern ? raw : cleanData(raw));
|
|
@@ -1293,11 +455,11 @@ async function readIndex(cacheDir) {
|
|
|
1293
455
|
}
|
|
1294
456
|
var loaded = /* @__PURE__ */ new Map();
|
|
1295
457
|
async function readAllIndex(cacheDir) {
|
|
1296
|
-
const raw = await readJson(
|
|
458
|
+
const raw = await readJson(join(cacheDir, ALL_INDEX_FILE));
|
|
1297
459
|
return raw === null ? null : DiscoverIndex.parse(cleanData(raw));
|
|
1298
460
|
}
|
|
1299
461
|
async function loadIndex(cacheDir) {
|
|
1300
|
-
const file =
|
|
462
|
+
const file = join(cacheDir, INDEX_FILE2);
|
|
1301
463
|
const mtimeMs = await stat(file).then((s) => s.mtimeMs, () => null);
|
|
1302
464
|
if (mtimeMs === null) throw new MissingIndexError(cacheDir);
|
|
1303
465
|
const hit = loaded.get(file);
|
|
@@ -1308,14 +470,14 @@ async function loadIndex(cacheDir) {
|
|
|
1308
470
|
return index;
|
|
1309
471
|
}
|
|
1310
472
|
async function readSignals(cacheDir) {
|
|
1311
|
-
const raw = await readJson(
|
|
473
|
+
const raw = await readJson(join(cacheDir, SIGNALS_FILE));
|
|
1312
474
|
return { npm: { ...raw?.npm }, github: { ...raw?.github }, registryRepos: { ...raw?.registryRepos } };
|
|
1313
475
|
}
|
|
1314
476
|
async function readHttpCache(cacheDir) {
|
|
1315
477
|
const cache = /* @__PURE__ */ new Map();
|
|
1316
|
-
const names = await readdir(
|
|
478
|
+
const names = await readdir(join(cacheDir, "listings")).catch(() => []);
|
|
1317
479
|
await Promise.all(names.map(async (name) => {
|
|
1318
|
-
const saved = await readJson(
|
|
480
|
+
const saved = await readJson(join(cacheDir, "listings", name));
|
|
1319
481
|
if (saved?.version !== 2 || typeof saved.url !== "string" || saved.data === void 0) return;
|
|
1320
482
|
const body = JSON.stringify(saved.data);
|
|
1321
483
|
cache.set(saved.url, { ...saved, body, sha256: sha256(body) });
|
|
@@ -1374,26 +536,26 @@ async function writeHttpCache(cacheDir, cache) {
|
|
|
1374
536
|
continue;
|
|
1375
537
|
}
|
|
1376
538
|
keep.add(`${sha256(url)}.json`);
|
|
1377
|
-
await writeJson(
|
|
539
|
+
await writeJson(join(cacheDir, "listings", `${sha256(url)}.json`), { version: 2, url, etag: entry.etag, lastModified: entry.lastModified, fetchedAt: entry.fetchedAt, data });
|
|
1378
540
|
}
|
|
1379
|
-
for (const name of await readdir(
|
|
1380
|
-
await rm(
|
|
1381
|
-
await rm(
|
|
541
|
+
for (const name of await readdir(join(cacheDir, "listings")).catch(() => [])) if (!keep.has(name)) await rm(join(cacheDir, "listings", name), { force: true });
|
|
542
|
+
await rm(join(cacheDir, BODIES_DIR), { recursive: true, force: true });
|
|
543
|
+
await rm(join(cacheDir, HTTP_FILE), { force: true });
|
|
1382
544
|
}
|
|
1383
545
|
async function readSources(cacheDir) {
|
|
1384
|
-
const value = await readJson(
|
|
546
|
+
const value = await readJson(join(cacheDir, "sources.json"));
|
|
1385
547
|
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [...CURATED];
|
|
1386
548
|
}
|
|
1387
549
|
async function writeSources(cacheDir, names) {
|
|
1388
|
-
await writeJson(
|
|
550
|
+
await writeJson(join(cacheDir, "sources.json"), [...new Set(names)].sort());
|
|
1389
551
|
}
|
|
1390
552
|
async function catalogBytes(cacheDir) {
|
|
1391
553
|
const files = await readdir(cacheDir, { recursive: true, withFileTypes: true });
|
|
1392
|
-
const sizes = await Promise.all(files.filter((f) => f.isFile() && !f.parentPath.includes(
|
|
554
|
+
const sizes = await Promise.all(files.filter((f) => f.isFile() && !f.parentPath.includes(join(cacheDir, "source")) && !f.name.startsWith(INDEX_FILE)).map((f) => stat(join(f.parentPath, f.name)).then((s) => s.size)));
|
|
1393
555
|
return sizes.reduce((a, b) => a + b, 0);
|
|
1394
556
|
}
|
|
1395
557
|
async function readHealth(cacheDir) {
|
|
1396
|
-
return await readJson(
|
|
558
|
+
return await readJson(join(cacheDir, HEALTH_FILE));
|
|
1397
559
|
}
|
|
1398
560
|
|
|
1399
561
|
// ../discover/src/http.ts
|
|
@@ -2016,7 +1178,7 @@ function applySignals(entry, signals) {
|
|
|
2016
1178
|
}
|
|
2017
1179
|
|
|
2018
1180
|
// ../discover/src/sync.ts
|
|
2019
|
-
import { join as
|
|
1181
|
+
import { join as join2 } from "node:path";
|
|
2020
1182
|
function status(id, url, result, items, health = null) {
|
|
2021
1183
|
if (!result.ok) return { id, url, outcome: "failed", reason: result.reason === "http" ? result.message : `${result.reason}: ${result.message}`, httpStatus: result.status, items: 0, health };
|
|
2022
1184
|
return { id, url, outcome: result.result.revalidated ? "revalidated" : "fetched", reason: null, httpStatus: result.result.status, items, health };
|
|
@@ -2120,7 +1282,7 @@ async function syncIndex(options) {
|
|
|
2120
1282
|
} else sources.push(official.ok ? { id: "shadcn", url: SHADCN.registry, outcome: "failed", reason: "not a registry", httpStatus: official.result.status, items: 0, health: null } : status("shadcn", SHADCN.registry, official, 0));
|
|
2121
1283
|
const listed = directory2.ok ? parseDirectory(directory2.value).registries : [];
|
|
2122
1284
|
sources.push(status("directory", SHADCN.directory, directory2, listed.length));
|
|
2123
|
-
await writeJson(
|
|
1285
|
+
await writeJson(join2(options.cacheDir, "directory.json"), listed);
|
|
2124
1286
|
const selected = await readSources(options.cacheDir);
|
|
2125
1287
|
const toFetch = [];
|
|
2126
1288
|
const previousHealth = await readHealth(options.cacheDir);
|
|
@@ -2184,10 +1346,10 @@ async function syncIndex(options) {
|
|
|
2184
1346
|
const { signals, report } = await refreshSignals(unique, homes, await readSignals(options.cacheDir), client, now(), { ...defaultBudgets(Boolean(options.token)), ...options.budgets });
|
|
2185
1347
|
const final = unique.map((entry) => applySignals(entry, signals)).filter((entry) => LibraryEntry.safeParse(entry).success);
|
|
2186
1348
|
const index = cleanData({ schema: "youi.discover-index/v1", format: 2, builtAt, sources, categories: Object.fromEntries(categories), entries: final });
|
|
2187
|
-
await writeJson(
|
|
2188
|
-
await writeJson(
|
|
1349
|
+
await writeJson(join2(options.cacheDir, SIGNALS_FILE), signals);
|
|
1350
|
+
await writeJson(join2(options.cacheDir, HEALTH_FILE), cleanData({ checkedAt: builtAt, sources }));
|
|
2189
1351
|
await writeHttpCache(options.cacheDir, client.cache);
|
|
2190
|
-
await writeJson(
|
|
1352
|
+
await writeJson(join2(options.cacheDir, INDEX_FILE2), index);
|
|
2191
1353
|
const registryStatuses = sources.filter((s) => s.health !== null);
|
|
2192
1354
|
const known = final.filter((e) => e.license && e.license !== "unknown").length;
|
|
2193
1355
|
return cleanData({
|
|
@@ -2211,9 +1373,9 @@ async function syncIndex(options) {
|
|
|
2211
1373
|
|
|
2212
1374
|
// ../discover/src/on-demand.ts
|
|
2213
1375
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
2214
|
-
import { join as
|
|
1376
|
+
import { join as join3 } from "node:path";
|
|
2215
1377
|
async function directory(cacheDir) {
|
|
2216
|
-
return JSON.parse(await readFile2(
|
|
1378
|
+
return JSON.parse(await readFile2(join3(cacheDir, "directory.json"), "utf8"));
|
|
2217
1379
|
}
|
|
2218
1380
|
async function selectSources(cacheDir, action = "list", name) {
|
|
2219
1381
|
const selected = await readSources(cacheDir);
|
|
@@ -2250,7 +1412,7 @@ async function expandAll(cacheDir, options = {}) {
|
|
|
2250
1412
|
const entries = dedupe([...index.entries, ...extra.flat()]).entries.filter((e) => !chosen.has(e.id));
|
|
2251
1413
|
const next = `${JSON.stringify(cleanData({ schema: index.schema, format: 2, builtAt: index.builtAt, sources: [], categories: index.categories, entries }))}
|
|
2252
1414
|
`;
|
|
2253
|
-
if (await readFile2(
|
|
1415
|
+
if (await readFile2(join3(cacheDir, ALL_INDEX_FILE), "utf8").catch(() => null) !== next) await writeFileAtomic(join3(cacheDir, ALL_INDEX_FILE), next);
|
|
2254
1416
|
}
|
|
2255
1417
|
async function findListing(cacheDir, id) {
|
|
2256
1418
|
try {
|
|
@@ -2275,7 +1437,7 @@ async function candidateSource(cacheDir, id, options = {}) {
|
|
|
2275
1437
|
const template = registry === SHADCN.name ? "https://ui.shadcn.com/r/styles/new-york-v4/{name}.json" : (await directory(cacheDir)).find((r) => r.name === registry)?.template;
|
|
2276
1438
|
if (!template) throw new Error("Registry URL is unavailable; run youiyoux discover sync");
|
|
2277
1439
|
const url = registryUrl(template, name);
|
|
2278
|
-
const file =
|
|
1440
|
+
const file = join3(cacheDir, "source", `${sha256(url)}.json`);
|
|
2279
1441
|
try {
|
|
2280
1442
|
return JSON.parse(await readFile2(file, "utf8"));
|
|
2281
1443
|
} catch {
|
|
@@ -2292,7 +1454,7 @@ async function candidateSource(cacheDir, id, options = {}) {
|
|
|
2292
1454
|
|
|
2293
1455
|
// ../discover/src/catalog.ts
|
|
2294
1456
|
import { stat as stat2 } from "node:fs/promises";
|
|
2295
|
-
import { join as
|
|
1457
|
+
import { join as join4 } from "node:path";
|
|
2296
1458
|
var CANDIDATES_PER_CLASS = 400;
|
|
2297
1459
|
var LINKS_PER_ROLE = 10;
|
|
2298
1460
|
var FINGERPRINT = "discover.fingerprint";
|
|
@@ -2336,7 +1498,7 @@ function itemOf(entry, scope, builtAt) {
|
|
|
2336
1498
|
};
|
|
2337
1499
|
}
|
|
2338
1500
|
async function fingerprint(cacheDir, file) {
|
|
2339
|
-
const at = await stat2(
|
|
1501
|
+
const at = await stat2(join4(cacheDir, file)).then((s) => `${s.size}:${Math.round(s.mtimeMs)}`, () => "-");
|
|
2340
1502
|
return JSON.stringify([cacheDir, at]);
|
|
2341
1503
|
}
|
|
2342
1504
|
function signalRows(signals) {
|
|
@@ -2382,7 +1544,7 @@ async function closeSharedStores() {
|
|
|
2382
1544
|
await Promise.all(stores.map(async (s) => (await s.catch(() => null))?.close()));
|
|
2383
1545
|
}
|
|
2384
1546
|
async function openCatalog(cacheDir, path, env = process.env) {
|
|
2385
|
-
if (!await stat2(
|
|
1547
|
+
if (!await stat2(join4(cacheDir, INDEX_FILE2)).then((s) => s.isFile(), () => false)) throw new MissingIndexError(cacheDir);
|
|
2386
1548
|
const store = await sharedStore(path, env);
|
|
2387
1549
|
const key = `${path}\0${cacheDir}`;
|
|
2388
1550
|
while (refreshing.has(key)) await refreshing.get(key);
|
|
@@ -2426,9 +1588,9 @@ function needTerms(need) {
|
|
|
2426
1588
|
const read = readNeed(cleanText(need));
|
|
2427
1589
|
const phrases = [...read.concepts.flatMap((c) => [...c.names, ...c.packages]), ...read.words].map(normalize);
|
|
2428
1590
|
return searchTerms(phrases.flatMap((phrase) => {
|
|
2429
|
-
const
|
|
2430
|
-
const specific =
|
|
2431
|
-
return specific.length ? specific :
|
|
1591
|
+
const words = phrase.split(" ").filter(Boolean);
|
|
1592
|
+
const specific = words.filter((w) => !GENERIC.has(w));
|
|
1593
|
+
return specific.length ? specific : words;
|
|
2432
1594
|
}));
|
|
2433
1595
|
}
|
|
2434
1596
|
async function searchCatalog(store, need, options = {}) {
|
|
@@ -2494,7 +1656,7 @@ async function linkCandidates(store) {
|
|
|
2494
1656
|
|
|
2495
1657
|
// ../discover/src/preview.ts
|
|
2496
1658
|
import { lstat, rm as rm2 } from "node:fs/promises";
|
|
2497
|
-
import { join as
|
|
1659
|
+
import { join as join5, posix } from "node:path";
|
|
2498
1660
|
var PREVIEW_DIR = "preview";
|
|
2499
1661
|
var MAX_FETCHED_PARTS = 3;
|
|
2500
1662
|
function safeRelative(path) {
|
|
@@ -2508,7 +1670,7 @@ function safeRelative(path) {
|
|
|
2508
1670
|
async function assertNoLinks(base, rel) {
|
|
2509
1671
|
let at = base;
|
|
2510
1672
|
for (const part of ["", ...rel.split("/")]) {
|
|
2511
|
-
at = part ?
|
|
1673
|
+
at = part ? join5(at, part) : at;
|
|
2512
1674
|
const info = await lstat(at).catch(() => null);
|
|
2513
1675
|
if (!info) return;
|
|
2514
1676
|
if (info.isSymbolicLink()) throw new Error(`refused: ${rel} leads through a symlink in the preview cache`);
|
|
@@ -2519,8 +1681,8 @@ function exportNames(source) {
|
|
|
2519
1681
|
for (const match of source.matchAll(/\bexport\s+(?:declare\s+)?(?:async\s+)?(?:function\*?|const|let|var|class)\s+([A-Za-z_$][\w$]*)/g)) names.add(match[1]);
|
|
2520
1682
|
for (const match of source.matchAll(/\bexport\s+(?!type\b)\{([^}]*)\}(?!\s*from)/g)) {
|
|
2521
1683
|
for (const item of match[1].split(",")) {
|
|
2522
|
-
const
|
|
2523
|
-
const name =
|
|
1684
|
+
const words = item.trim().split(/\s+/).filter((w) => w !== "type");
|
|
1685
|
+
const name = words.length >= 3 && words[1] === "as" ? words[2] : words[0];
|
|
2524
1686
|
if (name && /^[A-Za-z_$][\w$]*$/.test(name) && !/^type\s/.test(item.trim())) names.add(name);
|
|
2525
1687
|
}
|
|
2526
1688
|
}
|
|
@@ -2536,8 +1698,8 @@ function importedNames(source, spec) {
|
|
|
2536
1698
|
if (/\*\s+as\s+/.test(clause)) names.push("*");
|
|
2537
1699
|
const braces = /\{([^}]*)\}/.exec(clause);
|
|
2538
1700
|
if (braces) for (const item of braces[1].split(",")) {
|
|
2539
|
-
const
|
|
2540
|
-
if (
|
|
1701
|
+
const words = item.trim().split(/\s+/).filter((w) => w && w !== "type");
|
|
1702
|
+
if (words[0]) names.push(words[0]);
|
|
2541
1703
|
}
|
|
2542
1704
|
const head = clause.replace(/\{[^}]*\}/, "").replace(/\*\s+as\s+\w+/, "").replace(/,/g, " ").trim();
|
|
2543
1705
|
if (head && head !== "type") names.push("default");
|
|
@@ -2559,8 +1721,8 @@ async function preparePreview(cacheDir, id, options = {}) {
|
|
|
2559
1721
|
const registry = ref.slice(0, ref.indexOf("/"));
|
|
2560
1722
|
const source = await candidateSource(cacheDir, id, options);
|
|
2561
1723
|
const key = sha256(source.url).slice(0, 16);
|
|
2562
|
-
const root =
|
|
2563
|
-
const dir =
|
|
1724
|
+
const root = join5(cacheDir, PREVIEW_DIR);
|
|
1725
|
+
const dir = join5(root, key);
|
|
2564
1726
|
await assertNoLinks(cacheDir, `${PREVIEW_DIR}/${key}`);
|
|
2565
1727
|
await rm2(dir, { recursive: true, force: true });
|
|
2566
1728
|
const texts = /* @__PURE__ */ new Map();
|
|
@@ -2641,7 +1803,7 @@ async function preparePreview(cacheDir, id, options = {}) {
|
|
|
2641
1803
|
}
|
|
2642
1804
|
for (const [file, text] of rewritten) {
|
|
2643
1805
|
await assertNoLinks(dir, file);
|
|
2644
|
-
await writeFileAtomic(
|
|
1806
|
+
await writeFileAtomic(join5(dir, file), text);
|
|
2645
1807
|
}
|
|
2646
1808
|
const packages = [...new Set([...rewritten.values()].flatMap((t) => importedPackages(t)))].sort();
|
|
2647
1809
|
const declared = [...new Set([...source.dependencies ?? entry.install?.npm ?? [], ...partDeps].map(packageName))].sort();
|
|
@@ -2665,17 +1827,17 @@ async function preparePreview(cacheDir, id, options = {}) {
|
|
|
2665
1827
|
|
|
2666
1828
|
// ../discover/src/gaps.ts
|
|
2667
1829
|
import { existsSync, readFileSync } from "node:fs";
|
|
2668
|
-
import { join as
|
|
1830
|
+
import { join as join6 } from "node:path";
|
|
2669
1831
|
var STANDARD_KIT = ["button", "input", "card", "badge", "dialog", "alert", "drawer", "menu", "popover", "tooltip", "tabs", "avatar", "accordion", "table", "carousel", "slider", "switch", "checkbox", "radio", "select", "otp", "progress", "skeleton", "spinner", "sidebar", "navigation", "breadcrumb", "pagination", "command", "toast", "date-picker", "combobox", "chart"];
|
|
2670
1832
|
var ONLY_EXACT = /* @__PURE__ */ new Set(["hint", "tile", "range", "loading", "picker", "tree", "graph", "plot", "editor", "cta", "notice", "steps", "tag", "toggle", "meter", "command", "banner", "popup", "pager", "loader", "navigation", "chip", "pill"]);
|
|
2671
1833
|
var ROLE_CONCEPT2 = { button: "button", "link-as-button": "button", input: "input", card: "card", badge: "badge", dialog: "dialog", tabs: "tabs" };
|
|
2672
1834
|
var CANDIDATES = 3;
|
|
2673
1835
|
var DEFINITIONS_SHOWN = 5;
|
|
2674
1836
|
function conceptOfName(name) {
|
|
2675
|
-
const
|
|
2676
|
-
if (!
|
|
1837
|
+
const words = normalize(name.replace(/\.[a-z]+$/, ""));
|
|
1838
|
+
if (!words) return void 0;
|
|
2677
1839
|
const phrases = CONCEPTS.flatMap((concept) => concept.names.map((n) => ({ concept, name: normalize(n) }))).sort((a, b) => b.name.length - a.name.length);
|
|
2678
|
-
return phrases.find((p) =>
|
|
1840
|
+
return phrases.find((p) => words === p.name || !ONLY_EXACT.has(p.name) && ` ${words}`.endsWith(` ${p.name}`))?.concept;
|
|
2679
1841
|
}
|
|
2680
1842
|
function conceptOfPackage(pkg) {
|
|
2681
1843
|
const listed = CONCEPTS.find((c) => c.packages.includes(pkg));
|
|
@@ -2686,7 +1848,7 @@ function conceptOfPackage(pkg) {
|
|
|
2686
1848
|
function declaredPackages(roots) {
|
|
2687
1849
|
const found = {};
|
|
2688
1850
|
for (const [id, root] of roots) {
|
|
2689
|
-
const file =
|
|
1851
|
+
const file = join6(root, "package.json");
|
|
2690
1852
|
if (!existsSync(file)) continue;
|
|
2691
1853
|
try {
|
|
2692
1854
|
const pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
@@ -2827,12 +1989,6 @@ export {
|
|
|
2827
1989
|
MIN_WEEKLY_DOWNLOADS,
|
|
2828
1990
|
npmCandidates,
|
|
2829
1991
|
npmEntry,
|
|
2830
|
-
openSqlite,
|
|
2831
|
-
checkpointSqlite,
|
|
2832
|
-
indexHome,
|
|
2833
|
-
storeLocation,
|
|
2834
|
-
inventoryRun,
|
|
2835
|
-
projectGraph,
|
|
2836
1992
|
INDEX_FILE2 as INDEX_FILE,
|
|
2837
1993
|
ALL_INDEX_FILE,
|
|
2838
1994
|
HEALTH_FILE,
|