From 5e9bd54ab1451e5b1d2d61a3337d1bd6a620bd0b Mon Sep 17 00:00:00 2001 From: Evgenii Kniazev Date: Tue, 28 Jul 2026 11:12:41 +0100 Subject: [PATCH] fix(type-generator): treat empty/un-rendered definitions.json as no metric views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readMetricConfig threw a fatal "Failed to parse definitions.json" whenever the file was empty or still held its Go-template guard. The template ships config/metric-views/definitions.json wrapped in `{{if .plugins.analytics}}`, so: - scaffolding an app WITHOUT the analytics plugin renders an empty file, and - running typegen against the raw template (e.g. an `npm install` postinstall warmup before `appkit setup`) leaves the literal `{{...}}` in place. Both are "no metric views defined", not misconfiguration — but JSON.parse("") (or on `{{`) threw and aborted `appkit generate-types`, breaking `npm install` for any analytics-off app and any tooling that warms the raw template. Treat empty/whitespace-only or un-rendered (`{{`-prefixed) content the same as an absent file (return null), matching the documented additive contract. A non-empty, rendered file with genuinely malformed JSON still throws loudly. Added mv-registry tests for the empty, whitespace-only, and un-rendered cases. Co-authored-by: Isaac --- .../src/type-generator/mv-registry/config.ts | 22 ++++++++++++++---- .../type-generator/tests/mv-registry.test.ts | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/appkit/src/type-generator/mv-registry/config.ts b/packages/appkit/src/type-generator/mv-registry/config.ts index a204d3317..6a8b109e3 100644 --- a/packages/appkit/src/type-generator/mv-registry/config.ts +++ b/packages/appkit/src/type-generator/mv-registry/config.ts @@ -46,11 +46,17 @@ function compareKeys(a: string, b: string): number { * Read {@link METRIC_CONFIG_FILE} from a metric-views folder * (`config/metric-views/`). * - * Returns `null` if the file does not exist (the metric-view path is - * additive — apps without definitions.json must not be penalized). There is - * deliberately no fallback to a legacy filename. + * Returns `null` if the file does not exist, is empty, or has not been rendered + * yet (the metric-view path is additive — apps without a defined + * definitions.json must not be penalized). The template ships definitions.json + * wrapped in a `{{if .plugins.analytics}}` guard, so scaffolding without the + * analytics plugin leaves the file empty, and running typegen against the raw + * (un-rendered) template leaves the literal `{{...}}` in place — neither is a + * misconfiguration, so both are treated the same as an absent file rather than + * a fatal parse error. There is deliberately no fallback to a legacy filename. * - * Throws on JSON parse errors so misconfiguration surfaces loudly. + * Throws on JSON parse errors in a *non-empty, rendered* file so genuine + * misconfiguration still surfaces loudly. */ export async function readMetricConfig( metricViewsFolder: string, @@ -66,6 +72,14 @@ export async function readMetricConfig( throw err; } + // An empty file (analytics plugin off → the `{{if}}` guard renders nothing) + // or an un-rendered template (raw `{{...}}` left in place during typegen on + // the template itself) means "no metric views" — treat like an absent file. + const trimmed = raw.trim(); + if (trimmed === "" || trimmed.startsWith("{{")) { + return null; + } + let parsed: unknown; try { parsed = JSON.parse(raw); diff --git a/packages/appkit/src/type-generator/tests/mv-registry.test.ts b/packages/appkit/src/type-generator/tests/mv-registry.test.ts index 6fa77693e..ff9484b23 100644 --- a/packages/appkit/src/type-generator/tests/mv-registry.test.ts +++ b/packages/appkit/src/type-generator/tests/mv-registry.test.ts @@ -94,6 +94,29 @@ describe("readMetricConfig", () => { expect(await readMetricConfig(tmpDir)).toBeNull(); }); + test("returns null for an empty definitions.json (analytics plugin off)", async () => { + // The template wraps definitions.json in `{{if .plugins.analytics}}`, so + // scaffolding without the analytics plugin renders an empty file. That's + // "no metric views", not a parse error. + await fs.writeFile(path.join(tmpDir, "definitions.json"), ""); + expect(await readMetricConfig(tmpDir)).toBeNull(); + }); + + test("returns null for a whitespace-only definitions.json", async () => { + await fs.writeFile(path.join(tmpDir, "definitions.json"), "\n \n"); + expect(await readMetricConfig(tmpDir)).toBeNull(); + }); + + test("returns null for an un-rendered template definitions.json", async () => { + // Running typegen against the raw template (e.g. npm-install warmup before + // `appkit setup`) leaves the Go-template guard literal in place. + await fs.writeFile( + path.join(tmpDir, "definitions.json"), + '{{if .plugins.analytics -}}\n{ "metricViews": {} }\n{{- end}}\n', + ); + expect(await readMetricConfig(tmpDir)).toBeNull(); + }); + test("parses a valid definitions.json", async () => { await fs.writeFile( path.join(tmpDir, "definitions.json"),