irgen 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +312 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/dsl/aggregator.d.ts +8 -0
  9. package/dist/dsl/aggregator.d.ts.map +1 -0
  10. package/dist/dsl/aggregator.js +64 -0
  11. package/dist/dsl/aggregator.js.map +1 -0
  12. package/dist/dsl/frontend-runtime.d.ts +486 -0
  13. package/dist/dsl/frontend-runtime.d.ts.map +1 -0
  14. package/dist/dsl/frontend-runtime.js +232 -0
  15. package/dist/dsl/frontend-runtime.js.map +1 -0
  16. package/dist/dsl/runtime.d.ts +33 -0
  17. package/dist/dsl/runtime.d.ts.map +1 -0
  18. package/dist/dsl/runtime.js +120 -0
  19. package/dist/dsl/runtime.js.map +1 -0
  20. package/dist/emit/backend/adapters.d.ts +11 -0
  21. package/dist/emit/backend/adapters.d.ts.map +1 -0
  22. package/dist/emit/backend/adapters.js +374 -0
  23. package/dist/emit/backend/adapters.js.map +1 -0
  24. package/dist/emit/backend/backend-tsmorph.d.ts +5 -0
  25. package/dist/emit/backend/backend-tsmorph.d.ts.map +1 -0
  26. package/dist/emit/backend/backend-tsmorph.js +858 -0
  27. package/dist/emit/backend/backend-tsmorph.js.map +1 -0
  28. package/dist/emit/backend/fake-backend.d.ts +2 -0
  29. package/dist/emit/backend/fake-backend.d.ts.map +1 -0
  30. package/dist/emit/backend/fake-backend.js +19 -0
  31. package/dist/emit/backend/fake-backend.js.map +1 -0
  32. package/dist/emit/backend/packaging.d.ts +3 -0
  33. package/dist/emit/backend/packaging.d.ts.map +1 -0
  34. package/dist/emit/backend/packaging.js +71 -0
  35. package/dist/emit/backend/packaging.js.map +1 -0
  36. package/dist/emit/backend/server.d.ts +4 -0
  37. package/dist/emit/backend/server.d.ts.map +1 -0
  38. package/dist/emit/backend/server.js +169 -0
  39. package/dist/emit/backend/server.js.map +1 -0
  40. package/dist/emit/cli/cli-fake.d.ts +2 -0
  41. package/dist/emit/cli/cli-fake.d.ts.map +1 -0
  42. package/dist/emit/cli/cli-fake.js +33 -0
  43. package/dist/emit/cli/cli-fake.js.map +1 -0
  44. package/dist/emit/electron/electron-shell.d.ts +3 -0
  45. package/dist/emit/electron/electron-shell.d.ts.map +1 -0
  46. package/dist/emit/electron/electron-shell.js +454 -0
  47. package/dist/emit/electron/electron-shell.js.map +1 -0
  48. package/dist/emit/engine.d.ts +14 -0
  49. package/dist/emit/engine.d.ts.map +1 -0
  50. package/dist/emit/engine.js +25 -0
  51. package/dist/emit/engine.js.map +1 -0
  52. package/dist/emit/format.d.ts +2 -0
  53. package/dist/emit/format.d.ts.map +1 -0
  54. package/dist/emit/format.js +23 -0
  55. package/dist/emit/format.js.map +1 -0
  56. package/dist/emit/frontend/frontend-react.d.ts +4 -0
  57. package/dist/emit/frontend/frontend-react.d.ts.map +1 -0
  58. package/dist/emit/frontend/frontend-react.js +2021 -0
  59. package/dist/emit/frontend/frontend-react.js.map +1 -0
  60. package/dist/emit/frontend/registry.d.ts +20 -0
  61. package/dist/emit/frontend/registry.d.ts.map +1 -0
  62. package/dist/emit/frontend/registry.js +46 -0
  63. package/dist/emit/frontend/registry.js.map +1 -0
  64. package/dist/emit/frontend/runtime-emitter.d.ts +4 -0
  65. package/dist/emit/frontend/runtime-emitter.d.ts.map +1 -0
  66. package/dist/emit/frontend/runtime-emitter.js +435 -0
  67. package/dist/emit/frontend/runtime-emitter.js.map +1 -0
  68. package/dist/emit/frontend/runtime-template.d.ts +28 -0
  69. package/dist/emit/frontend/runtime-template.d.ts.map +1 -0
  70. package/dist/emit/frontend/runtime-template.js +218 -0
  71. package/dist/emit/frontend/runtime-template.js.map +1 -0
  72. package/dist/emit/frontend/ssg.d.ts +8 -0
  73. package/dist/emit/frontend/ssg.d.ts.map +1 -0
  74. package/dist/emit/frontend/ssg.js +219 -0
  75. package/dist/emit/frontend/ssg.js.map +1 -0
  76. package/dist/emit/registry.d.ts +17 -0
  77. package/dist/emit/registry.d.ts.map +1 -0
  78. package/dist/emit/registry.js +38 -0
  79. package/dist/emit/registry.js.map +1 -0
  80. package/dist/emit/static-site/css.d.ts +5 -0
  81. package/dist/emit/static-site/css.d.ts.map +1 -0
  82. package/dist/emit/static-site/css.js +872 -0
  83. package/dist/emit/static-site/css.js.map +1 -0
  84. package/dist/emit/static-site/enhancements.d.ts +11 -0
  85. package/dist/emit/static-site/enhancements.d.ts.map +1 -0
  86. package/dist/emit/static-site/enhancements.js +266 -0
  87. package/dist/emit/static-site/enhancements.js.map +1 -0
  88. package/dist/emit/static-site/static-site-html.d.ts +3 -0
  89. package/dist/emit/static-site/static-site-html.d.ts.map +1 -0
  90. package/dist/emit/static-site/static-site-html.js +1172 -0
  91. package/dist/emit/static-site/static-site-html.js.map +1 -0
  92. package/dist/emit/utils/sdk.d.ts +15 -0
  93. package/dist/emit/utils/sdk.d.ts.map +1 -0
  94. package/dist/emit/utils/sdk.js +34 -0
  95. package/dist/emit/utils/sdk.js.map +1 -0
  96. package/dist/extensions/context.d.ts +23 -0
  97. package/dist/extensions/context.d.ts.map +1 -0
  98. package/dist/extensions/context.js +43 -0
  99. package/dist/extensions/context.js.map +1 -0
  100. package/dist/index.d.ts +32 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +135 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/ir/decl/backend.raw.schema.d.ts +128 -0
  105. package/dist/ir/decl/backend.raw.schema.d.ts.map +1 -0
  106. package/dist/ir/decl/backend.raw.schema.js +24 -0
  107. package/dist/ir/decl/backend.raw.schema.js.map +1 -0
  108. package/dist/ir/decl/bundle.d.ts +15 -0
  109. package/dist/ir/decl/bundle.d.ts.map +1 -0
  110. package/dist/ir/decl/bundle.js +8 -0
  111. package/dist/ir/decl/bundle.js.map +1 -0
  112. package/dist/ir/decl/cli.raw.schema.d.ts +133 -0
  113. package/dist/ir/decl/cli.raw.schema.d.ts.map +1 -0
  114. package/dist/ir/decl/cli.raw.schema.js +20 -0
  115. package/dist/ir/decl/cli.raw.schema.js.map +1 -0
  116. package/dist/ir/decl/frontend.raw.schema.d.ts +6631 -0
  117. package/dist/ir/decl/frontend.raw.schema.d.ts.map +1 -0
  118. package/dist/ir/decl/frontend.raw.schema.js +272 -0
  119. package/dist/ir/decl/frontend.raw.schema.js.map +1 -0
  120. package/dist/ir/decl/index.d.ts +6 -0
  121. package/dist/ir/decl/index.d.ts.map +1 -0
  122. package/dist/ir/decl/index.js +6 -0
  123. package/dist/ir/decl/index.js.map +1 -0
  124. package/dist/ir/decl/normalize.schema.d.ts +9154 -0
  125. package/dist/ir/decl/normalize.schema.d.ts.map +1 -0
  126. package/dist/ir/decl/normalize.schema.js +71 -0
  127. package/dist/ir/decl/normalize.schema.js.map +1 -0
  128. package/dist/ir/domain/backend.d.ts +19 -0
  129. package/dist/ir/domain/backend.d.ts.map +1 -0
  130. package/dist/ir/domain/backend.js +3 -0
  131. package/dist/ir/domain/backend.js.map +1 -0
  132. package/dist/ir/domain/cli.d.ts +18 -0
  133. package/dist/ir/domain/cli.d.ts.map +1 -0
  134. package/dist/ir/domain/cli.js +2 -0
  135. package/dist/ir/domain/cli.js.map +1 -0
  136. package/dist/ir/domain/frontend/index.d.ts +190 -0
  137. package/dist/ir/domain/frontend/index.d.ts.map +1 -0
  138. package/dist/ir/domain/frontend/index.js +2 -0
  139. package/dist/ir/domain/frontend/index.js.map +1 -0
  140. package/dist/ir/domain/frontend.d.ts +2 -0
  141. package/dist/ir/domain/frontend.d.ts.map +1 -0
  142. package/dist/ir/domain/frontend.js +3 -0
  143. package/dist/ir/domain/frontend.js.map +1 -0
  144. package/dist/ir/frontend-contract.d.ts +187 -0
  145. package/dist/ir/frontend-contract.d.ts.map +1 -0
  146. package/dist/ir/frontend-contract.js +6 -0
  147. package/dist/ir/frontend-contract.js.map +1 -0
  148. package/dist/ir/target/backend.d.ts +11 -0
  149. package/dist/ir/target/backend.d.ts.map +1 -0
  150. package/dist/ir/target/backend.js +2 -0
  151. package/dist/ir/target/backend.js.map +1 -0
  152. package/dist/ir/target/backend.policy.d.ts +896 -0
  153. package/dist/ir/target/backend.policy.d.ts.map +1 -0
  154. package/dist/ir/target/backend.policy.js +106 -0
  155. package/dist/ir/target/backend.policy.js.map +1 -0
  156. package/dist/ir/target/cli.d.ts +3 -0
  157. package/dist/ir/target/cli.d.ts.map +1 -0
  158. package/dist/ir/target/cli.js +2 -0
  159. package/dist/ir/target/cli.js.map +1 -0
  160. package/dist/ir/target/electron.d.ts +99 -0
  161. package/dist/ir/target/electron.d.ts.map +1 -0
  162. package/dist/ir/target/electron.js +2 -0
  163. package/dist/ir/target/electron.js.map +1 -0
  164. package/dist/ir/target/electron.policy.d.ts +7015 -0
  165. package/dist/ir/target/electron.policy.d.ts.map +1 -0
  166. package/dist/ir/target/electron.policy.js +119 -0
  167. package/dist/ir/target/electron.policy.js.map +1 -0
  168. package/dist/ir/target/frontend.d.ts +12 -0
  169. package/dist/ir/target/frontend.d.ts.map +1 -0
  170. package/dist/ir/target/frontend.js +2 -0
  171. package/dist/ir/target/frontend.js.map +1 -0
  172. package/dist/ir/target/frontend.policy.d.ts +268 -0
  173. package/dist/ir/target/frontend.policy.d.ts.map +1 -0
  174. package/dist/ir/target/frontend.policy.js +33 -0
  175. package/dist/ir/target/frontend.policy.js.map +1 -0
  176. package/dist/ir/target/index.d.ts +6 -0
  177. package/dist/ir/target/index.d.ts.map +1 -0
  178. package/dist/ir/target/index.js +6 -0
  179. package/dist/ir/target/index.js.map +1 -0
  180. package/dist/ir/target/static-site.d.ts +18 -0
  181. package/dist/ir/target/static-site.d.ts.map +1 -0
  182. package/dist/ir/target/static-site.js +2 -0
  183. package/dist/ir/target/static-site.js.map +1 -0
  184. package/dist/ir/target/static-site.policy.d.ts +2911 -0
  185. package/dist/ir/target/static-site.policy.d.ts.map +1 -0
  186. package/dist/ir/target/static-site.policy.js +127 -0
  187. package/dist/ir/target/static-site.policy.js.map +1 -0
  188. package/dist/lowering/backend.d.ts +4 -0
  189. package/dist/lowering/backend.d.ts.map +1 -0
  190. package/dist/lowering/backend.js +57 -0
  191. package/dist/lowering/backend.js.map +1 -0
  192. package/dist/lowering/cli.d.ts +4 -0
  193. package/dist/lowering/cli.d.ts.map +1 -0
  194. package/dist/lowering/cli.js +22 -0
  195. package/dist/lowering/cli.js.map +1 -0
  196. package/dist/lowering/engine.d.ts +18 -0
  197. package/dist/lowering/engine.d.ts.map +1 -0
  198. package/dist/lowering/engine.js +47 -0
  199. package/dist/lowering/engine.js.map +1 -0
  200. package/dist/lowering/frontend.d.ts +9 -0
  201. package/dist/lowering/frontend.d.ts.map +1 -0
  202. package/dist/lowering/frontend.js +246 -0
  203. package/dist/lowering/frontend.js.map +1 -0
  204. package/dist/lowering/targets/to-backend.d.ts +9 -0
  205. package/dist/lowering/targets/to-backend.d.ts.map +1 -0
  206. package/dist/lowering/targets/to-backend.js +55 -0
  207. package/dist/lowering/targets/to-backend.js.map +1 -0
  208. package/dist/lowering/targets/to-cli.d.ts +4 -0
  209. package/dist/lowering/targets/to-cli.d.ts.map +1 -0
  210. package/dist/lowering/targets/to-cli.js +11 -0
  211. package/dist/lowering/targets/to-cli.js.map +1 -0
  212. package/dist/lowering/targets/to-electron.d.ts +30 -0
  213. package/dist/lowering/targets/to-electron.d.ts.map +1 -0
  214. package/dist/lowering/targets/to-electron.js +87 -0
  215. package/dist/lowering/targets/to-electron.js.map +1 -0
  216. package/dist/lowering/targets/to-frontend.d.ts +4 -0
  217. package/dist/lowering/targets/to-frontend.d.ts.map +1 -0
  218. package/dist/lowering/targets/to-frontend.js +30 -0
  219. package/dist/lowering/targets/to-frontend.js.map +1 -0
  220. package/dist/lowering/targets/to-static-site.d.ts +16 -0
  221. package/dist/lowering/targets/to-static-site.d.ts.map +1 -0
  222. package/dist/lowering/targets/to-static-site.js +30 -0
  223. package/dist/lowering/targets/to-static-site.js.map +1 -0
  224. package/dist/mappers/index.d.ts +12 -0
  225. package/dist/mappers/index.d.ts.map +1 -0
  226. package/dist/mappers/index.js +60 -0
  227. package/dist/mappers/index.js.map +1 -0
  228. package/dist/types/extension.d.ts +3 -0
  229. package/dist/types/extension.d.ts.map +1 -0
  230. package/dist/types/extension.js +2 -0
  231. package/dist/types/extension.js.map +1 -0
  232. package/dist/utils/array.d.ts +2 -0
  233. package/dist/utils/array.d.ts.map +1 -0
  234. package/dist/utils/array.js +4 -0
  235. package/dist/utils/array.js.map +1 -0
  236. package/dist/utils/index.d.ts +3 -0
  237. package/dist/utils/index.d.ts.map +1 -0
  238. package/dist/utils/index.js +3 -0
  239. package/dist/utils/index.js.map +1 -0
  240. package/dist/utils/string.d.ts +13 -0
  241. package/dist/utils/string.d.ts.map +1 -0
  242. package/dist/utils/string.js +56 -0
  243. package/dist/utils/string.js.map +1 -0
  244. package/package.json +112 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,113 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `irgen` project will be documented in this file.
