@meistrari/tela-build 1.0.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.
- package/README.md +75 -0
- package/app.config.ts +73 -0
- package/components/tela/animated/animated-calculating-number.vue +16 -0
- package/components/tela/animated/animated-number.mdx +248 -0
- package/components/tela/animated/animated-number.stories.ts +52 -0
- package/components/tela/animated/animated-number.vue +23 -0
- package/components/tela/animated/animated-text.vue +124 -0
- package/components/tela/animated/animated-value.vue +68 -0
- package/components/tela/avatar/avatar.mdx +117 -0
- package/components/tela/avatar/avatar.stories.ts +62 -0
- package/components/tela/avatar/avatar.vue +71 -0
- package/components/tela/avatar/group/avatar-group.stories.ts +78 -0
- package/components/tela/avatar/group/avatar-group.vue +46 -0
- package/components/tela/badge/badge.mdx +154 -0
- package/components/tela/badge/badge.stories.ts +82 -0
- package/components/tela/badge/badge.vue +41 -0
- package/components/tela/button/button.mdx +155 -0
- package/components/tela/button/button.stories.ts +202 -0
- package/components/tela/button/button.vue +107 -0
- package/components/tela/card.vue +30 -0
- package/components/tela/chart/chart-bar.vue +58 -0
- package/components/tela/chat/chat.mdx +268 -0
- package/components/tela/chat/chat.stories.ts +253 -0
- package/components/tela/chat/command/index.vue +41 -0
- package/components/tela/chat/command/mention/index.vue +138 -0
- package/components/tela/chat/index.vue +112 -0
- package/components/tela/chat/pure-text-input/chat-text-input.vue +190 -0
- package/components/tela/chat/text-input/chat-text-input.stories.ts +128 -0
- package/components/tela/chat/text-input/index.vue +217 -0
- package/components/tela/chat/text-message/chat-text-message.stories.ts +138 -0
- package/components/tela/chat/text-message/index.vue +355 -0
- package/components/tela/chat/types.ts +19 -0
- package/components/tela/checkbox/checkbox-card.vue +30 -0
- package/components/tela/checkbox/checkbox.mdx +164 -0
- package/components/tela/checkbox/checkbox.stories.ts +104 -0
- package/components/tela/checkbox/checkbox.vue +43 -0
- package/components/tela/collapsible/Collapsible.vue +15 -0
- package/components/tela/collapsible/CollapsibleContent.vue +59 -0
- package/components/tela/collapsible/CollapsibleTrigger.vue +12 -0
- package/components/tela/collapsible/collapsible.mdx +157 -0
- package/components/tela/collapsible-section/collapsible-section.mdx +180 -0
- package/components/tela/collapsible-section/collapsible-section.stories.ts +53 -0
- package/components/tela/collapsible-section/collapsible-section.vue +51 -0
- package/components/tela/collapsible-section-with-actions.vue +98 -0
- package/components/tela/combobox/combobox-anchor.vue +24 -0
- package/components/tela/combobox/combobox-empty.vue +19 -0
- package/components/tela/combobox/combobox-group.vue +24 -0
- package/components/tela/combobox/combobox-indicator.vue +22 -0
- package/components/tela/combobox/combobox-input.vue +31 -0
- package/components/tela/combobox/combobox-item.vue +28 -0
- package/components/tela/combobox/combobox-label.vue +24 -0
- package/components/tela/combobox/combobox-list.vue +90 -0
- package/components/tela/combobox/combobox-module-selector.vue +366 -0
- package/components/tela/combobox/combobox-root.vue +15 -0
- package/components/tela/combobox/combobox-trigger.vue +12 -0
- package/components/tela/combobox/combobox.mdx +285 -0
- package/components/tela/combobox/combobox.stories.ts +232 -0
- package/components/tela/combobox/combobox.vue +497 -0
- package/components/tela/command/command-dialog.vue +22 -0
- package/components/tela/command/command-empty.vue +25 -0
- package/components/tela/command/command-group.vue +46 -0
- package/components/tela/command/command-input.vue +38 -0
- package/components/tela/command/command-item.vue +78 -0
- package/components/tela/command/command-list.vue +78 -0
- package/components/tela/command/command-separator.vue +23 -0
- package/components/tela/command/command-shortcut.vue +13 -0
- package/components/tela/command/command.vue +88 -0
- package/components/tela/command/dialog-base.vue +15 -0
- package/components/tela/command/dialog-content.vue +50 -0
- package/components/tela/command/utils.ts +15 -0
- package/components/tela/complex-table/complex-table-cell.stories.ts +145 -0
- package/components/tela/complex-table/complex-table-cell.vue +45 -0
- package/components/tela/complex-table/complex-table-header-cell.stories.ts +103 -0
- package/components/tela/complex-table/complex-table-header-cell.vue +48 -0
- package/components/tela/complex-table/complex-table-header.stories.ts +89 -0
- package/components/tela/complex-table/complex-table-header.vue +70 -0
- package/components/tela/complex-table/complex-table-row.vue +199 -0
- package/components/tela/complex-table/complex-table-virtualized.vue +326 -0
- package/components/tela/complex-table/complex-table.stories.ts +358 -0
- package/components/tela/complex-table/complex-table.vue +237 -0
- package/components/tela/complex-table/composables/table-common.ts +93 -0
- package/components/tela/complex-table/composables/table-selection.ts +87 -0
- package/components/tela/complex-table/composables/virtual-scroll.ts +252 -0
- package/components/tela/complex-table/styles/table-shared.css +170 -0
- package/components/tela/complex-table/types.ts +63 -0
- package/components/tela/complex-table/utils.ts +35 -0
- package/components/tela/confirm-button/confirm-button.vue +137 -0
- package/components/tela/confirmation-modal/confirmation-modal.vue +72 -0
- package/components/tela/copy-button.vue +86 -0
- package/components/tela/date-range-picker.vue +221 -0
- package/components/tela/dialog/dialog.mdx +170 -0
- package/components/tela/dialog/dialog.vue +182 -0
- package/components/tela/disabled-area.vue +16 -0
- package/components/tela/disclaimer/disclaimer.mdx +238 -0
- package/components/tela/disclaimer/disclaimer.stories.ts +196 -0
- package/components/tela/disclaimer/disclaimer.vue +125 -0
- package/components/tela/dropdown-menu/DropdownMenu.vue +121 -0
- package/components/tela/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
- package/components/tela/dropdown-menu/DropdownMenuContent.vue +75 -0
- package/components/tela/dropdown-menu/DropdownMenuGroup.vue +12 -0
- package/components/tela/dropdown-menu/DropdownMenuItem.vue +137 -0
- package/components/tela/dropdown-menu/DropdownMenuLabel.vue +26 -0
- package/components/tela/dropdown-menu/DropdownMenuRadioGroup.vue +18 -0
- package/components/tela/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
- package/components/tela/dropdown-menu/DropdownMenuRoot.vue +15 -0
- package/components/tela/dropdown-menu/DropdownMenuSeparator.vue +21 -0
- package/components/tela/dropdown-menu/DropdownMenuShortcut.vue +14 -0
- package/components/tela/dropdown-menu/DropdownMenuSub.vue +18 -0
- package/components/tela/dropdown-menu/DropdownMenuSubContent.vue +30 -0
- package/components/tela/dropdown-menu/DropdownMenuSubTrigger.vue +35 -0
- package/components/tela/dropdown-menu/DropdownMenuTrigger.vue +14 -0
- package/components/tela/dropdown-menu/dropdown-menu.mdx +265 -0
- package/components/tela/dropdown-menu/dropdown-menu.stories.ts +156 -0
- package/components/tela/expandable-input.vue +96 -0
- package/components/tela/file-drop.vue +37 -0
- package/components/tela/file-upload/file-upload.mdx +189 -0
- package/components/tela/file-upload/file-upload.stories.ts +48 -0
- package/components/tela/file-upload/file-upload.vue +205 -0
- package/components/tela/filters/checkbox-filter.stories.ts +218 -0
- package/components/tela/filters/checkbox-filter.vue +165 -0
- package/components/tela/filters/date-filter.stories.ts +258 -0
- package/components/tela/filters/date-filter.vue +200 -0
- package/components/tela/filters/user-filter.stories.ts +344 -0
- package/components/tela/filters/user-filter.vue +271 -0
- package/components/tela/hover-card/hover-card.mdx +221 -0
- package/components/tela/hover-card/hover-card.stories.ts +87 -0
- package/components/tela/hover-card/hover-card.vue +61 -0
- package/components/tela/icon/custom.vue +319 -0
- package/components/tela/icon/spinner.vue +12 -0
- package/components/tela/icon-button/icon-button.vue +114 -0
- package/components/tela/icon.vue +37 -0
- package/components/tela/initials.vue +28 -0
- package/components/tela/inline-input.vue +77 -0
- package/components/tela/input/input.mdx +182 -0
- package/components/tela/input/input.stories.ts +153 -0
- package/components/tela/input/tela-input.vue +240 -0
- package/components/tela/kbd/kbd-return.vue +6 -0
- package/components/tela/kbd/kbd.mdx +238 -0
- package/components/tela/kbd/kbd.vue +18 -0
- package/components/tela/label/label.mdx +121 -0
- package/components/tela/label/label.stories.ts +37 -0
- package/components/tela/label/label.vue +25 -0
- package/components/tela/link-decoration/link-decoration.vue +19 -0
- package/components/tela/live-label.vue +32 -0
- package/components/tela/long-press-button.vue +98 -0
- package/components/tela/menubar/menubar-content.vue +77 -0
- package/components/tela/menubar/menubar-item.vue +32 -0
- package/components/tela/menubar/menubar-label.vue +14 -0
- package/components/tela/menubar/menubar-menu.vue +12 -0
- package/components/tela/menubar/menubar-root.vue +30 -0
- package/components/tela/menubar/menubar-separator.vue +17 -0
- package/components/tela/menubar/menubar-shortcut.vue +14 -0
- package/components/tela/menubar/menubar-sub-content.vue +36 -0
- package/components/tela/menubar/menubar-sub-trigger.vue +28 -0
- package/components/tela/menubar/menubar-sub.vue +20 -0
- package/components/tela/menubar/menubar-trigger.vue +27 -0
- package/components/tela/menubar/menubar.vue +298 -0
- package/components/tela/modal/modal.mdx +145 -0
- package/components/tela/modal/modal.vue +242 -0
- package/components/tela/multiple-select/multiple-select.mdx +274 -0
- package/components/tela/multiple-select/multiple-select.stories.ts +325 -0
- package/components/tela/multiple-select/multiple-select.vue +666 -0
- package/components/tela/pane.vue +110 -0
- package/components/tela/popover/popover-content.vue +48 -0
- package/components/tela/popover/popover-trigger.vue +12 -0
- package/components/tela/popover/popover.mdx +239 -0
- package/components/tela/popover/popover.stories.ts +150 -0
- package/components/tela/popover/popover.vue +15 -0
- package/components/tela/popover-list/popover-list-nested.vue +104 -0
- package/components/tela/popover-list/popover-list.stories.ts +330 -0
- package/components/tela/popover-list/popover-list.vue +191 -0
- package/components/tela/radio-button.vue +66 -0
- package/components/tela/radio-group/radio-group-item.vue +40 -0
- package/components/tela/radio-group/radio-group-root.vue +26 -0
- package/components/tela/radio-group/radio-group.mdx +78 -0
- package/components/tela/radio-group/radio-group.stories.ts +106 -0
- package/components/tela/radio-group/radio-group.vue +23 -0
- package/components/tela/range-calendar.stories.ts +110 -0
- package/components/tela/range-calendar.vue +109 -0
- package/components/tela/scroll-area/scroll-area.mdx +183 -0
- package/components/tela/scroll-area/scroll-area.vue +30 -0
- package/components/tela/scroll-area/scroll-bar.vue +31 -0
- package/components/tela/segment-toggle.stories.ts +114 -0
- package/components/tela/segment-toggle.vue +66 -0
- package/components/tela/select-menu/select-menu-content.vue +106 -0
- package/components/tela/select-menu/select-menu-down-button.vue +20 -0
- package/components/tela/select-menu/select-menu-group.vue +16 -0
- package/components/tela/select-menu/select-menu-item.vue +40 -0
- package/components/tela/select-menu/select-menu-root.vue +15 -0
- package/components/tela/select-menu/select-menu-trigger.vue +34 -0
- package/components/tela/select-menu/select-menu-up-button.vue +20 -0
- package/components/tela/select-menu/select-menu-value.vue +12 -0
- package/components/tela/select-menu/select-menu.mdx +221 -0
- package/components/tela/select-menu/select-menu.stories.ts +91 -0
- package/components/tela/select-menu/select-menu.vue +165 -0
- package/components/tela/selector/selector.vue +47 -0
- package/components/tela/sheet/sheet-close.vue +12 -0
- package/components/tela/sheet/sheet-content.vue +57 -0
- package/components/tela/sheet/sheet-description.vue +23 -0
- package/components/tela/sheet/sheet-footer.vue +18 -0
- package/components/tela/sheet/sheet-header.vue +15 -0
- package/components/tela/sheet/sheet-root.vue +18 -0
- package/components/tela/sheet/sheet-title.vue +23 -0
- package/components/tela/sheet/sheet-trigger.vue +12 -0
- package/components/tela/sheet/sheet.client.vue +150 -0
- package/components/tela/sheet/sheet.mdx +176 -0
- package/components/tela/sheet/sheet.stories.ts +201 -0
- package/components/tela/sheet/variants.ts +22 -0
- package/components/tela/side-sheet/side-sheet.mdx +131 -0
- package/components/tela/side-sheet/side-sheet.stories.ts +134 -0
- package/components/tela/side-sheet/side-sheet.vue +106 -0
- package/components/tela/skeleton/skeleton.mdx +165 -0
- package/components/tela/skeleton/skeleton.stories.ts +35 -0
- package/components/tela/skeleton/skeleton.vue +45 -0
- package/components/tela/skeleton-icon.vue +24 -0
- package/components/tela/span.vue +24 -0
- package/components/tela/star-button.vue +70 -0
- package/components/tela/status/status-lean.vue +30 -0
- package/components/tela/status/status.mdx +187 -0
- package/components/tela/status/status.stories.ts +160 -0
- package/components/tela/status/status.vue +420 -0
- package/components/tela/status-bar/status-bar.mdx +178 -0
- package/components/tela/status-bar/status-bar.stories.ts +64 -0
- package/components/tela/status-bar/status-bar.vue +56 -0
- package/components/tela/status-bar/types.ts +5 -0
- package/components/tela/switch/switch.mdx +118 -0
- package/components/tela/switch/switch.stories.ts +80 -0
- package/components/tela/switch/switch.vue +56 -0
- package/components/tela/table/table-body.vue +13 -0
- package/components/tela/table/table-caption.vue +13 -0
- package/components/tela/table/table-cell.vue +20 -0
- package/components/tela/table/table-empty.vue +37 -0
- package/components/tela/table/table-footer.vue +13 -0
- package/components/tela/table/table-head.vue +13 -0
- package/components/tela/table/table-header.vue +13 -0
- package/components/tela/table/table-row.vue +13 -0
- package/components/tela/table/table.mdx +230 -0
- package/components/tela/table/table.stories.ts +384 -0
- package/components/tela/table/table.vue +15 -0
- package/components/tela/tabs/tabs-content.vue +20 -0
- package/components/tela/tabs/tabs-indicator.vue +22 -0
- package/components/tela/tabs/tabs-list.vue +23 -0
- package/components/tela/tabs/tabs-root.vue +15 -0
- package/components/tela/tabs/tabs-trigger.vue +27 -0
- package/components/tela/tabs/tabs.mdx +138 -0
- package/components/tela/tabs/tabs.stories.ts +72 -0
- package/components/tela/tabs/tabs.vue +61 -0
- package/components/tela/tags/tags-select.mdx +318 -0
- package/components/tela/tags/tags-select.stories.ts +47 -0
- package/components/tela/tags/tags-select.vue +637 -0
- package/components/tela/tags/tags.mdx +151 -0
- package/components/tela/tags/tags.stories.ts +118 -0
- package/components/tela/tags/tags.vue +112 -0
- package/components/tela/textarea/textarea.mdx +102 -0
- package/components/tela/textarea/textarea.stories.ts +50 -0
- package/components/tela/textarea/textarea.vue +34 -0
- package/components/tela/toggle-group.vue +91 -0
- package/components/tela/tooltip/tooltip-content.vue +45 -0
- package/components/tela/tooltip/tooltip-provider.vue +12 -0
- package/components/tela/tooltip/tooltip-root.vue +15 -0
- package/components/tela/tooltip/tooltip-trigger.vue +12 -0
- package/components/tela/tooltip/tooltip.mdx +196 -0
- package/components/tela/tooltip/tooltip.stories.ts +200 -0
- package/components/tela/tooltip/tooltip.vue +91 -0
- package/components/tela/tooltip-group/tooltip-group-trigger.vue +92 -0
- package/components/tela/tooltip-group/tooltip-group.mdx +236 -0
- package/components/tela/tooltip-group/tooltip-group.stories.ts +465 -0
- package/components/tela/tooltip-group/tooltip-group.vue +35 -0
- package/components/tela/transparent-input.vue +151 -0
- package/components/tela/variable-icon.vue +28 -0
- package/components/tela/variable-input.vue +77 -0
- package/components/tela/wide-button/wide-button.vue +40 -0
- package/components.json +18 -0
- package/composables/status-toast.ts +67 -0
- package/css/reset.css +386 -0
- package/css/text.css +22 -0
- package/lib/doc-generator.ts +903 -0
- package/lib/extractors/volar-extract.ts +186 -0
- package/lib/type-resolver.ts +402 -0
- package/lib/utils.ts +6 -0
- package/modules/tela-build-docs/index.ts +139 -0
- package/nuxt.config.ts +80 -0
- package/package.json +84 -0
- package/plugins/test-id.ts +7 -0
- package/tsconfig.json +7 -0
- package/types/custom-icon.ts +1 -0
- package/types/index.ts +2 -0
- package/types/status.ts +1 -0
- package/unocss.config.ts +89 -0
- package/utils/component-utils.ts +30 -0
- package/utils/design-tokens.ts +431 -0
- package/utils/fold.ts +8 -0
- package/utils/select-menu.ts +10 -0
- package/utils/status.ts +1 -0
- package/utils/without-keys.ts +34 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { resolve, relative } from 'pathe'
|
|
2
|
+
import { existsSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import type { createChecker } from 'vue-component-meta'
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url)
|
|
7
|
+
|
|
8
|
+
type MetaChecker = ReturnType<typeof createChecker>
|
|
9
|
+
|
|
10
|
+
let loadChecker: typeof import('vue-component-meta').createChecker | null = null
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
loadChecker = require('vue-component-meta').createChecker
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
loadChecker = null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates (or reuses) a tiny tsconfig overlay so TypeScript/Volar can
|
|
21
|
+
* resolve imports from the cached layer location and the app root.
|
|
22
|
+
*
|
|
23
|
+
* We place it under <app>/.docs/tsconfig.tela-docs.json to ensure a writable path.
|
|
24
|
+
*/
|
|
25
|
+
export function ensureOverlayTsconfig(appRootDir: string, layerRoot: string): string {
|
|
26
|
+
const docsDir = resolve(appRootDir, '.docs')
|
|
27
|
+
if (!existsSync(docsDir)) {
|
|
28
|
+
mkdirSync(docsDir, { recursive: true })
|
|
29
|
+
}
|
|
30
|
+
const overlayPath = resolve(docsDir, 'tsconfig.tela-docs.json')
|
|
31
|
+
|
|
32
|
+
// Try to extend the app's root tsconfig.json if present
|
|
33
|
+
const appTsconfig = resolve(appRootDir, 'tsconfig.json')
|
|
34
|
+
const extendsPath = existsSync(appTsconfig) ? './tsconfig.json' : undefined
|
|
35
|
+
|
|
36
|
+
// Paths must be relative to the overlay file
|
|
37
|
+
const relLayer = relative(docsDir, layerRoot).replace(/\\/g, '/')
|
|
38
|
+
const relApp = relative(docsDir, appRootDir).replace(/\\/g, '/')
|
|
39
|
+
|
|
40
|
+
const content = {
|
|
41
|
+
...(extendsPath ? { extends: extendsPath } : {}),
|
|
42
|
+
compilerOptions: {
|
|
43
|
+
// Make the layer root the default base for bare imports (helps TS walk node_modules near layer)
|
|
44
|
+
baseUrl: relLayer,
|
|
45
|
+
// Common aliases that may be used by the build layer or apps
|
|
46
|
+
paths: {
|
|
47
|
+
'~/*': [`${relLayer}/*`],
|
|
48
|
+
'@/*': [`${relLayer}/*`],
|
|
49
|
+
},
|
|
50
|
+
// Keep type checking very light for speed; this file is only for metadata extraction
|
|
51
|
+
skipLibCheck: true,
|
|
52
|
+
},
|
|
53
|
+
include: [
|
|
54
|
+
`${relLayer}/**/*.ts`,
|
|
55
|
+
`${relLayer}/**/*.tsx`,
|
|
56
|
+
`${relLayer}/**/*.vue`,
|
|
57
|
+
// Let TS see the app's ambient types if there are any
|
|
58
|
+
`${relApp}/**/*.d.ts`,
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
writeFileSync(overlayPath, JSON.stringify(content, null, 2), 'utf-8')
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
}
|
|
67
|
+
return overlayPath
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface VolarExtractor {
|
|
71
|
+
checker: MetaChecker
|
|
72
|
+
/** Extracts normalized component API info or throws on hard failure */
|
|
73
|
+
extract: (fileAbsPath: string) => {
|
|
74
|
+
name: string
|
|
75
|
+
description: string
|
|
76
|
+
props: Array<{
|
|
77
|
+
name: string
|
|
78
|
+
required: boolean
|
|
79
|
+
type: string
|
|
80
|
+
defaultValue?: { value: string }
|
|
81
|
+
description?: string
|
|
82
|
+
}>
|
|
83
|
+
events: Array<{
|
|
84
|
+
name: string
|
|
85
|
+
type?: string
|
|
86
|
+
description?: string
|
|
87
|
+
}>
|
|
88
|
+
slots: Array<{
|
|
89
|
+
name: string
|
|
90
|
+
// list of scoped bindings as `{ name, type }`
|
|
91
|
+
bindings?: Array<{ name: string; type?: string }>
|
|
92
|
+
description?: string
|
|
93
|
+
}>
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Defensive stringify for type nodes coming from meta libs */
|
|
98
|
+
function displayType(t: any): string {
|
|
99
|
+
if (!t)
|
|
100
|
+
return 'any'
|
|
101
|
+
if (typeof t === 'string')
|
|
102
|
+
return t
|
|
103
|
+
// Try common shapes used by vue-component-meta
|
|
104
|
+
return (
|
|
105
|
+
t.display
|
|
106
|
+
?? t.name
|
|
107
|
+
?? (Array.isArray(t.names) ? t.names.join(' | ') : undefined)
|
|
108
|
+
?? t.text
|
|
109
|
+
?? 'any'
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Normalize props from either array or record shapes */
|
|
114
|
+
function normalizeArrayOrRecord<T extends object>(val: any): T[] {
|
|
115
|
+
if (!val)
|
|
116
|
+
return []
|
|
117
|
+
if (Array.isArray(val))
|
|
118
|
+
return val as T[]
|
|
119
|
+
if (typeof val === 'object')
|
|
120
|
+
return Object.values(val)
|
|
121
|
+
return []
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createVolarExtractor(appRootDir: string, layerRoot: string): VolarExtractor {
|
|
125
|
+
if (!loadChecker) {
|
|
126
|
+
throw new Error('vue-component-meta is not installed')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const createChecker = loadChecker
|
|
130
|
+
|
|
131
|
+
const tsconfigPath = ensureOverlayTsconfig(appRootDir, layerRoot)
|
|
132
|
+
|
|
133
|
+
const checker = createChecker(
|
|
134
|
+
tsconfigPath,
|
|
135
|
+
{
|
|
136
|
+
// Ensure the checker always uses the TypeScript program rather than a shim resolver
|
|
137
|
+
forceUseTs: true,
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
checker,
|
|
143
|
+
extract(fileAbsPath: string) {
|
|
144
|
+
const meta: any = checker.getComponentMeta(fileAbsPath)
|
|
145
|
+
|
|
146
|
+
const name
|
|
147
|
+
= meta?.name
|
|
148
|
+
|| meta?.displayName
|
|
149
|
+
// strip extension
|
|
150
|
+
|| fileAbsPath.substring(fileAbsPath.lastIndexOf('/') + 1).replace(/\.vue$/, '')
|
|
151
|
+
|
|
152
|
+
const description: string
|
|
153
|
+
= meta?.docs?.description || meta?.description || ''
|
|
154
|
+
|
|
155
|
+
const props = normalizeArrayOrRecord<any>(meta?.props).map((p: any) => ({
|
|
156
|
+
name: p.name,
|
|
157
|
+
required: !!p.required,
|
|
158
|
+
type: displayType(p.type),
|
|
159
|
+
defaultValue: p.default != null
|
|
160
|
+
? { value: String(p.default) }
|
|
161
|
+
: (p.defaultValue ? { value: String(p.defaultValue) } : undefined),
|
|
162
|
+
description: p.docs?.description || p.description,
|
|
163
|
+
}))
|
|
164
|
+
|
|
165
|
+
const events = normalizeArrayOrRecord<any>(meta?.emits).map((e: any) => ({
|
|
166
|
+
name: e.name,
|
|
167
|
+
type: displayType(e.type),
|
|
168
|
+
description: e.docs?.description || e.description,
|
|
169
|
+
}))
|
|
170
|
+
|
|
171
|
+
const slots = normalizeArrayOrRecord<any>(meta?.slots).map((s: any) => {
|
|
172
|
+
const bindings = normalizeArrayOrRecord<any>(s.props).map((b: any) => ({
|
|
173
|
+
name: b.name,
|
|
174
|
+
type: displayType(b.type),
|
|
175
|
+
}))
|
|
176
|
+
return {
|
|
177
|
+
name: s.name,
|
|
178
|
+
bindings,
|
|
179
|
+
description: s.docs?.description || s.description,
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
return { name, description, props, events, slots }
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// Use type-only import so runtime doesn't require ts-morph unless available
|
|
2
|
+
import type { Project, SourceFile, TypeAliasDeclaration, InterfaceDeclaration, EnumDeclaration, Node } from 'ts-morph'
|
|
3
|
+
import { join } from 'pathe'
|
|
4
|
+
import { glob } from 'glob'
|
|
5
|
+
import { readFileSync } from 'node:fs'
|
|
6
|
+
|
|
7
|
+
const colors = {
|
|
8
|
+
gray: '\x1B[90m',
|
|
9
|
+
orange: '\x1B[38;5;208m',
|
|
10
|
+
purple: '\x1B[38;5;141m',
|
|
11
|
+
reset: '\x1B[0m',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ResolvedType {
|
|
15
|
+
name: string
|
|
16
|
+
definition: string
|
|
17
|
+
raw: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class TypeResolver {
|
|
21
|
+
private project: Project | null = null
|
|
22
|
+
private typeRegistry = new Map<string, ResolvedType>()
|
|
23
|
+
private initialized = false
|
|
24
|
+
|
|
25
|
+
constructor(private projectRoot: string) {
|
|
26
|
+
// Defer creating ts-morph Project until initialize()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async initialize(): Promise<void> {
|
|
30
|
+
if (this.initialized)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
// Remove verbose initialization message
|
|
34
|
+
|
|
35
|
+
// Attempt to load ts-morph dynamically. If not available, fall back gracefully.
|
|
36
|
+
try {
|
|
37
|
+
const mod: any = await import('ts-morph')
|
|
38
|
+
this.project = new mod.Project({
|
|
39
|
+
skipFileDependencyResolution: true,
|
|
40
|
+
skipLoadingLibFiles: true,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// ts-morph not installed; we'll operate with an empty registry
|
|
45
|
+
this.project = null
|
|
46
|
+
this.initialized = true
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Find all TypeScript files in the project
|
|
51
|
+
const tsFiles = await glob('**/*.ts', {
|
|
52
|
+
cwd: this.projectRoot,
|
|
53
|
+
ignore: ['node_modules/**', 'dist/**', '.nuxt/**', '.output/**'],
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const vueFiles = await glob('**/*.vue', {
|
|
57
|
+
cwd: this.projectRoot,
|
|
58
|
+
ignore: ['node_modules/**', 'dist/**', '.nuxt/**', '.output/**'],
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Add all TypeScript files to the project
|
|
62
|
+
if (this.project) {
|
|
63
|
+
for (const file of tsFiles) {
|
|
64
|
+
const fullPath = join(this.projectRoot, file)
|
|
65
|
+
this.project.addSourceFileAtPath(fullPath)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Extract <script> content from Vue files and add as virtual TypeScript files
|
|
70
|
+
if (this.project) {
|
|
71
|
+
for (const file of vueFiles) {
|
|
72
|
+
try {
|
|
73
|
+
const fullPath = join(this.projectRoot, file)
|
|
74
|
+
const content = readFileSync(fullPath, 'utf-8')
|
|
75
|
+
const scriptMatch = content.match(/<script[^>]*lang=["']ts["'][^>]*>([\s\S]*?)<\/script>/i)
|
|
76
|
+
|
|
77
|
+
if (scriptMatch) {
|
|
78
|
+
const scriptContent = scriptMatch[1]
|
|
79
|
+
// Create a virtual .ts file for the Vue script content
|
|
80
|
+
const virtualPath = fullPath.replace('.vue', '.vue.ts')
|
|
81
|
+
this.project.createSourceFile(virtualPath, scriptContent, { overwrite: true })
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error: any) {
|
|
85
|
+
console.error(`${colors.gray}[tela/build] ${colors.orange}✗${colors.gray} Failed to process Vue file ${file}: ${error.message}${colors.reset}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Build type registry
|
|
91
|
+
if (this.project) {
|
|
92
|
+
this.buildTypeRegistry()
|
|
93
|
+
}
|
|
94
|
+
this.initialized = true
|
|
95
|
+
|
|
96
|
+
// Remove verbose type count message
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private buildTypeRegistry(): void {
|
|
100
|
+
if (!this.project)
|
|
101
|
+
return
|
|
102
|
+
for (const sourceFile of this.project.getSourceFiles()) {
|
|
103
|
+
this.extractTypesFromFile(sourceFile)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private extractTypesFromFile(sourceFile: SourceFile): void {
|
|
108
|
+
const isVueVirtual = sourceFile.getFilePath().endsWith('.vue.ts')
|
|
109
|
+
|
|
110
|
+
// Extract type aliases
|
|
111
|
+
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
112
|
+
if (typeAlias.isExported() || isVueVirtual) {
|
|
113
|
+
const resolved = this.resolveTypeAlias(typeAlias)
|
|
114
|
+
if (resolved) {
|
|
115
|
+
this.typeRegistry.set(resolved.name, resolved)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Extract interfaces
|
|
121
|
+
for (const interfaceDecl of sourceFile.getInterfaces()) {
|
|
122
|
+
if (interfaceDecl.isExported() || isVueVirtual) {
|
|
123
|
+
const resolved = this.resolveInterface(interfaceDecl)
|
|
124
|
+
if (resolved) {
|
|
125
|
+
this.typeRegistry.set(resolved.name, resolved)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Extract enums
|
|
131
|
+
for (const enumDecl of sourceFile.getEnums()) {
|
|
132
|
+
if (enumDecl.isExported() || isVueVirtual) {
|
|
133
|
+
const resolved = this.resolveEnum(enumDecl)
|
|
134
|
+
if (resolved) {
|
|
135
|
+
this.typeRegistry.set(resolved.name, resolved)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private resolveTypeAlias(typeAlias: TypeAliasDeclaration): ResolvedType | null {
|
|
142
|
+
try {
|
|
143
|
+
const name = typeAlias.getName()
|
|
144
|
+
const typeNode = typeAlias.getTypeNode()
|
|
145
|
+
|
|
146
|
+
if (!typeNode)
|
|
147
|
+
return null
|
|
148
|
+
|
|
149
|
+
const definition = this.formatTypeNode(typeNode)
|
|
150
|
+
const raw = typeAlias.getText()
|
|
151
|
+
|
|
152
|
+
return { name, definition, raw }
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Silently skip failed type alias resolution
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private resolveInterface(interfaceDecl: InterfaceDeclaration): ResolvedType | null {
|
|
161
|
+
try {
|
|
162
|
+
const name = interfaceDecl.getName()
|
|
163
|
+
const properties: string[] = []
|
|
164
|
+
|
|
165
|
+
for (const prop of interfaceDecl.getProperties()) {
|
|
166
|
+
const propName = prop.getName()
|
|
167
|
+
const propType = prop.getTypeNode()
|
|
168
|
+
const isOptional = prop.hasQuestionToken()
|
|
169
|
+
|
|
170
|
+
const typeStr = propType ? this.formatTypeNode(propType) : 'any'
|
|
171
|
+
const optionalMark = isOptional ? '?' : ''
|
|
172
|
+
|
|
173
|
+
// Add comment if available
|
|
174
|
+
const comment = this.extractComment(prop)
|
|
175
|
+
const commentStr = comment ? ` // ${comment}` : ''
|
|
176
|
+
|
|
177
|
+
properties.push(` ${propName}${optionalMark}: ${typeStr}${commentStr}`)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const definition = `{\n${properties.join('\n')}\n}`
|
|
181
|
+
const raw = interfaceDecl.getText()
|
|
182
|
+
|
|
183
|
+
return { name, definition, raw }
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Silently skip failed interface resolution
|
|
187
|
+
return null
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private resolveEnum(enumDecl: EnumDeclaration): ResolvedType | null {
|
|
192
|
+
try {
|
|
193
|
+
const name = enumDecl.getName()
|
|
194
|
+
const members = enumDecl.getMembers()
|
|
195
|
+
|
|
196
|
+
const values = members.map((member) => {
|
|
197
|
+
const memberName = member.getName()
|
|
198
|
+
const initializer = member.getInitializer()
|
|
199
|
+
|
|
200
|
+
if (initializer) {
|
|
201
|
+
const value = initializer.getText()
|
|
202
|
+
return value.startsWith('"') || value.startsWith('\'') ? value : `'${memberName}'`
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return `'${memberName}'`
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const definition = values.join(' | ')
|
|
209
|
+
const raw = enumDecl.getText()
|
|
210
|
+
|
|
211
|
+
return { name, definition, raw }
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Silently skip failed enum resolution
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private formatTypeNode(typeNode: Node): string {
|
|
220
|
+
const text = typeNode.getText()
|
|
221
|
+
|
|
222
|
+
// Handle array types
|
|
223
|
+
if (text.includes('Array<') || text.endsWith('[]')) {
|
|
224
|
+
return text
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Handle union types
|
|
228
|
+
if (text.includes(' | ')) {
|
|
229
|
+
return text
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Handle object types
|
|
233
|
+
if (text.startsWith('{') && text.endsWith('}')) {
|
|
234
|
+
return text
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Handle function types
|
|
238
|
+
if (text.includes('=>') || text.includes('function')) {
|
|
239
|
+
return text
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Default: return as-is
|
|
243
|
+
return text
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private extractComment(node: Node): string {
|
|
247
|
+
const jsDocTags = (node as any).getJsDocs()
|
|
248
|
+
if (jsDocTags.length > 0) {
|
|
249
|
+
const firstDoc = jsDocTags[0]
|
|
250
|
+
const description = firstDoc.getDescription()
|
|
251
|
+
return description.trim()
|
|
252
|
+
}
|
|
253
|
+
return ''
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public resolveType(typeName: string): string {
|
|
257
|
+
return this.resolveTypeInternal(typeName, new Set<string>())
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private resolveTypeInternal(typeName: string, seen: Set<string>): string {
|
|
261
|
+
if (!typeName)
|
|
262
|
+
return 'any'
|
|
263
|
+
|
|
264
|
+
const trimmed = typeName.trim()
|
|
265
|
+
if (!trimmed)
|
|
266
|
+
return 'any'
|
|
267
|
+
|
|
268
|
+
if (seen.has(trimmed)) {
|
|
269
|
+
return trimmed
|
|
270
|
+
}
|
|
271
|
+
seen.add(trimmed)
|
|
272
|
+
|
|
273
|
+
const direct = this.typeRegistry.get(trimmed)
|
|
274
|
+
if (direct) {
|
|
275
|
+
return direct.definition
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const arraySuffixMatch = trimmed.match(/^(.*)\[\]$/)
|
|
279
|
+
if (arraySuffixMatch) {
|
|
280
|
+
const elementType = arraySuffixMatch[1].trim()
|
|
281
|
+
const resolvedElement = this.resolveTypeInternal(elementType, seen)
|
|
282
|
+
return this.formatArrayType(resolvedElement)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (trimmed.startsWith('Array<') && trimmed.endsWith('>')) {
|
|
286
|
+
const inner = trimmed.slice(6, -1).trim()
|
|
287
|
+
const resolvedInner = this.resolveTypeInternal(inner, seen)
|
|
288
|
+
return this.formatArrayType(resolvedInner)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const unionParts = this.splitTopLevel(trimmed, '|')
|
|
292
|
+
if (unionParts.length > 1) {
|
|
293
|
+
return unionParts
|
|
294
|
+
.map(part => this.resolveTypeInternal(part.trim(), seen))
|
|
295
|
+
.join(' | ')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const intersectionParts = this.splitTopLevel(trimmed, '&')
|
|
299
|
+
if (intersectionParts.length > 1) {
|
|
300
|
+
return intersectionParts
|
|
301
|
+
.map(part => this.resolveTypeInternal(part.trim(), seen))
|
|
302
|
+
.join(' & ')
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const genericIndex = trimmed.indexOf('<')
|
|
306
|
+
if (genericIndex > 0 && trimmed.endsWith('>')) {
|
|
307
|
+
const base = trimmed.slice(0, genericIndex)
|
|
308
|
+
const inner = trimmed.slice(genericIndex + 1, -1)
|
|
309
|
+
const genericParts = this.splitTopLevel(inner, ',').map(part => part.trim()).filter(Boolean)
|
|
310
|
+
if (genericParts.length > 0) {
|
|
311
|
+
const resolvedParts = genericParts.map(part => this.resolveTypeInternal(part, seen))
|
|
312
|
+
return `${base}<${resolvedParts.join(', ')}>`
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
|
|
317
|
+
return this.resolveTypeInternal(trimmed.slice(1, -1), seen)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
switch (trimmed) {
|
|
321
|
+
case 'TSIndexedAccessType':
|
|
322
|
+
return 'string'
|
|
323
|
+
case 'TSTypeReference':
|
|
324
|
+
return 'any'
|
|
325
|
+
case 'TSFunctionType':
|
|
326
|
+
return '(...args: any[]) => any'
|
|
327
|
+
case 'TSUnionType':
|
|
328
|
+
case 'union':
|
|
329
|
+
return 'string'
|
|
330
|
+
default:
|
|
331
|
+
return trimmed
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public hasType(typeName: string): boolean {
|
|
336
|
+
return this.typeRegistry.has(typeName)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public getAllTypes(): Map<string, ResolvedType> {
|
|
340
|
+
return new Map(this.typeRegistry)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private formatArrayType(resolvedElement: string): string {
|
|
344
|
+
const value = resolvedElement.trim()
|
|
345
|
+
if (!value) {
|
|
346
|
+
return 'any[]'
|
|
347
|
+
}
|
|
348
|
+
if (value.startsWith('{') && value.endsWith('}')) {
|
|
349
|
+
return `${value}[]`
|
|
350
|
+
}
|
|
351
|
+
return `Array<${value}>`
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private splitTopLevel(input: string, delimiter: string): string[] {
|
|
355
|
+
const parts: string[] = []
|
|
356
|
+
let depth = 0
|
|
357
|
+
let buffer = ''
|
|
358
|
+
|
|
359
|
+
const openers = new Map([
|
|
360
|
+
['<', '>'],
|
|
361
|
+
['(', ')'],
|
|
362
|
+
['[', ']'],
|
|
363
|
+
['{', '}'],
|
|
364
|
+
])
|
|
365
|
+
const closers = new Map(Array.from(openers.entries()).map(([o, c]) => [c, o]))
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < input.length; i++) {
|
|
368
|
+
const char = input[i]
|
|
369
|
+
if (openers.has(char)) {
|
|
370
|
+
depth++
|
|
371
|
+
}
|
|
372
|
+
else if (closers.has(char)) {
|
|
373
|
+
depth = Math.max(0, depth - 1)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (char === delimiter && depth === 0) {
|
|
377
|
+
parts.push(buffer.trim())
|
|
378
|
+
buffer = ''
|
|
379
|
+
continue
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
buffer += char
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (buffer.trim()) {
|
|
386
|
+
parts.push(buffer.trim())
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return parts
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Global instance
|
|
394
|
+
let globalResolver: TypeResolver | null = null
|
|
395
|
+
|
|
396
|
+
export async function getTypeResolver(projectRoot: string, appRootDir?: string): Promise<TypeResolver> {
|
|
397
|
+
if (!globalResolver) {
|
|
398
|
+
globalResolver = new TypeResolver(projectRoot, appRootDir)
|
|
399
|
+
await globalResolver.initialize()
|
|
400
|
+
}
|
|
401
|
+
return globalResolver
|
|
402
|
+
}
|