@ramathibodi/nuxt-commons 0.1.74 → 0.1.75

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 (96) hide show
  1. package/README.md +115 -115
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/Alert.vue +58 -58
  4. package/dist/runtime/components/BarcodeReader.vue +130 -130
  5. package/dist/runtime/components/ExportCSV.vue +110 -110
  6. package/dist/runtime/components/FileBtn.vue +79 -79
  7. package/dist/runtime/components/ImportCSV.vue +151 -151
  8. package/dist/runtime/components/MrzReader.vue +168 -168
  9. package/dist/runtime/components/SplitterPanel.vue +67 -67
  10. package/dist/runtime/components/TabsGroup.vue +39 -39
  11. package/dist/runtime/components/TextBarcode.vue +66 -66
  12. package/dist/runtime/components/device/IdCardButton.vue +95 -95
  13. package/dist/runtime/components/device/IdCardWebSocket.vue +207 -207
  14. package/dist/runtime/components/device/Scanner.vue +350 -350
  15. package/dist/runtime/components/dialog/Confirm.vue +112 -112
  16. package/dist/runtime/components/dialog/Host.vue +88 -88
  17. package/dist/runtime/components/dialog/Index.vue +84 -84
  18. package/dist/runtime/components/dialog/Loading.vue +51 -51
  19. package/dist/runtime/components/dialog/default/Confirm.vue +112 -112
  20. package/dist/runtime/components/dialog/default/Loading.vue +60 -60
  21. package/dist/runtime/components/dialog/default/Notify.vue +82 -82
  22. package/dist/runtime/components/dialog/default/Printing.vue +46 -46
  23. package/dist/runtime/components/dialog/default/VerifyUser.vue +144 -144
  24. package/dist/runtime/components/document/Form.vue +50 -50
  25. package/dist/runtime/components/document/TemplateBuilder.vue +536 -536
  26. package/dist/runtime/components/form/ActionPad.vue +156 -156
  27. package/dist/runtime/components/form/Birthdate.vue +116 -116
  28. package/dist/runtime/components/form/CheckboxGroup.vue +99 -99
  29. package/dist/runtime/components/form/CodeEditor.vue +45 -45
  30. package/dist/runtime/components/form/Date.vue +270 -270
  31. package/dist/runtime/components/form/DateTime.vue +220 -220
  32. package/dist/runtime/components/form/Dialog.vue +178 -178
  33. package/dist/runtime/components/form/EditPad.vue +157 -157
  34. package/dist/runtime/components/form/File.vue +295 -295
  35. package/dist/runtime/components/form/Hidden.vue +44 -44
  36. package/dist/runtime/components/form/Iterator.vue +538 -538
  37. package/dist/runtime/components/form/Login.vue +143 -143
  38. package/dist/runtime/components/form/Pad.vue +399 -399
  39. package/dist/runtime/components/form/SignPad.vue +226 -226
  40. package/dist/runtime/components/form/System.vue +34 -34
  41. package/dist/runtime/components/form/Table.vue +391 -391
  42. package/dist/runtime/components/form/TableData.vue +236 -236
  43. package/dist/runtime/components/form/Time.vue +177 -177
  44. package/dist/runtime/components/form/images/Capture.vue +245 -245
  45. package/dist/runtime/components/form/images/Edit.vue +133 -133
  46. package/dist/runtime/components/form/images/Field.vue +331 -331
  47. package/dist/runtime/components/form/images/Pad.vue +54 -54
  48. package/dist/runtime/components/label/Date.vue +37 -37
  49. package/dist/runtime/components/label/DateAgo.vue +102 -102
  50. package/dist/runtime/components/label/DateCount.vue +152 -152
  51. package/dist/runtime/components/label/Field.vue +111 -111
  52. package/dist/runtime/components/label/FormatMoney.vue +37 -37
  53. package/dist/runtime/components/label/Mask.vue +46 -46
  54. package/dist/runtime/components/label/Object.vue +21 -21
  55. package/dist/runtime/components/master/Autocomplete.vue +89 -89
  56. package/dist/runtime/components/master/Combobox.vue +88 -88
  57. package/dist/runtime/components/master/RadioGroup.vue +90 -90
  58. package/dist/runtime/components/master/Select.vue +70 -70
  59. package/dist/runtime/components/master/label.vue +55 -55
  60. package/dist/runtime/components/model/Autocomplete.vue +91 -91
  61. package/dist/runtime/components/model/Combobox.vue +90 -90
  62. package/dist/runtime/components/model/Pad.vue +114 -114
  63. package/dist/runtime/components/model/Select.vue +78 -84
  64. package/dist/runtime/components/model/Table.vue +370 -370
  65. package/dist/runtime/components/model/iterator.vue +497 -497
  66. package/dist/runtime/components/model/label.vue +58 -58
  67. package/dist/runtime/components/pdf/Print.vue +75 -75
  68. package/dist/runtime/components/pdf/View.vue +146 -146
  69. package/dist/runtime/composables/dialog.d.ts +1 -1
  70. package/dist/runtime/composables/graphql.d.ts +1 -1
  71. package/dist/runtime/composables/graphqlModel.d.ts +9 -9
  72. package/dist/runtime/composables/graphqlModelItem.d.ts +7 -7
  73. package/dist/runtime/composables/graphqlModelOperation.d.ts +6 -6
  74. package/dist/runtime/composables/userPermission.d.ts +1 -1
  75. package/dist/runtime/labs/Calendar.vue +99 -99
  76. package/dist/runtime/labs/form/EditMobile.vue +152 -152
  77. package/dist/runtime/labs/form/TextFieldMask.vue +43 -43
  78. package/dist/runtime/plugins/clientConfig.d.ts +1 -1
  79. package/dist/runtime/plugins/default.d.ts +1 -1
  80. package/dist/runtime/plugins/dialogManager.d.ts +1 -1
  81. package/dist/runtime/plugins/permission.d.ts +1 -1
  82. package/dist/runtime/types/alert.d.ts +11 -11
  83. package/dist/runtime/types/clientConfig.d.ts +13 -13
  84. package/dist/runtime/types/dialogManager.d.ts +35 -35
  85. package/dist/runtime/types/formDialog.d.ts +5 -5
  86. package/dist/runtime/types/graphqlOperation.d.ts +23 -23
  87. package/dist/runtime/types/menu.d.ts +31 -31
  88. package/dist/runtime/types/modules.d.ts +7 -7
  89. package/dist/runtime/types/permission.d.ts +13 -13
  90. package/package.json +131 -131
  91. package/scripts/enrich-vue-docs-from-ai.mjs +197 -197
  92. package/scripts/generate-ai-summary.mjs +321 -321
  93. package/scripts/generate-composables-md.mjs +129 -129
  94. package/scripts/postInstall.cjs +70 -70
  95. package/templates/.codegen/codegen.ts +32 -32
  96. package/templates/.codegen/plugin-schema-object.js +161 -161
