Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controls: Add Channels API to search for files in the project root #26726

Merged
merged 42 commits into from
Apr 9, 2024

Conversation

valentinpalkovic
Copy link
Contributor

@valentinpalkovic valentinpalkovic commented Apr 3, 2024

Closes #26660

What I did

Experimental Channel API

I have added a new server channel preset to @storybook/addon-controls to search for files in the project root.

The search result will return a list of objects containing the full file path (relative to the project root) and a list of exports inside the file.

export function initFileSearchChannel(channel: Channel, options: Options) {
  channel.on(FILE_COMPONENT_SEARCH, async (data: Data) => {
    try {
      ...
      const files = await searchFiles(searchQuery, projectRoot, rendererName);

      channel.emit(FILE_COMPONENT_SEARCH_RESULT, { success: true, result: { searchQuery, files }, error: null });
    } catch (e) {
      channel.emit(FILE_COMPONENT_SEARCH_RESULT, { success: false, result: null, error: ...,
      });
    }
  });

  return channel;
}

Parser

I have created a generic parser to parse files using the packages es-module-lexer and cjs-module-lexer. These packages are pretty fast compared to, for example, Acorn (20x faster). We are already using these packages for the export-order-plugin.

The generic parser is currently enough to analyze files with React components. Indeed, the parser doesn't interpret all exports separately to determine whether they are React components. This is okay for now.

The architecture of the parser is built in an easily extensible way so that we can add parsers for vue, svelte, ... very quickly in the future.

export function getParser(renderer: SupportedRenderers | null): Parser {
  switch (renderer) {
    default:
      return new GenericParser();
  }
}

File search

The file search takes a .gitignore file in the project's root into account and will not search for ignored files. The node_modules folder is also always ignored, even if a .gitignore file doesn't exist. I am using globby to search for files because it has a built-in .gitignore mechanic.

The file search supports two modes:

  1. In the first mode, the user can search for a specific string that does NOT contain glob chars like *, +(...), and so on. In this particular case, the search query is transformed to two glob searches like this: [**/*${searchQuery}*, **/*${searchQuery}*/**];. This will match folders and files, which will partially match with the search query (see unit tests).
  2. The user can provide a glob pattern, which will be taken as is.

The search query is then passed to globby. The supported glob patterns are the ones from minimatch. Only files in the project root are considered.

export async function searchFiles(
  searchQuery: string,
  cwd: string,
  renderer: SupportedRenderers | null
): Promise<SearchResult> {
  const globbedSearchQuery = hasGlobChars ? searchQuery : `**/*${searchQuery}**`;
  const entries = await globby(globbedSearchQuery, {...});

  const files = entries.map(async (entry) => {
    const parser = getParser(renderer);
    const content = fs.readFileSync(path.join(cwd, entry.path), 'utf-8');

    try {
      const info = await parser.parse(content);

      return {
        filepath: entry.path,
        exportedComponents: info.exports,
      };
    } catch (e) {
      return null;
    }
  });
  return (await Promise.all(files)).filter(isNotNull);
}

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

  1. Run a sandbox for template, e.g. yarn task --task sandbox --start-from auto --template react-vite/default-ts
  2. Change src/stories/Button.stories.ts and adjust the meta section like this:
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
import { addons } from '@storybook/preview-api';

const channel = addons.getChannel();

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
  title: 'Example/Button',
  component: Button,
  parameters: {
    // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
    layout: 'centered',
  },
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
  tags: ['autodocs'],
  // More on argTypes: https://storybook.js.org/docs/api/argtypes
  argTypes: {
    backgroundColor: { control: 'color' },
  },
  // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
  args: { onClick: () => {
    channel.emit('fileComponentSearch', {
      searchQuery: 'code/addons',
    })
  } },
} satisfies Meta<typeof Button>;
  1. Click the button and see whether it triggers an event, and the result event is sent to the browser.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=<PR_NUMBER>

Copy link

nx-cloud bot commented Apr 3, 2024

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 4d055fb. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this CI Pipeline Execution


✅ Successfully ran 1 target

Sent with 💌 from NxCloud.

@valentinpalkovic valentinpalkovic force-pushed the valentin/add-file-search-api branch 3 times, most recently from 06844d3 to 8a17339 Compare April 4, 2024 16:25
@valentinpalkovic valentinpalkovic marked this pull request as ready for review April 4, 2024 16:25
Copy link
Member

@ghengeveld ghengeveld left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't judge whether this is functionally sound, but code-wise seems mostly okay. Please do consider the security implications of accepting user-input for file system operations though.

code/.eslintignore Outdated Show resolved Hide resolved
code/addons/controls/package.json Outdated Show resolved Hide resolved
code/addons/controls/src/preset.ts Outdated Show resolved Hide resolved
code/addons/controls/src/preset.ts Outdated Show resolved Hide resolved
code/addons/controls/src/utils/parser/generic-parser.ts Outdated Show resolved Hide resolved
code/addons/controls/src/utils/parser/generic-parser.ts Outdated Show resolved Hide resolved
code/addons/controls/src/utils/parser/index.ts Outdated Show resolved Hide resolved
code/addons/controls/src/utils/ts-utils.ts Outdated Show resolved Hide resolved
code/lib/core-common/src/utils/normalize-path.ts Outdated Show resolved Hide resolved
@valentinpalkovic valentinpalkovic merged commit 945843e into next Apr 9, 2024
61 checks passed
@valentinpalkovic valentinpalkovic deleted the valentin/add-file-search-api branch April 9, 2024 17:36
@github-actions github-actions bot mentioned this pull request Apr 9, 2024
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Project SfC]: Provide a server channel API to get a list of files on the filesystem
2 participants