@poveste/plugin-vue 0.13.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/dist/bundled/client/app/MountStory.js +3 -3
  3. package/dist/bundled/client/app/RenderStory.js +15 -9
  4. package/dist/bundled/client/app/Story.js +56 -43
  5. package/dist/bundled/client/app/Variant.js +32 -27
  6. package/dist/bundled/client/app/global-components.js +11 -10
  7. package/dist/bundled/client/app/host.js +2 -2
  8. package/dist/bundled/client/app/util.js +31 -26
  9. package/dist/bundled/client/client.js +4 -4
  10. package/dist/bundled/client/codegen.js +79 -60
  11. package/dist/bundled/client/server/Story.js +9 -7
  12. package/dist/bundled/client/server/Variant.js +1 -0
  13. package/dist/bundled/client/server/run.js +2 -2
  14. package/dist/client/app/MountStory.d.ts +3 -3
  15. package/dist/client/app/RenderStory.d.ts +1 -1
  16. package/dist/client/app/Story.d.ts +10 -7
  17. package/dist/client/app/Variant.d.ts +14 -13
  18. package/dist/client/app/render-context.d.ts +1 -1
  19. package/dist/client/app/util.d.ts +6 -3
  20. package/dist/client/codegen.d.ts +29 -0
  21. package/dist/client/server/Story.d.ts +17 -17
  22. package/dist/client/server/Variant.d.ts +10 -10
  23. package/dist/commands/generate-story.client.js +4 -4
  24. package/dist/commands/generate-story.server.js +2 -2
  25. package/dist/helpers.d.ts +2 -2
  26. package/dist/index.node.js +2 -0
  27. package/dist/util/list-components.d.ts +1 -1
  28. package/dist/util/list-components.js +63 -6
  29. package/dist/util/list-components.spec.d.ts +1 -0
  30. package/dist/util/list-components.spec.js +46 -0
  31. package/package.json +12 -11
  32. package/src/client/__tests__/codegen-attrs.spec.ts +149 -0
  33. package/src/client/app/MountStory.ts +5 -1
  34. package/src/client/app/RenderStory.ts +6 -1
  35. package/src/client/app/RouterLinkStub.ts +1 -1
  36. package/src/client/app/Story.ts +78 -37
  37. package/src/client/app/Variant.ts +26 -23
  38. package/src/client/app/__tests__/preview-variants.spec.ts +76 -0
  39. package/src/client/app/auto-props.ts +2 -2
  40. package/src/client/app/global-components.ts +20 -14
  41. package/src/client/app/host.ts +9 -6
  42. package/src/client/app/util.ts +17 -7
  43. package/src/client/codegen.ts +81 -29
  44. package/src/client/server/Story.ts +10 -4
  45. package/src/client/server/Variant.ts +3 -0
  46. package/src/client/server/run.ts +5 -4
  47. package/src/commands/generate-story.client.ts +4 -4
  48. package/src/commands/generate-story.server.ts +2 -2
  49. package/src/helpers.ts +2 -2
  50. package/src/index.node.ts +2 -0
  51. package/src/util/list-components.spec.ts +54 -0
  52. package/src/util/list-components.ts +68 -6
@@ -1,4 +1,4 @@
1
- import type { ServerRunPayload } from '@poveste/shared'
1
+ import type { ServerRunPayload, ServerStory } from '@poveste/shared'
2
2
  import type { Vue3StorySetupApi, Vue3StorySetupHandler } from '../../helpers.js'
3
3
  import { getSetupHook } from '@poveste/shared'
4
4
  // @ts-expect-error virtual module id
