From a066a66baf96183427d6b11b3d8e1d598b6f0bca Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 1 Oct 2026 23:16:29 +0200 Subject: [PATCH 1/8] perf(stdlib): cache needle source text and stop findBy at the first miss --- modules/stdlib/src/util.test.mts | 28 ++++++++++++++++++++++++++-- modules/stdlib/src/util.ts | 25 ++++++++++++++++++++----- modules/stdlib/src/webpack/index.ts | 15 ++------------- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/modules/stdlib/src/util.test.mts b/modules/stdlib/src/util.test.mts index f56d270a..957dc3e6 100644 --- a/modules/stdlib/src/util.test.mts +++ b/modules/stdlib/src/util.test.mts @@ -4,9 +4,9 @@ */ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it, test } from "node:test"; -import { findBy, matchLast, toPascalCase } from "./util.ts"; +import { findBy, matchLast, sourceOf, toPascalCase } from "./util.ts"; describe("toPascalCase", () => { // Golden values computed against the original hooks-era data-URL @@ -95,3 +95,27 @@ describe("matchLast", () => { assert.equal(m[1], "c"); }); }); + +test("findBy stops at the first failing test and stringifies each candidate once", () => { + let stringified = 0; + let laterTests = 0; + const candidate = { toString: () => (stringified++, "alpha beta") }; + const other = { toString: () => (stringified++, "gamma") }; + const find = findBy("gamma", (x) => (laterTests++, x === other), /gamma/); + assert.equal(find([candidate, other]), other); + assert.equal(laterTests, 1); + assert.equal(stringified, 2); + assert.equal(find([candidate, other]), other); + assert.equal(stringified, 2); +}); + +test("sourceOf caches function source and maps nullish values to an empty string", () => { + let calls = 0; + const fn = Object.assign(() => {}, { toString: () => (calls++, "source") }); + assert.equal(sourceOf(fn), "source"); + assert.equal(sourceOf(fn), "source"); + assert.equal(calls, 1); + assert.equal(sourceOf(null), ""); + assert.equal(sourceOf(undefined), ""); + assert.equal(sourceOf(42), "42"); +}); diff --git a/modules/stdlib/src/util.ts b/modules/stdlib/src/util.ts index 7b36cedf..c8155632 100644 --- a/modules/stdlib/src/util.ts +++ b/modules/stdlib/src/util.ts @@ -14,9 +14,10 @@ type Predicate = (value: A) => boolean; // Some client exports are functions whose own toString is not callable; // they can never match a needle, so they stringify to "". -const safeString = (x: any): string => { +const stringify = (x: any): string => { + if (x == null) return ""; try { - return x.toString(); + return String(x); } catch { try { return Function.prototype.toString.call(x); @@ -26,18 +27,32 @@ const safeString = (x: any): string => { } }; +const sources = new WeakMap(); + +// sourceOf is the source text needles match against, computed once per +// function or object for the whole session. +export const sourceOf = (x: unknown): string => { + if ((typeof x !== "object" && typeof x !== "function") || x === null) return stringify(x); + let source = sources.get(x); + if (source === undefined) { + source = stringify(x); + sources.set(x, source); + } + return source; +}; + export function findBy(...tests: Array>) { const testFns = tests.map((test): Predicate => { switch (typeof test) { case "string": - return (x) => safeString(x).includes(test); + return (x) => sourceOf(x).includes(test); case "function": return (x) => test(x); default: // assume regex - return (x) => test.test(safeString(x)); + return (x) => test.test(sourceOf(x)); } }); - const testFn = (x: any) => testFns.map((t) => t(x)).every(Boolean); + const testFn = (x: any) => testFns.every((t) => t(x)); return (xs: A[]) => xs.find(testFn)!; } diff --git a/modules/stdlib/src/webpack/index.ts b/modules/stdlib/src/webpack/index.ts index 76d58489..2b537f80 100644 --- a/modules/stdlib/src/webpack/index.ts +++ b/modules/stdlib/src/webpack/index.ts @@ -4,6 +4,7 @@ */ import { warn } from "../logger.ts"; +import { sourceOf } from "../util.ts"; import { postWebpackRequireHooks, WebpackModule, WebpackRequire, webpackRequire } from "../wpunpk.mix.ts"; import { createCaptureReadiness } from "./capture-readiness.ts"; @@ -18,19 +19,7 @@ export let exportedContexts: Array>; export let exportedForwardRefs: Array; export let exportedMemos: React.NamedExoticComponent[]; -// Some client exports are functions whose own toString is not callable; they -// can never match a needle, so they stringify to "". -export const src = (f: unknown): string => { - try { - return String(f); - } catch { - try { - return Function.prototype.toString.call(f); - } catch { - return ""; - } - } -}; +export const src = sourceOf; export const analyzeWebpackRequire = (webpackRequire: WebpackRequire) => { const modules = Object.entries(webpackRequire.m) as Array<[keyof any, WebpackModule]>; From e8ae6a07f7b75d55dc6b4124500f6a7b8883ab43 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 1 Oct 2026 23:16:30 +0200 Subject: [PATCH 2/8] perf(stdlib): skip non-component functions before matching encore names --- modules/stdlib/src/webpack/ComponentLibrary.xpui.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts b/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts index 531668e4..bd3de6e8 100644 --- a/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts +++ b/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts @@ -13,6 +13,14 @@ const componentNames = Object.keys( const componentRegexes = componentNames.map((n) => new RegExp(`"data-encore-id":(?:[a-zA-Z_\$][\w\$]*\\.){2}${n}\\b`)); const componentPairs = [exportedFunctions.map((f) => [f, f]), exportedForwardRefs.map((f) => [(f as any).render, f])] .flat() - .map(([s, f]) => [componentNames.find((n, i) => src(s).match(componentRegexes[i])), f]); + .map(([s, f]) => { + const code = src(s); + return [ + code.includes('"data-encore-id"') + ? componentNames.find((_, i) => componentRegexes[i].test(code)) + : undefined, + f, + ]; + }); export const UI: any = Object.fromEntries(componentPairs); From 89b50a0b03e7a5a6e8d58eb4407844be14bb2696 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 1 Oct 2026 23:16:30 +0200 Subject: [PATCH 3/8] perf(stdlib): release the xpui chunks 300ms after the registry settles --- modules/stdlib/src/webpack/index.ts | 36 ++++++------ .../src/webpack/registry-quiet.test.mts | 56 +++++++++++++++++++ modules/stdlib/src/webpack/registry-quiet.ts | 45 +++++++++++++++ 3 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 modules/stdlib/src/webpack/registry-quiet.test.mts create mode 100644 modules/stdlib/src/webpack/registry-quiet.ts diff --git a/modules/stdlib/src/webpack/index.ts b/modules/stdlib/src/webpack/index.ts index 2b537f80..240b38eb 100644 --- a/modules/stdlib/src/webpack/index.ts +++ b/modules/stdlib/src/webpack/index.ts @@ -7,6 +7,7 @@ import { warn } from "../logger.ts"; import { sourceOf } from "../util.ts"; import { postWebpackRequireHooks, WebpackModule, WebpackRequire, webpackRequire } from "../wpunpk.mix.ts"; import { createCaptureReadiness } from "./capture-readiness.ts"; +import { watchRegistryQuiet } from "./registry-quiet.ts"; export let modules: Array<[PropertyKey, WebpackModule]>; export let exports: Array>; @@ -73,18 +74,22 @@ Object.assign(CHUNKS, { // executed by capture time. Lazy route chunks go through the runtime's own // script loader (wpr.l) from then on. postWebpackRequireHooks.push((wpr: any) => { + let loading = 0; if (typeof wpr?.l === "function") { const load = wpr.l.bind(wpr); - wpr.l = (url: string, done: (event: unknown) => unknown, key?: string, chunkId?: unknown) => - load( + wpr.l = (url: string, done: (event: unknown) => unknown, key?: string, chunkId?: unknown) => { + loading++; + return load( url, (event: unknown) => { + loading--; resolveChunk(new URL(url, location.href).pathname); return done(event); }, key, chunkId, ); + }; } // Chunk scripts that finished loading before capture. The xpui entry // chunks are deliberately excluded: they are gated below on registry @@ -97,23 +102,18 @@ postWebpackRequireHooks.push((wpr: any) => { } } // Capture can fire while boot is still registering modules (xpui-modules - // and friends land after the runtime is up, and the analysis needles live - // there). The analysis below snapshots wpr.m once, so hold the xpui - // promises until the registry has been quiet for a few ticks — with a - // hard cap so a pathological boot still resolves. - let last = -1; - let stable = 0; - let ticks = 0; - const settle = setInterval(() => { - const count = Object.keys(wpr?.m ?? {}).length; - stable = count === last ? stable + 1 : 0; - last = count; - if (stable >= 3 || ++ticks > 100) { - clearInterval(settle); + // and the lazy chunks the client requests right after it land later, and + // analysis needles such as GenericModal live there). The analysis below + // snapshots wpr.m once, so hold the xpui promises until the registry has + // been quiet, with no chunk in flight, for 300ms. + watchRegistryQuiet({ + count: () => Object.keys(wpr?.m ?? {}).length, + pending: () => loading, + onQuiet: () => { resolveChunk("/vendor~xpui.js"); resolveChunk("/xpui.js"); - } - }, 100); + }, + }); }); // Capture subscribers run synchronously right after the analysis lands, so @@ -124,7 +124,7 @@ postWebpackRequireHooks.push((wpr: any) => { let captured = false; const captureSubscribers: Array<() => void> = []; const webpackCaptureReadiness = createCaptureReadiness({ - // Registry quiescence caps at about 10.1s. Leave room for its last tick, + // Registry quiescence caps at about 10s. Leave room for its last tick, // then release degraded so a changed runtime cannot deadlock all modules. timeoutMs: 12000, onTimeout: () => warn("[stdlib] capture health: webpack capture timed out; module surfaces will be degraded"), diff --git a/modules/stdlib/src/webpack/registry-quiet.test.mts b/modules/stdlib/src/webpack/registry-quiet.test.mts new file mode 100644 index 00000000..1bdd1d7b --- /dev/null +++ b/modules/stdlib/src/webpack/registry-quiet.test.mts @@ -0,0 +1,56 @@ +import assert from "node:assert/strict"; +import { test, type TestContext } from "node:test"; + +import { watchRegistryQuiet } from "./registry-quiet.ts"; + +const setup = (t: TestContext) => { + t.mock.timers.enable({ apis: ["setInterval", "Date"] }); + const state = { count: 100, pending: 0, quietAt: -1 }; + watchRegistryQuiet({ + count: () => state.count, + pending: () => state.pending, + onQuiet: () => (state.quietAt = Date.now()), + }); + const advance = (ms: number) => { + for (let i = 0; i < ms / 25; i++) t.mock.timers.tick(25); + }; + return { state, advance }; +}; + +test("releases once the registry has been unchanged for the quiet window", (t) => { + const { state, advance } = setup(t); + advance(200); + state.count = 150; + advance(275); + assert.equal(state.quietAt, -1); + advance(50); + assert.equal(state.quietAt, 525); +}); + +test("never releases while a chunk load is in flight, even with no new modules", (t) => { + const { state, advance } = setup(t); + state.pending = 1; + advance(1000); + assert.equal(state.quietAt, -1); + state.pending = 0; + advance(300); + assert.equal(state.quietAt, 1300); +}); + +test("releases at the cap when the registry never settles", (t) => { + const { state, advance } = setup(t); + for (let i = 0; i < 400; i++) { + state.count++; + advance(25); + } + assert.equal(state.quietAt, 10_000); +}); + +test("a chunk that never finishes holds back release for at most pendingMs", (t) => { + const { state, advance } = setup(t); + state.pending = 1; + advance(1975); + assert.equal(state.quietAt, -1); + advance(50); + assert.equal(state.quietAt, 2025); +}); diff --git a/modules/stdlib/src/webpack/registry-quiet.ts b/modules/stdlib/src/webpack/registry-quiet.ts new file mode 100644 index 00000000..2d8bb7bf --- /dev/null +++ b/modules/stdlib/src/webpack/registry-quiet.ts @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2026 Afonso Jorge Ramos + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export interface RegistryQuietOptions { + /** Number of modules currently registered. */ + count: () => number; + /** Chunk script loads that have started but not finished. */ + pending: () => number; + onQuiet: () => void; + quietMs?: number; + /** How long an in-flight chunk can hold back an otherwise unchanged registry. */ + pendingMs?: number; + tickMs?: number; + capMs?: number; + now?: () => number; +} + +// watchRegistryQuiet calls onQuiet once the module registry has not changed, +// and no chunk load has been in flight, for quietMs. A chunk that never +// finishes holds it back for at most pendingMs past the last registry change, +// and capMs bounds the whole wait so a pathological boot still resolves. +export function watchRegistryQuiet(options: RegistryQuietOptions): void { + const { quietMs = 300, pendingMs = 2000, tickMs = 25, capMs = 10_000, now = Date.now } = options; + const started = now(); + let last = -1; + let changedAt = started; + let quietSince = started; + const timer = setInterval(() => { + const count = options.count(); + const at = now(); + if (count !== last) { + last = count; + changedAt = at; + quietSince = at; + } else if (options.pending() > 0) { + quietSince = at; + } + if (at - quietSince >= quietMs || at - changedAt >= pendingMs || at - started >= capMs) { + clearInterval(timer); + options.onQuiet(); + } + }, tickMs); +} From 025e0262588b6779143c33b3053a655635babbbb Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 1 Oct 2026 23:16:39 +0200 Subject: [PATCH 4/8] chore(stdlib): bump to 1.13.2 --- modules/stdlib/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/stdlib/metadata.json b/modules/stdlib/metadata.json index c63f563b..ac19e3af 100644 --- a/modules/stdlib/metadata.json +++ b/modules/stdlib/metadata.json @@ -3,7 +3,7 @@ "kind": "lib", "hidden": true, "preview": "./assets/PREVIEW.png", - "version": "1.13.1", + "version": "1.13.2", "compat": ["0.3.0"], "authors": ["spicetify", "Delusoire"], "description": "The standard library", From 20b04cc64fd9a5d6f2ef82607a379bb980493f43 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 1 Oct 2026 23:22:04 +0200 Subject: [PATCH 5/8] fix(stdlib): hold the xpui chunks while any chunk is loading --- modules/stdlib/src/webpack/index.ts | 10 ++--- .../src/webpack/registry-quiet.test.mts | 33 +++++--------- modules/stdlib/src/webpack/registry-quiet.ts | 45 +++++++------------ 3 files changed, 32 insertions(+), 56 deletions(-) diff --git a/modules/stdlib/src/webpack/index.ts b/modules/stdlib/src/webpack/index.ts index 240b38eb..5bb29e80 100644 --- a/modules/stdlib/src/webpack/index.ts +++ b/modules/stdlib/src/webpack/index.ts @@ -106,14 +106,14 @@ postWebpackRequireHooks.push((wpr: any) => { // analysis needles such as GenericModal live there). The analysis below // snapshots wpr.m once, so hold the xpui promises until the registry has // been quiet, with no chunk in flight, for 300ms. - watchRegistryQuiet({ - count: () => Object.keys(wpr?.m ?? {}).length, - pending: () => loading, - onQuiet: () => { + watchRegistryQuiet( + () => Object.keys(wpr?.m ?? {}).length, + () => loading, + () => { resolveChunk("/vendor~xpui.js"); resolveChunk("/xpui.js"); }, - }); + ); }); // Capture subscribers run synchronously right after the analysis lands, so diff --git a/modules/stdlib/src/webpack/registry-quiet.test.mts b/modules/stdlib/src/webpack/registry-quiet.test.mts index 1bdd1d7b..cba9e089 100644 --- a/modules/stdlib/src/webpack/registry-quiet.test.mts +++ b/modules/stdlib/src/webpack/registry-quiet.test.mts @@ -6,18 +6,18 @@ import { watchRegistryQuiet } from "./registry-quiet.ts"; const setup = (t: TestContext) => { t.mock.timers.enable({ apis: ["setInterval", "Date"] }); const state = { count: 100, pending: 0, quietAt: -1 }; - watchRegistryQuiet({ - count: () => state.count, - pending: () => state.pending, - onQuiet: () => (state.quietAt = Date.now()), - }); + watchRegistryQuiet( + () => state.count, + () => state.pending, + () => (state.quietAt = Date.now()), + ); const advance = (ms: number) => { for (let i = 0; i < ms / 25; i++) t.mock.timers.tick(25); }; return { state, advance }; }; -test("releases once the registry has been unchanged for the quiet window", (t) => { +test("releases once the registry has been unchanged for 300ms", (t) => { const { state, advance } = setup(t); advance(200); state.count = 150; @@ -30,27 +30,18 @@ test("releases once the registry has been unchanged for the quiet window", (t) = test("never releases while a chunk load is in flight, even with no new modules", (t) => { const { state, advance } = setup(t); state.pending = 1; - advance(1000); + advance(5000); assert.equal(state.quietAt, -1); state.pending = 0; advance(300); - assert.equal(state.quietAt, 1300); + assert.equal(state.quietAt, 5300); }); -test("releases at the cap when the registry never settles", (t) => { - const { state, advance } = setup(t); - for (let i = 0; i < 400; i++) { - state.count++; - advance(25); - } - assert.equal(state.quietAt, 10_000); -}); - -test("a chunk that never finishes holds back release for at most pendingMs", (t) => { +test("releases at the 10s cap when a chunk never finishes", (t) => { const { state, advance } = setup(t); state.pending = 1; - advance(1975); + advance(9975); assert.equal(state.quietAt, -1); - advance(50); - assert.equal(state.quietAt, 2025); + advance(25); + assert.equal(state.quietAt, 10_000); }); diff --git a/modules/stdlib/src/webpack/registry-quiet.ts b/modules/stdlib/src/webpack/registry-quiet.ts index 2d8bb7bf..9e0b56f1 100644 --- a/modules/stdlib/src/webpack/registry-quiet.ts +++ b/modules/stdlib/src/webpack/registry-quiet.ts @@ -3,43 +3,28 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -export interface RegistryQuietOptions { - /** Number of modules currently registered. */ - count: () => number; - /** Chunk script loads that have started but not finished. */ - pending: () => number; - onQuiet: () => void; - quietMs?: number; - /** How long an in-flight chunk can hold back an otherwise unchanged registry. */ - pendingMs?: number; - tickMs?: number; - capMs?: number; - now?: () => number; -} +const QUIET_MS = 300; +const TICK_MS = 25; +const CAP_MS = 10_000; // watchRegistryQuiet calls onQuiet once the module registry has not changed, -// and no chunk load has been in flight, for quietMs. A chunk that never -// finishes holds it back for at most pendingMs past the last registry change, -// and capMs bounds the whole wait so a pathological boot still resolves. -export function watchRegistryQuiet(options: RegistryQuietOptions): void { - const { quietMs = 300, pendingMs = 2000, tickMs = 25, capMs = 10_000, now = Date.now } = options; - const started = now(); +// and no chunk load has been in flight, for 300ms. A 10s cap bounds the wait +// so a chunk that never finishes, or a registry that never settles, still +// resolves. +export function watchRegistryQuiet(count: () => number, pending: () => number, onQuiet: () => void): void { + const started = Date.now(); let last = -1; - let changedAt = started; let quietSince = started; const timer = setInterval(() => { - const count = options.count(); - const at = now(); - if (count !== last) { - last = count; - changedAt = at; - quietSince = at; - } else if (options.pending() > 0) { + const current = count(); + const at = Date.now(); + if (current !== last || pending() > 0) { + last = current; quietSince = at; } - if (at - quietSince >= quietMs || at - changedAt >= pendingMs || at - started >= capMs) { + if (at - quietSince >= QUIET_MS || at - started >= CAP_MS) { clearInterval(timer); - options.onQuiet(); + onQuiet(); } - }, tickMs); + }, TICK_MS); } From ef1bb25857c9910b53a6ded4af4c8c85599c8ea5 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 1 Oct 2026 23:22:05 +0200 Subject: [PATCH 6/8] fix(stdlib): finish chunk loads when resolving the chunk throws --- modules/stdlib/src/webpack/index.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/modules/stdlib/src/webpack/index.ts b/modules/stdlib/src/webpack/index.ts index 5bb29e80..030ca5ec 100644 --- a/modules/stdlib/src/webpack/index.ts +++ b/modules/stdlib/src/webpack/index.ts @@ -79,16 +79,25 @@ postWebpackRequireHooks.push((wpr: any) => { const load = wpr.l.bind(wpr); wpr.l = (url: string, done: (event: unknown) => unknown, key?: string, chunkId?: unknown) => { loading++; - return load( - url, - (event: unknown) => { - loading--; - resolveChunk(new URL(url, location.href).pathname); - return done(event); - }, - key, - chunkId, - ); + try { + return load( + url, + (event: unknown) => { + loading--; + try { + resolveChunk(new URL(url, location.href).pathname); + } catch (error) { + console.error(`[stdlib] could not resolve chunk ${url}:`, error); + } + return done(event); + }, + key, + chunkId, + ); + } catch (error) { + loading--; + throw error; + } }; } // Chunk scripts that finished loading before capture. The xpui entry From ba71f877b981af4c86ea99a7e8ff928257a1b76f Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 1 Oct 2026 23:24:40 +0200 Subject: [PATCH 7/8] fix(stdlib): match multi-letter module names in component needles --- modules/stdlib/src/webpack/ComponentLibrary.xpui.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts b/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts index bd3de6e8..91ccad50 100644 --- a/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts +++ b/modules/stdlib/src/webpack/ComponentLibrary.xpui.ts @@ -10,7 +10,9 @@ await CHUNKS.xpui.promise; const componentNames = Object.keys( exported.find((e) => e.BrowserDefaultFocusStyleProvider && Object.hasOwn(e, "BrowserDefaultFocusStyleProvider")), ); -const componentRegexes = componentNames.map((n) => new RegExp(`"data-encore-id":(?:[a-zA-Z_\$][\w\$]*\\.){2}${n}\\b`)); +const componentRegexes = componentNames.map( + (n) => new RegExp(String.raw`"data-encore-id":(?:[a-zA-Z_$][\w$]*\.){2}${n}\b`), +); const componentPairs = [exportedFunctions.map((f) => [f, f]), exportedForwardRefs.map((f) => [(f as any).render, f])] .flat() .map(([s, f]) => { From fc28fb028d52178e1f8738c6f32fcb7899788333 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Fri, 2 Oct 2026 00:00:15 +0200 Subject: [PATCH 8/8] chore(kit): track stdlib 1.13.2 --- packages/kit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/package.json b/packages/kit/package.json index 9c8dab32..048bfc3f 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -40,7 +40,7 @@ "access": "public" }, "spicetify": { - "stdlibVersion": "1.13.1" + "stdlibVersion": "1.13.2" }, "scripts": { "build": "tsc -p tsconfig.build.json",