@luna_ui/luna 0.3.3 → 0.3.5

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 (174) hide show
  1. package/dist/cli.mjs +1264 -27
  2. package/dist/css/index.d.ts +194 -0
  3. package/dist/css/index.js +721 -0
  4. package/dist/css/runtime.d.ts +92 -0
  5. package/dist/css/runtime.js +179 -0
  6. package/dist/index.js +1 -1
  7. package/dist/jsx-dev-runtime.js +1 -1
  8. package/dist/jsx-runtime.d.ts +5 -0
  9. package/dist/jsx-runtime.js +1 -1
  10. package/dist/src-CHiGeWfy.js +1 -0
  11. package/dist/vite-plugin.d.ts +122 -0
  12. package/dist/vite-plugin.js +1518 -0
  13. package/package.json +16 -2
  14. package/src/css/extract.ts +798 -0
  15. package/src/css/index.ts +10 -0
  16. package/src/css/inject.ts +205 -0
  17. package/src/css/inline.ts +182 -0
  18. package/src/css/minify.ts +70 -0
  19. package/src/css/optimizer.ts +6 -0
  20. package/src/css/runtime.ts +344 -0
  21. package/src/css-optimizer/README.md +353 -0
  22. package/src/css-optimizer/cooccurrence.ts +100 -0
  23. package/src/css-optimizer/core.ts +263 -0
  24. package/src/css-optimizer/extractors.ts +243 -0
  25. package/src/css-optimizer/hash.ts +54 -0
  26. package/src/css-optimizer/index.ts +129 -0
  27. package/src/css-optimizer/merge.ts +109 -0
  28. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  29. package/src/css-optimizer/parser.ts +120 -0
  30. package/src/css-optimizer/pattern.ts +171 -0
  31. package/src/css-optimizer/transformers.ts +301 -0
  32. package/src/css-optimizer/types.ts +128 -0
  33. package/src/event-utils.ts +227 -0
  34. package/src/index.ts +890 -0
  35. package/src/jsx-dev-runtime.ts +2 -0
  36. package/src/jsx-runtime.ts +398 -0
  37. package/src/vite-plugin.ts +718 -0
  38. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  39. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  40. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  41. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  42. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  43. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  44. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  45. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  46. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  50. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  51. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-empty-to-non-empty-transition-in-SVG-1.png +0 -0
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  58. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  59. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-renders-correctly-without-show--initial-items--1.png +0 -0
  60. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-with-context-renders-correctly-without-show-1.png +0 -0
  61. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-nested-components-with-context--forEach--and-show-1.png +0 -0
  62. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-inherit-context-from-Owner--fixed--1.png +0 -0
  63. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-work-together--context-uses-default--1.png +0 -0
  64. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  65. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  66. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  67. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  68. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  69. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  70. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  71. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  72. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  73. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  74. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  75. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-works-with-For-loop-items--component-body-style--1.png +0 -0
  76. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  77. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  78. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  79. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  80. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  81. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  82. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  83. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  84. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  85. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  117. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  118. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  119. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  120. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  121. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  122. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  123. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  124. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  125. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  137. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  138. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  139. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  140. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  141. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  142. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  143. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  144. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  145. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  147. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  148. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  149. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  150. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  151. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  152. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  153. package/tests/context.test.ts +118 -0
  154. package/tests/css-optimizer-extractors.test.ts +264 -0
  155. package/tests/css-optimizer-integration.test.ts +566 -0
  156. package/tests/css-optimizer-transformers.test.ts +301 -0
  157. package/tests/css-optimizer.test.ts +646 -0
  158. package/tests/css-runtime.bench.ts +442 -0
  159. package/tests/css-runtime.test.ts +342 -0
  160. package/tests/dom.test.ts +872 -0
  161. package/tests/integration.test.ts +405 -0
  162. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  163. package/tests/jsx-runtime.test.tsx +393 -0
  164. package/tests/lifecycle.test.ts +833 -0
  165. package/tests/move-before.bench.ts +304 -0
  166. package/tests/preact-signals-comparison.test.ts +1608 -0
  167. package/tests/resource.test.ts +160 -0
  168. package/tests/router.test.ts +117 -0
  169. package/tests/show-initial-mount-leak.test.tsx +182 -0
  170. package/tests/solidjs-api.test.ts +659 -0
  171. package/tests/static-perf.bench.ts +64 -0
  172. package/tests/store.test.ts +263 -0
  173. package/tests/tsx-syntax.test.tsx +404 -0
  174. package/dist/src-DGWY0NYx.js +0 -1
package/dist/cli.mjs CHANGED
@@ -1,24 +1,906 @@
1
1
  #!/usr/bin/env node
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
2
+ import * as fs$1 from "node:fs";
3
+ import * as path$1 from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import fs from "fs";
6
+ import path from "path";
5
7
 