@@ -15,7 +15,7 @@ export async function run({ file, storyData, el }: ServerRunPayload) {
15
15
 
16
16
  const app = createApp({
17
17
  provide: {
18
- addStory(data) {
18
+ addStory(data: ServerStory) {
19
19
  storyData.push(data)
20
20
  },
21
21
  hstStoryFile: file,
@@ -33,8 +33,9 @@ export async function run({ file, storyData, el }: ServerRunPayload) {
33
33
 
34
34
  const setupApi: Vue3StorySetupApi = {
35
35
  app,
36
- story: null,
37
- variant: null,
36
+ // No story or variant exists while collecting.
37
+ story: undefined,
38
+ variant: undefined,
38
39
  addWrapper: () => { /* noop */ },
39
40
  }
40
41
 
@@ -16,13 +16,13 @@ export default {
16
16
  label: 'File name',
17
17
  type: 'text',
18
18
  required: true,
19
- defaultValue: answers => answers.component?.replace(/[^/]+\/([^/]+)\.vue$/, '$1.story.vue'),
19
+ defaultValue: answers => answers['component']?.replace(/[^/]+\/([^/]+)\.vue$/, '$1.story.vue'),
20
20
  },
21
21
  ],
22
22
  clientAction: (params) => {
23
- const index = params.component.lastIndexOf('/')
24
- const dirname = params.component.substring(0, index + 1)
25
- const file = `${dirname}${params.fileName}`
23
+ const index = params['component'].lastIndexOf('/')
24
+ const dirname = params['component'].substring(0, index + 1)
25
+ const file = `${dirname}${params['fileName']}`
26
26
  const storyId = kebabCase(file.toLowerCase())
27
27
  openStory(storyId)
28
28
  },
@@ -9,13 +9,13 @@ export default {
9
9
  icon: 'https://vuejs.org/logo.svg',
10
10
  searchText: 'generate create',
11
11
  async serverAction(params) {
12
- const targetFile = path.join(path.dirname(params.component), params.fileName)
12
+ const targetFile = path.join(path.dirname(params['component']), params['fileName'])
13
13
 
14
14
  if (fs.existsSync(targetFile)) {
15
15
  throw new Error(`File ${targetFile} already exists`)
16
16
  }
17
17
 
18
- const { component, componentName, isTs } = await getComponentInfo(params.component)
18
+ const { component, componentName, isTs } = await getComponentInfo(params['component'])
19
19
 
20
20
  const content = `<script setup${isTs ? ' lang="ts"' : ''}>
21
21
  import ${componentName} from './${component}'
package/src/helpers.ts CHANGED
@@ -3,8 +3,8 @@ import type { App, Component } from 'vue'
3
3
 
4
4
  export interface Vue3StorySetupApi {
5
5
  app: App
6
- story?: Story
7
- variant?: Variant
6
+ story?: Story | undefined
7
+ variant?: Variant | undefined
8
8
  addWrapper: (wrapper: Component) => void
9
9
  }
10
10
 
package/src/index.node.ts CHANGED
@@ -32,6 +32,7 @@ export function HstVue(): Plugin {
32
32
  if ((this.meta as any).poveste?.isCollecting && id.endsWith('.vue')) {
33
33
  return `const _stubComponent = (name) => ['Story','Variant'].some(validName => name.toLowerCase() === validName.toLowerCase()) ? _resolveComponent(name) : ({ render: () => null });${code?.replaceAll('_resolveComponent(', '_stubComponent(') ?? ''}`
34
34
  }
35
+ return undefined
35
36
  },
36
37
  },
37
38
  ],
@@ -57,6 +58,7 @@ export function HstVue(): Plugin {
57
58
  return listComponentFiles(api.payload.search, api.getConfig().storyMatch)
58
59
  }
59
60
  }
61
+ return undefined
60
62
  },
61
63
  }
62
64
  }
@@ -0,0 +1,54 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import { dirname, join } from 'node:path'
4
+ import { afterEach, describe, expect, it } from 'vitest'
5
+ import { listComponentFiles } from './list-components.js'
6
+
7
+ const roots: string[] = []
8
+
9
+ function tree(files: Record<string, string>): string {
10
+ const root = mkdtempSync(join(tmpdir(), 'list-components-'))
11
+ roots.push(root)
12
+ for (const [path, content] of Object.entries(files)) {
13
+ mkdirSync(dirname(join(root, path)), { recursive: true })
14
+ writeFileSync(join(root, path), content)
15
+ }
16
+ return root
17
+ }
18
+
19
+ afterEach(() => {
20
+ for (const root of roots.splice(0)) {
21
+ rmSync(root, { recursive: true, force: true })
22
+ }
23
+ })
24
+
25
+ // Listing honoured .gitignore through globby's `gitignore: true`, and tinyglobby
26
+ // has no such option, so the filtering is ours now (#839).
27
+ describe('listComponentFiles', () => {
28
+ it('leaves out components git ignores, from the root .gitignore and a nested one', async () => {
29
+ const root = tree({
30
+ '.git/HEAD': 'ref: refs/heads/main\n',
31
+ '.gitignore': 'generated/\n',
32
+ 'app/.gitignore': 'scratch/*\n!scratch/Kept.vue\n',
33
+ 'app/src/Button.vue': '',
34
+ 'app/src/generated/Output.vue': '',
35
+ 'app/scratch/Draft.vue': '',
36
+ 'app/scratch/Kept.vue': '',
37
+ })
38
+
39
+ const files = await listComponentFiles('', [], 10, join(root, 'app'))
40
+
41
+ expect(files.sort()).toEqual(['scratch/Kept.vue', 'src/Button.vue'])
42
+ })
43
+
44
+ it('still skips node_modules and the patterns it is given', async () => {
45
+ const root = tree({
46
+ '.git/HEAD': 'ref: refs/heads/main\n',
47
+ 'src/Button.vue': '',
48
+ 'src/Button.story.vue': '',
49
+ 'node_modules/pkg/Dep.vue': '',
50
+ })
51
+
52
+ expect(await listComponentFiles('', ['**/*.story.vue'], 10, root)).toEqual(['src/Button.vue'])
53
+ })
54
+ })
@@ -1,13 +1,75 @@
1
- import { globby } from 'globby'
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { dirname, join, relative, resolve, sep } from 'node:path'
3
+ import ignore from 'ignore'
4
+ import { glob } from 'tinyglobby'
2
5
 
3
- export async function listComponentFiles(search = '', ignore: string[] = [], limit = 10) {
4
- let files = await globby('**/*.vue', {
5
- gitignore: true,
6
+ function toPosix(path: string): string {
7
+ return path.split(sep).join('/')
8
+ }
9
+
10
+ /**
11
+ * Whether a file under `cwd` is git-ignored, the way globby's `gitignore: true`
12
+ * decided it: every `.gitignore` from the repository root down, each pattern
13
+ * relative to its own file. tinyglobby reads none of them (#839).
14
+ */
15
+ async function gitIgnoredFilter(cwd: string): Promise<(file: string) => boolean> {
16
+ let root = cwd
17
+ while (!existsSync(join(root, '.git'))) {
18
+ const parent = dirname(root)
19
+ if (parent === root) {
20
+ root = cwd
21
+ break
22
+ }
23
+ root = parent
24
+ }
25
+
26
+ const ancestors: string[] = []
27
+ for (let dir = cwd; dir !== root;) {
28
+ dir = dirname(dir)
29
+ ancestors.unshift(join(dir, '.gitignore'))
30
+ }
31
+ // Shallow files first, so a nested `.gitignore` can override its parent.
32
+ const nested = (await glob('**/.gitignore', { cwd, absolute: true, dot: true, ignore: ['**/node_modules/**', '**/.git/**'] }))
33
+ .sort((a, b) => a.split('/').length - b.split('/').length || a.localeCompare(b))
34
+
35
+ const rules = ignore()
36
+ for (const file of [...ancestors, ...nested]) {
37
+ if (!existsSync(file)) {
38
+ continue
39
+ }
40
+ const base = toPosix(relative(root, dirname(file)))
41
+ for (const raw of readFileSync(file, 'utf8').split(/\r?\n/)) {
42
+ const line = raw.trimEnd()
43
+ if (!line || line.startsWith('#')) {
44
+ continue
45
+ }
46
+ const negated = line.startsWith('!')
47
+ let pattern = negated ? line.slice(1) : line
48
+ if (base) {
49
+ // A pattern with a slash before its end is anchored to its own directory;
50
+ // one without matches at any depth below it.
51
+ const anchored = pattern.replace(/\/$/, '').includes('/')
52
+ pattern = anchored ? `${base}/${pattern.replace(/^\//, '')}` : `${base}/**/${pattern}`
53
+ }
54
+ rules.add(negated ? `!${pattern}` : pattern)
55
+ }
56
+ }
57
+
58
+ return (file) => {
59
+ const path = toPosix(relative(root, resolve(cwd, file)))
60
+ return !path.startsWith('..') && rules.ignores(path)
61
+ }
62
+ }
63
+
64
+ export async function listComponentFiles(search = '', ignoredPatterns: string[] = [], limit = 10, cwd = process.cwd()) {
65
+ const isIgnored = await gitIgnoredFilter(cwd)
66
+ let files = (await glob('**/*.vue', {
67
+ cwd,
6
68
  ignore: [
7
69
  'node_modules',
8
- ...ignore,
70
+ ...ignoredPatterns,
9
71
  ],
10
- })
72
+ })).filter(file => !isIgnored(file))
11
73
  if (search) {
12
74
  const searchText = search.toLowerCase()
13
75
  files = files.filter(file => file.toLowerCase().includes(searchText))