4
+
5
+ ## [0.2.0] - 2026-01-11
6
+
7
+ ### General Purpose Frontend & Headless Runtime (Phase 3)
8
+
9
+ #### Core Architecture
10
+ - **Operation-Oriented Runtime**: Transitioned from a "frontend for a specific backend" to a "general-purpose webapp generator". The runtime now treats **Operations** as the atom of interaction.
11
+ - **Headless Client Runtime**: Implemented a backend-agnostic runtime (`lib/runtime.ts`) that manages operation execution, authentication, and response normalization without being tied to a specific UI framework.
12
+ - **React Hooks Integration**: Introduced `useOperation` and `useResource` hooks for headless interaction, providing built-in loading, error, and data state management.
13
+ - **DataSource Abstraction**: Support for multiple `datasources` with specialized `AuthStrategy`, `EnvelopeAdapter`, and `PaginationAdapter` to connect to any API (REST, GraphQL, etc.).
14
+
15
+ #### UI & Components
16
+ - **Operation-Bound Components**: Refactored Form and Select components to use the new `useOperation` hook.
17
+ - **Table Component**: New first-class `Table` component with direct binding to operations or resources, featuring premium styling and automatic data fetching.
18
+ - **Multi-App Deployment**: Support for `basePath` in frontend policies and IR, allowing multiple applications (e.g., `PublicSite` and `AdminPortal`) to be routed correctly under different subpaths.
19
+
20
+ #### DSL Enhancements
21
+ - **Modernized Frontend DSL**: `frontend()` now supports defining entities (`datasources`, `operations`, `resources`) directly via the options object or as standalone function calls within the callback.
22
+ - **Improved Type Safety**: Refined `RuntimeComponent` and DSL helper types for better developer experience.
23
+
24
+ ### Major Features (Phases 9-10)
25
+
26
+ #### Static-site (HTML-first)
27
+ - **Static-site target**: HTML-first emitter with policy-driven routing, SEO, theming, and asset management.
28
+ - **Progressive enhancements**: Optional sidebar toggle, copy-code, theme toggle, TOC scroll spy, and search.
29
+ - **Code highlighting**: Build-time Shiki highlighting with optional Prism client runtime.
30
+ - **SEO output**: `<title>`, description, canonical, OpenGraph, sitemap, and robots.txt.
31
+ - **Assets**: CSS generation, hashing, public asset passthrough, font preload hints, and CSP meta support.
32
+ - **Search**: Client-side search index + MiniSearch integration (fallback to basic search).
33
+ - **Examples & tests**: Static-site DSL examples and golden test coverage.
34
+
35
+ #### Frontend & UI
36
+ - **Global Dark Mode**: Implemented a comprehensive dark mode system with a persistent toggle in the main Navbar, `localStorage` state persistence, and adaptive styling for all components (Forms, Marketing, Layouts).
37
+ - **Multi-page SPA Support**: Added full support for complex, multi-page websites via `react-router-dom`, including a high-quality global Navbar and Footer.
38
+ - **Marketing Component Expansion**: Introduced 20+ rich marketing and content components (Hero, Features, Testimonials, FAQ, Logos, CTA, Stats, Timeline) with professional dark mode variants.
39
+ - **Native Syntax Highlighter**: Added a dedicated `CodeBlock` component and `code()` DSL helper, with automatic dependency management (injects `react-syntax-highlighter` into `package.json` only when needed).
40
+ - **Layout Refinement**: Enhanced Tabs, Panels, and Grid components with premium aesthetics, smooth transitions, and better active states.
41
+
42
+ #### CLI & Engine
43
+ - **CLI Flag: `--outDir`**: Added support for specifying a custom output directory via flag or positional argument.
44
+ - **Improved DSL Loading**: Solved relative import issues when loading DSLs through temporary transpile files by ensuring temp files reside in the same directory as the source.
45
+ - **Robustness**: Fixed interpolation bugs in the React emitter and relaxed Zod validation for internal URLs in form submissions.
46
+ - **Mapping & Lowering**: Updated mapping logic to preserve new component properties (like `codeBlock`) through the lowering pipeline.
47
+
48
+ #### Rebranding & Identity
49
+ - **Project Renaming**: Officially rebranded from `ir-codegen` to `irgen` for a modern, CLI-friendly identity (consistent with tools like `esbuild` and `vite`).
50
+ - **New Positioning**: Reframed the toolchain as a "compiler-style code generation toolchain built around Intermediate Representation (IR)".
51
+ - **Tagline Update**: Adopted "Compiler-style code generation via Intermediate Representation" as the primary tagline.
52
+ - **Documentation Overhaul**: Updated `README.md` and `ARCHITECTURE.md` to reflect the new policy-driven, compiler-oriented philosophy.
53
+ - **Example Refresh**: Updated `irgen-web.dsl.ts` with the new brand voice and positioning statements.
54
+
55
+ ## [0.1.0] - pre-public history
56
+
57
+ ### Architecture/IR
58
+ - Refined IR layering to match DeclIR → DomainIR → TargetIR: DSL schemas now live under `src/ir/decl/*`, DomainIR files are schema/policy-free, and TargetIR holds emitter-facing policies (backend target now carries `policies.backend.*`).
59
+ - Removed legacy/unified/compat shims in favor of explicit bundle/normalize (`DeclBundle`) aggregation.
60
+ - Mapper/CLI paths always go through bundle → mapper → lowering engine → target transform → emitter; backend target lowering now handles policy defaults/validation.
61
+ - DSL can carry `meta`/`policies` that flow into DeclBundle; CLI merges DSL policies with `--policies` overrides. Extensions can be loaded via CLI `--ext` or programmatic API.
62
+ - CLI: registers built-in mappers before extensions, supports `--ext` loading, defaults to frontend DSL for frontend-like targets (electron/electrobun), prefers frontend loader when those targets requested, and logs actual targets (mode auto when no flag).
63
+
64
+ ### Major Features (Phases 1-8 Completion)
65
+
66
+ #### Cross-cutting
67
+ - **Backend/Frontend decoupling**: Frontend generation is now driven by its own pipeline (lowering + emitter) and ships with its own `package.json`. Backend generation no longer injects frontend/tailwind dependencies or calls the frontend emitter; running backend and frontend together is an additive choice (run one, the other, or both).
68
+
69
+ #### Architecture
70
+ - **Generation Gap Pattern**: Refactored backend service generation to separate base classes (auto-generated) from user service implementations (scaffolded once).
71
+ - **Repository Pattern**: Introduced `BaseRepository` and concrete repositories with Dependency Injection (DI) support.
72
+ - **Prisma ORM**: Integrated Prisma as the data layer, replacing in-memory mocks.
73
+
74
+ #### Backend
75
+ - **Dependency Injection**: Services and Controllers now use constructor injection for better testability.
76
+ - **Extensibility Hooks**: Added `beforeCreate`, `afterCreate`, etc., hooks in `BaseService` for validation and business logic.
77
+ - **Automated Testing**: Integrated `vitest` and auto-generated test files for services.
78
+
79
+ #### Frontend
80
+ - **SSG/Hybrid Rendering (React)**: Added Vite-based SSG pipeline with SSR bundle + prerender step, manifest-based CSS/JS injection, static HTML output in root `outDir`, and SPA fallback preserved as `index.spa.html`. Hybrid mode hydrates only when needed (currently all routes due to App shell theme toggle).
81
+ - **Tailwind CSS**: Full integration with automated `tailwind.config.js` and `postcss.config.js` generation.
82
+ - **Rich UI Components**:
83
+ - Form fields now cover text/number/select/textarea/checkbox/radio/date/datetime/time/url/phone/password/daterange, slider, currency, tags/chips, file upload, signature.
84
+ - Select supports static options and async data sources with search/pagination/debounce, clearable, loading/error skeletons.
85
+ - Prefix/suffix/tooltip/helpHtml/className for richer templating; icons via Lucide React.
86
+ - **Client-Side Routing**: Transformed frontend output into a Single Page Application (SPA) using `react-router-dom` with auto-generated routes.
87
+ - **Optional PWA Output**: Frontend DSL/policies now accept `pwa.enabled=true` to emit `manifest.webmanifest`, service worker, and icons (defaults remain off).
88
+ - **Vite-Based Frontend Scaffolding**: Generated frontends include Vite config + plugins and ESM-compatible `postcss.config.js`, with entry pointing to `/src/index.tsx`.
89
+ - **React Import Safety**: Generated `App.tsx` explicitly imports React to avoid `React is not defined` when plugins are misconfigured.
90
+ - **Layout & Content Components**: Layout configs (row/column/panel/tabs) now render real child components; content/html blocks; CTA buttons with variants.
91
+ - **Validation & Logic**: JSONLogic-like sandbox (no `Function`), compare fields, min/max for numbers and dates (including date range), email/url built-ins, conditional visible/disabled/required-if, custom logic validators, required/empty checks that understand arrays/objects.
92
+ - **UX & Accessibility**: Async select UX (loading/error/search), multiple select, prefix/suffix/tooltip, aria-labels, error display; loading skeletons for async select.
93
+ - **Submission Pipeline & Actions**: Optional submit config (url/method/success/error messages) with loading/success/error UI, confirm dialog, lifecycle hooks (before/after submit, onSuccess/onError), redirect, draft save (localStorage), mock submit when not configured.
94
+ - **Frontend technical optimizations**:
95
+ - **Logic Lowering**: Moved complex validation rules, visibility predicates, and computed value logic from React emitters to the lowering stage, reducing runtime overhead and emitter complexity.
96
+ - **Shared Logic Library**: Extracted common evaluation logic (`evalLogic`, `getByPath`, `isEmptyVal`) into a generated `src/lib/logic.ts` library, shared across all components.
97
+ - **Policy-Driven UI**: Introduced `FrontendTargetIR` with resolved policies for styling (e.g., custom primary colors) and framework configuration, enabling consistent UI decisions across the generated project.
98
+ - **Dependency Tracking**: Implemented automatic dependency extraction for logical expressions, allowing generated `useEffect` hooks to trigger only when necessary (optimized rerenders).
99
+ - **Electron Target (Multi-frontend)**: Added Electron target lowering + emitter (`electron-shell`) that generates `main.ts`, `preload.ts`, `ipc-handlers.ts`, `package.json`, `tsconfig.json`, and helper scripts. IPC whitelist + custom handler stubs come from DSL policies (`policies.electron.ipc`), and the emitter avoids double-registering built-in handlers. FrontendIR is shared between Web/PWA and Electron via policy-driven lowering.
100
+ - **Electrobun Extension (sample)**: Optional extension emits Electrobun bundle (config, IR summary, page/component stubs, barrel, main stub, package.json with Bun+electrobun scripts). Upstream Electrobun CLI may have platform limitations (see electrobun issue #10).
101
+ - **Target lowering rename**: Target transforms standardized to `lowering/targets/to-*.ts` (to-backend, to-frontend, to-electron, etc.) for clarity.
102
+ - **Electron hardening & lifecycle**: Added security defaults (window.open/will-navigate guards, CSP header, eval/Function disabled), reliability (single instance, window state persist/restore), logging, crash reporting slot, auto-update wiring with renderer status events and retry-on-fail, and session-aware IPC cleanup. Auto-update policies support provider/url/channel, prerelease opt-in, headers, and retry tuning.
103
+
104
+ ### Pending / Parity Gaps vs Form.io (Future PRs)
105
+ - Components: survey, address/geo, select grid/resource grid, nested form/wizard/steps, edit grid/repeater, file upload storage adapter.
106
+ - Validation/Logic: stronger JSONLogic coverage (full operator set), i18n-friendly messages, richer unique/async validation.
107
+ - UX: dedicated i18n, richer per-field skeletons, configurable async select adapters, select grid.
108
+ - Actions: custom action handlers beyond submit (custom buttons/hooks).
109
+ - Styling/Templates: custom render templates per component/theme slots.
110
+ #### Developer Experience
111
+ - **Unified DSL**: Updated `app.dsl.ts` and introduced `fullstack.dsl.ts` examples.
112
+ - **Examples Organization**: `generate-examples.sh` script to manage multiple example outputs in dedicated folders.
113
+ - **Runtime Typing**: Improved TypeScript types for DSL runtime (`frontend-runtime.ts`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 irgen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # irgen (Robust Fullstack Generator)
2
+
3
+ [![CI](https://github.com/agusmade/irgen/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/agusmade/irgen/actions/workflows/ci.yml)
4
+
5
+ > **Compiler-style code generation via Intermediate Representation.**
6
+
7
+ irgen is a compiler-style code generation toolchain built around Intermediate Representation (IR). You describe your system in terms of **domain and policy**, and irgen performs IR transformations to generate backend, frontend, desktop, mobile, and documentation targets for you.
8
+
9
+ irgen is designed for developers who want architectural clarity without maintaining endless glue code. Unlike typical scaffolders, **source code is owned by the tool**, while **user code is preserved** via the [Generation Gap Pattern](https://martinfowler.com/dslwip/GenerationGap.html).
10
+
11
+ ### What irgen is NOT
12
+ irgen focuses on **architecture and determinism**, not convenience shortcuts. irgen is **not**:
13
+ - A framework or a library
14
+ - A simple scaffolding tool
15
+ - A template generator
16
+ - An AI-driven code writer
17
+
18
+ ## Key Features
19
+
20
+ ### Backend (Node.js/TypeScript)
21
+ - **Generation Gap Architecture**: Separates generated base classes from user implementation. Regenerate safely without losing manual changes.
22
+ - **Repository Pattern**: Auto-generated repositories with Dependency Injection (DI) support.
23
+ - **Prisma Integration**: Database schema and client management out-of-the-box.
24
+ - **Extensibility Hooks**: `beforeCreate`, `afterCreate`, etc., in services for custom business logic.
25
+ - **Automated Testing**: Auto-generated `vitest` unit tests for services.
26
+
27
+ ### Frontend (React/Vite)
28
+ - **General-Purpose Webapp Generator**: Move beyond "dashboard-only" UIs. Generate any web application by defining operations and data sources.
29
+ - **Headless Client Runtime**: Decoupled `lib/runtime.ts` and React hooks (`useOperation`, `useResource`) manage interaction with any backend (REST, GraphQL, etc.).
30
+ - **Operation-Oriented Architecture**: Actions (Operations) are the fundamental units of the client, allowing for complex command-oriented UIs.
31
+ - **DataSource Abstraction**: Connect to multiple backends with pluggable `AuthStrategy`, `EnvelopeAdapter`, and `PaginationAdapter`.
32
+ - **Global Dark Mode**: Built-in persistence (`localStorage`) and toggle in a sleek, glassmorphism Navbar.
33
+ - **Rich UI Components**:
34
+ - **Forms**: 30+ field types including slider, currency, tags, file upload, and signature.
35
+ - **Table**: First-class table component with direct operation/resource binding and pagination.
36
+ - **Marketing**: Responsive Hero, Features, Testimonials, FAQ, Logos, CTA, Stats, and Timeline sections.
37
+ - **Dev Tools**: Native **Syntax Highlighter** (`CodeBlock`) with automatic dependency management.
38
+ - **Multi-app Support**: Set `basePath` to deploy multiple apps (e.g., `/admin`, `/docs`) under a single project or domain.
39
+ - **Form Logic & UX**: JSONLogic predicates, dependency-tracked hooks, async select with debounce/pagination/search.
40
+ - **Submission Pipeline**: Lifecycle hooks, confirm dialogs, response handling, and draft persistence.
41
+
42
+ ### Static Site (HTML-first)
43
+ - **HTML-only output**: emits final HTML (no React hydration), JS is strictly progressive enhancement.
44
+ - **Policy-driven output**: routing, SEO/meta, assets, theming, and enhancements are controlled via `staticSite` policy.
45
+ - **Optional enhancements**: sidebar toggle, copy code, theme toggle, TOC scroll spy, and client-side search.
46
+ - **Build-time highlighting**: Shiki for pre-highlight, optional Prism runtime for client mode.
47
+
48
+ ## Quick Start
49
+
50
+ ### 1. Install Dependencies
51
+ ```bash
52
+ npm install
53
+ ```
54
+
55
+ ### Security note
56
+ If you enable JWT auth in generated backends, replace the default placeholder secret (`CHANGE_ME_SUPER_SECRET_MIN_16_CHARS`) with a real value.
57
+
58
+ ### 2. Run Examples
59
+ We provide a script to generate all example projects into `generated/` folders:
60
+
61
+ ```bash
62
+ ./scripts/generate-examples.sh
63
+ ```
64
+
65
+ ### 3. Explore Outputs
66
+ - **Backend Only**: `generated/backend-only/`
67
+ - **Frontend Only**: `generated/frontend-only/`
68
+ - **Rich Frontend**: `generated/form-io/` (Run `npm install && npm run dev` inside to see the UI)
69
+ - **Multi-page Website**: `generated/irgen-web/` (from `examples/irgen-web.dsl.ts`)
70
+ - **Fullstack**: `generated/fullstack/` (backend and frontend are generated independently)
71
+ - **Docs (PWA-ready)**: `generated/docs/` (from `examples/docs.dsl.ts`)
72
+ - **Static Docs (HTML-first)**: `generated/static-docs/` (from `examples/docs.dsl.ts` with `--targets=static-site`)
73
+ - **Static Site (No Enhance)**: `generated/static-no-enhance/`
74
+ - **Static Site (With Enhance)**: `generated/static-with-enhance/`
75
+
76
+ ## Manual Generation
77
+ You can generate artifacts from a specific DSL file using the CLI:
78
+
79
+ ```bash
80
+ # General Backend
81
+ npx irgen examples/app.dsl.ts generated/my-app --mode=backend
82
+
83
+ # Frontend (FormIO style)
84
+ npx irgen examples/form-io.dsl.ts generated/my-frontend --mode=frontend
85
+
86
+ # Backend + Frontend (separate targets)
87
+ npx irgen examples/app.dsl.ts --targets=backend,frontend --outDir=generated/fullstack
88
+ # -> backend in generated/fullstack/backend, frontend in generated/fullstack/frontend
89
+
90
+ # Static-site (HTML-first)
91
+ npx irgen examples/docs.dsl.ts --targets=static-site --outDir=generated/static-docs
92
+ ```
93
+
94
+ ### Optional: enable PWA for frontend outputs
95
+ - Opt-in via CLI policies:
96
+ `npx irgen examples/fullstack.dsl.ts generated/fullstack --targets=backend,frontend`
97
+ - This writes `manifest.webmanifest`, `icons/icon.svg`, and `pwa-sw.js`, then registers the service worker in the generated frontend entry. Defaults stay off unless you set `pwa.enabled=true` (recommended via the options argument to `frontend(...)` in your DSL; you can still override with `--policies='{"frontend":{"pwa":{"enabled":true,"name":"irgen Docs","shortName":"IRDocs"}}}'` if you prefer CLI flags).
98
+ - Frontend outputs now include a minimal Vite setup. After generation run `npm install` then `npm run dev` inside the frontend folder (e.g. `generated/fullstack/frontend`) to serve the app.
99
+ - Backend and frontend packages are decoupled: backend outputs stay backend-only; frontend outputs ship their own `package.json` with React/router/Tailwind toolchain.
100
+
101
+ ## JS Module API & Extensions
102
+ - Import the DSL helpers directly:
103
+ ```ts
104
+ import { app, frontend } from "irgen";
105
+
106
+ app("My Backend", { policies: { backend: { generateId: "uuid_v4" } } }, (be) => { /* ... */ });
107
+ frontend("My Frontend", { pwa: { enabled: true } }, (fe) => { /* ... */ });
108
+ ```
109
+ - Programmatic generation with extensions:
110
+ ```ts
111
+ import { Codegen } from "irgen";
112
+ import myExtension from "./my-extension.js";
113
+
114
+ const codegen = new Codegen({ extensions: [myExtension] });
115
+ await codegen.generate({ entries: ["./myapp.ts"], targets: ["backend", "frontend"], outDir: "generated" });
116
+ ```
117
+ - Extension shape: export a function `(ctx, options?) => void|Promise<void>` and use the provided `ctx` to register mappers/transforms/emitters/target mappings:
118
+ ```ts
119
+ // my-extension.ts
120
+ export default (ctx) => {
121
+ ctx.registerTargetEmitter("my-target", "my-emitter");
122
+ ctx.registerMapper("my-target", (decl) => {/* ... */}, { force: true });
123
+ };
124
+ ```
125
+ - CLI can load extensions too: `npx irgen ./myapp.ts --targets=backend,frontend --ext=./my-extension.ts`
126
+ - Electron target: IPC whitelist and handler stubs can be declared in the DSL via `policies.electron.ipc` (whitelist + `handlers`), and the emitter generates `ipc-handlers.ts` wired to `main.ts`, `preload.ts`, and `load-file.js`. Electron shell includes security hardening (contextIsolation/nodeIntegration off, navigation/CSP guards), session restore (window state persisted to userData), crash reporting slot, auto-update wiring with renderer status events (`auto-update-status`), and optional retry on failure.
127
+ - Extension namespacing & order: built-ins register first; extensions load in order. Use `ctx.namespace("myExt")` to prefix registrations (e.g., `myExt:frontend`) to avoid collisions. See `docs/EXTENSIONS.md` for details.
128
+
129
+ Notes:
130
+ - Published CLI (`npx irgen`) can load `.dsl.ts` (and its `.ts` imports) directly using the built-in tsx loader.
131
+ - For local development inside this repo, you can still run `npx tsx src/cli.ts ...` if preferred.
132
+
133
+ ## Release
134
+ Releases are published to npm automatically via GitHub Actions when a version tag is pushed.
135
+
136
+ ```bash
137
+ npm version <patch|minor|major>
138
+ git push origin main --follow-tags
139
+ ```
140
+
141
+ ## Contributing
142
+ Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a PR.
143
+
144
+ ## Security
145
+ If you believe you have found a security vulnerability, please follow [SECURITY.md](SECURITY.md).
146
+
147
+ ## Architecture
148
+ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for details on:
149
+ - Generation Gap Pattern
150
+ - Port & Adapters (Hexagonal)
151
+ - Frontend Runtime & IR
152
+ - Frontend SSG plan: [docs/FRONTEND-SSG-PLAN.md](docs/FRONTEND-SSG-PLAN.md)
153
+
154
+ ## Roadmap
155
+ - [x] Separation of Concerns (Generated vs User space)
156
+ - [x] Dependency Injection & Repositories
157
+ - [x] Prisma ORM Adapter
158
+ - [x] Extensibility Hooks
159
+ - [x] Automated Testing
160
+ - [x] Rich Frontend Components & Routing
161
+ - [x] Frontend SSG/Hybrid (Vite prerender)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { aggregateDecls } from "./dsl/aggregator.js";
5
+ import { registerBuiltins, runMapper } from "./mappers/index.js";
6
+ // Guard: require a modern Node (tsx loader relies on module.register)
7
+ const NODE_MAJOR = Number(process.versions.node.split(".")[0]);
8
+ if (Number.isFinite(NODE_MAJOR) && NODE_MAJOR < 18) {
9
+ console.error(`irgen requires Node.js >=18 (detected ${process.versions.node}). Please switch to a newer Node before running the CLI.`);
10
+ process.exit(1);
11
+ }
12
+ async function main() {
13
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
14
+ console.log(`
15
+ irgen — Policy-Driven Multi-Target Code Generation Toolchain
16
+
17
+ Usage:
18
+ irgen <dsl-file> [outDir] [options]
19
+
20
+ Options:
21
+ --mode=<mode> Target mode: backend, frontend, electron, static-site, combined (default: backend)
22
+ --targets=<list> Comma-separated list of targets to emit (overrides --mode)
23
+ --outDir=<path> Directory to write generated code (default: ./generated)
24
+ --ext=<path> Path to extension module(s)
25
+ --emitters List all registered emitters
26
+ --emitter=<name> Force run a specific emitter
27
+ --emitter-map=<json> Override emitter mapping for targets (e.g. '{"backend":"my-custom-node"}')
28
+ --policies=<json> Override policies from CLI
29
+ --version, -v Show version information
30
+ --help, -h Show this help message
31
+
32
+ Debugging / Inspection:
33
+ --inspect-decl Print the aggregated Declaration IR (input)
34
+ --inspect-domain Print the Domain IR (semantic model)
35
+ --inspect-ir Print the final Target IR (emitter contract)
36
+
37
+ Examples:
38
+ npx irgen examples/app.dsl.ts --mode=combined
39
+ npx irgen examples/frontend.dsl.ts --targets=frontend,electron --outDir=my-app
40
+ `);
41
+ process.exit(0);
42
+ }
43
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
44
+ console.log("irgen 0.1.0");
45
+ process.exit(0);
46
+ }
47
+ try {
48
+ const { register } = await import("tsx/esm/api");
49
+ register();
50
+ }
51
+ catch (err) {
52
+ console.warn("tsx loader unavailable; falling back to manual TS transpile where supported.", err instanceof Error ? err.message : err);
53
+ }
54
+ const modeFlag = process.argv.find(a => a.startsWith("--mode=")) ?? "--mode=backend";
55
+ const mode = modeFlag.split("=")[1];
56
+ // Additional flags: --emitters (list emitters), --emitter=<name> (run one emitter)
57
+ const showEmitters = process.argv.includes("--emitters");
58
+ const emitterFlag = process.argv.find(a => a.startsWith("--emitter=")) ?? null;
59
+ const emitterName = emitterFlag ? emitterFlag.split("=")[1] : null;
60
+ // parse positional args: first DSL entry and optional outDir
61
+ const rawArgs = process.argv.slice(2);
62
+ const extFlags = rawArgs.filter(a => a.startsWith("--ext="));
63
+ const extModules = extFlags.flatMap(f => f.replace("--ext=", "").split(",")).filter(Boolean);
64
+ const outDirFlag = process.argv.find(a => a.startsWith("--outDir=")) ?? null;
65
+ const entries = rawArgs.filter(a => a.endsWith(".dsl.ts"));
66
+ const entry = entries[0];
67
+ const entryIndex = entry ? rawArgs.indexOf(entry) : -1;
68
+ const maybeOut = entryIndex >= 0 ? rawArgs[entryIndex + 1] : undefined;
69
+ // Logic: Explicit flag > Positional arg (if valid) > Default
70
+ const outDir = outDirFlag
71
+ ? outDirFlag.split("=")[1]
72
+ : (maybeOut && !maybeOut.startsWith("--") ? maybeOut : "generated");
73
+ // helper: import all emit modules so they can register themselves
74
+ async function importAllEmitters() {
75
+ try {
76
+ const fs = await import("node:fs/promises");
77
+ async function walk(dir) {
78
+ const entries = await fs.readdir(dir, { withFileTypes: true });
79
+ for (const ent of entries) {
80
+ if (ent.isDirectory()) {
81
+ await walk(new URL(`./${ent.name}/`, dir));
82
+ }
83
+ else if (ent.name.endsWith(".js") || ent.name.endsWith(".ts")) {
84
+ try {
85
+ await import(new URL(`./${ent.name}`, dir).href);
86
+ }
87
+ catch (e) {
88
+ // ignore import errors for optional files
89
+ }
90
+ }
91
+ }
92
+ }
93
+ await walk(new URL("./emit/", import.meta.url));
94
+ }
95
+ catch (e) {
96
+ // ignore errors when emit directory is missing
97
+ }
98
+ }
99
+ async function importEmitterModule(relPath) {
100
+ const asUrl = (p) => new URL(p, import.meta.url).href;
101
+ try {
102
+ await import(asUrl(relPath));
103
+ return { ok: true, err: null };
104
+ }
105
+ catch (err) {
106
+ return { ok: false, err };
107
+ }
108
+ }
109
+ async function ensureEmitterRegistration(targets) {
110
+ if (!targets.includes("frontend"))
111
+ return;
112
+ const { emitterEngine } = await import("./emit/engine.js");
113
+ if (emitterEngine.getEmitter("frontend-tsmorph"))
114
+ return;
115
+ let lastErr = null;
116
+ let lastAttempt = null;
117
+ try {
118
+ const res = await importEmitterModule("./emit/frontend/frontend-react.js");
119
+ lastAttempt = "./emit/frontend/frontend-react.js";
120
+ if (!res.ok) {
121
+ throw res.err ?? new Error("frontend-react.js import failed");
122
+ }
123
+ }
124
+ catch (err) {
125
+ lastErr = err;
126
+ try {
127
+ const res = await importEmitterModule("./emit/frontend/frontend-react.ts");
128
+ lastAttempt = "./emit/frontend/frontend-react.ts";
129
+ if (!res.ok) {
130
+ throw res.err ?? new Error("frontend-react.ts import failed");
131
+ }
132
+ }
133
+ catch (err2) {
134
+ lastErr = err2;
135
+ }
136
+ }
137
+ if (!emitterEngine.getEmitter("frontend-tsmorph")) {
138
+ const msg = lastErr instanceof Error ? lastErr.message : String(lastErr ?? "unknown error");
139
+ const attempt = lastAttempt ? ` (${lastAttempt})` : "";
140
+ throw new Error(`failed to register frontend emitter${attempt}: ${msg}`);
141
+ }
142
+ }
143
+ const targetsFlag = process.argv.find(a => a.startsWith("--targets=")) ?? null;
144
+ const targetsFromFlag = targetsFlag ? targetsFlag.split("=")[1].split(",").map(t => t.trim()).filter(Boolean) : null;
145
+ const normalizeModeToTargets = (m) => {
146
+ if (targetsFromFlag && targetsFromFlag.length > 0)
147
+ return targetsFromFlag;
148
+ if (m === "combined")
149
+ return ["backend", "frontend"];
150
+ if (m === "frontend" || m === "backend" || m === "electron" || m === "static-site")
151
+ return [m];
152
+ if (m === "electrobun")
153
+ return ["electrobun"];
154
+ return ["backend"];
155
+ };
156
+ const targets = normalizeModeToTargets(mode);
157
+ if (showEmitters) {
158
+ await importAllEmitters();
159
+ await ensureEmitterRegistration(targets);
160
+ const { emitterEngine } = await import("./emit/engine.js");
161
+ console.log("Registered emitters:", emitterEngine.listEmitters().join(", "));
162
+ process.exit(0);
163
+ }
164
+ const defaultEntry = entry
165
+ ?? ((targets.includes("frontend") || targets.includes("electron") || targets.includes("electrobun") || targets.includes("static-site"))
166
+ ? "examples/frontend.dsl.ts"
167
+ : "examples/app.dsl.ts");
168
+ // normalise entries array (fallback to example DSL when not provided)
169
+ const entriesArr = entries.length ? entries : [defaultEntry];
170
+ const inspectIR = process.argv.includes("--inspect-ir");
171
+ const inspectDomain = process.argv.includes("--inspect-domain");
172
+ const inspectDecl = process.argv.includes("--inspect-decl");
173
+ const policiesFlag = process.argv.find(a => a.startsWith("--policies=")) ?? null;
174
+ const policiesFromCli = policiesFlag ? JSON.parse(policiesFlag.split("=")[1]) : undefined;
175
+ const emitterMapFlag = process.argv.find(a => a.startsWith("--emitter-map=")) ?? null;
176
+ const emitterMap = emitterMapFlag ? JSON.parse(emitterMapFlag.split("=")[1]) : undefined;
177
+ // Aggregate all entries into DeclUnified (validation/normalization inside)
178
+ const prefer = targets.some(t => t === "frontend" || t === "electron" || t === "electrobun" || t === "static-site") ? "frontend" : "backend";
179
+ const unified = await aggregateDecls(entriesArr, { prefer });
180
+ if (inspectDecl)
181
+ console.log("INSPECT-DECL:", JSON.stringify(unified, null, 2));
182
+ // ensure built-in mappers are available before extensions register/compose
183
+ registerBuiltins();
184
+ async function loadExtensions() {
185
+ if (!extModules.length)
186
+ return;
187
+ const { createExtensionContext } = await import("./extensions/context.js");
188
+ const ctx = createExtensionContext();
189
+ for (const modPath of extModules) {
190
+ const abs = path.isAbsolute(modPath) ? modPath : path.resolve(process.cwd(), modPath);
191
+ const modUrl = pathToFileURL(abs).href;
192
+ const imported = await import(modUrl);
193
+ const fn = (imported.default ?? imported.extension ?? imported);
194
+ if (typeof fn === "function") {
195
+ fn(ctx, imported.options ?? undefined);
196
+ }
197
+ else {
198
+ console.warn(`extension module ${modPath} did not export a function`);
199
+ }
200
+ }
201
+ }
202
+ await loadExtensions();
203
+ const bundlePolicies = unified?.meta?.policies;
204
+ const pickPolicy = (src, target) => {
205
+ if (!src)
206
+ return undefined;
207
+ if (src[target])
208
+ return src[target];
209
+ // Also check for kebab-case and camelCase variations
210
+ const camelVariant = target.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
211
+ const targetVariations = [
212
+ target,
213
+ target.replace(/-/g, ""),
214
+ target.replace(/([A-Z])/g, "-$1").toLowerCase(),
215
+ camelVariant,
216
+ ];
217
+ for (const variant of targetVariations) {
218
+ if (src[variant])
219
+ return src[variant];
220
+ }
221
+ const keys = Object.keys(src);
222
+ const looksNamespaced = keys.some(k => ["backend", "frontend", "electron", "cli", "static-site", "staticSite"].includes(k));
223
+ return looksNamespaced ? undefined : src;
224
+ };
225
+ const policyForTarget = (target) => {
226
+ const fromDsl = pickPolicy(bundlePolicies, target);
227
+ const fromCli = pickPolicy(policiesFromCli, target);
228
+ if (fromDsl && fromCli)
229
+ return { ...fromDsl, ...fromCli };
230
+ return fromCli ?? fromDsl;
231
+ };
232
+ // ensure emitters and transforms are registered
233
+ await importAllEmitters();
234
+ await ensureEmitterRegistration(targets);
235
+ // ensure target lowering transforms are registered when available
236
+ if (targets.includes("backend"))
237
+ await import("./lowering/targets/to-backend.js");
238
+ if (targets.includes("frontend"))
239
+ await import("./lowering/targets/to-frontend.js");
240
+ if (targets.includes("electron"))
241
+ await import("./lowering/targets/to-electron.js");
242
+ if (targets.includes("static-site"))
243
+ await import("./lowering/targets/to-static-site.js");
244
+ const { engine } = await import("./lowering/engine.js");
245
+ const { emitterEngine } = await import("./emit/engine.js");
246
+ const { getEmitterForTarget } = await import("./emit/registry.js");
247
+ const singleTargetOutDir = (target) => {
248
+ const multipleTargets = targets.length > 1;
249
+ return multipleTargets ? path.resolve(process.cwd(), outDir, target) : path.resolve(process.cwd(), outDir);
250
+ };
251
+ if (emitterName) {
252
+ if (!emitterEngine.getEmitter(emitterName)) {
253
+ throw new Error(`emitter not registered: ${emitterName}`);
254
+ }
255
+ // infer mode if user didn't pass --mode
256
+ let chosenMode = mode;
257
+ if (!process.argv.find(a => a.startsWith("--mode="))) {
258
+ if (emitterName.includes("backend"))
259
+ chosenMode = "backend";
260
+ else if (emitterName.includes("frontend"))
261
+ chosenMode = "frontend";
262
+ }
263
+ const domainIr = await runMapper(chosenMode, unified, policyForTarget(chosenMode));
264
+ if (inspectDomain)
265
+ console.log(`INSPECT-DOMAIN (${chosenMode}):`, JSON.stringify(domainIr, null, 2));
266
+ const transformName = `${chosenMode}-target`;
267
+ const targetIr = engine.getTransform(transformName)
268
+ ? await engine.runTransform(transformName, domainIr, policyForTarget(chosenMode))
269
+ : domainIr;
270
+ if (inspectIR)
271
+ console.log(`INSPECT-IR (${chosenMode}):`, JSON.stringify(targetIr, null, 2));
272
+ await emitterEngine.runEmitter(emitterName, targetIr, singleTargetOutDir(chosenMode));
273
+ console.log(`Ran emitter ${emitterName} (${chosenMode}) -> ${outDir}`);
274
+ process.exit(0);
275
+ }
276
+ for (const target of targets) {
277
+ let ir;
278
+ try {
279
+ const domainIr = await runMapper(target, unified, policyForTarget(target));
280
+ if (inspectDomain)
281
+ console.log(`INSPECT-DOMAIN (${target}):`, JSON.stringify(domainIr, null, 2));
282
+ const transformName = `${target}-target`;
283
+ ir = engine.getTransform(transformName)
284
+ ? await engine.runTransform(transformName, domainIr, policyForTarget(target))
285
+ : domainIr;
286
+ }
287
+ catch (err) {
288
+ console.error(`failed to map target ${target}:`, err);
289
+ continue;
290
+ }
291
+ if (inspectIR)
292
+ console.log(`INSPECT-IR (${target}):`, JSON.stringify(ir, null, 2));
293
+ const chosenEmitter = (emitterMap?.[target]) ??
294
+ getEmitterForTarget(target) ??
295
+ (target === "backend" ? "backend-tsmorph" : target === "frontend" ? "frontend-tsmorph" : target === "electron" ? "electron-shell" : target === "static-site" ? "static-site-html" : null);
296
+ if (!chosenEmitter) {
297
+ console.warn(`no emitter mapping for target ${target}, skipping emit`);
298
+ continue;
299
+ }
300
+ await emitterEngine.runEmitter(chosenEmitter, ir, singleTargetOutDir(target));
301
+ }
302
+ console.log("OK");
303
+ console.log("MODE:", process.argv.find(a => a.startsWith("--mode=")) ? mode : "(auto)");
304
+ console.log("TARGETS:", targets.join(", "));
305
+ console.log("DSL :", entriesArr.join(", "));
306
+ console.log("OUT :", outDir);
307
+ }
308
+ main().catch((err) => {
309
+ console.error(err);
310
+ process.exit(1);
311
+ });
312
+ //# sourceMappingURL=cli.js.map