8
+ //#region src/css/extract.ts
9
+ /**
10
+ * Static CSS Extractor for Luna CSS Module
11
+ *
12
+ * Extracts all CSS declarations from .mbt files at build time.
13
+ * This ensures all styles are collected regardless of runtime branches.
14
+ */
15
+ const CSS_PATTERN = /\bcss\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
16
+ const STYLES_PATTERN = /\bstyles\s*\(\s*\[([\s\S]*?)\]\s*\)/g;
17
+ const STYLES_PAIR_PATTERN = /\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
18
+ const HOVER_PATTERN = /\bhover\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
19
+ const FOCUS_PATTERN = /\bfocus\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
20
+ const ACTIVE_PATTERN = /\bactive\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
21
+ const ON_PATTERN = /\bon\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
22
+ const MEDIA_PATTERN = /\bmedia\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
23
+ const AT_SM_PATTERN = /\bat_sm\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
24
+ const AT_MD_PATTERN = /\bat_md\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
25
+ const AT_LG_PATTERN = /\bat_lg\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
26
+ const AT_XL_PATTERN = /\bat_xl\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
27
+ const DARK_PATTERN = /\bdark\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
28
+ const UCSS_PATTERN = /\bucss\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
29
+ const USTYLES_PATTERN = /\bustyles\s*\(\s*\[([\s\S]*?)\]\s*\)/g;
30
+ const UHOVER_PATTERN = /\buhover\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
31
+ const UFOCUS_PATTERN = /\bufocus\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
32
+ const UACTIVE_PATTERN = /\buactive\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
33
+ const UON_PATTERN = /\buon\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
34
+ const UAT_MD_PATTERN = /\buat_md\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
35
+ const UAT_LG_PATTERN = /\buat_lg\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
36
+ const UDARK_PATTERN = /\budark\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
37
+ const ARG_PATTERN = `(?:"(?:[^"\\\\]|\\\\.)*"|[^,)]+)`;
38
+ const WARN_PATTERNS = [
39
+ {
40
+ name: "css",
41
+ pattern: new RegExp(`\\b(u?css)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
42
+ args: 2
43
+ },
44
+ {
45
+ name: "hover",
46
+ pattern: new RegExp(`\\b(u?hover)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
47
+ args: 2
48
+ },
49
+ {
50
+ name: "focus",
51
+ pattern: new RegExp(`\\b(u?focus)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
52
+ args: 2
53
+ },
54
+ {
55
+ name: "active",
56
+ pattern: new RegExp(`\\b(u?active)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
57
+ args: 2
58
+ },
59
+ {
60
+ name: "at_sm",
61
+ pattern: new RegExp(`\\bat_sm\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
62
+ args: 2,
63
+ noPrefix: true
64
+ },
65
+ {
66
+ name: "at_md",
67
+ pattern: new RegExp(`\\b(u?at_md)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
68
+ args: 2
69
+ },
70
+ {
71
+ name: "at_lg",
72
+ pattern: new RegExp(`\\b(u?at_lg)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
73
+ args: 2
74
+ },
75
+ {
76
+ name: "at_xl",
77
+ pattern: new RegExp(`\\bat_xl\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
78
+ args: 2,
79
+ noPrefix: true
80
+ },
81
+ {
82
+ name: "dark",
83
+ pattern: new RegExp(`\\b(u?dark)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
84
+ args: 2
85
+ },
86
+ {
87
+ name: "on",
88
+ pattern: new RegExp(`\\b(u?on)\\s*\\(\\s*"(::?[^"]+)"\\s*,\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
89
+ args: 3,
90
+ pseudoInMatch: true
91
+ },
92
+ {
93
+ name: "media",
94
+ pattern: new RegExp(`\\bmedia\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
95
+ args: 3,
96
+ noPrefix: true
97
+ }
98
+ ];
99
+ function isStringLiteral(str) {
100
+ const trimmed = str.trim();
101
+ return trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'");
102
+ }
103
+ function isFunctionDefinition(code) {
104
+ return /:\s*String/.test(code);
105
+ }
106
+ function getLineNumber(content, position) {
107
+ return content.substring(0, position).split("\n").length;
108
+ }
109
+ function detectWarnings(content, filePath) {
110
+ const warnings = [];
111
+ for (const { name, pattern, args, noPrefix, pseudoInMatch } of WARN_PATTERNS) {
112
+ pattern.lastIndex = 0;
113
+ for (const match of content.matchAll(pattern)) {
114
+ const fullMatch = match[0];
115
+ const position = match.index;
116
+ const line = getLineNumber(content, position);
117
+ if (isFunctionDefinition(fullMatch)) continue;
118
+ const extractedArgs = [];
119
+ if (args === 2) {
120
+ const startIdx = noPrefix ? 1 : 2;
121
+ extractedArgs.push(match[startIdx], match[startIdx + 1]);
122
+ } else if (args === 3) if (pseudoInMatch) extractedArgs.push(match[3], match[4]);
123
+ else {
124
+ const startIdx = noPrefix ? 1 : 2;
125
+ extractedArgs.push(match[startIdx], match[startIdx + 1], match[startIdx + 2]);
126
+ }
127
+ for (let i = 0; i < extractedArgs.length; i++) {
128
+ const arg = extractedArgs[i];
129
+ if (arg && !isStringLiteral(arg)) {
130
+ const argNum = pseudoInMatch ? i + 2 : i + 1;
131
+ warnings.push({
132
+ file: filePath,
133
+ line,
134
+ func: name,
135
+ code: fullMatch.trim(),
136
+ reason: `Argument ${argNum} is not a string literal: ${arg.trim()}`
137
+ });
138
+ break;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return warnings;
144
+ }
145
+ function extractFromContent(content) {
146
+ const base = /* @__PURE__ */ new Set();
147
+ const pseudo = [];
148
+ const media = [];
149
+ function extractBase(pattern) {
150
+ for (const match of content.matchAll(pattern)) base.add(`${match[1]}:${match[2]}`);
151
+ }
152
+ function extractStyles(pattern) {
153
+ for (const match of content.matchAll(pattern)) {
154
+ const pairs = match[1];
155
+ for (const pair of pairs.matchAll(STYLES_PAIR_PATTERN)) base.add(`${pair[1]}:${pair[2]}`);
156
+ }
157
+ }
158
+ function extractPseudo(pattern, pseudoSelector) {
159
+ for (const match of content.matchAll(pattern)) pseudo.push({
160
+ pseudo: pseudoSelector,
161
+ property: match[1],
162
+ value: match[2]
163
+ });
164
+ }
165
+ function extractOn(pattern) {
166
+ for (const match of content.matchAll(pattern)) pseudo.push({
167
+ pseudo: match[1],
168
+ property: match[2],
169
+ value: match[3]
170
+ });
171
+ }
172
+ function extractMedia(pattern, condition = null) {
173
+ for (const match of content.matchAll(pattern)) if (condition) media.push({
174
+ condition,
175
+ property: match[1],
176
+ value: match[2]
177
+ });
178
+ else media.push({
179
+ condition: match[1],
180
+ property: match[2],
181
+ value: match[3]
182
+ });
183
+ }
184
+ extractBase(CSS_PATTERN);
185
+ extractBase(UCSS_PATTERN);
186
+ extractStyles(STYLES_PATTERN);
187
+ extractStyles(USTYLES_PATTERN);
188
+ extractPseudo(HOVER_PATTERN, ":hover");
189
+ extractPseudo(UHOVER_PATTERN, ":hover");
190
+ extractPseudo(FOCUS_PATTERN, ":focus");
191
+ extractPseudo(UFOCUS_PATTERN, ":focus");
192
+ extractPseudo(ACTIVE_PATTERN, ":active");
193
+ extractPseudo(UACTIVE_PATTERN, ":active");
194
+ extractOn(ON_PATTERN);
195
+ extractOn(UON_PATTERN);
196
+ extractMedia(MEDIA_PATTERN);
197
+ extractMedia(AT_SM_PATTERN, "min-width:640px");
198
+ extractMedia(AT_MD_PATTERN, "min-width:768px");
199
+ extractMedia(UAT_MD_PATTERN, "min-width:768px");
200
+ extractMedia(AT_LG_PATTERN, "min-width:1024px");
201
+ extractMedia(UAT_LG_PATTERN, "min-width:1024px");
202
+ extractMedia(AT_XL_PATTERN, "min-width:1280px");
203
+ extractMedia(DARK_PATTERN, "prefers-color-scheme:dark");
204
+ extractMedia(UDARK_PATTERN, "prefers-color-scheme:dark");
205
+ return {
206
+ base,
207
+ pseudo,
208
+ media
209
+ };
210
+ }
211
+ function findMbtFiles$1(dir) {
212
+ const files = [];
213
+ function walk(currentDir) {
214
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
215
+ for (const entry of entries) {
216
+ const fullPath = path.join(currentDir, entry.name);
217
+ if (entry.isDirectory()) {
218
+ if (![
219
+ "node_modules",
220
+ ".git",
221
+ "target",
222
+ ".mooncakes"
223
+ ].includes(entry.name)) walk(fullPath);
224
+ } else if (entry.isFile() && entry.name.endsWith(".mbt")) files.push(fullPath);
225
+ }
226
+ }
227
+ walk(dir);
228
+ return files;
229
+ }
230
+ /**
231
+ * DJB2 hash function - must match MoonBit registry.mbt implementation
232
+ */
233
+ function djb2Hash(s) {
234
+ let hash = 5381;
235
+ for (let i = 0; i < s.length; i++) {
236
+ const c = s.charCodeAt(i);
237
+ hash = (hash << 5) + hash + c >>> 0;
238
+ }
239
+ return hash;
240
+ }
241
+ /**
242
+ * Convert hash to base36 string (0-9a-z)
243
+ * Takes lower 24 bits to keep class names short (4-5 chars)
244
+ */
245
+ function toBase36(n) {
246
+ const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
247
+ n = n & 16777215;
248
+ if (n === 0) return "0";
249
+ let result = "";
250
+ while (n > 0) {
251
+ result = chars[n % 36] + result;
252
+ n = Math.floor(n / 36);
253
+ }
254
+ return result;
255
+ }
256
+ /**
257
+ * Generate class name from declaration hash
258
+ */
259
+ function hashClassName(decl) {
260
+ return "_" + toBase36(djb2Hash(decl));
261
+ }
262
+ function generateCSS(styles, options = {}) {
263
+ const { pretty = false } = options;
264
+ const mapping = {};
265
+ const parts = [];
266
+ for (const decl of styles.base) {
267
+ const cls = hashClassName(decl);
268
+ mapping[decl] = cls;
269
+ if (pretty) parts.push(`.${cls} { ${decl} }`);
270
+ else parts.push(`.${cls}{${decl}}`);
271
+ }
272
+ for (const { pseudo, property, value } of styles.pseudo) {
273
+ const hashKey = `${pseudo}:${property}:${value}`;
274
+ const cls = hashClassName(hashKey);
275
+ mapping[hashKey] = cls;
276
+ if (pretty) parts.push(`.${cls}${pseudo} { ${property}: ${value} }`);
277
+ else parts.push(`.${cls}${pseudo}{${property}:${value}}`);
278
+ }
279
+ const mediaGroups = /* @__PURE__ */ new Map();
280
+ for (const { condition, property, value } of styles.media) {
281
+ if (!mediaGroups.has(condition)) mediaGroups.set(condition, []);
282
+ mediaGroups.get(condition).push({
283
+ property,
284
+ value
285
+ });
286
+ }
287
+ for (const [condition, declarations] of mediaGroups) {
288
+ const rules = [];
289
+ for (const { property, value } of declarations) {
290
+ const hashKey = `@media(${condition}):${property}:${value}`;
291
+ const cls = hashClassName(hashKey);
292
+ mapping[hashKey] = cls;
293
+ if (pretty) rules.push(` .${cls} { ${property}: ${value} }`);
294
+ else rules.push(`.${cls}{${property}:${value}}`);
295
+ }
296
+ if (pretty) parts.push(`@media (${condition}) {\n${rules.join("\n")}\n}`);
297
+ else parts.push(`@media(${condition}){${rules.join("")}}`);
298
+ }
299
+ const separator = pretty ? "\n" : "";
300
+ return {
301
+ css: parts.join(separator),
302
+ mapping
303
+ };
304
+ }
305
+ function extract(dir, options = {}) {
306
+ const { pretty = false, warn = true, strict = false, verbose = false } = options;
307
+ const files = findMbtFiles$1(dir);
308
+ if (verbose) console.error(`Found ${files.length} .mbt files`);
309
+ const combined = {
310
+ base: /* @__PURE__ */ new Set(),
311
+ pseudo: [],
312
+ media: []
313
+ };
314
+ const allWarnings = [];
315
+ for (const file of files) {
316
+ const content = fs.readFileSync(file, "utf-8");
317
+ const extracted = extractFromContent(content);
318
+ for (const decl of extracted.base) combined.base.add(decl);
319
+ combined.pseudo.push(...extracted.pseudo);
320
+ combined.media.push(...extracted.media);
321
+ if (warn) {
322
+ const warnings = detectWarnings(content, file);
323
+ allWarnings.push(...warnings);
324
+ }
325
+ }
326
+ if (warn && allWarnings.length > 0 && verbose) {
327
+ console.error(`\n⚠️ ${allWarnings.length} warning(s): non-literal CSS arguments detected\n`);
328
+ for (const w of allWarnings) {
329
+ console.error(` ${w.file}:${w.line}`);
330
+ console.error(` ${w.func}(...) - ${w.reason}`);
331
+ console.error(` Code: ${w.code}`);
332
+ console.error("");
333
+ }
334
+ if (strict) throw new Error("Strict mode: non-literal CSS arguments detected");
335
+ }
336
+ if (verbose) {
337
+ console.error(`Extracted:`);
338
+ console.error(` - ${combined.base.size} base declarations`);
339
+ console.error(` - ${combined.pseudo.length} pseudo-class declarations`);
340
+ console.error(` - ${combined.media.length} media query declarations`);
341
+ }
342
+ const { css, mapping } = generateCSS(combined, { pretty });
343
+ return {
344
+ css,
345
+ mapping,
346
+ stats: {
347
+ base: combined.base.size,
348
+ pseudo: combined.pseudo.length,
349
+ media: combined.media.length
350
+ },
351
+ warnings: warn ? allWarnings : void 0
352
+ };
353
+ }
354
+ /**
355
+ * Extract CSS with split output by file or directory.
356
+ * Shared CSS (used in 3+ files/dirs) is separated into a shared chunk.
357
+ */
358
+ function extractSplit(dir, mode, options = {}) {
359
+ const { pretty = false, warn = true, strict = false, verbose = false, sharedThreshold = 3 } = options;
360
+ const files = findMbtFiles$1(dir);
361
+ if (verbose) console.error(`Found ${files.length} .mbt files`);
362
+ const declUsage = /* @__PURE__ */ new Map();
363
+ const pseudoUsage = /* @__PURE__ */ new Map();
364
+ const mediaUsage = /* @__PURE__ */ new Map();
365
+ const perChunk = /* @__PURE__ */ new Map();
366
+ const allWarnings = [];
367
+ for (const file of files) {
368
+ const content = fs.readFileSync(file, "utf-8");
369
+ const extracted = extractFromContent(content);
370
+ const relPath = path.relative(dir, file);
371
+ const chunkKey = mode === "file" ? relPath : path.dirname(relPath) || ".";
372
+ if (!perChunk.has(chunkKey)) perChunk.set(chunkKey, {
373
+ base: /* @__PURE__ */ new Set(),
374
+ pseudo: [],
375
+ media: []
376
+ });
377
+ const chunk = perChunk.get(chunkKey);
378
+ for (const decl of extracted.base) {
379
+ chunk.base.add(decl);
380
+ if (!declUsage.has(decl)) declUsage.set(decl, /* @__PURE__ */ new Set());
381
+ declUsage.get(decl).add(chunkKey);
382
+ }
383
+ for (const p of extracted.pseudo) {
384
+ const key = `${p.pseudo}:${p.property}:${p.value}`;
385
+ chunk.pseudo.push(p);
386
+ if (!pseudoUsage.has(key)) pseudoUsage.set(key, /* @__PURE__ */ new Set());
387
+ pseudoUsage.get(key).add(chunkKey);
388
+ }
389
+ for (const m of extracted.media) {
390
+ const key = `@media(${m.condition}):${m.property}:${m.value}`;
391
+ chunk.media.push(m);
392
+ if (!mediaUsage.has(key)) mediaUsage.set(key, /* @__PURE__ */ new Set());
393
+ mediaUsage.get(key).add(chunkKey);
394
+ }
395
+ if (warn) {
396
+ const warnings = detectWarnings(content, file);
397
+ allWarnings.push(...warnings);
398
+ }
399
+ }
400
+ const sharedBase = /* @__PURE__ */ new Set();
401
+ const sharedPseudo = [];
402
+ const sharedMedia = [];
403
+ for (const [decl, chunks] of declUsage) if (chunks.size >= sharedThreshold) sharedBase.add(decl);
404
+ const seenPseudo = /* @__PURE__ */ new Set();
405
+ for (const [key, chunks] of pseudoUsage) if (chunks.size >= sharedThreshold && !seenPseudo.has(key)) {
406
+ seenPseudo.add(key);
407
+ const [pseudo, property, value] = key.split(":");
408
+ sharedPseudo.push({
409
+ pseudo,
410
+ property,
411
+ value
412
+ });
413
+ }
414
+ const seenMedia = /* @__PURE__ */ new Set();
415
+ for (const [key, chunks] of mediaUsage) if (chunks.size >= sharedThreshold && !seenMedia.has(key)) {
416
+ seenMedia.add(key);
417
+ const match = key.match(/^@media\(([^)]+)\):([^:]+):(.+)$/);
418
+ if (match) sharedMedia.push({
419
+ condition: match[1],
420
+ property: match[2],
421
+ value: match[3]
422
+ });
423
+ }
424
+ const finalChunks = /* @__PURE__ */ new Map();
425
+ const chunkStats = /* @__PURE__ */ new Map();
426
+ for (const [chunkKey, chunk] of perChunk) {
427
+ const chunkOnlyBase = /* @__PURE__ */ new Set();
428
+ for (const decl of chunk.base) if (!sharedBase.has(decl)) chunkOnlyBase.add(decl);
429
+ const chunkOnlyPseudo = chunk.pseudo.filter((p) => {
430
+ const key = `${p.pseudo}:${p.property}:${p.value}`;
431
+ return !seenPseudo.has(key);
432
+ });
433
+ const chunkOnlyMedia = chunk.media.filter((m) => {
434
+ const key = `@media(${m.condition}):${m.property}:${m.value}`;
435
+ return !seenMedia.has(key);
436
+ });
437
+ const chunkStyles = {
438
+ base: chunkOnlyBase,
439
+ pseudo: chunkOnlyPseudo,
440
+ media: chunkOnlyMedia
441
+ };
442
+ const { css } = generateCSS(chunkStyles, { pretty });
443
+ finalChunks.set(chunkKey, {
444
+ css,
445
+ styles: chunkStyles
446
+ });
447
+ chunkStats.set(chunkKey, {
448
+ base: chunkOnlyBase.size,
449
+ pseudo: chunkOnlyPseudo.length,
450
+ media: chunkOnlyMedia.length
451
+ });
452
+ }
453
+ const sharedStyles = {
454
+ base: sharedBase,
455
+ pseudo: sharedPseudo,
456
+ media: sharedMedia
457
+ };
458
+ const { css: sharedCss, mapping } = generateCSS(sharedStyles, { pretty });
459
+ const combinedStyles = {
460
+ base: /* @__PURE__ */ new Set(),
461
+ pseudo: [],
462
+ media: []
463
+ };
464
+ for (const decl of sharedBase) combinedStyles.base.add(decl);
465
+ for (const [, chunk] of finalChunks) {
466
+ for (const decl of chunk.styles.base) combinedStyles.base.add(decl);
467
+ combinedStyles.pseudo.push(...chunk.styles.pseudo);
468
+ combinedStyles.media.push(...chunk.styles.media);
469
+ }
470
+ combinedStyles.pseudo.push(...sharedPseudo);
471
+ combinedStyles.media.push(...sharedMedia);
472
+ const { css: combinedCss, mapping: fullMapping } = generateCSS(combinedStyles, { pretty });
473
+ if (verbose) {
474
+ console.error(`Split mode: ${mode}`);
475
+ console.error(`Chunks: ${finalChunks.size}`);
476
+ console.error(`Shared declarations (≥${sharedThreshold} usages):`);
477
+ console.error(` - ${sharedBase.size} base`);
478
+ console.error(` - ${sharedPseudo.length} pseudo`);
479
+ console.error(` - ${sharedMedia.length} media`);
480
+ }
481
+ if (warn && allWarnings.length > 0 && verbose) console.error(`\n⚠️ ${allWarnings.length} warning(s)\n`);
482
+ if (strict && allWarnings.length > 0) throw new Error("Strict mode: non-literal CSS arguments detected");
483
+ return {
484
+ chunks: finalChunks,
485
+ shared: {
486
+ css: sharedCss,
487
+ styles: sharedStyles
488
+ },
489
+ combined: combinedCss,
490
+ mapping: fullMapping,
491
+ stats: chunkStats,
492
+ warnings: warn ? allWarnings : void 0
493
+ };
494
+ }
495
+
496
+ //#endregion
497
+ //#region src/css/minify.ts
498
+ /**
499
+ * Minify CSS content
500
+ */
501
+ function minifyCSS(css) {
502
+ return css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{};:,])\s*/g, "$1").replace(/;}/g, "}").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").trim();
503
+ }
504
+ /**
505
+ * Format file size
506
+ */
507
+ function formatSize(bytes) {
508
+ if (bytes < 1024) return `${bytes} B`;
509
+ return `${(bytes / 1024).toFixed(1)} KB`;
510
+ }
511
+ /**
512
+ * Minify CSS and return result with stats
513
+ */
514
+ function minify(css, options = {}) {
515
+ const originalSize = Buffer.byteLength(css, "utf-8");
516
+ const minified = minifyCSS(css);
517
+ const minifiedSize = Buffer.byteLength(minified, "utf-8");
518
+ const reduction = (1 - minifiedSize / originalSize) * 100;
519
+ if (options.verbose) console.error(`Minified: ${formatSize(originalSize)} → ${formatSize(minifiedSize)} (${reduction.toFixed(1)}% reduction)`);
520
+ return {
521
+ minified,
522
+ originalSize,
523
+ minifiedSize,
524
+ reduction
525
+ };
526
+ }
527
+
528
+ //#endregion
529
+ //#region src/css/inline.ts
530
+ /**
531
+ * CSS Class Name Inliner
532
+ *
533
+ * Replaces CSS utility function calls with pre-computed class names.
534
+ * This enables true zero-runtime CSS by eliminating runtime style registration.
535
+ */
536
+ const REGISTER_DECL_PATTERN = /mizchi\$luna\$luna\$css\$\$register_decl\s*\(\s*"([^"]+)"\s*\)/g;
537
+ const REGISTER_PSEUDO_PATTERN = /mizchi\$luna\$luna\$css\$\$register_pseudo\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
538
+ const REGISTER_MEDIA_PATTERN = /mizchi\$luna\$luna\$css\$\$register_media\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
539
+ /**
540
+ * Replace CSS function calls with pre-computed class names
541
+ */
542
+ function inlineCSS(code, mapping, options = {}) {
543
+ const { verbose = false } = options;
544
+ const replacements = [];
545
+ const originalSize = Buffer.byteLength(code, "utf-8");
546
+ let result = code;
547
+ result = result.replace(REGISTER_DECL_PATTERN, (match, decl) => {
548
+ const className = mapping[decl];
549
+ if (className) {
550
+ replacements.push({
551
+ type: "base",
552
+ from: match,
553
+ to: `"${className}"`,
554
+ decl
555
+ });
556
+ return `"${className}"`;
557
+ }
558
+ if (verbose) console.error(`Warning: No mapping for base declaration: ${decl}`);
559
+ return match;
560
+ });
561
+ result = result.replace(REGISTER_PSEUDO_PATTERN, (match, pseudo, property, value) => {
562
+ const key = `${pseudo}:${property}:${value}`;
563
+ const className = mapping[key];
564
+ if (className) {
565
+ replacements.push({
566
+ type: "pseudo",
567
+ from: match,
568
+ to: `"${className}"`,
569
+ key
570
+ });
571
+ return `"${className}"`;
572
+ }
573
+ if (verbose) console.error(`Warning: No mapping for pseudo: ${key}`);
574
+ return match;
575
+ });
576
+ result = result.replace(REGISTER_MEDIA_PATTERN, (match, condition, property, value) => {
577
+ const key = `@media(${condition}):${property}:${value}`;
578
+ const className = mapping[key];
579
+ if (className) {
580
+ replacements.push({
581
+ type: "media",
582
+ from: match,
583
+ to: `"${className}"`,
584
+ key
585
+ });
586
+ return `"${className}"`;
587
+ }
588
+ if (verbose) console.error(`Warning: No mapping for media: ${key}`);
589
+ return match;
590
+ });
591
+ const finalSize = Buffer.byteLength(result, "utf-8");
592
+ return {
593
+ code: result,
594
+ replacements,
595
+ originalSize,
596
+ finalSize
597
+ };
598
+ }
599
+ /**
600
+ * Remove CSS registry code (dead code after inlining)
601
+ */
602
+ function removeRegistryCode(code) {
603
+ const patterns = [
604
+ /const mizchi\$luna\$luna\$css\$\$registry\s*=\s*\{[^}]+\};?\n?/g,
605
+ /const mizchi\$luna\$luna\$css\$\$pseudo_registry\s*=\s*\{[^}]+\};?\n?/g,
606
+ /const mizchi\$luna\$luna\$css\$\$media_registry\s*=\s*\{[^}]+\};?\n?/g,
607
+ /const mizchi\$luna\$luna\$css\$\$class_chars\s*=\s*"[^"]+";?\n?/g,
608
+ /const mizchi\$luna\$luna\$css\$\$[a-z_]+\$46\$42\$bind[^;]+;?\n?/g
609
+ ];
610
+ let result = code;
611
+ for (const pattern of patterns) result = result.replace(pattern, "");
612
+ return result;
613
+ }
614
+ /**
615
+ * Inline CSS with mapping extracted from source directory
616
+ */
617
+ function inlineFromSource(code, srcDir, options = {}) {
618
+ const { mapping } = extract(srcDir, { warn: false });
619
+ let result = inlineCSS(code, mapping, options);
620
+ if (options.removeRegistry) result = {
621
+ ...result,
622
+ code: removeRegistryCode(result.code),
623
+ finalSize: Buffer.byteLength(removeRegistryCode(result.code), "utf-8")
624
+ };
625
+ return result;
626
+ }
627
+
628
+ //#endregion
629
+ //#region src/css/inject.ts
630
+ /**
631
+ * CSS Injection into HTML files
632
+ *
633
+ * Replaces CSS between markers in HTML files with extracted CSS.
634
+ * Supports inline embedding or external file generation.
635
+ */
636
+ const CSS_START_MARKER = "/* LUNA_CSS_START */";
637
+ const CSS_END_MARKER = "/* LUNA_CSS_END */";
638
+ const LINK_MARKER = "<!-- LUNA_CSS_LINK -->";
639
+ /**
640
+ * Inject extracted CSS into HTML file between markers
641
+ */
642
+ function injectCssToHtml(options) {
643
+ const { srcDir, htmlFile, mode = "inline", threshold = 4096, cssFileName = "luna.css", verbose = false } = options;
644
+ const { css } = extract(srcDir, { warn: false });
645
+ if (verbose) console.error(`Extracted CSS: ${css.length} bytes`);
646
+ const html = fs.readFileSync(htmlFile, "utf-8");
647
+ let actualMode = mode;
648
+ if (mode === "auto") {
649
+ actualMode = css.length > threshold ? "external" : "inline";
650
+ if (verbose) console.error(`Auto mode: ${css.length} bytes ${css.length > threshold ? ">" : "<="} ${threshold} threshold → ${actualMode}`);
651
+ }
652
+ if (actualMode === "external") return injectExternal(html, css, htmlFile, cssFileName, verbose);
653
+ else return injectInline(html, css, htmlFile, verbose);
654
+ }
655
+ /**
656
+ * Inject CSS inline between markers
657
+ */
658
+ function injectInline(html, css, htmlFile, verbose) {
659
+ const startIdx = html.indexOf(CSS_START_MARKER);
660
+ const endIdx = html.indexOf(CSS_END_MARKER);
661
+ if (startIdx === -1 || endIdx === -1) {
662
+ if (verbose) console.error(`Warning: Markers not found in ${htmlFile}. Add /* LUNA_CSS_START */ and /* LUNA_CSS_END */ to your HTML.`);
663
+ return {
664
+ html,
665
+ css,
666
+ replaced: false,
667
+ mode: "inline"
668
+ };
669
+ }
670
+ const newHtml = `${html.substring(0, startIdx + 20)}\n ${css}\n ${html.substring(endIdx)}`;
671
+ if (verbose) console.error(`Injected CSS inline into ${htmlFile}`);
672
+ return {
673
+ html: newHtml,
674
+ css,
675
+ replaced: true,
676
+ mode: "inline"
677
+ };
678
+ }
679
+ /**
680
+ * Create external CSS file and add link tag
681
+ */
682
+ function injectExternal(html, css, htmlFile, cssFileName, verbose) {
683
+ const htmlDir = path.dirname(htmlFile);
684
+ const cssFilePath = path.join(htmlDir, cssFileName);
685
+ const hasLinkMarker = html.includes(LINK_MARKER);
686
+ const startIdx = html.indexOf(CSS_START_MARKER);
687
+ const endIdx = html.indexOf(CSS_END_MARKER);
688
+ let newHtml = html;
689
+ let replaced = false;
690
+ if (hasLinkMarker) {
691
+ newHtml = html.replace(LINK_MARKER, `<link rel="stylesheet" href="${cssFileName}">`);
692
+ replaced = true;
693
+ } else if (startIdx !== -1 && endIdx !== -1) {
694
+ newHtml = `${html.substring(0, startIdx + 20)}\n /* External: ${cssFileName} */\n ${html.substring(endIdx)}`;
695
+ if (!newHtml.includes(`href="${cssFileName}"`)) newHtml = newHtml.replace("</head>", ` <link rel="stylesheet" href="${cssFileName}">\n</head>`);
696
+ replaced = true;
697
+ } else if (verbose) console.error(`Warning: No markers found in ${htmlFile}. Add <!-- LUNA_CSS_LINK --> or /* LUNA_CSS_START/END */ markers.`);
698
+ if (verbose && replaced) console.error(`Created external CSS: ${cssFilePath}`);
699
+ return {
700
+ html: newHtml,
701
+ css,
702
+ replaced,
703
+ cssFile: cssFilePath,
704
+ mode: "external"
705
+ };
706
+ }
707
+ /**
708
+ * Inject CSS and write to file(s)
709
+ */
710
+ function injectAndWrite(options) {
711
+ const result = injectCssToHtml(options);
712
+ const outputFile = options.outputFile || options.htmlFile;
713
+ if (result.replaced) {
714
+ fs.writeFileSync(outputFile, result.html);
715
+ if (options.verbose) console.error(`Written HTML to: ${outputFile}`);
716
+ if (result.mode === "external" && result.cssFile) {
717
+ fs.writeFileSync(result.cssFile, result.css);
718
+ if (options.verbose) console.error(`Written CSS to: ${result.cssFile}`);
719
+ }
720
+ }
721
+ return result;
722
+ }
723
+
724
+ //#endregion
725
+ //#region src/css-optimizer/moonbit-analyzer.ts
726
+ /**
727
+ * MoonBit Static Analyzer Integration
728
+ *
729
+ * Calls the MoonBit-based CSS static analyzer to extract class co-occurrences
730
+ * from MoonBit source files.
731
+ */
732
+ let analyzerModule = null;
733
+ /**
734
+ * Get the path to the MoonBit analyzer JS module
735
+ */
736
+ function getAnalyzerPath() {
737
+ const __dirname = path$1.dirname(fileURLToPath(import.meta.url));
738
+ const candidates = [
739
+ path$1.resolve(__dirname, "../../../target/js/release/build/luna/css/analyzer/analyzer.js"),
740
+ path$1.resolve(__dirname, "../../../../target/js/release/build/luna/css/analyzer/analyzer.js"),
741
+ path$1.resolve(__dirname, "../moonbit/analyzer.js")
742
+ ];
743
+ for (const candidate of candidates) if (fs$1.existsSync(candidate)) return candidate;
744
+ throw new Error(`MoonBit analyzer not found. Tried:\n${candidates.join("\n")}\nRun 'moon build --target js src/luna/css/analyzer' first.`);
745
+ }
746
+ /**
747
+ * Load the MoonBit analyzer module
748
+ */
749
+ async function loadAnalyzer() {
750
+ if (analyzerModule) return analyzerModule;
751
+ analyzerModule = await import(getAnalyzerPath());
752
+ return analyzerModule;
753
+ }
754
+ /**
755
+ * Analyze a single MoonBit source file
756
+ */
757
+ async function analyzeFile(source, filePath) {
758
+ const analyzer = await loadAnalyzer();
759
+ if (!analyzer) throw new Error("Failed to load MoonBit analyzer");
760
+ const jsonResult = analyzer.analyze_file_json(source, filePath);
761
+ return JSON.parse(jsonResult);
762
+ }
763
+ /**
764
+ * Analyze all .mbt files in a directory
765
+ */
766
+ async function analyzeDirectory(dir, options = {}) {
767
+ const { recursive = true } = options;
768
+ const allCooccurrences = [];
769
+ const allWarnings = [];
770
+ const files = findMbtFiles(dir, recursive);
771
+ for (const file of files) {
772
+ const result = await analyzeFile(fs$1.readFileSync(file, "utf-8"), file);
773
+ allCooccurrences.push(...result.cooccurrences);
774
+ allWarnings.push(...result.warnings);
775
+ }
776
+ return {
777
+ cooccurrences: allCooccurrences,
778
+ warnings: allWarnings
779
+ };
780
+ }
781
+ /**
782
+ * Find all .mbt files in a directory
783
+ */
784
+ function findMbtFiles(dir, recursive) {
785
+ const files = [];
786
+ function walk(currentDir) {
787
+ const entries = fs$1.readdirSync(currentDir, { withFileTypes: true });
788
+ for (const entry of entries) {
789
+ const fullPath = path$1.join(currentDir, entry.name);
790
+ if (entry.isDirectory()) {
791
+ if (entry.name === "node_modules" || entry.name === "target" || entry.name === ".git" || entry.name === ".mooncakes") continue;
792
+ if (recursive) walk(fullPath);
793
+ } else if (entry.isFile() && entry.name.endsWith(".mbt")) {
794
+ if (!entry.name.endsWith("_test.mbt")) files.push(fullPath);
795
+ }
796
+ }
797
+ }
798
+ walk(dir);
799
+ return files;
800
+ }
801
+
802
+ //#endregion
6
803
  //#region bin/cli.ts
7
- path.dirname(fileURLToPath(import.meta.url));
8
- function printHelp() {
804
+ path$1.dirname(fileURLToPath(import.meta.url));
805
+ function printMainHelp() {
9
806
  console.log(`
10
807
  @luna_ui/luna CLI
11
808
 
12
809
  Usage:
13
- npx @luna_ui/luna new <project-name> [options]
810
+ luna <command> [options]
811
+
812
+ Commands:
813
+ new <name> Create a new Luna project
814
+ css CSS utilities (extract, minify, inline)
815
+
816
+ Options:
817
+ --help, -h Show this help message
818
+
819
+ Examples:
820
+ luna new myapp # Create TSX project
821
+ luna new myapp --mbt # Create MoonBit project
822
+ luna css extract src # Extract CSS from source
823
+ luna css minify style.css # Minify CSS file
824
+ `);
825
+ }
826
+ function printNewHelp() {
827
+ console.log(`
828
+ luna new - Create a new Luna project
829
+
830
+ Usage:
831
+ luna new <project-name> [options]
14
832
 
15
833
  Options:
16
834
  --mbt Generate MoonBit template (default: TSX)
17
835
  --help, -h Show this help message
18
836
 
19
837
  Examples:
20
- npx @luna_ui/luna new myapp # TSX template
21
- npx @luna_ui/luna new myapp --mbt # MoonBit template
838
+ luna new myapp # TSX template
839
+ luna new myapp --mbt # MoonBit template
840
+ `);
841
+ }
842
+ function printCssHelp() {
843
+ console.log(`
844
+ luna css - CSS utilities
845
+
846
+ Usage:
847
+ luna css <subcommand> [options]
848
+
849
+ Subcommands:
850
+ extract <dir> Extract CSS from .mbt files
851
+ minify <file> Minify CSS file
852
+ inline <file> Inline CSS class names into compiled JS
853
+ inject <html> Inject extracted CSS into HTML file
854
+ analyze-mbt <dir> Analyze MoonBit source for CSS co-occurrences
855
+
856
+ Extract Options:
857
+ -o, --output <file> Output file (default: stdout)
858
+ --pretty Pretty print output
859
+ --json Output as JSON with mapping
860
+ -v, --verbose Show extraction details
861
+ --no-warn Disable non-literal argument warnings
862
+ --strict Exit with error if warnings found
863
+ --split Split CSS by file (outputs to --output-dir)
864
+ --split-dir Split CSS by directory (outputs to --output-dir)
865
+ --output-dir <dir> Output directory for split mode
866
+ --shared-threshold <n> Min usages to be "shared" (default: 3)
867
+
868
+ Minify Options:
869
+ -o, --output <file> Output file (default: stdout)
870
+ -v, --verbose Show minification stats
871
+
872
+ Inline Options:
873
+ -m, --mapping <file> JSON file with css -> classname mapping
874
+ --extract-from <dir> Extract mapping from source directory
875
+ -o, --output <file> Output file (default: stdout)
876
+ --remove-registry Remove CSS registry code after inlining
877
+ -v, --verbose Show replacement details
878
+ --dry-run Show what would be replaced
879
+
880
+ Inject Options:
881
+ --src <dir> Source directory for CSS extraction (required)
882
+ -o, --output <file> Output file (default: modify in-place)
883
+ -m, --mode <mode> Output mode: inline, external, or auto (default: inline)
884
+ -t, --threshold <n> Size threshold for auto mode in bytes (default: 4096)
885
+ --css-file <name> CSS filename for external mode (default: luna.css)
886
+ -v, --verbose Show injection details
887
+
888
+ Analyze-mbt Options:
889
+ -o, --output <file> Output file (default: stdout)
890
+ --no-recursive Don't search recursively
891
+ -v, --verbose Show analysis details
892
+
893
+ Examples:
894
+ luna css extract src -o dist/styles.css
895
+ luna css extract src --json -o mapping.json
896
+ luna css extract src --split --output-dir dist/css
897
+ luna css extract src --split-dir --output-dir dist/css --shared-threshold 2
898
+ luna css minify input.css -o output.min.css
899
+ luna css inline bundle.js --extract-from src -o bundle.inlined.js
900
+ luna css inject index.html --src src
901
+ luna css inject index.html --src src --mode external --css-file styles.css
902
+ luna css inject index.html --src src --mode auto --threshold 2048
903
+ luna css analyze-mbt src/luna -o cooccurrences.json
22
904
  `);
23
905
  }
24
906
  function getTsxTemplates(projectName) {
@@ -303,35 +1185,28 @@ target
303
1185
  ];
304
1186
  }
305
1187
  function createProject(projectName, templates, targetDir) {
306
- if (fs.existsSync(targetDir)) {
1188
+ if (fs$1.existsSync(targetDir)) {
307
1189
  console.error(`Error: Directory "${projectName}" already exists.`);
308
1190
  process.exit(1);
309
1191
  }
310
- fs.mkdirSync(targetDir, { recursive: true });
1192
+ fs$1.mkdirSync(targetDir, { recursive: true });
311
1193
  for (const template of templates) {
312
- const filePath = path.join(targetDir, template.path);
313
- const dir = path.dirname(filePath);
314
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
315
- fs.writeFileSync(filePath, template.content);
1194
+ const filePath = path$1.join(targetDir, template.path);
1195
+ const dir = path$1.dirname(filePath);
1196
+ if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
1197
+ fs$1.writeFileSync(filePath, template.content);
316
1198
  console.log(` Created: ${template.path}`);
317
1199
  }
318
1200
  }
319
- function main() {
320
- const args = process.argv.slice(2);
321
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
322
- printHelp();
1201
+ function handleNew(args) {
1202
+ if (args.includes("--help") || args.includes("-h")) {
1203
+ printNewHelp();
323
1204
  process.exit(0);
324
1205
  }
325
- const command = args[0];
326
- if (command !== "new") {
327
- console.error(`Unknown command: ${command}`);
328
- printHelp();
329
- process.exit(1);
330
- }
331
- const projectName = args[1];
1206
+ const projectName = args.find((a) => !a.startsWith("-"));
332
1207
  if (!projectName) {
333
1208
  console.error("Error: Project name is required.");
334
- printHelp();
1209
+ printNewHelp();
335
1210
  process.exit(1);
336
1211
  }
337
1212
  if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
@@ -339,7 +1214,7 @@ function main() {
339
1214
  process.exit(1);
340
1215
  }
341
1216
  const useMbt = args.includes("--mbt");
342
- const targetDir = path.resolve(process.cwd(), projectName);
1217
+ const targetDir = path$1.resolve(process.cwd(), projectName);
343
1218
  console.log(`\nCreating ${useMbt ? "MoonBit" : "TSX"} project: ${projectName}\n`);
344
1219
  createProject(projectName, useMbt ? getMbtTemplates(projectName) : getTsxTemplates(projectName), targetDir);
345
1220
  console.log(`\nDone! To get started:\n`);
@@ -355,7 +1230,369 @@ function main() {
355
1230
  }
356
1231
  console.log();
357
1232
  }
358
- main();
1233
+ function handleCssExtract(args) {
1234
+ let dir = ".";
1235
+ let outputFile = null;
1236
+ let outputDir = null;
1237
+ let pretty = false;
1238
+ let jsonOutput = false;
1239
+ let verbose = false;
1240
+ let warn = true;
1241
+ let strict = false;
1242
+ let splitMode = null;
1243
+ let sharedThreshold = 3;
1244
+ for (let i = 0; i < args.length; i++) {
1245
+ const arg = args[i];
1246
+ if (arg === "--output" || arg === "-o") outputFile = args[++i];
1247
+ else if (arg === "--output-dir") outputDir = args[++i];
1248
+ else if (arg === "--pretty") pretty = true;
1249
+ else if (arg === "--json") jsonOutput = true;
1250
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
1251
+ else if (arg === "--no-warn") warn = false;
1252
+ else if (arg === "--strict") {
1253
+ strict = true;
1254
+ warn = true;
1255
+ } else if (arg === "--split") splitMode = "file";
1256
+ else if (arg === "--split-dir") splitMode = "dir";
1257
+ else if (arg === "--shared-threshold") {
1258
+ sharedThreshold = parseInt(args[++i], 10);
1259
+ if (isNaN(sharedThreshold) || sharedThreshold < 1) {
1260
+ console.error("Error: --shared-threshold must be a positive number");
1261
+ process.exit(1);
1262
+ }
1263
+ } else if (!arg.startsWith("-")) dir = arg;
1264
+ }
1265
+ try {
1266
+ if (splitMode) {
1267
+ if (!outputDir) {
1268
+ console.error("Error: --output-dir is required for split mode");
1269
+ process.exit(1);
1270
+ }
1271
+ const result$1 = extractSplit(dir, splitMode, {
1272
+ pretty,
1273
+ warn,
1274
+ strict,
1275
+ verbose,
1276
+ sharedThreshold
1277
+ });
1278
+ fs$1.mkdirSync(outputDir, { recursive: true });
1279
+ const sharedPath = path$1.join(outputDir, "_shared.css");
1280
+ fs$1.writeFileSync(sharedPath, result$1.shared.css);
1281
+ if (verbose) console.error(`Written shared CSS to ${sharedPath}`);
1282
+ for (const [chunkKey, chunk] of result$1.chunks) {
1283
+ if (!chunk.css) continue;
1284
+ let chunkPath;
1285
+ if (splitMode === "file") chunkPath = path$1.join(outputDir, chunkKey.replace(/\.mbt$/, ".css"));
1286
+ else {
1287
+ const dirName = chunkKey === "." ? "root" : chunkKey.replace(/\//g, "_");
1288
+ chunkPath = path$1.join(outputDir, `${dirName}.css`);
1289
+ }
1290
+ fs$1.mkdirSync(path$1.dirname(chunkPath), { recursive: true });
1291
+ fs$1.writeFileSync(chunkPath, chunk.css);
1292
+ if (verbose) console.error(`Written chunk CSS to ${chunkPath}`);
1293
+ }
1294
+ if (jsonOutput) {
1295
+ const mappingPath = path$1.join(outputDir, "mapping.json");
1296
+ const mappingData = {
1297
+ mapping: result$1.mapping,
1298
+ chunks: Object.fromEntries(Array.from(result$1.chunks.entries()).map(([k, v]) => [k, { size: v.css.length }])),
1299
+ shared: { size: result$1.shared.css.length },
1300
+ warnings: result$1.warnings
1301
+ };
1302
+ fs$1.writeFileSync(mappingPath, JSON.stringify(mappingData, null, pretty ? 2 : 0));
1303
+ if (verbose) console.error(`Written mapping to ${mappingPath}`);
1304
+ }
1305
+ if (verbose) {
1306
+ console.error("\nSplit extraction summary:");
1307
+ console.error(` Shared: ${result$1.shared.css.length} bytes`);
1308
+ for (const [chunkKey, chunk] of result$1.chunks) console.error(` ${chunkKey}: ${chunk.css.length} bytes`);
1309
+ console.error(` Combined: ${result$1.combined.length} bytes`);
1310
+ }
1311
+ if (warn && result$1.warnings && result$1.warnings.length > 0) printWarnings(result$1.warnings, strict);
1312
+ return;
1313
+ }
1314
+ const result = extract(dir, {
1315
+ pretty,
1316
+ warn,
1317
+ strict,
1318
+ verbose
1319
+ });
1320
+ let output;
1321
+ if (jsonOutput) output = JSON.stringify({
1322
+ css: result.css,
1323
+ mapping: result.mapping,
1324
+ stats: result.stats,
1325
+ warnings: result.warnings
1326
+ }, null, pretty ? 2 : 0);
1327
+ else output = result.css;
1328
+ if (outputFile) {
1329
+ fs$1.writeFileSync(outputFile, output);
1330
+ if (verbose) console.error(`Written to ${outputFile}`);
1331
+ } else console.log(output);
1332
+ if (warn && result.warnings && result.warnings.length > 0) printWarnings(result.warnings, strict);
1333
+ } catch (err) {
1334
+ console.error(`Error: ${err.message}`);
1335
+ process.exit(1);
1336
+ }
1337
+ }
1338
+ function printWarnings(warnings, strict) {
1339
+ console.error(`\n⚠️ ${warnings.length} warning(s): non-literal CSS arguments detected\n`);
1340
+ for (const w of warnings) {
1341
+ console.error(` ${w.file}:${w.line}`);
1342
+ console.error(` ${w.func}(...) - ${w.reason}`);
1343
+ console.error(` Code: ${w.code}`);
1344
+ console.error("");
1345
+ }
1346
+ if (strict) {
1347
+ console.error("❌ Strict mode: exiting with error due to warnings.");
1348
+ process.exit(1);
1349
+ }
1350
+ }
1351
+ function handleCssMinify(args) {
1352
+ let inputFile = null;
1353
+ let outputFile = null;
1354
+ let verbose = false;
1355
+ for (let i = 0; i < args.length; i++) {
1356
+ const arg = args[i];
1357
+ if (arg === "--output" || arg === "-o") outputFile = args[++i];
1358
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
1359
+ else if (!arg.startsWith("-")) inputFile = arg;
1360
+ }
1361
+ if (!inputFile) {
1362
+ console.error("Error: No input file specified");
1363
+ printCssHelp();
1364
+ process.exit(1);
1365
+ }
1366
+ try {
1367
+ const result = minify(fs$1.readFileSync(inputFile, "utf-8"), { verbose });
1368
+ if (outputFile) {
1369
+ fs$1.writeFileSync(outputFile, result.minified);
1370
+ console.error(`Minified: ${formatSize(result.originalSize)} → ${formatSize(result.minifiedSize)} (${result.reduction.toFixed(1)}% reduction)`);
1371
+ console.error(`Written to: ${outputFile}`);
1372
+ } else console.log(result.minified);
1373
+ } catch (err) {
1374
+ console.error(`Error: ${err.message}`);
1375
+ process.exit(1);
1376
+ }
1377
+ }
1378
+ function handleCssInline(args) {
1379
+ let inputFile = null;
1380
+ let outputFile = null;
1381
+ let mappingFile = null;
1382
+ let extractFrom = null;
1383
+ let verbose = false;
1384
+ let dryRun = false;
1385
+ let removeRegistry = false;
1386
+ for (let i = 0; i < args.length; i++) {
1387
+ const arg = args[i];
1388
+ if (arg === "--mapping" || arg === "-m") mappingFile = args[++i];
1389
+ else if (arg === "--extract-from") extractFrom = args[++i];
1390
+ else if (arg === "--output" || arg === "-o") outputFile = args[++i];
1391
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
1392
+ else if (arg === "--dry-run") {
1393
+ dryRun = true;
1394
+ verbose = true;
1395
+ } else if (arg === "--remove-registry") removeRegistry = true;
1396
+ else if (!arg.startsWith("-")) inputFile = arg;
1397
+ }
1398
+ if (!inputFile) {
1399
+ console.error("Error: No input file specified");
1400
+ printCssHelp();
1401
+ process.exit(1);
1402
+ }
1403
+ if (!mappingFile && !extractFrom) {
1404
+ console.error("Error: Must specify --mapping or --extract-from");
1405
+ printCssHelp();
1406
+ process.exit(1);
1407
+ }
1408
+ try {
1409
+ const code = fs$1.readFileSync(inputFile, "utf-8");
1410
+ let result;
1411
+ if (extractFrom) result = inlineFromSource(code, extractFrom, {
1412
+ verbose,
1413
+ dryRun,
1414
+ removeRegistry
1415
+ });
1416
+ else {
1417
+ const mappingContent = fs$1.readFileSync(mappingFile, "utf-8");
1418
+ const mappingData = JSON.parse(mappingContent);
1419
+ result = inlineCSS(code, mappingData.mapping || mappingData, {
1420
+ verbose,
1421
+ dryRun
1422
+ });
1423
+ if (removeRegistry) result = {
1424
+ ...result,
1425
+ code: removeRegistryCode(result.code),
1426
+ finalSize: Buffer.byteLength(removeRegistryCode(result.code), "utf-8")
1427
+ };
1428
+ }
1429
+ if (verbose) {
1430
+ console.error(`Loaded mapping, found ${result.replacements.length} replacements`);
1431
+ for (const r of result.replacements) console.error(` [${r.type}] ${r.key || r.decl} → ${r.to}`);
1432
+ console.error(`\nSize: ${result.originalSize} → ${result.finalSize} (${((1 - result.finalSize / result.originalSize) * 100).toFixed(1)}% reduction)`);
1433
+ }
1434
+ if (dryRun) console.error("\n[Dry run - no files modified]");
1435
+ else if (outputFile) {
1436
+ fs$1.writeFileSync(outputFile, result.code);
1437
+ if (verbose) console.error(`Written to ${outputFile}`);
1438
+ } else console.log(result.code);
1439
+ } catch (err) {
1440
+ console.error(`Error: ${err.message}`);
1441
+ process.exit(1);
1442
+ }
1443
+ }
1444
+ function handleCssInject(args) {
1445
+ let htmlFile = null;
1446
+ let srcDir = null;
1447
+ let outputFile = null;
1448
+ let mode = "inline";
1449
+ let threshold = 4096;
1450
+ let cssFileName = "luna.css";
1451
+ let verbose = false;
1452
+ for (let i = 0; i < args.length; i++) {
1453
+ const arg = args[i];
1454
+ if (arg === "--src") srcDir = args[++i];
1455
+ else if (arg === "--output" || arg === "-o") outputFile = args[++i];
1456
+ else if (arg === "--mode" || arg === "-m") {
1457
+ const m = args[++i];
1458
+ if (m === "inline" || m === "external" || m === "auto") mode = m;
1459
+ else {
1460
+ console.error(`Error: Invalid mode "${m}". Use inline, external, or auto.`);
1461
+ process.exit(1);
1462
+ }
1463
+ } else if (arg === "--threshold" || arg === "-t") {
1464
+ threshold = parseInt(args[++i], 10);
1465
+ if (isNaN(threshold)) {
1466
+ console.error("Error: --threshold must be a number");
1467
+ process.exit(1);
1468
+ }
1469
+ } else if (arg === "--css-file") cssFileName = args[++i];
1470
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
1471
+ else if (!arg.startsWith("-")) htmlFile = arg;
1472
+ }
1473
+ if (!htmlFile) {
1474
+ console.error("Error: No HTML file specified");
1475
+ printCssHelp();
1476
+ process.exit(1);
1477
+ }
1478
+ if (!srcDir) {
1479
+ console.error("Error: --src <dir> is required");
1480
+ printCssHelp();
1481
+ process.exit(1);
1482
+ }
1483
+ try {
1484
+ const result = injectAndWrite({
1485
+ srcDir,
1486
+ htmlFile,
1487
+ outputFile: outputFile || void 0,
1488
+ mode,
1489
+ threshold,
1490
+ cssFileName,
1491
+ verbose
1492
+ });
1493
+ if (!result.replaced) {
1494
+ console.error("Warning: CSS markers not found. Add /* LUNA_CSS_START */ and /* LUNA_CSS_END */ to your HTML.");
1495
+ process.exit(1);
1496
+ }
1497
+ if (verbose) {
1498
+ console.error(`Mode: ${result.mode}, injected ${result.css.length} bytes of CSS`);
1499
+ if (result.cssFile) console.error(`External CSS file: ${result.cssFile}`);
1500
+ }
1501
+ } catch (err) {
1502
+ console.error(`Error: ${err.message}`);
1503
+ process.exit(1);
1504
+ }
1505
+ }
1506
+ async function handleCssAnalyzeMbt(args) {
1507
+ let dir = ".";
1508
+ let outputFile = null;
1509
+ let recursive = true;
1510
+ let verbose = false;
1511
+ for (let i = 0; i < args.length; i++) {
1512
+ const arg = args[i];
1513
+ if (arg === "--output" || arg === "-o") outputFile = args[++i];
1514
+ else if (arg === "--no-recursive") recursive = false;
1515
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
1516
+ else if (!arg.startsWith("-")) dir = arg;
1517
+ }
1518
+ try {
1519
+ if (verbose) console.error(`Analyzing MoonBit files in: ${dir}`);
1520
+ const result = await analyzeDirectory(dir, { recursive });
1521
+ const output = JSON.stringify(result, null, 2);
1522
+ if (outputFile) {
1523
+ fs$1.writeFileSync(outputFile, output);
1524
+ if (verbose) console.error(`Written to ${outputFile}`);
1525
+ } else console.log(output);
1526
+ if (verbose) {
1527
+ console.error(`\nFound ${result.cooccurrences.length} class co-occurrences`);
1528
+ if (result.warnings.length > 0) {
1529
+ console.error(`Warnings: ${result.warnings.length}`);
1530
+ for (const w of result.warnings) console.error(` ${w.file}:${w.line} - ${w.kind}: ${w.message}`);
1531
+ }
1532
+ }
1533
+ } catch (err) {
1534
+ console.error(`Error: ${err.message}`);
1535
+ process.exit(1);
1536
+ }
1537
+ }
1538
+ async function handleCss(args) {
1539
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
1540
+ printCssHelp();
1541
+ process.exit(0);
1542
+ }
1543
+ const subcommand = args[0];
1544
+ const subArgs = args.slice(1);
1545
+ switch (subcommand) {
1546
+ case "extract":
1547
+ handleCssExtract(subArgs);
1548
+ break;
1549
+ case "minify":
1550
+ handleCssMinify(subArgs);
1551
+ break;
1552
+ case "inline":
1553
+ handleCssInline(subArgs);
1554
+ break;
1555
+ case "inject":
1556
+ handleCssInject(subArgs);
1557
+ break;
1558
+ case "analyze-mbt":
1559
+ await handleCssAnalyzeMbt(subArgs);
1560
+ break;
1561
+ default:
1562
+ console.error(`Unknown css subcommand: ${subcommand}`);
1563
+ printCssHelp();
1564
+ process.exit(1);
1565
+ }
1566
+ }
1567
+ async function main() {
1568
+ const args = process.argv.slice(2);
1569
+ if (args.length === 0) {
1570
+ printMainHelp();
1571
+ process.exit(0);
1572
+ }
1573
+ const command = args[0];
1574
+ if (command === "--help" || command === "-h") {
1575
+ printMainHelp();
1576
+ process.exit(0);
1577
+ }
1578
+ const commandArgs = args.slice(1);
1579
+ switch (command) {
1580
+ case "new":
1581
+ handleNew(commandArgs);
1582
+ break;
1583
+ case "css":
1584
+ await handleCss(commandArgs);
1585
+ break;
1586
+ default:
1587
+ console.error(`Unknown command: ${command}`);
1588
+ printMainHelp();
1589
+ process.exit(1);
1590
+ }
1591
+ }
1592
+ main().catch((err) => {
1593
+ console.error(`Error: ${err.message}`);
1594
+ process.exit(1);
1595
+ });
359
1596
 
360
1597
  //#endregion
361
1598
  export { };