Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion code/frameworks/react-vite/src/plugins/react-docgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export async function reactDocgen({
const cwd = process.cwd();
const filter = createFilter(include, exclude);

const tsconfigPath = find.up('tsconfig.json', { cwd, last: getProjectRoot() });
const projectRoot = getProjectRoot();
const tsconfigPath =
find.up('tsconfig.json', { cwd, last: projectRoot }) ??
find.up('tsconfig.base.json', { cwd, last: projectRoot }) ??
find.up('tsconfig.app.json', { cwd, last: projectRoot });
const tsconfig = TsconfigPaths.loadConfig(tsconfigPath);

let matchPath: TsconfigPaths.MatchPath | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ export default async function reactDocgenLoader(
const { debug = false } = options;

if (!tsconfigPathsInitialized) {
const tsconfigPath = find.up('tsconfig.json', { cwd: process.cwd(), last: getProjectRoot() });
const projectRoot = getProjectRoot();
const cwd = process.cwd();
const tsconfigPath =
find.up('tsconfig.json', { cwd, last: projectRoot }) ??
find.up('tsconfig.base.json', { cwd, last: projectRoot }) ??
find.up('tsconfig.app.json', { cwd, last: projectRoot });
const tsconfig = TsconfigPaths.loadConfig(tsconfigPath);

if (tsconfig.resultType === 'success') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from 'react-docgen-typescript';
import type ts from 'typescript';

import { logger } from 'storybook/internal/node-logger';

import { asyncCache, findTsconfigPath } from './utils';

export type ComponentDocWithExportName = ComponentDoc & { exportName: string };
Expand Down Expand Up @@ -204,6 +206,12 @@ async function getParser(userOptions?: ParserOptions) {
);
cachedCompilerOptions = { ...parsed.options, noErrorTruncation: true };
cachedFileNames = parsed.fileNames;
} else {
logger.warn(
'No tsconfig.json (or tsconfig.base.json / tsconfig.app.json) found. ' +
'TypeScript component props will not be documented by react-docgen-typescript. ' +
'Create a tsconfig.json in your project root to enable automatic controls.'
);
}

const program = typescript.createProgram(
Expand Down
86 changes: 84 additions & 2 deletions code/renderers/react/src/componentManifest/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { expect, test, vi } from 'vitest';
import { beforeEach, describe, expect, test, vi } from 'vitest';

import { asyncCache, cached, groupBy, invalidateCache, invariant } from './utils';
vi.mock('empathic/find', { spy: true });
vi.mock('storybook/internal/common', { spy: true });
vi.mock('storybook/internal/node-logger', { spy: true });

import { getProjectRoot } from 'storybook/internal/common';
import { logger } from 'storybook/internal/node-logger';

import * as find from 'empathic/find';

import { asyncCache, cached, findTsconfigPath, groupBy, invalidateCache, invariant } from './utils';

// Helpers
const calls = () => {
Expand Down Expand Up @@ -177,3 +186,76 @@ test('invalidateCache clears async module-level memo store', async () => {
expect(await m(2)).toBe(4);
expect(c.count()).toBe(2);
});

describe('findTsconfigPath', () => {
beforeEach(() => {
invalidateCache();
vi.mocked(getProjectRoot).mockReturnValue('/project-root');
});

test('returns tsconfig.json when found', () => {
vi.mocked(find.up).mockImplementation((name) => {
if (name === 'tsconfig.json') {
return '/project-root/tsconfig.json';
}
return undefined;
});

const result = findTsconfigPath('/project-root');

expect(result).toBe('/project-root/tsconfig.json');
expect(logger.warn).not.toHaveBeenCalled();
});

test('falls back to tsconfig.base.json when tsconfig.json is not found', () => {
vi.mocked(find.up).mockImplementation((name) => {
if (name === 'tsconfig.base.json') {
return '/project-root/tsconfig.base.json';
}
return undefined;
});

const result = findTsconfigPath('/project-root');

expect(result).toBe('/project-root/tsconfig.base.json');
});

test('falls back to tsconfig.app.json when neither tsconfig.json nor tsconfig.base.json is found', () => {
vi.mocked(find.up).mockImplementation((name) => {
if (name === 'tsconfig.app.json') {
return '/project-root/tsconfig.app.json';
}
return undefined;
});

const result = findTsconfigPath('/project-root');

expect(result).toBe('/project-root/tsconfig.app.json');
});

test('returns undefined when no tsconfig variant is found', () => {
vi.mocked(find.up).mockReturnValue(undefined);

const result = findTsconfigPath('/project-root');

expect(result).toBeUndefined();
expect(logger.warn).not.toHaveBeenCalled();
});

test('prefers tsconfig.json over fallback variants', () => {
vi.mocked(find.up).mockImplementation((name) => {
if (name === 'tsconfig.json') {
return '/project-root/tsconfig.json';
}
if (name === 'tsconfig.base.json') {
return '/project-root/tsconfig.base.json';
}
return undefined;
});
Comment thread
valentinpalkovic marked this conversation as resolved.

const result = findTsconfigPath('/project-root');

expect(result).toBe('/project-root/tsconfig.json');
expect(logger.warn).not.toHaveBeenCalled();
});
});
18 changes: 17 additions & 1 deletion code/renderers/react/src/componentManifest/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,25 @@ export const cachedResolveImport: typeof resolveImport = cached(resolveImport, {
name: 'resolveImport',
}) as typeof resolveImport;

/**
* Tsconfig filenames to try, in order. Monorepo setups (e.g. Nx) often keep only
* `tsconfig.base.json` at the repository root, so we fall back to common alternatives
* when `tsconfig.json` is not found.
*/
const TSCONFIG_CANDIDATES = ['tsconfig.json', 'tsconfig.base.json', 'tsconfig.app.json'] as const;

export const findTsconfigPath = cached(
(cwd: string): string | undefined => {
return find.up('tsconfig.json', { cwd, last: getProjectRoot() });
const projectRoot = getProjectRoot();

for (const candidate of TSCONFIG_CANDIDATES) {
const found = find.up(candidate, { cwd, last: projectRoot });
if (found) {
return found;
}
}

return undefined;
},
{ name: 'findTsconfigPath' }
);
Loading