@@ -1,321 +1,321 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
-
4
- const rootDir = process.cwd()
5
- const componentsDir = path.join(rootDir, 'src/runtime/components')
6
- const composablesDir = path.join(rootDir, 'src/runtime/composables')
7
- const outMdFile = path.join(rootDir, 'docs/ai-summary.md')
8
- const outJsonFile = path.join(rootDir, 'docs/ai-summary.json')
9
- const outTypeIndexFile = path.join(rootDir, 'docs/type-index.json')
10
-
11
- function walkFiles(dir, exts = new Set(['.vue', '.ts'])) {
12
- const result = []
13
- if (!fs.existsSync(dir)) return result
14
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
15
- const full = path.join(dir, entry.name)
16
- if (entry.isDirectory()) {
17
- result.push(...walkFiles(full, exts))
18
- } else if (exts.has(path.extname(entry.name))) {
19
- result.push(full)
20
- }
21
- }
22
- return result.sort((a, b) => a.localeCompare(b))
23
- }
24
-
25
- function toPosix(p) {
26
- return p.split(path.sep).join('/')
27
- }
28
-
29
- function extractTopDescription(content) {
30
- const m = content.match(/\/\*\*([\s\S]*?)\*\//)
31
- if (!m) return 'No top-level description found.'
32
- const lines = m[1]
33
- .split('\n')
34
- .map((l) => l.replace(/^\s*\*\s?/, '').trim())
35
- .filter(Boolean)
36
- .filter((l) => !l.toLowerCase().includes('vue-docgen'))
37
- return lines[0] || 'No top-level description found.'
38
- }
39
-
40
- function compact(str) {
41
- return str.replace(/\s+/g, ' ').trim()
42
- }
43
-
44
- function findInterfaceBody(content, interfaceName) {
45
- const needle = `interface ${interfaceName}`
46
- const start = content.indexOf(needle)
47
- if (start === -1) return null
48
-
49
- const braceStart = content.indexOf('{', start)
50
- if (braceStart === -1) return null
51
-
52
- let depth = 0
53
- for (let i = braceStart; i < content.length; i++) {
54
- const ch = content[i]
55
- if (ch === '{') depth++
56
- if (ch === '}') depth--
57
- if (depth === 0) {
58
- return content.slice(braceStart + 1, i)
59
- }
60
- }
61
- return null
62
- }
63
-
64
- function extractWithDefaults(content) {
65
- const defaults = {}
66
- const m = content.match(/withDefaults\s*\(\s*defineProps[\s\S]*?,\s*\{([\s\S]*?)\}\s*\)/)
67
- if (!m) return defaults
68
-
69
- const lines = m[1].split('\n')
70
- for (const line of lines) {
71
- const clean = line.trim()
72
- if (!clean || clean.startsWith('//')) continue
73
- const kv = clean.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.+?)(?:,\s*)?(?:\/\/\s*(.*))?$/)
74
- if (!kv) continue
75
- const key = kv[1]
76
- const rawValue = kv[2].trim()
77
- const description = (kv[3] || '').trim()
78
- defaults[key] = { value: rawValue, description }
79
- }
80
-
81
- return defaults
82
- }
83
-
84
- function inferTypeFromDefault(rawValue) {
85
- if (rawValue === 'true' || rawValue === 'false') return 'boolean'
86
- if (rawValue === 'null') return 'null'
87
- if (/^['"].*['"]$/.test(rawValue)) return 'string'
88
- if (/^-?\d+(\.\d+)?$/.test(rawValue)) return 'number'
89
- if (/^\(\)\s*=>\s*\[\s*\]$/.test(rawValue)) return 'any[]'
90
- if (/^\(\)\s*=>\s*\(\s*\{\s*\}\s*\)$/.test(rawValue)) return 'Record<string, any>'
91
- if (/^\(\)\s*=>\s*\[/.test(rawValue)) return 'any[]'
92
- if (/^\(\)\s*=>\s*\(\s*\{/.test(rawValue)) return 'Record<string, any>'
93
- return 'unknown'
94
- }
95
-
96
- function extractProps(content) {
97
- const props = []
98
- const ifaceBody = findInterfaceBody(content, 'Props')
99
- const defaults = extractWithDefaults(content)
100
-
101
- if (ifaceBody) {
102
- const lines = ifaceBody.split('\n')
103
- for (const line of lines) {
104
- const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)(\?)?\s*:\s*([^/;]+(?:\|[^/;]+)*)[;]?\s*(?:\/\/\s*(.+))?\s*$/)
105
- if (m) {
106
- const name = m[1]
107
- const optional = !!m[2]
108
- const type = compact(m[3].trim())
109
- const description = (m[4] || '').trim()
110
- props.push({
111
- name,
112
- type,
113
- required: !optional,
114
- default: Object.prototype.hasOwnProperty.call(defaults, name) ? defaults[name].value : undefined,
115
- description: description || (defaults[name]?.description || ''),
116
- })
117
- }
118
- }
119
- }
120
-
121
- // Fallback for components where Props is empty and typed props come from imported intersections.
122
- if (!props.length && Object.keys(defaults).length) {
123
- for (const [name, meta] of Object.entries(defaults)) {
124
- props.push({
125
- name,
126
- type: inferTypeFromDefault(meta.value),
127
- required: false,
128
- default: meta.value,
129
- description: meta.description || 'Inferred from withDefaults()',
130
- })
131
- }
132
- }
133
-
134
- return props
135
- }
136
-
137
- function extractEmits(content) {
138
- const emits = []
139
-
140
- const typed = content.match(/defineEmits<\{([\s\S]*?)\}>\(\)/)
141
- if (typed) {
142
- const overloads = typed[1].matchAll(/\(\s*e\s*:\s*'([^']+)'\s*(?:,\s*([^)]*))?\)\s*:\s*void/g)
143
- for (const m of overloads) {
144
- emits.push({
145
- event: m[1],
146
- payload: m[2] ? compact(m[2]) : undefined,
147
- })
148
- }
149
- }
150
-
151
- const arr = content.match(/defineEmits\s*\(\s*\[([\s\S]*?)\]\s*\)/)
152
- if (arr) {
153
- const names = arr[1].matchAll(/'([^']+)'|"([^"]+)"/g)
154
- for (const m of names) {
155
- emits.push({ event: m[1] || m[2] })
156
- }
157
- }
158
-
159
- return emits
160
- }
161
-
162
- function extractExports(content) {
163
- const functions = []
164
- const constants = []
165
- const interfaces = []
166
- const types = []
167
- const enums = []
168
-
169
- const fnMatches = content.matchAll(/export\s+function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([\s\S]*?)\)\s*(?::\s*([^({;\n]+(?:<[\s\S]*?>)?))?\s*\{/g)
170
- for (const m of fnMatches) {
171
- functions.push({
172
- name: m[1],
173
- params: compact(m[2]),
174
- returns: m[3] ? compact(m[3]) : undefined,
175
- signature: compact(`function ${m[1]}(${m[2]})${m[3] ? `: ${m[3]}` : ''}`),
176
- })
177
- }
178
-
179
- const constMatches = content.matchAll(/export\s+const\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*([^=;\n]+))?\s*=/g)
180
- for (const m of constMatches) {
181
- constants.push({
182
- name: m[1],
183
- type: m[2] ? compact(m[2]) : undefined,
184
- })
185
- }
186
-
187
- const interfaceMatches = content.matchAll(/export\s+interface\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\n\}/g)
188
- for (const m of interfaceMatches) {
189
- interfaces.push({
190
- name: m[1],
191
- shape: compact(m[2]),
192
- })
193
- }
194
-
195
- const typeMatches = content.matchAll(/export\s+type\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^;\n]+(?:\n[^;\n]+)*)/g)
196
- for (const m of typeMatches) {
197
- types.push({
198
- name: m[1],
199
- definition: compact(m[2].replace(/\n/g, ' ')),
200
- })
201
- }
202
-
203
- const enumMatches = content.matchAll(/export\s+enum\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}/g)
204
- for (const m of enumMatches) {
205
- const members = m[2]
206
- .split('\n')
207
- .map((line) => line.trim())
208
- .filter(Boolean)
209
- .map((line) => line.replace(/,$/, ''))
210
- enums.push({
211
- name: m[1],
212
- members,
213
- })
214
- }
215
-
216
- return { functions, constants, interfaces, types, enums }
217
- }
218
-
219
- const componentFiles = walkFiles(componentsDir, new Set(['.vue']))
220
- const composableFiles = walkFiles(composablesDir, new Set(['.ts']))
221
-
222
- const generatedAt = new Date().toISOString()
223
- const docModel = {
224
- generatedAt,
225
- scope: {
226
- componentCount: componentFiles.length,
227
- composableCount: composableFiles.length,
228
- },
229
- components: [],
230
- composables: [],
231
- }
232
-
233
- const lines = []
234
- lines.push('# AI Summary')
235
- lines.push('')
236
- lines.push('AI-first summary generated from source files with explicit type metadata.')
237
- lines.push('')
238
- lines.push(`Generated at: ${generatedAt}`)
239
- lines.push('')
240
- lines.push('## Scope')
241
- lines.push('')
242
- lines.push(`- Components: ${componentFiles.length}`)
243
- lines.push(`- Composables: ${composableFiles.length}`)
244
- lines.push('')
245
- lines.push('## Components')
246
- lines.push('')
247
-
248
- for (const file of componentFiles) {
249
- const rel = toPosix(path.relative(rootDir, file))
250
- const content = fs.readFileSync(file, 'utf8')
251
- const summary = extractTopDescription(content)
252
- const props = extractProps(content)
253
- const emits = extractEmits(content)
254
- const componentEntry = {
255
- path: rel,
256
- summary,
257
- props,
258
- emits,
259
- }
260
- docModel.components.push(componentEntry)
261
-
262
- lines.push(`### \`${rel}\``)
263
- lines.push(`- Summary: ${summary}`)
264
- if (props.length) {
265
- lines.push(`- Props (${props.length}):`)
266
- for (const prop of props) {
267
- const chunks = [
268
- `type=${prop.type}`,
269
- prop.required ? 'required=true' : 'required=false',
270
- ]
271
- if (prop.default !== undefined) chunks.push(`default=${prop.default}`)
272
- if (prop.description) chunks.push(`desc=${prop.description}`)
273
- lines.push(` - \`${prop.name}\` { ${chunks.join(' | ')} }`)
274
- }
275
- } else {
276
- lines.push('- Props: None declared in a local `Props` interface.')
277
- }
278
- if (emits.length) {
279
- lines.push(`- Emits (${emits.length}):`)
280
- for (const e of emits) {
281
- lines.push(` - \`${e.event}\`${e.payload ? ` payload: ${e.payload}` : ''}`)
282
- }
283
- } else {
284
- lines.push('- Emits: None detected.')
285
- }
286
- lines.push('')
287
- }
288
-
289
- lines.push('## Composables')
290
- lines.push('')
291
-
292
- for (const file of composableFiles) {
293
- const rel = toPosix(path.relative(rootDir, file))
294
- const content = fs.readFileSync(file, 'utf8')
295
- const summary = extractTopDescription(content)
296
- const exports = extractExports(content)
297
- const composableEntry = {
298
- path: rel,
299
- summary,
300
- exports,
301
- }
302
- docModel.composables.push(composableEntry)
303
-
304
- lines.push(`### \`${rel}\``)
305
- lines.push(`- Summary: ${summary}`)
306
- lines.push(`- Export functions (${exports.functions.length}): ${exports.functions.map((f) => `\`${f.signature}\``).join(', ') || 'none'}`)
307
- lines.push(`- Export interfaces (${exports.interfaces.length}): ${exports.interfaces.map((i) => `\`${i.name}\``).join(', ') || 'none'}`)
308
- lines.push(`- Export types (${exports.types.length}): ${exports.types.map((t) => `\`${t.name}\``).join(', ') || 'none'}`)
309
- lines.push(`- Export consts (${exports.constants.length}): ${exports.constants.map((c) => `\`${c.name}\``).join(', ') || 'none'}`)
310
- lines.push(`- Export enums (${exports.enums.length}): ${exports.enums.map((e) => `\`${e.name}\``).join(', ') || 'none'}`)
311
- lines.push('')
312
- }
313
-
314
- fs.mkdirSync(path.dirname(outMdFile), { recursive: true })
315
- fs.writeFileSync(outMdFile, lines.join('\n'))
316
- fs.writeFileSync(outJsonFile, JSON.stringify(docModel, null, 2))
317
- fs.writeFileSync(outTypeIndexFile, JSON.stringify(docModel, null, 2))
318
-
319
- console.log(`Generated ${toPosix(path.relative(rootDir, outMdFile))}`)
320
- console.log(`Generated ${toPosix(path.relative(rootDir, outJsonFile))}`)
321
- console.log(`Generated ${toPosix(path.relative(rootDir, outTypeIndexFile))}`)
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ const rootDir = process.cwd()
5
+ const componentsDir = path.join(rootDir, 'src/runtime/components')
6
+ const composablesDir = path.join(rootDir, 'src/runtime/composables')
7
+ const outMdFile = path.join(rootDir, 'docs/ai-summary.md')
8
+ const outJsonFile = path.join(rootDir, 'docs/ai-summary.json')
9
+ const outTypeIndexFile = path.join(rootDir, 'docs/type-index.json')
10
+
11
+ function walkFiles(dir, exts = new Set(['.vue', '.ts'])) {
12
+ const result = []
13
+ if (!fs.existsSync(dir)) return result
14
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
15
+ const full = path.join(dir, entry.name)
16
+ if (entry.isDirectory()) {
17
+ result.push(...walkFiles(full, exts))
18
+ } else if (exts.has(path.extname(entry.name))) {
19
+ result.push(full)
20
+ }
21
+ }
22
+ return result.sort((a, b) => a.localeCompare(b))
23
+ }
24
+
25
+ function toPosix(p) {
26
+ return p.split(path.sep).join('/')
27
+ }
28
+
29
+ function extractTopDescription(content) {
30
+ const m = content.match(/\/\*\*([\s\S]*?)\*\//)
31
+ if (!m) return 'No top-level description found.'
32
+ const lines = m[1]
33
+ .split('\n')
34
+ .map((l) => l.replace(/^\s*\*\s?/, '').trim())
35
+ .filter(Boolean)
36
+ .filter((l) => !l.toLowerCase().includes('vue-docgen'))
37
+ return lines[0] || 'No top-level description found.'
38
+ }
39
+
40
+ function compact(str) {
41
+ return str.replace(/\s+/g, ' ').trim()
42
+ }
43
+
44
+ function findInterfaceBody(content, interfaceName) {
45
+ const needle = `interface ${interfaceName}`
46
+ const start = content.indexOf(needle)
47
+ if (start === -1) return null
48
+
49
+ const braceStart = content.indexOf('{', start)
50
+ if (braceStart === -1) return null
51
+
52
+ let depth = 0
53
+ for (let i = braceStart; i < content.length; i++) {
54
+ const ch = content[i]
55
+ if (ch === '{') depth++
56
+ if (ch === '}') depth--
57
+ if (depth === 0) {
58
+ return content.slice(braceStart + 1, i)
59
+ }
60
+ }
61
+ return null
62
+ }
63
+
64
+ function extractWithDefaults(content) {
65
+ const defaults = {}
66
+ const m = content.match(/withDefaults\s*\(\s*defineProps[\s\S]*?,\s*\{([\s\S]*?)\}\s*\)/)
67
+ if (!m) return defaults
68
+
69
+ const lines = m[1].split('\n')
70
+ for (const line of lines) {
71
+ const clean = line.trim()
72
+ if (!clean || clean.startsWith('//')) continue
73
+ const kv = clean.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.+?)(?:,\s*)?(?:\/\/\s*(.*))?$/)
74
+ if (!kv) continue
75
+ const key = kv[1]
76
+ const rawValue = kv[2].trim()
77
+ const description = (kv[3] || '').trim()
78
+ defaults[key] = { value: rawValue, description }
79
+ }
80
+
81
+ return defaults
82
+ }
83
+
84
+ function inferTypeFromDefault(rawValue) {
85
+ if (rawValue === 'true' || rawValue === 'false') return 'boolean'
86
+ if (rawValue === 'null') return 'null'
87
+ if (/^['"].*['"]$/.test(rawValue)) return 'string'
88
+ if (/^-?\d+(\.\d+)?$/.test(rawValue)) return 'number'
89
+ if (/^\(\)\s*=>\s*\[\s*\]$/.test(rawValue)) return 'any[]'
90
+ if (/^\(\)\s*=>\s*\(\s*\{\s*\}\s*\)$/.test(rawValue)) return 'Record<string, any>'
91
+ if (/^\(\)\s*=>\s*\[/.test(rawValue)) return 'any[]'
92
+ if (/^\(\)\s*=>\s*\(\s*\{/.test(rawValue)) return 'Record<string, any>'
93
+ return 'unknown'
94
+ }
95
+
96
+ function extractProps(content) {
97
+ const props = []
98
+ const ifaceBody = findInterfaceBody(content, 'Props')
99
+ const defaults = extractWithDefaults(content)
100
+
101
+ if (ifaceBody) {
102
+ const lines = ifaceBody.split('\n')
103
+ for (const line of lines) {
104
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)(\?)?\s*:\s*([^/;]+(?:\|[^/;]+)*)[;]?\s*(?:\/\/\s*(.+))?\s*$/)
105
+ if (m) {
106
+ const name = m[1]
107
+ const optional = !!m[2]
108
+ const type = compact(m[3].trim())
109
+ const description = (m[4] || '').trim()
110
+ props.push({
111
+ name,
112
+ type,
113
+ required: !optional,
114
+ default: Object.prototype.hasOwnProperty.call(defaults, name) ? defaults[name].value : undefined,
115
+ description: description || (defaults[name]?.description || ''),
116
+ })
117
+ }
118
+ }
119
+ }
120
+
121
+ // Fallback for components where Props is empty and typed props come from imported intersections.
122
+ if (!props.length && Object.keys(defaults).length) {
123
+ for (const [name, meta] of Object.entries(defaults)) {
124
+ props.push({
125
+ name,
126
+ type: inferTypeFromDefault(meta.value),
127
+ required: false,
128
+ default: meta.value,
129
+ description: meta.description || 'Inferred from withDefaults()',
130
+ })
131
+ }
132
+ }
133
+
134
+ return props
135
+ }
136
+
137
+ function extractEmits(content) {
138
+ const emits = []
139
+
140
+ const typed = content.match(/defineEmits<\{([\s\S]*?)\}>\(\)/)
141
+ if (typed) {
142
+ const overloads = typed[1].matchAll(/\(\s*e\s*:\s*'([^']+)'\s*(?:,\s*([^)]*))?\)\s*:\s*void/g)
143
+ for (const m of overloads) {
144
+ emits.push({
145
+ event: m[1],
146
+ payload: m[2] ? compact(m[2]) : undefined,
147
+ })
148
+ }
149
+ }
150
+
151
+ const arr = content.match(/defineEmits\s*\(\s*\[([\s\S]*?)\]\s*\)/)
152
+ if (arr) {
153
+ const names = arr[1].matchAll(/'([^']+)'|"([^"]+)"/g)
154
+ for (const m of names) {
155
+ emits.push({ event: m[1] || m[2] })
156
+ }
157
+ }
158
+
159
+ return emits
160
+ }
161
+
162
+ function extractExports(content) {
163
+ const functions = []
164
+ const constants = []
165
+ const interfaces = []
166
+ const types = []
167
+ const enums = []
168
+
169
+ const fnMatches = content.matchAll(/export\s+function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([\s\S]*?)\)\s*(?::\s*([^({;\n]+(?:<[\s\S]*?>)?))?\s*\{/g)
170
+ for (const m of fnMatches) {
171
+ functions.push({
172
+ name: m[1],
173
+ params: compact(m[2]),
174
+ returns: m[3] ? compact(m[3]) : undefined,
175
+ signature: compact(`function ${m[1]}(${m[2]})${m[3] ? `: ${m[3]}` : ''}`),
176
+ })
177
+ }
178
+
179
+ const constMatches = content.matchAll(/export\s+const\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*([^=;\n]+))?\s*=/g)
180
+ for (const m of constMatches) {
181
+ constants.push({
182
+ name: m[1],
183
+ type: m[2] ? compact(m[2]) : undefined,
184
+ })
185
+ }
186
+
187
+ const interfaceMatches = content.matchAll(/export\s+interface\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\n\}/g)
188
+ for (const m of interfaceMatches) {
189
+ interfaces.push({
190
+ name: m[1],
191
+ shape: compact(m[2]),
192
+ })
193
+ }
194
+
195
+ const typeMatches = content.matchAll(/export\s+type\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^;\n]+(?:\n[^;\n]+)*)/g)
196
+ for (const m of typeMatches) {
197
+ types.push({
198
+ name: m[1],
199
+ definition: compact(m[2].replace(/\n/g, ' ')),
200
+ })
201
+ }
202
+
203
+ const enumMatches = content.matchAll(/export\s+enum\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}/g)
204
+ for (const m of enumMatches) {
205
+ const members = m[2]
206
+ .split('\n')
207
+ .map((line) => line.trim())
208
+ .filter(Boolean)
209
+ .map((line) => line.replace(/,$/, ''))
210
+ enums.push({
211
+ name: m[1],
212
+ members,
213
+ })
214
+ }
215
+
216
+ return { functions, constants, interfaces, types, enums }
217
+ }
218
+
219
+ const componentFiles = walkFiles(componentsDir, new Set(['.vue']))
220
+ const composableFiles = walkFiles(composablesDir, new Set(['.ts']))
221
+
222
+ const generatedAt = new Date().toISOString()
223
+ const docModel = {
224
+ generatedAt,
225
+ scope: {
226
+ componentCount: componentFiles.length,
227
+ composableCount: composableFiles.length,
228
+ },
229
+ components: [],
230
+ composables: [],
231
+ }
232
+
233
+ const lines = []
234
+ lines.push('# AI Summary')
235
+ lines.push('')
236
+ lines.push('AI-first summary generated from source files with explicit type metadata.')
237
+ lines.push('')
238
+ lines.push(`Generated at: ${generatedAt}`)
239
+ lines.push('')
240
+ lines.push('## Scope')
241
+ lines.push('')
242
+ lines.push(`- Components: ${componentFiles.length}`)
243
+ lines.push(`- Composables: ${composableFiles.length}`)
244
+ lines.push('')
245
+ lines.push('## Components')
246
+ lines.push('')
247
+
248
+ for (const file of componentFiles) {
249
+ const rel = toPosix(path.relative(rootDir, file))
250
+ const content = fs.readFileSync(file, 'utf8')
251
+ const summary = extractTopDescription(content)
252
+ const props = extractProps(content)
253
+ const emits = extractEmits(content)
254
+ const componentEntry = {
255
+ path: rel,
256
+ summary,
257
+ props,
258
+ emits,
259
+ }
260
+ docModel.components.push(componentEntry)
261
+
262
+ lines.push(`### \`${rel}\``)
263
+ lines.push(`- Summary: ${summary}`)
264
+ if (props.length) {
265
+ lines.push(`- Props (${props.length}):`)
266
+ for (const prop of props) {
267
+ const chunks = [
268
+ `type=${prop.type}`,
269
+ prop.required ? 'required=true' : 'required=false',
270
+ ]
271
+ if (prop.default !== undefined) chunks.push(`default=${prop.default}`)
272
+ if (prop.description) chunks.push(`desc=${prop.description}`)
273
+ lines.push(` - \`${prop.name}\` { ${chunks.join(' | ')} }`)
274
+ }
275
+ } else {
276
+ lines.push('- Props: None declared in a local `Props` interface.')
277
+ }
278
+ if (emits.length) {
279
+ lines.push(`- Emits (${emits.length}):`)
280
+ for (const e of emits) {
281
+ lines.push(` - \`${e.event}\`${e.payload ? ` payload: ${e.payload}` : ''}`)
282
+ }
283
+ } else {
284
+ lines.push('- Emits: None detected.')
285
+ }
286
+ lines.push('')
287
+ }
288
+
289
+ lines.push('## Composables')
290
+ lines.push('')
291
+
292
+ for (const file of composableFiles) {
293
+ const rel = toPosix(path.relative(rootDir, file))
294
+ const content = fs.readFileSync(file, 'utf8')
295
+ const summary = extractTopDescription(content)
296
+ const exports = extractExports(content)
297
+ const composableEntry = {
298
+ path: rel,
299
+ summary,
300
+ exports,
301
+ }
302
+ docModel.composables.push(composableEntry)
303
+
304
+ lines.push(`### \`${rel}\``)
305
+ lines.push(`- Summary: ${summary}`)
306
+ lines.push(`- Export functions (${exports.functions.length}): ${exports.functions.map((f) => `\`${f.signature}\``).join(', ') || 'none'}`)
307
+ lines.push(`- Export interfaces (${exports.interfaces.length}): ${exports.interfaces.map((i) => `\`${i.name}\``).join(', ') || 'none'}`)
308
+ lines.push(`- Export types (${exports.types.length}): ${exports.types.map((t) => `\`${t.name}\``).join(', ') || 'none'}`)
309
+ lines.push(`- Export consts (${exports.constants.length}): ${exports.constants.map((c) => `\`${c.name}\``).join(', ') || 'none'}`)
310
+ lines.push(`- Export enums (${exports.enums.length}): ${exports.enums.map((e) => `\`${e.name}\``).join(', ') || 'none'}`)
311
+ lines.push('')
312
+ }
313
+
314
+ fs.mkdirSync(path.dirname(outMdFile), { recursive: true })
315
+ fs.writeFileSync(outMdFile, lines.join('\n'))
316
+ fs.writeFileSync(outJsonFile, JSON.stringify(docModel, null, 2))
317
+ fs.writeFileSync(outTypeIndexFile, JSON.stringify(docModel, null, 2))
318
+
319
+ console.log(`Generated ${toPosix(path.relative(rootDir, outMdFile))}`)
320
+ console.log(`Generated ${toPosix(path.relative(rootDir, outJsonFile))}`)
321
+ console.log(`Generated ${toPosix(path.relative(rootDir, outTypeIndexFile))}`)