Initial commit from monorepo
This commit is contained in:
115
scripts/generate-stories.mjs
Normal file
115
scripts/generate-stories.mjs
Normal file
@@ -0,0 +1,115 @@
|
||||
import { promises as fs } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const componentsRoot = path.resolve(process.cwd(), 'app/components')
|
||||
const storyExtensions = ['.stories.ts', '.stories.js', '.stories.tsx', '.stories.jsx']
|
||||
|
||||
const titleCase = (str) =>
|
||||
str
|
||||
.split(/[-_/]+/)
|
||||
.filter(Boolean)
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join('/')
|
||||
|
||||
const toIdentifier = (str) =>
|
||||
str
|
||||
.split(/[^a-zA-Z0-9]+/)
|
||||
.filter(Boolean)
|
||||
.map((part, idx) => {
|
||||
const lower = part.toLowerCase()
|
||||
if (idx === 0) return lower.charAt(0).toUpperCase() + lower.slice(1)
|
||||
return part.charAt(0).toUpperCase() + part.slice(1)
|
||||
})
|
||||
.join('') || 'Component'
|
||||
|
||||
const hasStorySibling = async (dir, baseName) => {
|
||||
const files = await fs.readdir(dir)
|
||||
return files.some((file) => {
|
||||
const match = file.match(/^(.+)\.stories\.(t|j)sx?$/i)
|
||||
if (!match) return false
|
||||
return match[1] === baseName
|
||||
})
|
||||
}
|
||||
|
||||
const walkVueFiles = async (dir) => {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true })
|
||||
const files = []
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...(await walkVueFiles(fullPath)))
|
||||
continue
|
||||
}
|
||||
if (entry.isFile() && entry.name.endsWith('.vue')) {
|
||||
files.push(fullPath)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
const buildStoryContent = (filePath) => {
|
||||
const baseName = path.basename(filePath, '.vue')
|
||||
const importPath = `./${path.basename(filePath)}`
|
||||
const relative = path.relative(componentsRoot, filePath).replace(/\\/g, '/')
|
||||
const title = titleCase(relative.replace('.vue', ''))
|
||||
const componentId = toIdentifier(baseName)
|
||||
|
||||
return `import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
import StoryComponent from '${importPath}'
|
||||
|
||||
const meta: Meta<typeof StoryComponent> = {
|
||||
title: '${title}',
|
||||
component: StoryComponent,
|
||||
render: (args) => ({
|
||||
components: { StoryComponent },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: '<StoryComponent v-bind=\"args\" />'
|
||||
})
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
const ensureDir = async (dir) => {
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
const vueFiles = await walkVueFiles(componentsRoot)
|
||||
const generated = []
|
||||
|
||||
for (const filePath of vueFiles) {
|
||||
const dir = path.dirname(filePath)
|
||||
const baseName = path.basename(filePath, '.vue')
|
||||
const storyExists = await hasStorySibling(dir, baseName)
|
||||
if (storyExists) continue
|
||||
|
||||
const storyPath = path.join(dir, `${baseName}.stories.ts`)
|
||||
await ensureDir(dir)
|
||||
const content = buildStoryContent(filePath)
|
||||
await fs.writeFile(storyPath, content, 'utf8')
|
||||
generated.push(path.relative(process.cwd(), storyPath))
|
||||
}
|
||||
|
||||
if (generated.length === 0) {
|
||||
console.log('No new stories generated. All components already have stories.')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Generated ${generated.length} stories:`)
|
||||
generated.forEach((p) => console.log(`- ${p}`))
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error('Failed to generate stories', err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user