Merge pull request #1946 from divyam234/fix/theme-runtime-symlink-themes
fix: load symlinked custom themes
This commit is contained in:
@@ -116,7 +116,7 @@ export const createThemeRuntime = (dependencies) => {
|
||||
const seen = new Set();
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
||||
if (!entry.name.toLowerCase().endsWith('.json')) continue;
|
||||
|
||||
const filePath = path.join(themesDir, entry.name);
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { createThemeRuntime } from './theme-runtime.js';
|
||||
|
||||
const validTheme = (id = 'custom-theme') => ({
|
||||
metadata: {
|
||||
id,
|
||||
name: 'Custom Theme',
|
||||
variant: 'dark',
|
||||
},
|
||||
colors: {
|
||||
primary: {
|
||||
base: '#ffffff',
|
||||
foreground: '#000000',
|
||||
},
|
||||
surface: {
|
||||
background: '#000000',
|
||||
foreground: '#ffffff',
|
||||
muted: '#111111',
|
||||
mutedForeground: '#eeeeee',
|
||||
elevated: '#222222',
|
||||
elevatedForeground: '#dddddd',
|
||||
subtle: '#333333',
|
||||
},
|
||||
interactive: {
|
||||
border: '#444444',
|
||||
selection: '#555555',
|
||||
selectionForeground: '#ffffff',
|
||||
focusRing: '#666666',
|
||||
hover: '#777777',
|
||||
},
|
||||
status: {
|
||||
error: '#ff0000',
|
||||
errorForeground: '#ffffff',
|
||||
errorBackground: '#330000',
|
||||
errorBorder: '#660000',
|
||||
warning: '#ffaa00',
|
||||
warningForeground: '#000000',
|
||||
warningBackground: '#332200',
|
||||
warningBorder: '#664400',
|
||||
success: '#00ff00',
|
||||
successForeground: '#000000',
|
||||
successBackground: '#003300',
|
||||
successBorder: '#006600',
|
||||
info: '#0000ff',
|
||||
infoForeground: '#ffffff',
|
||||
infoBackground: '#000033',
|
||||
infoBorder: '#000066',
|
||||
},
|
||||
syntax: {
|
||||
base: {
|
||||
background: '#000000',
|
||||
foreground: '#ffffff',
|
||||
keyword: '#ff00ff',
|
||||
string: '#00ff00',
|
||||
number: '#ffaa00',
|
||||
function: '#00ffff',
|
||||
variable: '#ffffff',
|
||||
type: '#ffff00',
|
||||
comment: '#888888',
|
||||
operator: '#ffffff',
|
||||
},
|
||||
highlights: {
|
||||
diffAdded: '#003300',
|
||||
diffRemoved: '#330000',
|
||||
lineNumber: '#888888',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const fileEntry = (name, type = 'file') => ({
|
||||
name,
|
||||
isFile: () => type === 'file',
|
||||
isDirectory: () => type === 'directory',
|
||||
isSymbolicLink: () => type === 'symlink',
|
||||
});
|
||||
|
||||
const createTestRuntime = ({ entries, files, stats }) => createThemeRuntime({
|
||||
fsPromises: {
|
||||
readdir: async () => entries,
|
||||
stat: async (filePath) => stats[filePath],
|
||||
readFile: async (filePath) => files[filePath],
|
||||
},
|
||||
path: { join: (...parts) => parts.join('/') },
|
||||
themesDir: '/themes',
|
||||
maxThemeJsonBytes: 512 * 1024,
|
||||
logger: { warn: () => {} },
|
||||
});
|
||||
|
||||
describe('theme runtime', () => {
|
||||
describe('readCustomThemesFromDisk', () => {
|
||||
it('loads valid theme files', async () => {
|
||||
const runtime = createTestRuntime({
|
||||
entries: [fileEntry('direct.json')],
|
||||
files: { '/themes/direct.json': JSON.stringify(validTheme('direct-theme')) },
|
||||
stats: { '/themes/direct.json': { isFile: () => true, size: 1024 } },
|
||||
});
|
||||
|
||||
const themes = await runtime.readCustomThemesFromDisk();
|
||||
|
||||
expect(themes.map((theme) => theme.metadata.id)).toEqual(['direct-theme']);
|
||||
});
|
||||
|
||||
it('loads JSON themes whose directory entry is a symbolic link', async () => {
|
||||
const runtime = createTestRuntime({
|
||||
entries: [fileEntry('linked.json', 'symlink')],
|
||||
files: { '/themes/linked.json': JSON.stringify(validTheme('linked-theme')) },
|
||||
stats: { '/themes/linked.json': { isFile: () => true, size: 1024 } },
|
||||
});
|
||||
|
||||
const themes = await runtime.readCustomThemesFromDisk();
|
||||
|
||||
expect(themes.map((theme) => theme.metadata.id)).toEqual(['linked-theme']);
|
||||
});
|
||||
|
||||
it('skips JSON directories after stat resolution', async () => {
|
||||
const runtime = createTestRuntime({
|
||||
entries: [fileEntry('directory.json', 'directory')],
|
||||
files: { '/themes/directory.json': JSON.stringify(validTheme('directory-theme')) },
|
||||
stats: { '/themes/directory.json': { isFile: () => false, size: 1024 } },
|
||||
});
|
||||
|
||||
const themes = await runtime.readCustomThemesFromDisk();
|
||||
|
||||
expect(themes).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user