ruvnet-brain 4.4.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/install.mjs +612 -113
- package/console/app.js +178 -81
- package/console/index.html +1 -1
- package/console/install-architecture.html +1 -0
- package/console/scope.css +4 -1
- package/console/style.css +13 -0
- package/console/tips.html +4 -4
- package/kb/brain-profile.mjs +1 -0
- package/kb/corpus-release-identity.mjs +1 -1
- package/kb/forge-update.mjs +41 -17
- package/kb/model-requirements.mjs +4 -1
- package/kb/update-storage-transaction.mjs +79 -0
- package/kb/zip-extract.mjs +22 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/brain-console.md +5 -4
- package/plugin/commands/configure.md +5 -4
- package/plugin/commands/rnb-brief.md +41 -0
- package/plugin/commands/rnb.md +80 -0
- package/plugin/commands/rnbc.md +80 -0
- package/plugin/commands/rvbc.md +5 -4
- package/plugin/commands/rvcb.md +5 -4
- package/plugin/commands/whats-new.md +4 -4
- package/plugin/mcp/server.mjs +10 -2
- package/plugin/scripts/advocacy-route.mjs +59 -23
- package/plugin/scripts/anticipate.sh +4 -0
- package/plugin/scripts/brain-confirmation.mjs +258 -0
- package/plugin/scripts/brain-footprint.mjs +494 -0
- package/plugin/scripts/brain-location.mjs +47 -0
- package/plugin/scripts/capability-registry.mjs +11 -1
- package/plugin/scripts/continuity-brief.mjs +324 -0
- package/plugin/scripts/continuity-events.mjs +327 -0
- package/plugin/scripts/continuity-journal.mjs +500 -0
- package/plugin/scripts/decision-gate.mjs +56 -3
- package/plugin/scripts/footprint-io.mjs +186 -0
- package/plugin/scripts/ground-before-write.sh +8 -1
- package/plugin/scripts/ground-ruvnet.sh +103 -12
- package/plugin/scripts/grounding-answer.mjs +2 -1
- package/plugin/scripts/grounding-stamp.sh +3 -0
- package/plugin/scripts/grounding-substance.mjs +1 -1
- package/plugin/scripts/grounding-turn-evidence.mjs +17 -2
- package/plugin/scripts/hook-input.mjs +78 -4
- package/plugin/scripts/kb-copy-proof.mjs +148 -0
- package/plugin/scripts/lesson-bridge.mjs +6 -2
- package/plugin/scripts/nightly-controller.mjs +8 -1
- package/plugin/scripts/node-sqlite.mjs +41 -0
- package/plugin/scripts/package-cards.json +797 -0
- package/plugin/scripts/package-cards.rvf +0 -0
- package/plugin/scripts/package-cards.rvf.idmap.json +1 -0
- package/plugin/scripts/package-cards.rvf.meta.json +1 -0
- package/plugin/scripts/package-recommender-client.mjs +138 -0
- package/plugin/scripts/package-recommender-flag.mjs +30 -0
- package/plugin/scripts/package-recommender.mjs +391 -0
- package/plugin/scripts/project-progression-outbox.mjs +26 -8
- package/plugin/scripts/project-progression-reader.mjs +4 -2
- package/plugin/scripts/protect-brain-state.sh +4 -1
- package/plugin/scripts/session-snapshot-hook.mjs +27 -3
- package/plugin/scripts/session-start-budget.mjs +1 -0
- package/plugin/scripts/session-start-core.mjs +34 -4
- package/plugin/scripts/session-start-health.mjs +7 -1
- package/plugin/scripts/session-start-update-plane.mjs +35 -0
- package/plugin/scripts/turn-outcome-capture.mjs +12 -1
- package/plugin/scripts/unprompted-runtime.mjs +2 -2
- package/plugin/skills/brain-console/SKILL.md +3 -3
- package/plugin/skills/rnbc/SKILL.md +24 -0
- package/plugin/skills/rvbc/SKILL.md +2 -2
- package/scripts/approved-runtime.mjs +2 -2
- package/scripts/ci/warm-brain-models.mjs +28 -0
- package/scripts/codex-hook-trust.mjs +94 -0
- package/scripts/console-runtime-identity.mjs +5 -0
- package/scripts/corpus-canary.mjs +46 -6
- package/scripts/corpus-dispatch-decision.mjs +2 -2
- package/scripts/corpus-promotion.mjs +1 -1
- package/scripts/hook-qualify-hosts.mjs +15 -3
- package/scripts/host-install-matrix.mjs +63 -2
- package/scripts/human-approval-phrases.mjs +46 -0
- package/scripts/installed-brain-health.mjs +53 -0
- package/scripts/move-brain.mjs +310 -0
- package/scripts/onboarding-console.mjs +93 -10
- package/scripts/oracle/abstain-threshold-sweep.mjs +62 -0
- package/scripts/oracle/abstain-trace.mjs +139 -0
- package/scripts/oracle/doc2query-generate.mjs +162 -0
- package/scripts/oracle/doc2query-reach.mjs +110 -0
- package/scripts/oracle/judge-train.mjs +158 -0
- package/scripts/oracle/need-set-split.mjs +48 -0
- package/scripts/oracle/sona-query-adapter-eval.mjs +139 -0
- package/scripts/package-cards.mjs +374 -0
- package/scripts/publication-receipt.mjs +37 -9
- package/scripts/recommendation-e2e.mjs +110 -0
- package/scripts/recommendation-eval.mjs +105 -0
- package/scripts/recommendation-floor.mjs +56 -0
- package/scripts/recommendation-judge-score.mjs +74 -0
- package/scripts/recommendation-latency.mjs +95 -0
- package/scripts/recommendation-real-host-score.mjs +76 -0
- package/scripts/recommendation-real-host.mjs +137 -0
- package/scripts/release-channel-kind.mjs +1 -1
- package/scripts/release-environment-policy.mjs +33 -0
- package/scripts/single-source-check.mjs +15 -10
- package/scripts/sync-commands.mjs +5 -2
- package/scripts/wired-check.mjs +17 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// DISTINCT-FROM: kb/forge-update.mjs reclaimBackups/assertRedundantBackup — that proof requires every byte of a copy to survive in live, which no older generation satisfies; this one requires byte-identity only for private-store files and public provenance for stores the live brain lacks (ADR-0098).
|
|
2
|
+
//
|
|
3
|
+
// kb-copy-proof.mjs — may a full copy of the knowledge base be deleted? The ONE proof used by the
|
|
4
|
+
// footprint sweep (plugin/scripts/brain-footprint.mjs) and by the installer right after it activates a
|
|
5
|
+
// new generation (bin/install.mjs unzipInto). Pure read; never follows a link; never writes.
|
|
6
|
+
//
|
|
7
|
+
// A copy is DISPOSABLE only when nothing in it is unique. Every file must be one of:
|
|
8
|
+
// * a PRIVATE-store file (names from the PRIVATE-STORES.json fence of the live brain AND of the copy, plus
|
|
9
|
+
// every updateManaged:false store in either SOURCE.json; membership rule = kb/forge-update.mjs
|
|
10
|
+
// capturePrivateOverlayState) that exists BYTE-IDENTICAL at the same path in the live brain — nothing
|
|
11
|
+
// else excuses a private file;
|
|
12
|
+
// * a public release file: listed with these exact bytes in the copy's own ARCHIVE-MANIFEST.json, or a
|
|
13
|
+
// name the live generation ships, or a member of a public store family (named by either COVERAGE.json
|
|
14
|
+
// or the live SOURCE.json) that a newer release replaced or retired;
|
|
15
|
+
// * installer-written or reinstallable (node_modules/, the updater/validator files the installer places);
|
|
16
|
+
// * a symbolic link identical in the live brain (links are compared, never followed).
|
|
17
|
+
// Anything else — a user's own file, an unfenced store, a link the live brain lacks — KEEPS the copy, and
|
|
18
|
+
// is named. The live brain itself must be present, so a copy is never removed while it may be the only one.
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import crypto from 'node:crypto';
|
|
22
|
+
import { isVolumeMetadata } from './footprint-io.mjs';
|
|
23
|
+
|
|
24
|
+
const readJson = (file) => { try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; } };
|
|
25
|
+
const lstat = (file) => { try { return fs.lstatSync(file); } catch { return null; } };
|
|
26
|
+
const names = (dir) => { try { return fs.readdirSync(dir).sort(); } catch { return []; } };
|
|
27
|
+
const sha256File = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
28
|
+
|
|
29
|
+
/** Every regular file and link under `root`, relative, without following links. macOS volume metadata
|
|
30
|
+
* (AppleDouble `._*` shadows on an exFAT disk, .DS_Store, …) is the volume's, never a copy's unique data. */
|
|
31
|
+
function walk(root, prefix = '', out = []) {
|
|
32
|
+
for (const name of names(path.join(root, prefix)).filter((n) => !isVolumeMetadata(n))) {
|
|
33
|
+
const relative = prefix ? path.join(prefix, name) : name;
|
|
34
|
+
const st = lstat(path.join(root, relative));
|
|
35
|
+
if (!st) continue;
|
|
36
|
+
if (st.isSymbolicLink()) out.push({ relative, link: true });
|
|
37
|
+
else if (st.isDirectory()) walk(root, relative, out);
|
|
38
|
+
else if (st.isFile()) out.push({ relative, link: false, size: st.size });
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const storeList = (source) => {
|
|
44
|
+
const stores = source?.stores;
|
|
45
|
+
if (Array.isArray(stores)) return stores;
|
|
46
|
+
return stores && typeof stores === 'object' ? Object.entries(stores).map(([kbName, value]) => ({ kbName, ...value })) : [];
|
|
47
|
+
};
|
|
48
|
+
const stemOf = (file) => path.basename(String(file)).replace(/(?:\.big)?\.rvf$/i, '').toLowerCase();
|
|
49
|
+
// The store a sidecar belongs to: <store>.big.rvf[.embed|.idmap].json, <store>.meta.json, <store>.passages.jsonl,
|
|
50
|
+
// <store>.symbols.json, <store>-primer.md.
|
|
51
|
+
const storeStem = (file) => path.basename(String(file)).toLowerCase()
|
|
52
|
+
.replace(/-primer\.md$/, '').replace(/(?:\.big)?\.rvf(?:\.[a-z]+\.json)?$/, '').replace(/\.(?:meta|symbols)\.json$|\.passages\.jsonl$/, '');
|
|
53
|
+
// Files the installer/updater writes into a KB that no bundle ships (bin/install.mjs placeUpdater,
|
|
54
|
+
// placeTrustedCoverageValidator, ensureVerifier; the updater's snapshot receipt). Re-created on every install.
|
|
55
|
+
const INSTALLER_WRITTEN = new Set(['coverage-integrity.mjs', 'RUNTIME-IDENTITY.json', '.refresh-snapshot.json',
|
|
56
|
+
'forge-update.mjs', 'zip-extract.mjs', 'brain-profile.mjs', 'refresh-run.mjs', 'update-storage-transaction.mjs',
|
|
57
|
+
'lifecycle-evidence-retention.mjs', 'corpus-release-identity.mjs', 'verify-citation.mjs', 'package-lock.json', '.DS_Store']);
|
|
58
|
+
|
|
59
|
+
/** A directory that is a knowledge-base tree (SOURCE.json plus a store or the search entry point). */
|
|
60
|
+
export const isKbTree = (dir) => Boolean(lstat(path.join(dir, 'SOURCE.json')))
|
|
61
|
+
&& names(dir).some((n) => /\.rvf$/i.test(n) || n === 'forge-mcp-all.mjs');
|
|
62
|
+
|
|
63
|
+
/** Private store names the given trees declare: PRIVATE-STORES.json fences and updateManaged:false stores. */
|
|
64
|
+
export function privateStoreNames(dirs) {
|
|
65
|
+
const out = new Set();
|
|
66
|
+
for (const dir of dirs) {
|
|
67
|
+
const fence = readJson(path.join(dir, 'PRIVATE-STORES.json'))?.privateStores;
|
|
68
|
+
for (const name of Array.isArray(fence) ? fence : []) out.add(String(name).toLowerCase());
|
|
69
|
+
for (const store of storeList(readJson(path.join(dir, 'SOURCE.json')))) {
|
|
70
|
+
if (store?.updateManaged === false && store.kbName) out.add(String(store.kbName).toLowerCase());
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The capturePrivateOverlayState membership rule (kb/forge-update.mjs), applied to one relative path. */
|
|
77
|
+
function belongsToPrivate(relative, privateNames, privateArtifacts) {
|
|
78
|
+
const base = path.basename(relative).toLowerCase();
|
|
79
|
+
for (const name of privateNames) if (base === name || base.startsWith(`${name}.`) || base.startsWith(`${name}-`)) return true;
|
|
80
|
+
return privateArtifacts.some(({ directory, basename, stem }) => path.dirname(relative) === directory
|
|
81
|
+
&& (base === basename || base.startsWith(`${basename}.`) || base.startsWith(`${stem}.`) || base.startsWith(`${stem}-`)));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const sameBytes = (left, right) => {
|
|
85
|
+
const a = lstat(left); const b = lstat(right);
|
|
86
|
+
if (!a || !b || !a.isFile() || !b.isFile() || a.isSymbolicLink() || b.isSymbolicLink() || a.size !== b.size) return false;
|
|
87
|
+
try { return sha256File(left) === sha256File(right); } catch { return false; }
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @returns {{disposable: boolean, unique: {file: string, why: string}[], reason: string}} — a kept copy
|
|
92
|
+
* always names the files that keep it.
|
|
93
|
+
*/
|
|
94
|
+
export function kbCopyProof({ copyDir, liveDir }) {
|
|
95
|
+
const copy = lstat(copyDir);
|
|
96
|
+
if (!copy || copy.isSymbolicLink() || !copy.isDirectory()) return { disposable: false, unique: [], reason: 'not a real directory (a link is never entered)' };
|
|
97
|
+
const live = lstat(liveDir);
|
|
98
|
+
if (!live || !live.isDirectory() || !readJson(path.join(liveDir, 'SOURCE.json'))
|
|
99
|
+
|| !names(liveDir).some((name) => /\.rvf$/i.test(name))) {
|
|
100
|
+
return { disposable: false, unique: [], reason: 'the live brain is missing or incomplete, so this copy may be the only good one' };
|
|
101
|
+
}
|
|
102
|
+
const privateNames = privateStoreNames([liveDir, copyDir]);
|
|
103
|
+
const generations = readJson(path.join(copyDir, 'RVF-GENERATIONS.json'))?.stores || {};
|
|
104
|
+
const privateArtifacts = Object.entries(generations).filter(([name]) => privateNames.has(name.toLowerCase()))
|
|
105
|
+
.map(([, g]) => String(g?.file || '')).filter(Boolean).map((file) => ({ directory: path.dirname(path.normalize(file)),
|
|
106
|
+
basename: path.basename(file).toLowerCase(), stem: stemOf(file) }));
|
|
107
|
+
const coverageNames = (dir) => (readJson(path.join(dir, 'COVERAGE.json'))?.rows || [])
|
|
108
|
+
.flatMap((row) => [row?.artifact?.store, row?.name]).filter(Boolean).map((n) => String(n).toLowerCase());
|
|
109
|
+
const publicNames = new Set([...coverageNames(liveDir), ...coverageNames(copyDir),
|
|
110
|
+
...storeList(readJson(path.join(liveDir, 'SOURCE.json'))).filter((s) => s?.updateManaged !== false)
|
|
111
|
+
.map((s) => String(s.kbName || '').toLowerCase())]);
|
|
112
|
+
for (const name of privateNames) publicNames.delete(name); // a fenced name is never "public provenance"
|
|
113
|
+
// The copy's own signed release manifest (scripts/build-bundle.mjs, every bundle since 2026-08-23): a file
|
|
114
|
+
// it lists with these exact bytes is a public, re-downloadable release file.
|
|
115
|
+
const shipped = new Map((readJson(path.join(copyDir, 'ARCHIVE-MANIFEST.json'))?.files || [])
|
|
116
|
+
.filter((f) => typeof f?.path === 'string').map((f) => [path.normalize(f.path), f]));
|
|
117
|
+
const unique = [];
|
|
118
|
+
let files;
|
|
119
|
+
try { files = walk(copyDir); } catch (error) { return { disposable: false, unique, reason: `unreadable copy: ${error.message}` }; }
|
|
120
|
+
for (const { relative, link, size } of files) {
|
|
121
|
+
// node_modules (npm reinstalls it) and .console-runtime (bin/install.mjs installConsoleRuntime re-places it)
|
|
122
|
+
if (['node_modules', '.console-runtime'].includes(relative.split(path.sep)[0])
|
|
123
|
+
|| (!relative.includes(path.sep) && INSTALLER_WRITTEN.has(relative))) continue;
|
|
124
|
+
const isPrivate = belongsToPrivate(relative, privateNames, privateArtifacts);
|
|
125
|
+
const inLive = path.join(liveDir, relative);
|
|
126
|
+
if (link) {
|
|
127
|
+
const liveLink = lstat(inLive);
|
|
128
|
+
const same = liveLink?.isSymbolicLink() && fs.readlinkSync(inLive) === fs.readlinkSync(path.join(copyDir, relative));
|
|
129
|
+
if (!same) unique.push({ file: relative, why: 'a symbolic link the live brain does not have (never followed)' });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (isPrivate) {
|
|
133
|
+
if (!sameBytes(path.join(copyDir, relative), inLive)) {
|
|
134
|
+
unique.push({ file: relative, why: lstat(inLive) ? 'private file differs from the live brain' : 'private file absent from the live brain' });
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (relative === 'ARCHIVE-MANIFEST.json' && shipped.size) continue; // the release manifest itself
|
|
139
|
+
const listed = shipped.get(relative);
|
|
140
|
+
if (listed && listed.bytes === size && listed.sha256 === sha256File(path.join(copyDir, relative))) continue;
|
|
141
|
+
if (lstat(inLive)) continue; // a release-owned name the live generation ships (same or newer bytes)
|
|
142
|
+
if (publicNames.has(storeStem(relative))) continue; // a public store family a newer release replaced or retired
|
|
143
|
+
unique.push({ file: relative, why: 'not in the live brain, not in this copy\'s release manifest, not a public store' });
|
|
144
|
+
}
|
|
145
|
+
return unique.length
|
|
146
|
+
? { disposable: false, unique, reason: `holds ${unique.length} file(s) the live brain does not: ${unique.slice(0, 4).map((u) => u.file).join(', ')}${unique.length > 4 ? ', …' : ''}` }
|
|
147
|
+
: { disposable: true, unique, reason: 'every private file is byte-identical in the live brain; public bytes are re-downloadable' };
|
|
148
|
+
}
|
|
@@ -23,6 +23,7 @@ import path from 'node:path';
|
|
|
23
23
|
import { execFileSync } from 'node:child_process';
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
25
25
|
import { makeLesson, updateLessons, loadLessons, ENFORCEMENT, ORIGIN, STATUS, TRIGGERS } from './lesson-store.mjs';
|
|
26
|
+
import { loadNodeSqlite } from './node-sqlite.mjs';
|
|
26
27
|
|
|
27
28
|
/** Every bridged lesson id starts with this. It is how a merge knows which rows it owns. */
|
|
28
29
|
export const BRIDGE_PREFIX = 'G-';
|
|
@@ -108,8 +109,11 @@ export function readProjectRows(dbPath = PROJECT_DB) {
|
|
|
108
109
|
} catch { return []; }
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Indirection so a missing node:sqlite is a caught throw rather than a module-load crash, loaded
|
|
114
|
+
* through node-sqlite.mjs so Node 22's SQLite ExperimentalWarning never reaches hook output.
|
|
115
|
+
*/
|
|
116
|
+
function require$(id) { return id === 'node:sqlite' ? loadNodeSqlite() : (process.getBuiltinModule ? process.getBuiltinModule(id) : null); }
|
|
113
117
|
|
|
114
118
|
// ── Row → lesson ─────────────────────────────────────────────────────────────────────────────────
|
|
115
119
|
|
|
@@ -73,6 +73,11 @@ export function nightlyStatus(options = {}) {
|
|
|
73
73
|
testMode: options.testMode ?? env.RUVNET_BRAIN_SCHEDULER_TEST === '1' });
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export function stripAnsi(text) {
|
|
77
|
+
// eslint-disable-next-line no-control-regex
|
|
78
|
+
return String(text).replace(/\u001b\[[0-9;]*[A-Za-z]/g, '');
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
export function applyNightlyChoice(enabled, options = {}) {
|
|
77
82
|
if (typeof enabled !== 'boolean') return { ok: false, log: 'nightly must be true or false' };
|
|
78
83
|
const env = schedulerEnvironment(options.env || process.env);
|
|
@@ -97,6 +102,8 @@ export function applyNightlyChoice(enabled, options = {}) {
|
|
|
97
102
|
after,
|
|
98
103
|
log: ok
|
|
99
104
|
? `Nightly refresh is ${desired}; ${after.evidence}.`
|
|
100
|
-
|
|
105
|
+
// The installer colours its terminal output; this string is shown in the console page, where
|
|
106
|
+
// raw ANSI escapes render as "[31m✗" noise (RNBC QA 2026-10-01). Strip them here, once.
|
|
107
|
+
: `Nightly refresh did not reach ${desired}: ${stripAnsi(run.error?.message || run.stderr?.trim() || run.stdout?.trim() || `exit ${run.status}`)}`,
|
|
101
108
|
};
|
|
102
109
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* node-sqlite.mjs — the ONE way plugin code loads `node:sqlite`, lazily and without hook noise.
|
|
3
|
+
*
|
|
4
|
+
* WHY (measured 2026-10-01, Node 22.13.1 locally and Node 22 on the Linux CI full suite, run 36894944735):
|
|
5
|
+
* on Node 22 the first load of `node:sqlite` prints
|
|
6
|
+
* (node:PID) ExperimentalWarning: SQLite is an experimental feature and might change at any time
|
|
7
|
+
* to stderr. SessionStart reads the store through project-progression-reader.mjs, so every SessionStart
|
|
8
|
+
* in an adopted git project on Node 22 carried that line — hook noise a customer sees on every session.
|
|
9
|
+
* Node 24 does not warn, which is why the owner's machine never showed it.
|
|
10
|
+
*
|
|
11
|
+
* WHAT: the module is required only when a caller actually needs it (never at import time), and during
|
|
12
|
+
* that one synchronous require `process.emitWarning` drops exactly ONE warning shape — type
|
|
13
|
+
* ExperimentalWarning whose message names SQLite — and passes every other warning through untouched.
|
|
14
|
+
* The original `process.emitWarning` is restored before returning, whether the require worked or not.
|
|
15
|
+
* Node's emitExperimentalWarning calls the public process.emitWarning synchronously while the builtin
|
|
16
|
+
* is being compiled (node:internal/util emitExperimentalWarning), which is what makes the scope exact.
|
|
17
|
+
*/
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
|
|
20
|
+
const requireBuiltin = createRequire(import.meta.url);
|
|
21
|
+
let loaded;
|
|
22
|
+
|
|
23
|
+
/** True for the one warning this module exists to keep out of hook output. */
|
|
24
|
+
export function isSqliteExperimentalWarning(warning, typeOrOptions) {
|
|
25
|
+
const type = typeof typeOrOptions === 'string' ? typeOrOptions : typeOrOptions?.type;
|
|
26
|
+
const name = warning && typeof warning === 'object' ? warning.name : undefined;
|
|
27
|
+
const message = typeof warning === 'string' ? warning : warning?.message;
|
|
28
|
+
return (type === 'ExperimentalWarning' || name === 'ExperimentalWarning') && /\bSQLite\b/.test(String(message ?? ''));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The `node:sqlite` module, or null where this Node has none. Loaded once; never warns about SQLite. */
|
|
32
|
+
export function loadNodeSqlite({ load = (id) => requireBuiltin(id) } = {}) {
|
|
33
|
+
if (loaded !== undefined) return loaded;
|
|
34
|
+
const original = process.emitWarning;
|
|
35
|
+
process.emitWarning = function emitWarningExceptSqlite(warning, ...rest) {
|
|
36
|
+
if (isSqliteExperimentalWarning(warning, rest[0])) return undefined;
|
|
37
|
+
return original.call(this, warning, ...rest);
|
|
38
|
+
};
|
|
39
|
+
try { loaded = load('node:sqlite'); } catch { loaded = null; } finally { process.emitWarning = original; }
|
|
40
|
+
return loaded;
|
|
41
|
+
}
|