diff --git a/src/components/examples/ExampleWorkbench.client.tsx b/src/components/examples/ExampleWorkbench.client.tsx index 8737cbaae..9e6124390 100644 --- a/src/components/examples/ExampleWorkbench.client.tsx +++ b/src/components/examples/ExampleWorkbench.client.tsx @@ -809,6 +809,7 @@ export function ExampleWorkbench({ title: definition.title, description: definition.description, initialFile: activePath, + hiddenFiles: definition.hiddenFiles, runtime: definition.runtime, workspace, }), @@ -822,7 +823,9 @@ export function ExampleWorkbench({ } } - const filePaths = Object.keys(workspace.files).sort() + const filePaths = Object.keys(workspace.files) + .filter((path) => !definition.hiddenFiles?.includes(path)) + .sort() const fileTree = React.useMemo(() => createFileTree(filePaths), [filePaths]) const activeSource = workspace.files[activePath] ?? '' const statusLabel = getStatusLabel(status) @@ -1389,9 +1392,12 @@ function getInitialFile( workspace: ExampleWorkspace, ) { return definition.initialFile && - workspace.files[definition.initialFile] !== undefined + workspace.files[definition.initialFile] !== undefined && + !definition.hiddenFiles?.includes(definition.initialFile) ? definition.initialFile - : workspace.entry + : Object.keys(workspace.files).find( + (path) => !definition.hiddenFiles?.includes(path), + ) || workspace.entry } function readTheme() { diff --git a/src/utils/charts-catalog-example.ts b/src/utils/charts-catalog-example.ts index 627bee248..e05182f61 100644 --- a/src/utils/charts-catalog-example.ts +++ b/src/utils/charts-catalog-example.ts @@ -86,6 +86,7 @@ export function createChartsCatalogExampleDefinition({ title, ...(description === undefined ? {} : { description }), initialFile, + hiddenFiles: [generatedEntryPath, generatedDocumentPath], workspace: createExampleWorkspace({ entry: generatedEntryPath, files: workspaceFiles, diff --git a/src/utils/example-project.ts b/src/utils/example-project.ts index ecafe856a..878c72913 100644 --- a/src/utils/example-project.ts +++ b/src/utils/example-project.ts @@ -14,6 +14,7 @@ export type SharedExampleProject = { title: string description: string initialFile?: string + hiddenFiles?: ReadonlyArray runtime?: ExampleRuntime workspace: ExampleWorkspace } @@ -22,12 +23,14 @@ export function createSharedExampleProject({ title, description = '', initialFile, + hiddenFiles, runtime, workspace, }: { title: string description?: string initialFile?: string + hiddenFiles?: ReadonlyArray runtime?: ExampleRuntime workspace: ExampleWorkspace }): SharedExampleProject { @@ -36,6 +39,7 @@ export function createSharedExampleProject({ title, description, ...(initialFile ? { initialFile } : {}), + ...(hiddenFiles?.length ? { hiddenFiles: [...hiddenFiles] } : {}), ...(runtime ? { runtime } : {}), workspace, } @@ -47,6 +51,9 @@ export function serializeSharedExampleProject(project: SharedExampleProject) { title: project.title, description: project.description, ...(project.initialFile ? { initialFile: project.initialFile } : {}), + ...(project.hiddenFiles?.length + ? { hiddenFiles: project.hiddenFiles } + : {}), ...(project.runtime ? { runtime: project.runtime } : {}), workspace: JSON.parse(serializeExampleWorkspace(project.workspace)), }) @@ -62,6 +69,7 @@ export function parseSharedExampleProject( 'title', 'description', 'initialFile', + 'hiddenFiles', 'runtime', 'workspace', ]) || @@ -71,6 +79,12 @@ export function parseSharedExampleProject( (value.initialFile !== undefined && (typeof value.initialFile !== 'string' || !isCanonicalExamplePath(value.initialFile))) || + (value.hiddenFiles !== undefined && + (!Array.isArray(value.hiddenFiles) || + !value.hiddenFiles.every( + (path) => typeof path === 'string' && isCanonicalExamplePath(path), + ) || + new Set(value.hiddenFiles).size !== value.hiddenFiles.length)) || (value.runtime !== undefined && !isExampleRuntime(value.runtime)) ) { throw new Error('Invalid shared example project') @@ -83,11 +97,20 @@ export function parseSharedExampleProject( ) { throw new Error('Invalid shared example project') } + if ( + value.hiddenFiles?.some( + (path) => + workspace.files[path] === undefined || path === value.initialFile, + ) + ) { + throw new Error('Invalid shared example project') + } return createSharedExampleProject({ title: value.title, description: value.description, initialFile: value.initialFile, + hiddenFiles: value.hiddenFiles, runtime: value.runtime, workspace, }) @@ -102,6 +125,9 @@ export function sharedProjectToExampleDefinition( title: project.title, ...(project.description ? { description: project.description } : {}), ...(project.initialFile ? { initialFile: project.initialFile } : {}), + ...(project.hiddenFiles?.length + ? { hiddenFiles: project.hiddenFiles } + : {}), ...(project.runtime ? { runtime: project.runtime } : {}), workspace: project.workspace, } diff --git a/src/utils/example-workspace.ts b/src/utils/example-workspace.ts index a7c6bdaa2..96d208c75 100644 --- a/src/utils/example-workspace.ts +++ b/src/utils/example-workspace.ts @@ -36,6 +36,7 @@ export type ExampleDefinition = { title: string description?: string initialFile?: string + hiddenFiles?: ReadonlyArray runtime?: ExampleRuntime workspace: ExampleWorkspace } diff --git a/tests/charts-catalog-example.test.ts b/tests/charts-catalog-example.test.ts index 45536efa9..d75aa10ae 100644 --- a/tests/charts-catalog-example.test.ts +++ b/tests/charts-catalog-example.test.ts @@ -40,6 +40,7 @@ describe('Charts catalog example workspaces', () => { '/cases/bar-vertical-sorted/example.tsx', ) assert.equal(definition.workspace.entry, '/__catalog.tsx') + assert.deepEqual(definition.hiddenFiles, ['/__catalog.tsx', '/index.html']) assert.equal( definition.workspace.files['/cases/bar-vertical-sorted/example.tsx'], entrySource, diff --git a/tests/example-workspace.test.ts b/tests/example-workspace.test.ts index 679c99781..917a202f7 100644 --- a/tests/example-workspace.test.ts +++ b/tests/example-workspace.test.ts @@ -74,6 +74,7 @@ describe('example workspaces', () => { title: 'Sorted bars', description: 'A browser-only Charts example.', initialFile: '/src/chart.tsx', + hiddenFiles: ['/src/main.tsx'], workspace: { version: 1, entry: '/src/main.tsx', @@ -241,4 +242,34 @@ describe('example workspaces', () => { }), ) }) + + test('rejects invalid hidden shared-project files', () => { + const project = { + version: 1, + title: 'Sorted bars', + description: '', + initialFile: '/src/chart.tsx', + workspace: { + version: 1, + entry: '/src/main.tsx', + files: { + '/src/chart.tsx': '', + '/src/main.tsx': '', + }, + }, + } + + assert.throws(() => + parseSharedExampleProject({ + ...project, + hiddenFiles: ['/src/missing.tsx'], + }), + ) + assert.throws(() => + parseSharedExampleProject({ + ...project, + hiddenFiles: ['/src/chart.tsx'], + }), + ) + }) })