From be275697a0c4dbc6bcd0baf02a10397ad9a20bd2 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher <239676+necolas@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:50:54 -0700 Subject: [PATCH] Fix theme metadata names Store the machine-readable theme identifier in name and the display label in displayName for generated theme files. Pass both fields explicitly at VS Code theme call sites and add validation so all shipped themes keep the metadata format stable. --- src/build.ts | 61 ++++++++++++++++++++++++--- src/test.ts | 71 ++++++++++++++++++++++---------- src/theme.ts | 11 ++++- themes/pierre-dark-soft.json | 3 +- themes/pierre-dark-vibrant.json | 3 +- themes/pierre-dark.json | 3 +- themes/pierre-light-soft.json | 3 +- themes/pierre-light-vibrant.json | 3 +- themes/pierre-light.json | 3 +- 9 files changed, 127 insertions(+), 34 deletions(-) diff --git a/src/build.ts b/src/build.ts index a415655..c6d312c 100644 --- a/src/build.ts +++ b/src/build.ts @@ -16,12 +16,60 @@ const rolesDarkP3 = convertRolesToP3(rolesDark); // VS Code Themes // ============================================ const vscodeThemes = [ - { file: "themes/pierre-light.json", theme: makeTheme("Pierre Light", "light", rolesLight) }, - { file: "themes/pierre-light-soft.json", theme: makeTheme("Pierre Light Soft", "light", rolesLightSoft) }, - { file: "themes/pierre-dark.json", theme: makeTheme("Pierre Dark", "dark", rolesDark) }, - { file: "themes/pierre-dark-soft.json", theme: makeTheme("Pierre Dark Soft", "dark", rolesDarkSoft) }, - { file: "themes/pierre-light-vibrant.json", theme: makeTheme("Pierre Light Vibrant", "light", rolesLightP3) }, - { file: "themes/pierre-dark-vibrant.json", theme: makeTheme("Pierre Dark Vibrant", "dark", rolesDarkP3) } + { + file: "themes/pierre-light.json", + theme: makeTheme({ + name: "pierre-light", + displayName: "Pierre Light", + type: "light", + roles: rolesLight + }) + }, + { + file: "themes/pierre-light-soft.json", + theme: makeTheme({ + name: "pierre-light-soft", + displayName: "Pierre Light Soft", + type: "light", + roles: rolesLightSoft + }) + }, + { + file: "themes/pierre-dark.json", + theme: makeTheme({ + name: "pierre-dark", + displayName: "Pierre Dark", + type: "dark", + roles: rolesDark + }) + }, + { + file: "themes/pierre-dark-soft.json", + theme: makeTheme({ + name: "pierre-dark-soft", + displayName: "Pierre Dark Soft", + type: "dark", + roles: rolesDarkSoft + }) + }, + { + file: "themes/pierre-light-vibrant.json", + theme: makeTheme({ + name: "pierre-light-vibrant", + displayName: "Pierre Light Vibrant", + type: "light", + roles: rolesLightP3 + }) + }, + { + file: "themes/pierre-dark-vibrant.json", + theme: makeTheme({ + name: "pierre-dark-vibrant", + displayName: "Pierre Dark Vibrant", + type: "dark", + roles: rolesDarkP3 + }) + } ]; for (const {file, theme} of vscodeThemes) { @@ -53,6 +101,7 @@ const themeNames: string[] = []; const themeDts = `/** VS Code / TextMate theme object (frozen at runtime). */ interface PierreTheme { readonly name: string; + readonly displayName: string; readonly type: "light" | "dark"; readonly colors: Readonly>; readonly tokenColors: ReadonlyArray<{ diff --git a/src/test.ts b/src/test.ts index 258cb97..4ebbaa2 100644 --- a/src/test.ts +++ b/src/test.ts @@ -42,26 +42,46 @@ function collectColors(obj: any, path = ""): string[] { return issues; } -function testThemeGeneration(themeName: string, themeType: "light" | "dark", roles: any) { - console.log(`\n๐Ÿงช Testing ${themeName}...`); +function testThemeGeneration( + expectedName: string, + displayName: string, + themeType: "light" | "dark", + roles: any +) { + console.log(`\n๐Ÿงช Testing ${displayName}...`); const errors: string[] = []; try { - const theme = makeTheme(themeName, themeType, roles); + const theme = makeTheme({ + name: expectedName, + displayName, + type: themeType, + roles + }); + const themeMetadata = theme as { name?: string; displayName?: string }; // Test 1: Required properties exist - if (!theme.name) errors.push("Missing theme name"); + if (!themeMetadata.name) errors.push("Missing theme name"); + if (!themeMetadata.displayName) errors.push("Missing theme displayName"); if (!theme.type) errors.push("Missing theme type"); if (!theme.colors) errors.push("Missing colors object"); if (!theme.tokenColors) errors.push("Missing tokenColors array"); if (!theme.semanticTokenColors) errors.push("Missing semanticTokenColors object"); - // Test 2: Type is correct + // Test 2: Theme metadata uses package-safe names plus display labels + if (themeMetadata.name !== expectedName) { + errors.push(`Expected name "${expectedName}" but got "${themeMetadata.name}"`); + } + if (themeMetadata.displayName !== displayName) { + errors.push(`Expected displayName "${displayName}" but got "${themeMetadata.displayName}"`); + } + + // Test 3: Type is correct if (theme.type !== themeType) { errors.push(`Expected type "${themeType}" but got "${theme.type}"`); } - // Test 3: Critical editor colors exist + // Test 4: Critical editor colors exist const criticalColors = [ "editor.background", "editor.foreground", @@ -78,25 +98,25 @@ function testThemeGeneration(themeName: string, themeType: "light" | "dark", rol } } - // Test 4: Validate all color values + // Test 5: Validate all color values const colorIssues = collectColors(theme.colors); errors.push(...colorIssues); - // Test 5: Check for undefined/null values in colors + // Test 6: Check for undefined/null values in colors for (const [key, value] of Object.entries(theme.colors)) { if (value === undefined || value === null) { errors.push(`Color "${key}" is ${value}`); } } - // Test 6: TokenColors should be an array with entries + // Test 7: TokenColors should be an array with entries if (!Array.isArray(theme.tokenColors)) { errors.push("tokenColors is not an array"); } else if (theme.tokenColors.length === 0) { errors.push("tokenColors array is empty"); } - // Test 7: Validate tokenColors structure + // Test 8: Validate tokenColors structure theme.tokenColors.forEach((token, idx) => { if (!token.scope) { errors.push(`tokenColors[${idx}] missing scope`); @@ -111,7 +131,7 @@ function testThemeGeneration(themeName: string, themeType: "light" | "dark", rol } }); - // Test 8: Semantic tokens validation + // Test 9: Semantic tokens validation for (const [key, value] of Object.entries(theme.semanticTokenColors)) { if (typeof value === "string") { usedColors.add(value); @@ -130,15 +150,15 @@ function testThemeGeneration(themeName: string, themeType: "light" | "dark", rol } if (errors.length === 0) { - console.log(`โœ… ${themeName} passed all checks`); + console.log(`โœ… ${displayName} passed all checks`); return true; } else { - console.error(`โŒ ${themeName} failed with ${errors.length} error(s):`); + console.error(`โŒ ${displayName} failed with ${errors.length} error(s):`); errors.forEach(err => console.error(` - ${err}`)); return false; } } catch (error) { - console.error(`โŒ ${themeName} threw an error:`, error); + console.error(`โŒ ${displayName} threw an error:`, error); return false; } } @@ -148,13 +168,15 @@ function testGeneratedFiles() { const errors: string[] = []; const files = [ - { path: "themes/pierre-light.json", expectedType: "light" }, - { path: "themes/pierre-dark.json", expectedType: "dark" }, - { path: "themes/pierre-light-vibrant.json", expectedType: "light" }, - { path: "themes/pierre-dark-vibrant.json", expectedType: "dark" } + { path: "themes/pierre-light.json", expectedType: "light", expectedName: "pierre-light", expectedDisplayName: "Pierre Light" }, + { path: "themes/pierre-light-soft.json", expectedType: "light", expectedName: "pierre-light-soft", expectedDisplayName: "Pierre Light Soft" }, + { path: "themes/pierre-dark.json", expectedType: "dark", expectedName: "pierre-dark", expectedDisplayName: "Pierre Dark" }, + { path: "themes/pierre-dark-soft.json", expectedType: "dark", expectedName: "pierre-dark-soft", expectedDisplayName: "Pierre Dark Soft" }, + { path: "themes/pierre-light-vibrant.json", expectedType: "light", expectedName: "pierre-light-vibrant", expectedDisplayName: "Pierre Light Vibrant" }, + { path: "themes/pierre-dark-vibrant.json", expectedType: "dark", expectedName: "pierre-dark-vibrant", expectedDisplayName: "Pierre Dark Vibrant" } ]; - for (const { path, expectedType } of files) { + for (const { path, expectedType, expectedName, expectedDisplayName } of files) { // Test 1: File exists if (!existsSync(path)) { errors.push(`File does not exist: ${path}`); @@ -173,6 +195,13 @@ function testGeneratedFiles() { // Test 3: Has required structure if (!theme.name) errors.push(`${path}: Missing name`); + if (!theme.displayName) errors.push(`${path}: Missing displayName`); + if (theme.name !== expectedName) { + errors.push(`${path}: Expected name "${expectedName}" but got "${theme.name}"`); + } + if (theme.displayName !== expectedDisplayName) { + errors.push(`${path}: Expected displayName "${expectedDisplayName}" but got "${theme.displayName}"`); + } if (!theme.type) errors.push(`${path}: Missing type`); if (theme.type !== expectedType) { errors.push(`${path}: Expected type "${expectedType}" but got "${theme.type}"`); @@ -253,8 +282,8 @@ let allPassed = true; allPassed = testPaletteRoles() && allPassed; // Test theme generation -allPassed = testThemeGeneration("Pierre Light", "light", rolesLight) && allPassed; -allPassed = testThemeGeneration("Pierre Dark", "dark", rolesDark) && allPassed; +allPassed = testThemeGeneration("pierre-light", "Pierre Light", "light", rolesLight) && allPassed; +allPassed = testThemeGeneration("pierre-dark", "Pierre Dark", "dark", rolesDark) && allPassed; // Test generated files (only if they exist - they should after build) allPassed = testGeneratedFiles() && allPassed; diff --git a/src/theme.ts b/src/theme.ts index e9a0e6d..e20e87e 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -3,15 +3,24 @@ import type { Roles } from "./palette"; type VSCodeTheme = { name: string; + displayName: string; type: "light" | "dark"; colors: Record; tokenColors: any[]; semanticTokenColors: Record; }; -export function makeTheme(name: string, kind: "light"|"dark", c: Roles): VSCodeTheme { +type MakeThemeOptions = { + name: string; + displayName: string; + type: "light" | "dark"; + roles: Roles; +}; + +export function makeTheme({ name, displayName, type: kind, roles: c }: MakeThemeOptions): VSCodeTheme { return { name, + displayName, type: kind, colors: { // Core editor & text diff --git a/themes/pierre-dark-soft.json b/themes/pierre-dark-soft.json index c52d4f8..d56d5d1 100644 --- a/themes/pierre-dark-soft.json +++ b/themes/pierre-dark-soft.json @@ -1,5 +1,6 @@ { - "name": "Pierre Dark Soft", + "name": "pierre-dark-soft", + "displayName": "Pierre Dark Soft", "type": "dark", "colors": { "editor.background": "#171717", diff --git a/themes/pierre-dark-vibrant.json b/themes/pierre-dark-vibrant.json index 6a2854e..0d4a757 100644 --- a/themes/pierre-dark-vibrant.json +++ b/themes/pierre-dark-vibrant.json @@ -1,5 +1,6 @@ { - "name": "Pierre Dark Vibrant", + "name": "pierre-dark-vibrant", + "displayName": "Pierre Dark Vibrant", "type": "dark", "colors": { "editor.background": "color(display-p3 0.039216 0.039216 0.039216)", diff --git a/themes/pierre-dark.json b/themes/pierre-dark.json index 65f92b2..f074471 100644 --- a/themes/pierre-dark.json +++ b/themes/pierre-dark.json @@ -1,5 +1,6 @@ { - "name": "Pierre Dark", + "name": "pierre-dark", + "displayName": "Pierre Dark", "type": "dark", "colors": { "editor.background": "#0a0a0a", diff --git a/themes/pierre-light-soft.json b/themes/pierre-light-soft.json index ee6a5e8..17464b0 100644 --- a/themes/pierre-light-soft.json +++ b/themes/pierre-light-soft.json @@ -1,5 +1,6 @@ { - "name": "Pierre Light Soft", + "name": "pierre-light-soft", + "displayName": "Pierre Light Soft", "type": "light", "colors": { "editor.background": "#ffffff", diff --git a/themes/pierre-light-vibrant.json b/themes/pierre-light-vibrant.json index 1e093a0..43375e3 100644 --- a/themes/pierre-light-vibrant.json +++ b/themes/pierre-light-vibrant.json @@ -1,5 +1,6 @@ { - "name": "Pierre Light Vibrant", + "name": "pierre-light-vibrant", + "displayName": "Pierre Light Vibrant", "type": "light", "colors": { "editor.background": "color(display-p3 1.000000 1.000000 1.000000)", diff --git a/themes/pierre-light.json b/themes/pierre-light.json index c22032e..b833798 100644 --- a/themes/pierre-light.json +++ b/themes/pierre-light.json @@ -1,5 +1,6 @@ { - "name": "Pierre Light", + "name": "pierre-light", + "displayName": "Pierre Light", "type": "light", "colors": { "editor.background": "#ffffff",