@zenithbuild/core 0.1.0 → 0.3.1

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 (90) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +24 -40
  3. package/bin/zen-build.ts +2 -0
  4. package/bin/zen-dev.ts +2 -0
  5. package/bin/zen-preview.ts +2 -0
  6. package/bin/zenith.ts +2 -0
  7. package/cli/commands/add.ts +37 -0
  8. package/cli/commands/build.ts +37 -0
  9. package/cli/commands/create.ts +702 -0
  10. package/cli/commands/dev.ts +197 -0
  11. package/cli/commands/index.ts +112 -0
  12. package/cli/commands/preview.ts +62 -0
  13. package/cli/commands/remove.ts +33 -0
  14. package/cli/index.ts +10 -0
  15. package/cli/main.ts +101 -0
  16. package/cli/utils/branding.ts +153 -0
  17. package/cli/utils/logger.ts +40 -0
  18. package/cli/utils/plugin-manager.ts +114 -0
  19. package/cli/utils/project.ts +71 -0
  20. package/compiler/build-analyzer.ts +122 -0
  21. package/compiler/discovery/layouts.ts +61 -0
  22. package/compiler/index.ts +40 -24
  23. package/compiler/ir/types.ts +1 -0
  24. package/compiler/parse/parseScript.ts +29 -5
  25. package/compiler/parse/parseTemplate.ts +96 -58
  26. package/compiler/parse/scriptAnalysis.ts +77 -0
  27. package/compiler/runtime/dataExposure.ts +49 -31
  28. package/compiler/runtime/generateDOM.ts +18 -17
  29. package/compiler/runtime/generateHydrationBundle.ts +24 -5
  30. package/compiler/runtime/transformIR.ts +140 -49
  31. package/compiler/runtime/wrapExpressionWithLoop.ts +11 -11
  32. package/compiler/spa-build.ts +70 -153
  33. package/compiler/ssg-build.ts +412 -0
  34. package/compiler/transform/layoutProcessor.ts +132 -0
  35. package/compiler/transform/transformNode.ts +19 -19
  36. package/dist/cli.js +11648 -0
  37. package/dist/zen-build.js +11659 -0
  38. package/dist/zen-dev.js +11659 -0
  39. package/dist/zen-preview.js +11659 -0
  40. package/dist/zenith.js +11659 -0
  41. package/package.json +22 -2
  42. package/runtime/bundle-generator.ts +416 -0
  43. package/runtime/client-runtime.ts +532 -0
  44. package/.eslintignore +0 -15
  45. package/.gitattributes +0 -2
  46. package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +0 -25
  47. package/.github/ISSUE_TEMPLATE/new_ticket.yaml +0 -34
  48. package/.github/pull_request_template.md +0 -15
  49. package/.github/workflows/discord-changelog.yml +0 -141
  50. package/.github/workflows/discord-notify.yml +0 -242
  51. package/.github/workflows/discord-version.yml +0 -195
  52. package/.prettierignore +0 -13
  53. package/.prettierrc +0 -21
  54. package/.zen.d.ts +0 -15
  55. package/app/components/Button.zen +0 -46
  56. package/app/components/Link.zen +0 -11
  57. package/app/favicon.ico +0 -0
  58. package/app/layouts/Main.zen +0 -59
  59. package/app/pages/about.zen +0 -23
  60. package/app/pages/blog/[id].zen +0 -53
  61. package/app/pages/blog/index.zen +0 -32
  62. package/app/pages/dynamic-dx.zen +0 -712
  63. package/app/pages/dynamic-primitives.zen +0 -453
  64. package/app/pages/index.zen +0 -154
  65. package/app/pages/navigation-demo.zen +0 -229
  66. package/app/pages/posts/[...slug].zen +0 -61
  67. package/app/pages/primitives-demo.zen +0 -273
  68. package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
  69. package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
  70. package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
  71. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
  72. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +0 -601
  73. package/assets/logos/README.md +0 -54
  74. package/assets/logos/zen.icns +0 -0
  75. package/bun.lock +0 -39
  76. package/compiler/legacy/binding.ts +0 -254
  77. package/compiler/legacy/bindings.ts +0 -338
  78. package/compiler/legacy/component-process.ts +0 -1208
  79. package/compiler/legacy/component.ts +0 -301
  80. package/compiler/legacy/event.ts +0 -50
  81. package/compiler/legacy/expression.ts +0 -1149
  82. package/compiler/legacy/mutation.ts +0 -280
  83. package/compiler/legacy/parse.ts +0 -299
  84. package/compiler/legacy/split.ts +0 -608
  85. package/compiler/legacy/types.ts +0 -32
  86. package/docs/COMMENTS.md +0 -111
  87. package/docs/COMMITS.md +0 -36
  88. package/docs/CONTRIBUTING.md +0 -116
  89. package/docs/STYLEGUIDE.md +0 -62
  90. package/scripts/webhook-proxy.ts +0 -213
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @zenith/cli - Logger Utility
3
+ *
4
+ * Colored console output for CLI feedback
5
+ */
6
+
7
+ const colors = {
8
+ reset: '\x1b[0m',
9
+ bright: '\x1b[1m',
10
+ dim: '\x1b[2m',
11
+ red: '\x1b[31m',
12
+ green: '\x1b[32m',
13
+ yellow: '\x1b[33m',
14
+ blue: '\x1b[34m',
15
+ cyan: '\x1b[36m'
16
+ }
17
+
18
+ export function log(message: string): void {
19
+ console.log(`${colors.cyan}[zenith]${colors.reset} ${message}`)
20
+ }
21
+
22
+ export function success(message: string): void {
23
+ console.log(`${colors.green}✓${colors.reset} ${message}`)
24
+ }
25
+
26
+ export function warn(message: string): void {
27
+ console.log(`${colors.yellow}⚠${colors.reset} ${message}`)
28
+ }
29
+
30
+ export function error(message: string): void {
31
+ console.error(`${colors.red}✗${colors.reset} ${message}`)
32
+ }
33
+
34
+ export function info(message: string): void {
35
+ console.log(`${colors.blue}ℹ${colors.reset} ${message}`)
36
+ }
37
+
38
+ export function header(title: string): void {
39
+ console.log(`\n${colors.bright}${colors.cyan}${title}${colors.reset}\n`)
40
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @zenith/cli - Plugin Manager
3
+ *
4
+ * Manages zenith.plugins.json for plugin registration
5
+ */
6
+
7
+ import fs from 'fs'
8
+ import path from 'path'
9
+ import { findProjectRoot } from './project'
10
+ import * as logger from './logger'
11
+
12
+ export interface PluginConfig {
13
+ name: string
14
+ installedAt: string
15
+ options?: Record<string, unknown>
16
+ }
17
+
18
+ export interface PluginsFile {
19
+ plugins: PluginConfig[]
20
+ }
21
+
22
+ const PLUGINS_FILE = 'zenith.plugins.json'
23
+
24
+ /**
25
+ * Get path to plugins file
26
+ */
27
+ function getPluginsPath(): string {
28
+ const root = findProjectRoot()
29
+ if (!root) {
30
+ throw new Error('Not in a Zenith project')
31
+ }
32
+ return path.join(root, PLUGINS_FILE)
33
+ }
34
+
35
+ /**
36
+ * Read plugins file
37
+ */
38
+ export function readPlugins(): PluginsFile {
39
+ const pluginsPath = getPluginsPath()
40
+
41
+ if (!fs.existsSync(pluginsPath)) {
42
+ return { plugins: [] }
43
+ }
44
+
45
+ try {
46
+ return JSON.parse(fs.readFileSync(pluginsPath, 'utf-8'))
47
+ } catch {
48
+ return { plugins: [] }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Write plugins file
54
+ */
55
+ function writePlugins(data: PluginsFile): void {
56
+ const pluginsPath = getPluginsPath()
57
+ fs.writeFileSync(pluginsPath, JSON.stringify(data, null, 2))
58
+ }
59
+
60
+ /**
61
+ * Add a plugin to the registry
62
+ */
63
+ export function addPlugin(name: string, options?: Record<string, unknown>): boolean {
64
+ const data = readPlugins()
65
+
66
+ // Check if already installed
67
+ if (data.plugins.some(p => p.name === name)) {
68
+ logger.warn(`Plugin "${name}" is already registered`)
69
+ return false
70
+ }
71
+
72
+ data.plugins.push({
73
+ name,
74
+ installedAt: new Date().toISOString(),
75
+ options
76
+ })
77
+
78
+ writePlugins(data)
79
+ logger.success(`Added plugin "${name}"`)
80
+ return true
81
+ }
82
+
83
+ /**
84
+ * Remove a plugin from the registry
85
+ */
86
+ export function removePlugin(name: string): boolean {
87
+ const data = readPlugins()
88
+ const initialLength = data.plugins.length
89
+
90
+ data.plugins = data.plugins.filter(p => p.name !== name)
91
+
92
+ if (data.plugins.length === initialLength) {
93
+ logger.warn(`Plugin "${name}" is not registered`)
94
+ return false
95
+ }
96
+
97
+ writePlugins(data)
98
+ logger.success(`Removed plugin "${name}"`)
99
+ return true
100
+ }
101
+
102
+ /**
103
+ * List all registered plugins
104
+ */
105
+ export function listPlugins(): PluginConfig[] {
106
+ return readPlugins().plugins
107
+ }
108
+
109
+ /**
110
+ * Check if a plugin is registered
111
+ */
112
+ export function hasPlugin(name: string): boolean {
113
+ return readPlugins().plugins.some(p => p.name === name)
114
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @zenith/cli - Project Utility
3
+ *
4
+ * Detects Zenith project root and configuration
5
+ */
6
+
7
+ import fs from 'fs'
8
+ import path from 'path'
9
+
10
+ export interface ZenithProject {
11
+ root: string
12
+ pagesDir: string
13
+ distDir: string
14
+ hasZenithDeps: boolean
15
+ }
16
+
17
+ /**
18
+ * Find the project root by looking for package.json with @zenith dependencies
19
+ */
20
+ export function findProjectRoot(startDir: string = process.cwd()): string | null {
21
+ let current = startDir
22
+
23
+ while (current !== path.dirname(current)) {
24
+ const pkgPath = path.join(current, 'package.json')
25
+
26
+ if (fs.existsSync(pkgPath)) {
27
+ try {
28
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
29
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
30
+
31
+ // Check for any @zenith/* dependency
32
+ const hasZenith = Object.keys(deps).some(d => d.startsWith('@zenith/'))
33
+ if (hasZenith) {
34
+ return current
35
+ }
36
+ } catch {
37
+ // Invalid JSON, skip
38
+ }
39
+ }
40
+
41
+ current = path.dirname(current)
42
+ }
43
+
44
+ return null
45
+ }
46
+
47
+ /**
48
+ * Get project configuration
49
+ */
50
+ export function getProject(cwd: string = process.cwd()): ZenithProject | null {
51
+ const root = findProjectRoot(cwd)
52
+ if (!root) return null
53
+
54
+ return {
55
+ root,
56
+ pagesDir: path.join(root, 'app/pages'),
57
+ distDir: path.join(root, 'app/dist'),
58
+ hasZenithDeps: true
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Ensure we're in a Zenith project
64
+ */
65
+ export function requireProject(cwd: string = process.cwd()): ZenithProject {
66
+ const project = getProject(cwd)
67
+ if (!project) {
68
+ throw new Error('Not in a Zenith project. Run this command from a directory with @zenith/* dependencies.')
69
+ }
70
+ return project
71
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Zenith Build Analyzer
3
+ *
4
+ * Analyzes .zen page source to determine build strategy:
5
+ * - Static: Pure HTML+CSS, no JS needed
6
+ * - Hydration: Has state/events/hooks, needs page-specific JS
7
+ * - SSR: Uses useFetchServer, needs server rendering
8
+ * - SPA: Uses ZenLink with passHref, needs client router
9
+ */
10
+
11
+ export interface PageAnalysis {
12
+ /** Page has state declarations that need hydration */
13
+ hasState: boolean
14
+ /** Page has event handlers (onclick, etc.) */
15
+ hasEventHandlers: boolean
16
+ /** Page uses lifecycle hooks (zenOnMount, zenOnUnmount) */
17
+ hasLifecycleHooks: boolean
18
+ /** Page uses useFetchServer (requires SSR) */
19
+ usesServerFetch: boolean
20
+ /** Page uses useFetchClient (client-side data) */
21
+ usesClientFetch: boolean
22
+ /** Page uses ZenLink with passHref (SPA navigation) */
23
+ usesZenLink: boolean
24
+ /** Page uses reactive expressions in templates */
25
+ hasReactiveExpressions: boolean
26
+
27
+ /** Computed: page needs any JavaScript */
28
+ needsHydration: boolean
29
+ /** Computed: page is purely static (no JS) */
30
+ isStatic: boolean
31
+ /** Computed: page needs SSR */
32
+ needsSSR: boolean
33
+ }
34
+
35
+ /**
36
+ * Analyze a .zen page source to determine build requirements
37
+ */
38
+ export function analyzePageSource(source: string): PageAnalysis {
39
+ // Extract script content for analysis
40
+ const scriptMatch = source.match(/<script[^>]*>([\s\S]*?)<\/script>/i)
41
+ const scriptContent = scriptMatch?.[1] || ''
42
+
43
+ // Extract template content (everything outside script/style)
44
+ const templateContent = source
45
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
46
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
47
+
48
+ // Check for state declarations: "state varName = ..."
49
+ const hasState = /\bstate\s+\w+\s*=/.test(scriptContent)
50
+
51
+ // Check for event handlers in template
52
+ const hasEventHandlers = /\bon(click|change|input|submit|focus|blur|keydown|keyup|keypress|mousedown|mouseup|mouseover|mouseout|mouseenter|mouseleave)\s*=\s*["'{]/.test(templateContent)
53
+
54
+ // Check for lifecycle hooks
55
+ const hasLifecycleHooks = /\bzen(OnMount|OnUnmount)\s*\(/.test(scriptContent)
56
+
57
+ // Check for server fetch
58
+ const usesServerFetch = /\buseFetchServer\s*\(/.test(scriptContent)
59
+
60
+ // Check for client fetch
61
+ const usesClientFetch = /\buseFetchClient\s*\(/.test(scriptContent)
62
+
63
+ // Check for ZenLink with passHref
64
+ const usesZenLink = /<ZenLink[^>]*passHref[^>]*>/.test(templateContent)
65
+
66
+ // Check for reactive expressions in template: {expression}
67
+ // Must be actual expressions, not just static text
68
+ // Exclude attribute values like href="/path"
69
+ const hasReactiveExpressions = /{[a-zA-Z_][a-zA-Z0-9_]*}/.test(templateContent)
70
+
71
+ // Compute derived properties
72
+ const needsHydration = hasState || hasEventHandlers || hasLifecycleHooks ||
73
+ usesClientFetch || hasReactiveExpressions
74
+ const isStatic = !needsHydration && !usesServerFetch
75
+ const needsSSR = usesServerFetch
76
+
77
+ return {
78
+ hasState,
79
+ hasEventHandlers,
80
+ hasLifecycleHooks,
81
+ usesServerFetch,
82
+ usesClientFetch,
83
+ usesZenLink,
84
+ hasReactiveExpressions,
85
+ needsHydration,
86
+ isStatic,
87
+ needsSSR
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get a human-readable summary of the page analysis
93
+ */
94
+ export function getAnalysisSummary(analysis: PageAnalysis): string {
95
+ const flags: string[] = []
96
+
97
+ if (analysis.isStatic) {
98
+ flags.push('STATIC (no JS)')
99
+ } else {
100
+ if (analysis.hasState) flags.push('state')
101
+ if (analysis.hasEventHandlers) flags.push('events')
102
+ if (analysis.hasLifecycleHooks) flags.push('lifecycle')
103
+ if (analysis.hasReactiveExpressions) flags.push('reactive')
104
+ if (analysis.usesClientFetch) flags.push('clientFetch')
105
+ }
106
+
107
+ if (analysis.needsSSR) flags.push('SSR')
108
+ if (analysis.usesZenLink) flags.push('SPA')
109
+
110
+ return flags.length > 0 ? flags.join(', ') : 'minimal'
111
+ }
112
+
113
+ /**
114
+ * Determine build output type for a page
115
+ */
116
+ export type BuildOutputType = 'static' | 'hydrated' | 'ssr'
117
+
118
+ export function getBuildOutputType(analysis: PageAnalysis): BuildOutputType {
119
+ if (analysis.needsSSR) return 'ssr'
120
+ if (analysis.needsHydration) return 'hydrated'
121
+ return 'static'
122
+ }
@@ -0,0 +1,61 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { parseTemplate } from '../parse/parseTemplate'
4
+ import { parseScript } from '../parse/parseScript'
5
+ import { extractProps, extractStateDeclarations } from '../parse/scriptAnalysis'
6
+
7
+ export interface LayoutMetadata {
8
+ name: string
9
+ filePath: string
10
+ props: string[]
11
+ states: Map<string, string>
12
+ html: string
13
+ scripts: string[]
14
+ styles: string[]
15
+ }
16
+
17
+ /**
18
+ * Discover layouts in a directory
19
+ */
20
+ export function discoverLayouts(layoutsDir: string): Map<string, LayoutMetadata> {
21
+ const layouts = new Map<string, LayoutMetadata>()
22
+
23
+ if (!fs.existsSync(layoutsDir)) return layouts
24
+
25
+ const files = fs.readdirSync(layoutsDir)
26
+ for (const file of files) {
27
+ if (file.endsWith('.zen')) {
28
+ const filePath = path.join(layoutsDir, file)
29
+ const name = path.basename(file, '.zen')
30
+ const source = fs.readFileSync(filePath, 'utf-8')
31
+
32
+ const script = parseScript(source)
33
+ const props = script ? extractProps(script.raw) : []
34
+ const states = script ? extractStateDeclarations(script.raw) : new Map()
35
+
36
+ // Extract styles
37
+ const styles: string[] = []
38
+ const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi
39
+ let match
40
+ while ((match = styleRegex.exec(source)) !== null) {
41
+ if (match[1]) styles.push(match[1].trim())
42
+ }
43
+
44
+ // Extract HTML (everything except script/style)
45
+ let html = source.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
46
+ html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '').trim()
47
+
48
+ layouts.set(name, {
49
+ name,
50
+ filePath,
51
+ props,
52
+ states,
53
+ html,
54
+ scripts: script ? [script.raw] : [],
55
+ styles
56
+ })
57
+ }
58
+ }
59
+
60
+ return layouts
61
+ }
package/compiler/index.ts CHANGED
@@ -1,44 +1,60 @@
1
- /**
2
- * Zenith Compiler
3
- *
4
- * Phase 1: Parse & Extract
5
- * Phase 2: Transform IR → Static HTML + Runtime Bindings
6
- * Phase 8/9/10: Finalize Output with Validation
7
- *
8
- * This compiler observes .zen files, extracts their structure,
9
- * transforms them into static HTML with explicit bindings,
10
- * and validates/finalizes output for browser execution.
11
- */
12
-
13
- import { parseZenFile } from './parse/parseZenFile'
1
+ import { readFileSync } from 'fs'
2
+ import { parseTemplate } from './parse/parseTemplate'
3
+ import { parseScript } from './parse/parseScript'
14
4
  import { transformTemplate } from './transform/transformTemplate'
15
5
  import { finalizeOutputOrThrow } from './finalize/finalizeOutput'
16
- import type { ZenIR } from './ir/types'
6
+ import type { ZenIR, StyleIR } from './ir/types'
17
7
  import type { CompiledTemplate } from './output/types'
18
8
  import type { FinalizedOutput } from './finalize/finalizeOutput'
19
9
 
20
10
  /**
21
11
  * Compile a .zen file into IR and CompiledTemplate
22
- *
23
- * Phase 1: Parses and extracts structure
24
- * Phase 2: Transforms IR into static HTML with bindings
25
- * Phase 8/9/10: Validates and finalizes output
26
12
  */
27
- export function compileZen(filePath: string): {
13
+ export function compileZen(filePath: string): {
28
14
  ir: ZenIR
29
15
  compiled: CompiledTemplate
30
16
  finalized?: FinalizedOutput
31
17
  } {
32
- const ir = parseZenFile(filePath)
18
+ const source = readFileSync(filePath, 'utf-8')
19
+ return compileZenSource(source, filePath)
20
+ }
21
+
22
+ /**
23
+ * Compile Zen source string into IR and CompiledTemplate
24
+ */
25
+ export function compileZenSource(source: string, filePath: string): {
26
+ ir: ZenIR
27
+ compiled: CompiledTemplate
28
+ finalized?: FinalizedOutput
29
+ } {
30
+ // Parse template
31
+ const template = parseTemplate(source, filePath)
32
+
33
+ // Parse script
34
+ const script = parseScript(source)
35
+
36
+ // Parse styles
37
+ const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi
38
+ const styles: StyleIR[] = []
39
+ let match
40
+ while ((match = styleRegex.exec(source)) !== null) {
41
+ if (match[1]) styles.push({ raw: match[1].trim() })
42
+ }
43
+
44
+ const ir: ZenIR = {
45
+ filePath,
46
+ template,
47
+ script,
48
+ styles
49
+ }
50
+
33
51
  const compiled = transformTemplate(ir)
34
-
35
- // Phase 8/9/10: Finalize output with validation
36
- // This ensures build fails on invalid expressions
52
+
37
53
  try {
38
54
  const finalized = finalizeOutputOrThrow(ir, compiled)
39
55
  return { ir, compiled, finalized }
40
56
  } catch (error: any) {
41
- // Re-throw with context
42
57
  throw new Error(`Failed to finalize output for ${filePath}:\n${error.message}`)
43
58
  }
44
59
  }
60
+
@@ -70,6 +70,7 @@ export type ExpressionIR = {
70
70
 
71
71
  export type ScriptIR = {
72
72
  raw: string
73
+ attributes: Record<string, string>
73
74
  }
74
75
 
75
76
  export type StyleIR = {
@@ -8,15 +8,39 @@
8
8
  import type { ScriptIR } from '../ir/types'
9
9
 
10
10
  export function parseScript(html: string): ScriptIR | null {
11
- // Extract script content using regex (simple extraction for Phase 1)
12
- const scriptMatch = html.match(/<script[^>]*>([\s\S]*?)<\/script>/i)
13
-
14
- if (!scriptMatch || !scriptMatch[1]) {
11
+ const scripts: string[] = []
12
+ const attributes: Record<string, string> = {}
13
+
14
+ const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi
15
+ let match
16
+
17
+ while ((match = scriptRegex.exec(html)) !== null) {
18
+ const attrString = match[1] || ''
19
+ const content = match[2] || ''
20
+
21
+ // Parse attributes
22
+ const attrRegex = /([a-z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^>\s]+)))?/gi
23
+ let attrMatch
24
+ while ((attrMatch = attrRegex.exec(attrString)) !== null) {
25
+ const name = attrMatch[1]
26
+ if (name) {
27
+ const value = attrMatch[2] || attrMatch[3] || attrMatch[4] || 'true'
28
+ attributes[name] = value
29
+ }
30
+ }
31
+
32
+ if (content) {
33
+ scripts.push(content.trim())
34
+ }
35
+ }
36
+
37
+ if (scripts.length === 0) {
15
38
  return null
16
39
  }
17
40
 
18
41
  return {
19
- raw: scriptMatch[1].trim()
42
+ raw: scripts.join('\n\n'),
43
+ attributes
20
44
  }
21
45
  }
22
46