diff --git a/.bun-version b/.bun-version deleted file mode 100644 index 4e036596e..000000000 --- a/.bun-version +++ /dev/null @@ -1 +0,0 @@ -1.1.19 diff --git a/.github/workflows/ci-bun.yml b/.github/workflows/ci-bun.yml index 16e82ded9..d96552d0a 100644 --- a/.github/workflows/ci-bun.yml +++ b/.github/workflows/ci-bun.yml @@ -22,8 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - with: - bun-version-file: '.bun-version' + - run: bun upgrade --canary - name: Install dependencies run: bun install --ignore-scripts --frozen-lockfile - name: Run formatter, linter, import sorter diff --git a/packages/knip/fixtures/plugins/ladle/.ladle/components.tsx b/packages/knip/fixtures/plugins/ladle/.ladle/components.tsx new file mode 100644 index 000000000..3bd6ef1cb --- /dev/null +++ b/packages/knip/fixtures/plugins/ladle/.ladle/components.tsx @@ -0,0 +1,5 @@ +import { type GlobalProvider } from "@ladle/react"; +import React from "react"; + +// For details see https://ladle.dev/docs/providers +export const Provider: GlobalProvider = ({ children }) =>
{children}
; diff --git a/packages/knip/fixtures/plugins/ladle/.ladle/config.mjs b/packages/knip/fixtures/plugins/ladle/.ladle/config.mjs new file mode 100644 index 000000000..b1f4d496a --- /dev/null +++ b/packages/knip/fixtures/plugins/ladle/.ladle/config.mjs @@ -0,0 +1,6 @@ +// For details see https://ladle.dev/docs/config +/** @type {import('@ladle/react').UserConfig} */ +export default { + stories: "app/**/*.stories.{tsx,mdx}", + viteConfig: "./.ladle/vite.config.ts", +}; diff --git a/packages/knip/fixtures/plugins/ladle/.ladle/vite.config.ts b/packages/knip/fixtures/plugins/ladle/.ladle/vite.config.ts new file mode 100644 index 000000000..6e16a4c90 --- /dev/null +++ b/packages/knip/fixtures/plugins/ladle/.ladle/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + server: { + open: false, + }, +}); diff --git a/packages/knip/fixtures/plugins/ladle/app/basic.stories.tsx b/packages/knip/fixtures/plugins/ladle/app/basic.stories.tsx new file mode 100644 index 000000000..f01781339 --- /dev/null +++ b/packages/knip/fixtures/plugins/ladle/app/basic.stories.tsx @@ -0,0 +1,39 @@ +export const Hello = () =>

Simple Story

