@rimelight/cms 0.0.2 → 0.0.4

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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bylines-Csggkl-g.d.mts +405 -0
  3. package/dist/index.d.mts +724 -0
  4. package/dist/index.mjs +2382 -0
  5. package/dist/integration.d.mts +33 -0
  6. package/dist/integration.mjs +161 -0
  7. package/dist/schema/index.d.mts +2063 -0
  8. package/dist/schema/index.mjs +176 -0
  9. package/dist/schema/sqlite.d.mts +2132 -0
  10. package/dist/schema/sqlite.mjs +137 -0
  11. package/dist/storage/index.d.mts +59 -0
  12. package/dist/storage/index.mjs +133 -0
  13. package/package.json +36 -18
  14. package/src/admin/api/media-file.ts +39 -0
  15. package/src/admin/api/media.ts +224 -0
  16. package/src/admin/layouts/CMSDashboardLayout.astro +191 -0
  17. package/src/admin/layouts/DefaultParentLayout.astro +50 -0
  18. package/src/admin/pages/assets.astro +424 -0
  19. package/src/admin/pages/byline-schema.astro +62 -0
  20. package/src/admin/pages/bylines.astro +16 -0
  21. package/src/admin/pages/categories.astro +21 -0
  22. package/src/admin/pages/content-types.astro +55 -0
  23. package/src/admin/pages/dashboard.astro +61 -0
  24. package/src/admin/pages/pages-edit.astro +595 -0
  25. package/src/admin/pages/pages-index.astro +146 -0
  26. package/src/admin/pages/pages-new.astro +130 -0
  27. package/src/admin/pages/pages-preview.astro +111 -0
  28. package/src/admin/pages/pages-review.astro +236 -0
  29. package/src/admin/pages/settings.astro +203 -0
  30. package/src/admin/pages/tags.astro +21 -0
  31. package/src/admin/pages/templates-edit.astro +234 -0
  32. package/src/admin/pages/templates-index.astro +95 -0
  33. package/src/admin/pages/templates-new.astro +193 -0
  34. package/src/admin/pages/users.astro +74 -0
  35. package/src/admin/pages/versions.astro +74 -0
  36. package/src/astro/BlockRenderer.astro +44 -102
  37. package/src/astro/PageRenderer.astro +12 -5
  38. package/src/astro/blocks/CalloutBlock.astro +7 -7
  39. package/src/astro/blocks/CardBlock.astro +5 -5
  40. package/src/astro/blocks/CodeBlock.astro +2 -2
  41. package/src/astro/blocks/FileTreeBlock.astro +7 -7
  42. package/src/astro/blocks/ImageBlock.astro +2 -2
  43. package/src/astro/blocks/OrderedListBlock.astro +2 -2
  44. package/src/astro/blocks/ParagraphBlock.astro +3 -3
  45. package/src/astro/blocks/SectionBlock.astro +6 -6
  46. package/src/astro/blocks/StepItemBlock.astro +3 -3
  47. package/src/astro/blocks/StepsBlock.astro +1 -1
  48. package/src/astro/blocks/TableBlock.astro +6 -6
  49. package/src/astro/blocks/TabsBlock.astro +9 -9
  50. package/src/astro/blocks/UnorderedListBlock.astro +2 -2
  51. package/src/astro/blocks/index.ts +35 -0
  52. package/src/client/components/RimelightBlock.tsx +19 -19
  53. package/src/client/components/RimelightBlockPicker.tsx +9 -13
  54. package/src/client/components/RimelightBylinesManager.tsx +438 -0
  55. package/src/client/components/RimelightEditor.tsx +3 -3
  56. package/src/client/components/RimelightPreview.tsx +37 -45
  57. package/src/client/components/RimelightPropertiesPanel.tsx +368 -26
  58. package/src/client/components/RimelightTaxonomyManager.tsx +369 -0
  59. package/src/client/components/RimelightTaxonomyMasterDetail.tsx +412 -0
  60. package/src/client/components/RimelightTemplateEditor.tsx +6 -8
  61. package/src/client/components/editors/CalloutEditor.tsx +2 -4
  62. package/src/client/components/editors/CardsEditor.tsx +2 -4
  63. package/src/client/components/editors/CodeEditor.tsx +1 -3
  64. package/src/client/components/editors/FileTreeEditor.tsx +2 -4
  65. package/src/client/components/editors/ImageEditor.tsx +3 -3
  66. package/src/client/components/editors/ParagraphEditor.tsx +2 -2
  67. package/src/client/components/editors/SceneEditor.tsx +6 -12
  68. package/src/client/components/editors/ScriptEditor.tsx +8 -16
  69. package/src/client/components/editors/SectionEditor.tsx +4 -4
  70. package/src/client/components/editors/StepItemEditor.tsx +2 -2
  71. package/src/client/components/editors/StepsEditor.tsx +2 -4
  72. package/src/client/components/editors/TabItemEditor.tsx +3 -3
  73. package/src/client/components/editors/TableEditor.tsx +5 -5
  74. package/src/client/components/editors/TabsEditor.tsx +2 -4
  75. package/src/client/components/index.ts +3 -0
  76. package/src/client/loader.ts +3 -0
  77. package/src/core/diff.ts +145 -0
  78. package/src/core/excerpt.ts +159 -0
  79. package/src/core/preview.ts +87 -0
  80. package/src/core/registry.ts +0 -86
  81. package/src/core/types/blocks.ts +6 -1
  82. package/src/core/types/bylines.ts +14 -0
  83. package/src/core/types/media.ts +13 -0
  84. package/src/core/types/taxonomies.ts +20 -0
  85. package/src/cron/scheduled.ts +57 -0
  86. package/src/docs/agent.ts +116 -0
  87. package/src/docs/config.ts +25 -0
  88. package/src/docs/index.ts +5 -0
  89. package/src/docs/routing.ts +104 -0
  90. package/src/docs/sidebar.ts +251 -0
  91. package/src/docs/toc.ts +47 -0
  92. package/src/env.d.ts +19 -0
  93. package/src/index.ts +57 -12
  94. package/src/integration.ts +250 -0
  95. package/src/loader/live.ts +254 -0
  96. package/src/markdown/index.ts +0 -1
  97. package/src/mcp/index.ts +228 -0
  98. package/src/schema/bylines.ts +23 -0
  99. package/src/schema/index.ts +3 -4
  100. package/src/schema/page_versions.ts +2 -0
  101. package/src/schema/pages.ts +2 -0
  102. package/src/schema/site_settings.ts +41 -0
  103. package/src/schema/sqlite.ts +344 -0
  104. package/src/schema/taxonomies.ts +49 -0
  105. package/src/services/bylines.ts +74 -0
  106. package/src/services/search-indexer.ts +61 -0
  107. package/src/services/site-settings.ts +108 -0
  108. package/src/services/taxonomies.ts +276 -0
  109. package/src/storage/index.ts +256 -0
  110. package/src/astro/blocks/ApiDocumentationBlock.astro +0 -350
  111. package/src/astro/blocks/DialogueBlock.astro +0 -23
  112. package/src/astro/blocks/LivePreviewBlock.astro +0 -27
  113. package/src/astro/blocks/SceneBlock.astro +0 -65
  114. package/src/astro/blocks/ScriptBlock.astro +0 -59
  115. package/src/core/template-sync.ts +0 -75
  116. package/src/markdown/parser.ts +0 -347
  117. package/src/migration-tools.ts +0 -334
  118. package/src/migration.ts +0 -155
  119. package/src/schema/component_dependencies.ts +0 -21
  120. package/src/schema/component_metadata.ts +0 -31
  121. package/src/schema/component_metadata_cache.ts +0 -15
  122. package/src/schema/component_versions.ts +0 -19
  123. package/src/search/sync.ts +0 -32
  124. package/src/services/component-metadata.ts +0 -134