; + +export const Responsive = () => { + return ( + <> +
+ Header +
+ + + ); +}; +Responsive.meta = { + width: 'xsmall', +}; + diff --git a/packages/knip/fixtures/plugins/ladle/app/control.stories.tsx b/packages/knip/fixtures/plugins/ladle/app/control.stories.tsx new file mode 100644 index 000000000..d0e9b9bc8 --- /dev/null +++ b/packages/knip/fixtures/plugins/ladle/app/control.stories.tsx @@ -0,0 +1,37 @@ +import type { Story } from "@ladle/react"; + +export const Controls: Story<{ + label: string; + disabled: boolean; + count: number; + colors: string[]; + variant: string; + size: string; +}> = ({ count, disabled, label, colors, variant, size }) => ( + <> +

Count: {count}

+

Disabled: {disabled ? "yes" : "no"}

+

Label: {label}

+

Colors: {colors.join(",")}

+

Variant: {variant}

+

Size: {size}

+ +); + +Controls.args = { + label: "Hello world", + disabled: false, + count: 2, + colors: ["Red", "Blue"], +}; +Controls.argTypes = { + variant: { + options: ["primary", "secondary"], + control: { type: "radio" }, + defaultValue: "primary", + }, + size: { + options: ["small", "medium", "big", "huuuuge"], + control: { type: "select" }, + }, +}; diff --git a/packages/knip/fixtures/plugins/ladle/package.json b/packages/knip/fixtures/plugins/ladle/package.json new file mode 100644 index 000000000..500ef6a74 --- /dev/null +++ b/packages/knip/fixtures/plugins/ladle/package.json @@ -0,0 +1,19 @@ +{ + "name": "@fixtures/ladle", + "type": "module", + "scripts": { + "dev": "ladle serve --port 4123", + "build": "ladle build" + }, + "dependencies": { + "@ladle/react": "*", + "react": "*", + "react-dom": "*" + }, + "devDependencies": { + "@types/node": "*", + "@types/react": "*", + "@types/react-dom": "*", + "typescript": "*" + } +} diff --git a/packages/knip/fixtures/plugins/ladle/tsconfig.json b/packages/knip/fixtures/plugins/ladle/tsconfig.json new file mode 100644 index 000000000..621557798 --- /dev/null +++ b/packages/knip/fixtures/plugins/ladle/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/packages/knip/schema.json b/packages/knip/schema.json index a827d44a7..22eeb2127 100644 --- a/packages/knip/schema.json +++ b/packages/knip/schema.json @@ -356,6 +356,10 @@ "title": "Jest plugin configuration (https://knip.dev/reference/plugins/jest)", "$ref": "#/definitions/plugin" }, + "ladle": { + "title": "ladle plugin configuration (https://knip.dev/reference/plugins/ladle)", + "$ref": "#/definitions/plugin" + }, "lefthook": { "title": "lefthook plugin configuration (https://knip.dev/reference/plugins/lefthook)", "$ref": "#/definitions/plugin" diff --git a/packages/knip/src/ConfigurationValidator.ts b/packages/knip/src/ConfigurationValidator.ts index 95956a060..c3f4500d6 100644 --- a/packages/knip/src/ConfigurationValidator.ts +++ b/packages/knip/src/ConfigurationValidator.ts @@ -96,6 +96,7 @@ const pluginsSchema = z.object({ 'graphql-codegen': pluginSchema, husky: pluginSchema, jest: pluginSchema, + ladle: pluginSchema, lefthook: pluginSchema, 'lint-staged': pluginSchema, linthtml: pluginSchema, diff --git a/packages/knip/src/binaries/resolvers/bun.ts b/packages/knip/src/binaries/resolvers/bun.ts index c171d3b54..d088938f9 100644 --- a/packages/knip/src/binaries/resolvers/bun.ts +++ b/packages/knip/src/binaries/resolvers/bun.ts @@ -2,7 +2,7 @@ import parseArgs from 'minimist'; import type { Resolver } from '../types.js'; import { tryResolveFilePath } from '../util.js'; -const commands = ['add', 'create', 'init', 'install', 'link', 'pm', 'remove', 'run', 'test', 'update']; +const commands = ['add', 'create', 'init', 'install', 'link', 'pm', 'remove', 'run', 'test', 'update', 'upgrade']; export const resolve: Resolver = (_binary, args, { manifestScriptNames, cwd, fromArgs }) => { const parsed = parseArgs(args); diff --git a/packages/knip/src/plugins/index.ts b/packages/knip/src/plugins/index.ts index 32ac5129a..b62cb4ff8 100644 --- a/packages/knip/src/plugins/index.ts +++ b/packages/knip/src/plugins/index.ts @@ -17,6 +17,7 @@ export { default as githubActions } from './github-actions/index.js'; export { default as graphqlCodegen } from './graphql-codegen/index.js'; export { default as husky } from './husky/index.js'; export { default as jest } from './jest/index.js'; +export { default as ladle } from './ladle/index.js'; export { default as lefthook } from './lefthook/index.js'; export { default as linthtml } from './linthtml/index.js'; export { default as lintStaged } from './lint-staged/index.js'; diff --git a/packages/knip/src/plugins/ladle/index.ts b/packages/knip/src/plugins/ladle/index.ts new file mode 100644 index 000000000..b260833ec --- /dev/null +++ b/packages/knip/src/plugins/ladle/index.ts @@ -0,0 +1,50 @@ +import type { EnablerPatterns } from '#p/types/config.js'; +import type { IsPluginEnabled, Plugin, ResolveConfig, ResolveEntryPaths } from '#p/types/plugins.js'; +import { toAbsolute } from '#p/util/path.js'; +import { hasDependency, load } from '#p/util/plugin.js'; +import { toEntryPattern } from '../../util/protocols.js'; +import { resolveConfig as resolveVitestConfig } from '../vitest/index.js'; +import type { LadleConfig } from './types.js'; + +// https://ladle.dev/docs/config + +const title = 'Ladle'; + +const enablers: EnablerPatterns = ['@ladle/react']; + +const isEnabled: IsPluginEnabled = ({ dependencies }) => hasDependency(dependencies, enablers); + +const config = ['.ladle/config.{mjs,js,ts}']; + +const stories = ['src/**/*.stories.{js,jsx,ts,tsx,mdx}']; +const restEntry = ['.ladle/components.{js,jsx,ts,tsx}']; +const entry = [...restEntry, ...stories]; + +const project = ['.ladle/**/*.{js,jsx,ts,tsx}']; + +const resolveEntryPaths: ResolveEntryPaths = (localConfig, options) => { + const localStories = typeof localConfig.stories === 'string' ? [localConfig.stories] : localConfig.stories; + const viteConfig = localConfig.viteConfig ? [toAbsolute(localConfig.viteConfig, options.cwd)] : []; + const patterns = [...restEntry, ...(localStories ?? stories), ...viteConfig]; + return patterns.map(toEntryPattern); +}; + +const resolveConfig: ResolveConfig = async (localConfig, options) => { + if (localConfig.viteConfig) { + const viteConfigPath = toAbsolute(localConfig.viteConfig, options.cwd); + const viteConfig = await load(viteConfigPath); + return resolveVitestConfig(viteConfig, options); + } + return []; +}; + +export default { + title, + enablers, + isEnabled, + config, + entry, + project, + resolveEntryPaths, + resolveConfig, +} satisfies Plugin; diff --git a/packages/knip/src/plugins/ladle/types.ts b/packages/knip/src/plugins/ladle/types.ts new file mode 100644 index 000000000..7e2f2487d --- /dev/null +++ b/packages/knip/src/plugins/ladle/types.ts @@ -0,0 +1,4 @@ +export type LadleConfig = { + stories?: string | string[]; + viteConfig?: string; +}; diff --git a/packages/knip/test/plugins/ladle.test.ts b/packages/knip/test/plugins/ladle.test.ts new file mode 100644 index 000000000..b5dc8d842 --- /dev/null +++ b/packages/knip/test/plugins/ladle.test.ts @@ -0,0 +1,30 @@ +import { test } from 'bun:test'; +import assert from 'node:assert/strict'; +import { main } from '../../src/index.js'; +import { resolve } from '../../src/util/path.js'; +import baseArguments from '../helpers/baseArguments.js'; +import baseCounters from '../helpers/baseCounters.js'; + +const cwd = resolve('fixtures/plugins/ladle'); + +test('Find dependencies with the ladle plugin', async () => { + const { issues, counters } = await main({ + ...baseArguments, + cwd, + }); + + assert(issues.dependencies['package.json']['react-dom']); + assert(issues.devDependencies['package.json']['@types/react-dom']); + assert(issues.binaries['package.json']['ladle']); + assert(issues.unlisted['.ladle/vite.config.ts']['vite']); + + assert.deepEqual(counters, { + ...baseCounters, + binaries: 1, + dependencies: 1, + devDependencies: 1, + unlisted: 1, + processed: 5, + total: 5, + }); +});