@@ -1,347 +0,0 @@
1
- import type { BaseBlock, HeadingLevel } from "../core/types/blocks"
2
- import type { InlineNode } from "../core/types/inline"
3
-
4
- function genId(): string {
5
- return typeof crypto !== "undefined" && crypto.randomUUID
6
- ? crypto.randomUUID()
7
- : Math.random().toString(36).slice(2, 11)
8
- }
9
-
10
- export function parseInlines(text: string): InlineNode[] {
11
- if (!text) return []
12
-
13
- const inlines: InlineNode[] = []
14
- // Regex to match markdown links: [text](url)
15
- const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g
16
- let lastIndex = 0
17
- let match: RegExpExecArray | null
18
-
19
- while ((match = linkRegex.exec(text)) !== null) {
20
- const beforeText = text.substring(lastIndex, match.index)
21
- if (beforeText) {
22
- inlines.push(...parseFormattedText(beforeText))
23
- }
24
-
25
- const linkText = match[1] || ""
26
- const linkUrl = match[2] || ""
27
-
28
- inlines.push({
29
- type: "link",
30
- url: linkUrl,
31
- children: [{ type: "text", text: linkText }]
32
- })
33
-
34
- lastIndex = linkRegex.lastIndex
35
- }
36
-
37
- const remainingText = text.substring(lastIndex)
38
- if (remainingText) {
39
- inlines.push(...parseFormattedText(remainingText))
40
- }
41
-
42
- return inlines.length > 0 ? inlines : [{ type: "text", text }]
43
- }
44
-
45
- function parseFormattedText(str: string): InlineNode[] {
46
- if (!str) return []
47
-
48
- // Tokenize bold, italic, code marks
49
- // Format: `code`, **bold**, *italic*, ~~strikethrough~~
50
- const result: InlineNode[] = []
51
- const tokenRegex = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|~~[^~]+~~)/g
52
- let lastIdx = 0
53
- let match: RegExpExecArray | null
54
-
55
- while ((match = tokenRegex.exec(str)) !== null) {
56
- const rawBefore = str.substring(lastIdx, match.index)
57
- if (rawBefore) {
58
- result.push({ type: "text", text: rawBefore })
59
- }
60
-
61
- const token = match[1] || ""
62
- if (token.startsWith("`") && token.endsWith("`")) {
63
- result.push({
64
- type: "text",
65
- text: token.slice(1, -1),
66
- marks: ["code"]
67
- })
68
- } else if (token.startsWith("**") && token.endsWith("**")) {
69
- result.push({
70
- type: "text",
71
- text: token.slice(2, -2),
72
- marks: ["bold"]
73
- })
74
- } else if (token.startsWith("*") && token.endsWith("*")) {
75
- result.push({
76
- type: "text",
77
- text: token.slice(1, -1),
78
- marks: ["italic"]
79
- })
80
- } else if (token.startsWith("~~") && token.endsWith("~~")) {
81
- result.push({
82
- type: "text",
83
- text: token.slice(2, -2),
84
- marks: ["strikethrough"]
85
- })
86
- }
87
-
88
- lastIdx = tokenRegex.lastIndex
89
- }
90
-
91
- const remaining = str.substring(lastIdx)
92
- if (remaining) {
93
- result.push({ type: "text", text: remaining })
94
- }
95
-
96
- return result
97
- }
98
-
99
- export function parseMarkdownTable(tableText: string): BaseBlock | null {
100
- const lines = tableText
101
- .trim()
102
- .split("\n")
103
- .map((l) => l.trim())
104
- .filter(Boolean)
105
- if (lines.length < 2) return null
106
-
107
- const parseRow = (line: string) =>
108
- line
109
- .replace(/^\|/, "")
110
- .replace(/\|$/, "")
111
- .split("|")
112
- .map((c) => c.trim())
113
-
114
- const headerCells = parseRow(lines[0] || "")
115
- const alignCells = parseRow(lines[1] || "")
116
-
117
- // Verify second line is alignment separator (e.g. ---, :---:, ---:)
118
- if (!alignCells.every((c) => /^[:-]+$/.test(c))) {
119
- return null
120
- }
121
-
122
- const columns = headerCells.map((header, idx) => {
123
- const alignStr = alignCells[idx] || ""
124
- let align: "left" | "center" | "right" = "left"
125
- if (alignStr.startsWith(":") && alignStr.endsWith(":")) {
126
- align = "center"
127
- } else if (alignStr.endsWith(":")) {
128
- align = "right"
129
- }
130
- return {
131
- key: `col_${idx}`,
132
- header,
133
- align
134
- }
135
- })
136
-
137
- const rows: Record<string, string>[] = []
138
- for (let i = 2; i < lines.length; i++) {
139
- const cells = parseRow(lines[i] || "")
140
- const rowObj: Record<string, string> = {}
141
- columns.forEach((col, idx) => {
142
- rowObj[col.key] = cells[idx] || ""
143
- })
144
- rows.push(rowObj)
145
- }
146
-
147
- return {
148
- id: genId(),
149
- type: "TableBlock",
150
- props: {
151
- columns,
152
- rows,
153
- caption: ""
154
- }
155
- }
156
- }
157
-
158
- export function markdownToBlocks(markdown: string): BaseBlock[] {
159
- if (!markdown) return []
160
-
161
- // Strip frontmatter if present
162
- let cleanMd = markdown.trim()
163
- if (cleanMd.startsWith("---")) {
164
- const endFm = cleanMd.indexOf("---", 3)
165
- if (endFm !== -1) {
166
- cleanMd = cleanMd.substring(endFm + 3).trim()
167
- }
168
- }
169
-
170
- const rootBlocks: BaseBlock[] = []
171
- const sectionStack: { level: number; block: BaseBlock }[] = []
172
-
173
- const appendToCurrentScope = (block: BaseBlock) => {
174
- if (sectionStack.length > 0) {
175
- const topSection = sectionStack[sectionStack.length - 1]!.block
176
- if (!topSection.children) topSection.children = []
177
- if (!topSection.props.children) topSection.props.children = []
178
- topSection.children.push(block)
179
- topSection.props.children.push(block)
180
- } else {
181
- rootBlocks.push(block)
182
- }
183
- }
184
-
185
- const lines = cleanMd.split("\n")
186
- let i = 0
187
-
188
- while (i < lines.length) {
189
- const line = lines[i] || ""
190
- const trimmed = line.trim()
191
-
192
- if (!trimmed) {
193
- i++
194
- continue
195
- }
196
-
197
- // 1. Code Block Fence (```)
198
- if (trimmed.startsWith("```")) {
199
- const lang = trimmed.slice(3).trim()
200
- const codeLines: string[] = []
201
- i++
202
- while (i < lines.length && !(lines[i] || "").trim().startsWith("```")) {
203
- codeLines.push(lines[i] || "")
204
- i++
205
- }
206
- i++ // Skip closing ```
207
- appendToCurrentScope({
208
- id: genId(),
209
- type: "CodeBlock",
210
- props: {
211
- language: lang || "text",
212
- code: codeLines.join("\n"),
213
- caption: ""
214
- }
215
- })
216
- continue
217
- }
218
-
219
- // 2. Headings (# to ######)
220
- const headingMatch = line.match(/^(#{1,6})\s(.*)$/)
221
- if (headingMatch) {
222
- const hashes = headingMatch[1] || ""
223
- const title = (headingMatch[2] || "").trim()
224
- const level = Math.min(6, Math.max(2, hashes.length)) as HeadingLevel
225
-
226
- const newSection: BaseBlock = {
227
- id: genId(),
228
- type: "SectionBlock",
229
- children: [],
230
- props: {
231
- title,
232
- level,
233
- description: "",
234
- children: []
235
- }
236
- }
237
-
238
- // Pop sections with higher or equal level
239
- while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1]!.level >= level) {
240
- sectionStack.pop()
241
- }
242
-
243
- appendToCurrentScope(newSection)
244
- sectionStack.push({ level, block: newSection })
245
- i++
246
- continue
247
- }
248
-
249
- // 3. Callouts / Blockquotes (> [!NOTE], > [!WARNING], > etc.)
250
- if (trimmed.startsWith(">")) {
251
- const calloutLines: string[] = []
252
- while (i < lines.length && (lines[i] || "").trim().startsWith(">")) {
253
- calloutLines.push((lines[i] || "").trim().replace(/^>\s?/, ""))
254
- i++
255
- }
256
- const firstLine = calloutLines[0] || ""
257
- const variantMatch = firstLine.match(/^\[!([A-Z]+)\]/i)
258
- let variant = "info"
259
- let contentLines = calloutLines
260
-
261
- if (variantMatch) {
262
- variant = (variantMatch[1] || "info").toLowerCase()
263
- contentLines = calloutLines.slice(1)
264
- }
265
-
266
- const childMd = contentLines.join("\n").trim()
267
- const calloutChildren = childMd ? markdownToBlocks(childMd) : []
268
-
269
- appendToCurrentScope({
270
- id: genId(),
271
- type: "CalloutBlock",
272
- children: calloutChildren,
273
- props: {
274
- variant,
275
- children: calloutChildren
276
- }
277
- })
278
- continue
279
- }
280
-
281
- // 4. Markdown Table (| Header | Header |)
282
- if (trimmed.startsWith("|") && trimmed.endsWith("|")) {
283
- const tableLines: string[] = []
284
- while (
285
- i < lines.length &&
286
- (lines[i] || "").trim().startsWith("|") &&
287
- (lines[i] || "").trim().endsWith("|")
288
- ) {
289
- tableLines.push(lines[i] || "")
290
- i++
291
- }
292
- const tableBlock = parseMarkdownTable(tableLines.join("\n"))
293
- if (tableBlock) {
294
- appendToCurrentScope(tableBlock)
295
- continue
296
- }
297
- }
298
-
299
- // 5. Image (![alt](src))
300
- const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/)
301
- if (imageMatch) {
302
- appendToCurrentScope({
303
- id: genId(),
304
- type: "ImageBlock",
305
- props: {
306
- alt: imageMatch[1] || "",
307
- src: imageMatch[2] || "",
308
- caption: ""
309
- }
310
- })
311
- i++
312
- continue
313
- }
314
-
315
- // 6. Regular Paragraph (gather lines until blank line or block start)
316
- const paragraphLines: string[] = []
317
- while (
318
- i < lines.length &&
319
- (lines[i] || "").trim() &&
320
- !(lines[i] || "").trim().startsWith("```") &&
321
- !(lines[i] || "").trim().startsWith("#") &&
322
- !(lines[i] || "").trim().startsWith(">") &&
323
- !((lines[i] || "").trim().startsWith("|") && (lines[i] || "").trim().endsWith("|")) &&
324
- !/^[0-9]+\.\s/.test((lines[i] || "").trim())
325
- ) {
326
- paragraphLines.push(lines[i] || "")
327
- i++
328
- }
329
-
330
- if (paragraphLines.length > 0) {
331
- const paragraphText = paragraphLines.join(" ").trim()
332
- const inlines = parseInlines(paragraphText)
333
- appendToCurrentScope({
334
- id: genId(),
335
- type: "ParagraphBlock",
336
- props: {
337
- text: inlines
338
- }
339
- })
340
- } else {
341
- // Advance if no block consumed
342
- i++
343
- }
344
- }
345
-
346
- return rootBlocks
347
- }
@@ -1,334 +0,0 @@
1
- import type { BaseBlock, ComponentShowcaseBlock } from "./core/types/blocks.ts"
2
-
3
- export interface ComponentReference {
4
- componentName: string
5
- componentPath: string
6
- framework: "astro" | "vue" | "solid"
7
- propsConfig: Record<string, string[]> | undefined
8
- defaultProps: Record<string, any> | undefined
9
- togglingPattern: "icon" | "select-option" | "tab-segmented" | "pill-toggle" | undefined
10
- codeExamples: {
11
- astro?: string
12
- vue?: string
13
- solid?: string
14
- } | undefined
15
- playgroundUrl: string | undefined
16
- }
17
-
18
- export interface MigrationReport {
19
- totalFiles: number
20
- migratedFiles: number
21
- totalComponentShowcases: number
22
- migratedComponentShowcases: number
23
- errors: string[]
24
- warnings: string[]
25
- }
26
-
27
- export interface ValidationResult {
28
- valid: boolean
29
- errors: string[]
30
- warnings: string[]
31
- }
32
-
33
- export class MigrationTools {
34
- convertMDXToBlocks(mdxContent: string): BaseBlock[] {
35
- const blocks: BaseBlock[] = []
36
- const lines = mdxContent.split("\n")
37
- let i = 0
38
-
39
- while (i < lines.length) {
40
- const line = lines[i] || ""
41
- const trimmed = line.trim()
42
-
43
- if (!trimmed) {
44
- i++
45
- continue
46
- }
47
-
48
- if (trimmed.startsWith("```")) {
49
- const lang = trimmed.slice(3).trim()
50
- const codeLines: string[] = []
51
- i++
52
- while (i < lines.length && !(lines[i] || "").trim().startsWith("```")) {
53
- codeLines.push(lines[i] || "")
54
- i++
55
- }
56
- i++
57
- blocks.push({
58
- id: crypto.randomUUID(),
59
- type: "CodeBlock",
60
- props: {
61
- language: lang || "text",
62
- code: codeLines.join("\n"),
63
- caption: ""
64
- }
65
- })
66
- continue
67
- }
68
-
69
- if (trimmed.startsWith("#")) {
70
- const match = line.match(/^(#{1,6})\s(.*)$/)
71
- if (match) {
72
- const level = Math.min(6, Math.max(2, match[1]!.length)) as 2 | 3 | 4 | 5 | 6
73
- blocks.push({
74
- id: crypto.randomUUID(),
75
- type: "SectionBlock",
76
- props: {
77
- title: match[2] || "",
78
- level,
79
- description: "",
80
- children: []
81
- },
82
- children: []
83
- })
84
- i++
85
- continue
86
- }
87
- }
88
-
89
- if (trimmed.startsWith(">")) {
90
- const calloutLines: string[] = []
91
- while (i < lines.length && (lines[i] || "").trim().startsWith(">")) {
92
- calloutLines.push((lines[i] || "").trim().replace(/^>\s?/, ""))
93
- i++
94
- }
95
- const firstLine = calloutLines[0] || ""
96
- const variantMatch = firstLine.match(/^\[!([A-Z]+)\]/i)
97
- const variant = variantMatch ? (variantMatch[1] || "info").toLowerCase() : "info"
98
-
99
- blocks.push({
100
- id: crypto.randomUUID(),
101
- type: "CalloutBlock",
102
- props: {
103
- variant: variant as "info" | "success" | "warning" | "error" | "commentary" | "ideation" | "source",
104
- children: []
105
- },
106
- children: []
107
- })
108
- continue
109
- }
110
-
111
- if (trimmed.startsWith("|") && trimmed.endsWith("|")) {
112
- const tableLines: string[] = []
113
- while (i < lines.length && (lines[i] || "").trim().startsWith("|") && (lines[i] || "").trim().endsWith("|")) {
114
- tableLines.push(lines[i] || "")
115
- i++
116
- }
117
- const tableBlock = this.parseMarkdownTable(tableLines.join("\n"))
118
- if (tableBlock) {
119
- blocks.push(tableBlock)
120
- continue
121
- }
122
- }
123
-
124
- if (trimmed.startsWith("!")) {
125
- const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/)
126
- if (imageMatch) {
127
- blocks.push({
128
- id: crypto.randomUUID(),
129
- type: "ImageBlock",
130
- props: {
131
- alt: imageMatch[1] || "",
132
- src: imageMatch[2] || "",
133
- caption: ""
134
- }
135
- })
136
- i++
137
- continue
138
- }
139
- }
140
-
141
- const paragraphLines: string[] = []
142
- while (
143
- i < lines.length &&
144
- (lines[i] || "").trim() &&
145
- !(lines[i] || "").trim().startsWith("```") &&
146
- !(lines[i] || "").trim().startsWith("#") &&
147
- !(lines[i] || "").trim().startsWith(">") &&
148
- !((lines[i] || "").trim().startsWith("|") && (lines[i] || "").trim().endsWith("|"))
149
- ) {
150
- paragraphLines.push(lines[i] || "")
151
- i++
152
- }
153
-
154
- if (paragraphLines.length > 0) {
155
- blocks.push({
156
- id: crypto.randomUUID(),
157
- type: "ParagraphBlock",
158
- props: {
159
- text: { en: paragraphLines.join(" ").trim() }
160
- }
161
- })
162
- } else {
163
- i++
164
- }
165
- }
166
-
167
- return blocks
168
- }
169
-
170
- extractComponentReferences(mdxContent: string): ComponentReference[] {
171
- const references: ComponentReference[] = []
172
- const componentRegex = /<ComponentShowcase[^>]*>/g
173
- let match: RegExpExecArray | null
174
-
175
- while ((match = componentRegex.exec(mdxContent)) !== null) {
176
- const tag = match[0]
177
- const componentNameMatch = tag.match(/componentName=["']([^"']+)["']/)
178
- const componentPathMatch = tag.match(/componentPath=["']([^"']+)["']/)
179
- const frameworkMatch = tag.match(/framework=["']([^"']+)["']/)
180
- const propsConfigMatch = tag.match(/propsConfig=\{\{([^}]+)\}\}/)
181
- const defaultPropsMatch = tag.match(/defaultProps=\{\{([^}]+)\}\}/)
182
- const togglingPatternMatch = tag.match(/togglingPattern=["']([^"']+)["']/)
183
-
184
- references.push({
185
- componentName: componentNameMatch?.[1] || "unknown",
186
- componentPath: componentPathMatch?.[1] || "",
187
- framework: (frameworkMatch?.[1] as "astro" | "vue" | "solid") || "astro",
188
- propsConfig: propsConfigMatch?.[1] ? this.parseSimpleObject(propsConfigMatch[1]) : undefined,
189
- defaultProps: defaultPropsMatch?.[1] ? this.parseSimpleObject(defaultPropsMatch[1]) : undefined,
190
- togglingPattern: togglingPatternMatch?.[1] as "icon" | "select-option" | "tab-segmented" | "pill-toggle" || "tab-segmented",
191
- codeExamples: undefined,
192
- playgroundUrl: undefined
193
- })
194
- }
195
-
196
- return references
197
- }
198
-
199
- validateMigratedContent(blocks: BaseBlock[]): ValidationResult {
200
- const errors: string[] = []
201
- const warnings: string[] = []
202
-
203
- const seenIds = new Set<string>()
204
- const blockCounts: Record<string, number> = {}
205
-
206
- function checkBlocks(nodes: BaseBlock[]) {
207
- for (const b of nodes) {
208
- if (!b.id || typeof b.id !== "string") {
209
- errors.push(`Block of type '${b.type}' missing id`)
210
- } else if (seenIds.has(b.id)) {
211
- warnings.push(`Duplicate block id: ${b.id}`)
212
- } else {
213
- seenIds.add(b.id)
214
- }
215
-
216
- if (!b.type || typeof b.type !== "string") {
217
- errors.push("Block missing valid 'type' field")
218
- }
219
-
220
- blockCounts[b.type] = (blockCounts[b.type] || 0) + 1
221
-
222
- if (b.children && Array.isArray(b.children)) {
223
- checkBlocks(b.children)
224
- }
225
- }
226
- }
227
-
228
- checkBlocks(blocks)
229
-
230
- return {
231
- valid: errors.length === 0,
232
- errors,
233
- warnings
234
- }
235
- }
236
-
237
- generateMigrationReport(original: string, migrated: BaseBlock[]): MigrationReport {
238
- const componentRefs = this.extractComponentReferences(original)
239
- const validation = this.validateMigratedContent(migrated)
240
-
241
- return {
242
- totalFiles: 1,
243
- migratedFiles: validation.valid ? 1 : 0,
244
- totalComponentShowcases: componentRefs.length,
245
- migratedComponentShowcases: componentRefs.length,
246
- errors: validation.errors,
247
- warnings: validation.warnings
248
- }
249
- }
250
-
251
- convertComponentShowcaseToBlock(ref: ComponentReference): ComponentShowcaseBlock {
252
- return {
253
- id: crypto.randomUUID(),
254
- type: "ComponentShowcaseBlock",
255
- props: {
256
- componentName: ref.componentName,
257
- componentPath: ref.componentPath,
258
- framework: ref.framework,
259
- defaultProps: ref.defaultProps || {},
260
- propsConfig: ref.propsConfig || {},
261
- togglingPattern: ref.togglingPattern || "tab-segmented",
262
- codeExamples: ref.codeExamples || {},
263
- playgroundUrl: ref.playgroundUrl
264
- },
265
- children: []
266
- }
267
- }
268
-
269
- private parseSimpleObject(str: string): Record<string, any> {
270
- try {
271
- return new Function(`return ${str}`)()
272
- } catch {
273
- return {}
274
- }
275
- }
276
-
277
- private parseMarkdownTable(tableText: string): BaseBlock | null {
278
- const lines = tableText
279
- .trim()
280
- .split("\n")
281
- .map((l) => l.trim())
282
- .filter(Boolean)
283
- if (lines.length < 2) return null
284
-
285
- const parseRow = (line: string) =>
286
- line
287
- .replace(/^\|/, "")
288
- .replace(/\|$/, "")
289
- .split("|")
290
- .map((c) => c.trim())
291
-
292
- const headerCells = parseRow(lines[0] || "")
293
- const alignCells = parseRow(lines[1] || "")
294
-
295
- if (!alignCells.every((c) => /^[:-]+$/.test(c))) {
296
- return null
297
- }
298
-
299
- const columns = headerCells.map((header, idx) => {
300
- const alignStr = alignCells[idx] || ""
301
- let align: "left" | "center" | "right" = "left"
302
- if (alignStr.startsWith(":") && alignStr.endsWith(":")) {
303
- align = "center"
304
- } else if (alignStr.endsWith(":")) {
305
- align = "right"
306
- }
307
- return {
308
- key: `col_${idx}`,
309
- header,
310
- align
311
- }
312
- })
313
-
314
- const rows: Record<string, string>[] = []
315
- for (let i = 2; i < lines.length; i++) {
316
- const cells = parseRow(lines[i] || "")
317
- const rowObj: Record<string, string> = {}
318
- columns.forEach((col, idx) => {
319
- rowObj[col.key] = cells[idx] || ""
320
- })
321
- rows.push(rowObj)
322
- }
323
-
324
- return {
325
- id: crypto.randomUUID(),
326
- type: "TableBlock",
327
- props: {
328
- columns,
329
- rows,
330
- caption: ""
331
- }
332
- }
333
- }
334
- }