irgen 0.2.2 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to the `irgen` project will be documented in this file.
4
4
 
5
+ ## [0.3.0] - 2026-02-01
6
+
7
+ ### New Features
8
+ - **Project Scaffolding**: Introduced `irgen init` for interactive project setup and best-practice templates.
9
+ - **Semantic Validator (Linker)**: New `irgen check` command to validate DSL integrity, including entity references, name uniqueness, and cross-file consistency.
10
+ - **Studio Preview Dashboard**: Real-time web-based visualization tool (`irgen studio`) to see project architecture and component trees as you edit.
11
+ - **Structured Logging**: Built-in `pino` integration in generated backends for production-ready observability.
12
+ - **Health & Metrics**: Automatic generation of `/health` and `/metrics` (Prometheus) endpoints.
13
+ - **Error Boundary Contract**: Policy-driven Error Boundary for frontend reliability and user feedback.
14
+
15
+ ### Enhancements
16
+ - **CLI Robustness**: Resolved command fall-through issues and improved error reporting.
17
+ - **Documentation**: Significant updates to policy references, architecture guides, and roadmaps.
18
+
19
+ ## [0.2.3] - 2026-01-21
20
+
21
+ ### Bug Fixes
22
+ - **Frontend Emitter**: Fixed `ReferenceError: rowActionIcons is not defined` in `frontend-react.ts` when rendering table row actions.
23
+ - **Frontend Components**: Resolved `[object Object]` AST leak in code blocks by ensuring `React` is correctly imported and utilized for runtime element creation.
24
+
25
+ ### Documentation & Verification
26
+ - **Test Suite**: Verified all core golden tests (Backend, Electron, Static Site) pass with the latest fixes.
27
+ - **Cleanup**: Improved documentation quality and consistency in core emitters.
28
+
5
29
  ## [0.2.2] - 2026-01-18
6
30
 
7
31
  ### Core Extensions & Architecture (Phase 10)
package/README.md CHANGED
@@ -18,15 +18,16 @@ irgen focuses on **architecture and determinism**, not convenience shortcuts. ir
18
18
  ## Key Features
19
19
 
20
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.
21
+ - **Generation Gap Architecture**: Separates generated base classes from user implementation.
22
+ - **Built-in Logging**: Structured JSON logging via `pino`.
23
+ - **Health-check Emitter**: Automatic `/health` and `/metrics` (Prometheus) generation.
23
24
  - **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.
25
+ - **Automated Testing**: Auto-generated `vitest` unit tests.
26
26
 
27
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.).
28
+ - **General-Purpose Webapp Generator**: Move beyond "dashboard-only" UIs.
29
+ - **Error Boundary Contract**: Wrap your application in a policy-driven Error Boundary.
30
+ - **Headless Client Runtime**: Decoupled `lib/runtime.ts` and React hooks.
30
31
  - **Operation-Oriented Architecture**: Actions (Operations) are the fundamental units of the client, allowing for complex command-oriented UIs.
31
32
  - **DataSource Abstraction**: Connect to multiple backends with pluggable `AuthStrategy`, `EnvelopeAdapter`, and `PaginationAdapter`.
32
33
  - **Global Dark Mode**: Built-in persistence (`localStorage`) and toggle in a sleek, glassmorphism Navbar.
@@ -90,6 +91,17 @@ npx irgen examples/app.dsl.ts --targets=backend,frontend --outDir=generated/full
90
91
 
91
92
  # Static-site (HTML-first)
92
93
  npx irgen examples/docs.dsl.ts --targets=static-site --outDir=generated/static-docs
94
+
95
+ ## Specialized Commands
96
+
97
+ ### Project Scaffolding
98
+ npx irgen init my-new-project
99
+
100
+ ### Semantic Validation (Linker)
101
+ npx irgen check examples/*.dsl.ts
102
+
103
+ ### Preview Studio
104
+ npx irgen studio examples/app.dsl.ts
93
105
  ```
94
106
 
95
107
  ### Optional: enable PWA for frontend outputs
@@ -160,3 +172,10 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for details on:
160
172
  - [x] Automated Testing
161
173
  - [x] Rich Frontend Components & Routing
162
174
  - [x] Frontend SSG/Hybrid (Vite prerender)
175
+ - [x] **v0.3.0 Release (Enterprise & Observability)**
176
+ - [x] Structured Logging (Pino)
177
+ - [x] Health-check & Metrics Emitters
178
+ - [x] Error Boundary Policy
179
+ - [x] `irgen init`, `check`, and `studio` commands
180
+
181
+ See [docs/ROADMAP-FUTURE.md](docs/ROADMAP-FUTURE.md) for the Stage 4 (Deployment & Cloud Native) plan.
@@ -0,0 +1 @@
1
+ export declare function runCheck(args: string[]): Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { aggregateDecls } from "../dsl/aggregator.js";
2
+ import { validateSemantics } from "../dsl/validator.js";
3
+ export async function runCheck(args) {
4
+ const dslFiles = args.filter(a => a.endsWith(".dsl.ts"));
5
+ if (dslFiles.length === 0) {
6
+ console.error("Usage: irgen check <dsl-file>...");
7
+ process.exit(1);
8
+ }
9
+ console.log(`Checking semantic integrity for: ${dslFiles.join(", ")}...`);
10
+ try {
11
+ const decl = await aggregateDecls(dslFiles);
12
+ const messages = validateSemantics(decl);
13
+ if (messages.length === 0) {
14
+ console.log("✅ No semantic errors found.");
15
+ process.exit(0);
16
+ }
17
+ let hasError = false;
18
+ for (const msg of messages) {
19
+ const icon = msg.type === "error" ? "❌" : "⚠️";
20
+ const loc = msg.location ? ` [${msg.location}]` : "";
21
+ console.log(`${icon} ${msg.type.toUpperCase()}: ${msg.message}${loc}`);
22
+ if (msg.type === "error") {
23
+ hasError = true;
24
+ }
25
+ }
26
+ if (hasError) {
27
+ console.error("\nValidation failed.");
28
+ process.exit(1);
29
+ }
30
+ else {
31
+ console.log("\nValidation passed (with warnings).");
32
+ process.exit(0);
33
+ }
34
+ }
35
+ catch (err) {
36
+ console.error("Failed to load or validate DSL:", err.message);
37
+ process.exit(1);
38
+ }
39
+ }
@@ -0,0 +1 @@
1
+ export declare function runInit(args: string[]): Promise<void>;
@@ -0,0 +1,111 @@
1
+ import prompts from "prompts";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ export async function runInit(args) {
5
+ const defaultName = args[0] || "my-irgen-app";
6
+ const response = await prompts([
7
+ {
8
+ type: args[0] ? null : "text",
9
+ name: "projectName",
10
+ message: "Project name:",
11
+ initial: defaultName
12
+ },
13
+ {
14
+ type: "select",
15
+ name: "template",
16
+ message: "Select a startup template:",
17
+ choices: [
18
+ { title: "Fullstack (Backend + Frontend)", value: "fullstack" },
19
+ { title: "Backend Only", value: "backend" },
20
+ { title: "Frontend Only", value: "frontend" },
21
+ ],
22
+ initial: 0
23
+ }
24
+ ]);
25
+ const projectName = response.projectName || defaultName;
26
+ const template = response.template || "fullstack";
27
+ const projectDir = path.resolve(process.cwd(), projectName);
28
+ if (fs.existsSync(projectDir)) {
29
+ console.error(`Error: Directory ${projectName} already exists.`);
30
+ process.exit(1);
31
+ }
32
+ console.log(`\nScaffolding project in ${projectDir}...`);
33
+ fs.mkdirSync(projectDir, { recursive: true });
34
+ // 1. package.json
35
+ const pkgJson = {
36
+ name: projectName,
37
+ version: "0.0.1",
38
+ type: "module",
39
+ scripts: {
40
+ "gen": "irgen examples/app.dsl.ts --mode=combined",
41
+ "gen:backend": "irgen examples/app.dsl.ts --mode=backend",
42
+ "gen:frontend": "irgen examples/app.dsl.ts --mode=frontend",
43
+ },
44
+ dependencies: {
45
+ "irgen": "latest" // In a real scenario this would be the actual version
46
+ },
47
+ devDependencies: {
48
+ "typescript": "^5.0.0",
49
+ "tsx": "^4.0.0"
50
+ }
51
+ };
52
+ if (template === "backend") {
53
+ if (pkgJson.scripts["gen:frontend"])
54
+ delete pkgJson.scripts["gen:frontend"];
55
+ pkgJson.scripts["gen"] = "irgen examples/app.dsl.ts --mode=backend";
56
+ }
57
+ else if (template === "frontend") {
58
+ if (pkgJson.scripts["gen:backend"])
59
+ delete pkgJson.scripts["gen:backend"];
60
+ pkgJson.scripts["gen"] = "irgen examples/app.dsl.ts --mode=frontend";
61
+ }
62
+ fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify(pkgJson, null, 2));
63
+ // 2. tsconfig.json
64
+ const tsConfig = {
65
+ compilerOptions: {
66
+ target: "ES2022",
67
+ module: "NodeNext",
68
+ moduleResolution: "NodeNext",
69
+ strict: true,
70
+ skipLibCheck: true,
71
+ outDir: "dist"
72
+ },
73
+ include: ["src/**/*", "examples/**/*"]
74
+ };
75
+ fs.writeFileSync(path.join(projectDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
76
+ // 3. Create folder structure
77
+ ensureDir(path.join(projectDir, "src", "ir", "target"));
78
+ ensureDir(path.join(projectDir, "examples"));
79
+ // 4. Default Policies
80
+ const policyContent = (type) => `
81
+ import { ${type}Policy } from "irgen";
82
+
83
+ export const My${type}Policy = ${type}Policy({
84
+ // Add policy overrides here
85
+ });
86
+ `.trim();
87
+ if (template !== "frontend") {
88
+ fs.writeFileSync(path.join(projectDir, "src/ir/target/backend.policy.ts"), policyContent("Backend"));
89
+ }
90
+ if (template !== "backend") {
91
+ fs.writeFileSync(path.join(projectDir, "src/ir/target/frontend.policy.ts"), policyContent("Frontend"));
92
+ }
93
+ // 5. Example DSL
94
+ let dslContent = `import { app } from "irgen";\n\napp("${projectName}", (t) => {\n`;
95
+ if (template !== "frontend") {
96
+ dslContent += ` t.entity("User", (e) => {\n e.field("email", "string", { unique: true });\n });\n`;
97
+ }
98
+ if (template !== "backend") {
99
+ dslContent += ` t.page("Home", { path: "/" }, (p) => {\n p.component("Welcome");\n });\n`;
100
+ }
101
+ dslContent += `});\n`;
102
+ fs.writeFileSync(path.join(projectDir, "examples/app.dsl.ts"), dslContent);
103
+ console.log("\nDone! Now run:\n");
104
+ console.log(` cd ${projectName}`);
105
+ console.log(" npm install");
106
+ console.log(" npm run gen");
107
+ }
108
+ function ensureDir(p) {
109
+ if (!fs.existsSync(p))
110
+ fs.mkdirSync(p, { recursive: true });
111
+ }
@@ -0,0 +1 @@
1
+ export declare function runStudio(args: string[]): Promise<void>;
@@ -0,0 +1,369 @@
1
+ import express from "express";
2
+ import open from "open";
3
+ import { aggregateDecls } from "../dsl/aggregator.js";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ export async function runStudio(args) {
7
+ const dslFiles = args.filter(a => a.endsWith(".dsl.ts"));
8
+ if (dslFiles.length === 0) {
9
+ console.error("Usage: irgen studio <dsl-file>...");
10
+ process.exit(1);
11
+ }
12
+ const app = express();
13
+ const port = 3000;
14
+ // Cache for IR
15
+ let currentIR = null;
16
+ const reload = async () => {
17
+ try {
18
+ console.log("Loading DSL for Studio...");
19
+ currentIR = await aggregateDecls(dslFiles);
20
+ console.log("DSL loaded successfully.");
21
+ }
22
+ catch (err) {
23
+ console.error("Failed to load DSL in Studio:", err.message);
24
+ }
25
+ };
26
+ await reload();
27
+ // Simple Watcher
28
+ dslFiles.forEach(file => {
29
+ fs.watch(path.resolve(process.cwd(), file), async (event) => {
30
+ if (event === "change") {
31
+ console.log(`Changes detected in ${file}, reloading...`);
32
+ await reload();
33
+ }
34
+ });
35
+ });
36
+ // API
37
+ app.get("/api/ir", (req, res) => {
38
+ if (!currentIR)
39
+ return res.status(500).json({ error: "IR not loaded" });
40
+ res.json(currentIR);
41
+ });
42
+ // Static UI
43
+ // For now, let's embed a simple but beautiful HTML
44
+ app.get("/", (req, res) => {
45
+ res.send(getStudioHtml());
46
+ });
47
+ app.listen(port, () => {
48
+ console.log(`Studio Dashboard running at http://localhost:${port}`);
49
+ console.log("Press Ctrl+C to stop.");
50
+ if (process.env.OPEN_BROWSER !== "false") {
51
+ open(`http://localhost:${port}`);
52
+ }
53
+ });
54
+ }
55
+ function getStudioHtml() {
56
+ return `
57
+ <!DOCTYPE html>
58
+ <html lang="en">
59
+ <head>
60
+ <meta charset="UTF-8">
61
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
62
+ <title>irgen Studio Dashboard</title>
63
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&family=JetBrains+Mono&display=swap" rel="stylesheet">
64
+ <style>
65
+ :root {
66
+ --bg: #0b0f19;
67
+ --sidebar: #111827;
68
+ --card: #1f2937;
69
+ --accent: #38bdf8;
70
+ --accent-glow: rgba(56, 189, 248, 0.4);
71
+ --text: #f3f4f6;
72
+ --text-dim: #9ca3af;
73
+ --success: #10b981;
74
+ --border: rgba(255, 255, 255, 0.08);
75
+ }
76
+ * { box-sizing: border-box; }
77
+ body {
78
+ font-family: 'Outfit', sans-serif;
79
+ background: var(--bg);
80
+ color: var(--text);
81
+ margin: 0;
82
+ display: flex;
83
+ height: 100vh;
84
+ overflow: hidden;
85
+ }
86
+ aside {
87
+ width: 280px;
88
+ background: var(--sidebar);
89
+ border-right: 1px solid var(--border);
90
+ display: flex;
91
+ flex-direction: column;
92
+ padding: 24px;
93
+ }
94
+ main {
95
+ flex: 1;
96
+ padding: 40px;
97
+ overflow-y: auto;
98
+ background: radial-gradient(circle at 50% 50%, rgba(56, 189, 248, 0.03) 0%, transparent 100%);
99
+ }
100
+ .logo {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 12px;
104
+ margin-bottom: 40px;
105
+ font-weight: 600;
106
+ font-size: 1.25rem;
107
+ color: var(--accent);
108
+ }
109
+ .logo svg { width: 32px; height: 32px; filter: drop-shadow(0 0 8px var(--accent-glow)); }
110
+
111
+ .nav-section { margin-bottom: 32px; }
112
+ .nav-label {
113
+ font-size: 11px;
114
+ text-transform: uppercase;
115
+ letter-spacing: 1px;
116
+ color: var(--text-dim);
117
+ margin-bottom: 12px;
118
+ opacity: 0.6;
119
+ }
120
+ .nav-item {
121
+ padding: 10px 12px;
122
+ border-radius: 8px;
123
+ cursor: pointer;
124
+ color: var(--text-dim);
125
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 10px;
129
+ font-size: 14px;
130
+ margin-bottom: 4px;
131
+ }
132
+ .nav-item:hover { background: rgba(255,255,255,0.03); color: var(--text); }
133
+ .nav-item.active { background: rgba(56, 189, 248, 0.1); color: var(--accent); border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0; margin-left: -24px; padding-left: 21px; }
134
+
135
+ .header {
136
+ display: flex;
137
+ justify-content: space-between;
138
+ align-items: flex-start;
139
+ margin-bottom: 40px;
140
+ }
141
+ .title-area h2 { margin: 0; font-size: 2rem; font-weight: 600; }
142
+ .title-area p { margin: 8px 0 0; color: var(--text-dim); }
143
+
144
+ .stats-row {
145
+ display: grid;
146
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
147
+ gap: 20px;
148
+ margin-bottom: 40px;
149
+ }
150
+ .stat-card {
151
+ background: var(--card);
152
+ padding: 24px;
153
+ border-radius: 16px;
154
+ border: 1px solid var(--border);
155
+ transition: transform 0.3s;
156
+ }
157
+ .stat-card:hover { transform: translateY(-5px); }
158
+ .stat-value { font-size: 24px; font-weight: 600; color: var(--accent); }
159
+ .stat-label { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
160
+
161
+ .card {
162
+ background: var(--card);
163
+ border-radius: 16px;
164
+ padding: 32px;
165
+ border: 1px solid var(--border);
166
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
167
+ animation: fadeIn 0.5s ease-out;
168
+ }
169
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
170
+
171
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; }
172
+
173
+ .badge {
174
+ font-size: 10px;
175
+ padding: 4px 8px;
176
+ border-radius: 6px;
177
+ font-weight: 600;
178
+ background: rgba(56, 189, 248, 0.1);
179
+ color: var(--accent);
180
+ border: 1px solid rgba(56, 189, 248, 0.2);
181
+ }
182
+
183
+ table { width: 100%; border-collapse: collapse; margin-top: 20px; }
184
+ th { text-align: left; padding: 12px; color: var(--text-dim); font-weight: 400; border-bottom: 1px solid var(--border); font-size: 13px; }
185
+ td { padding: 16px 12px; border-bottom: 1px solid var(--border); font-size: 14px; }
186
+ .mono { font-family: 'JetBrains Mono', monospace; font-size: 12px; }
187
+
188
+ .indicator {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 8px;
192
+ background: rgba(16, 185, 129, 0.1);
193
+ color: var(--success);
194
+ padding: 6px 12px;
195
+ border-radius: 20px;
196
+ font-size: 12px;
197
+ font-weight: 600;
198
+ }
199
+ .pulse { width: 8px; height: 8px; background: var(--success); border-radius: 50%; animation: pulse 2s infinite; }
200
+ @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); } 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } }
201
+ </style>
202
+ </head>
203
+ <body>
204
+ <aside>
205
+ <div class="logo">
206
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
207
+ irgen Studio
208
+ </div>
209
+ <div class="nav-section">
210
+ <div class="nav-label">Overview</div>
211
+ <div class="nav-item active" onclick="renderOverview()">Project Overview</div>
212
+ </div>
213
+ <div id="nav-apps"></div>
214
+ </aside>
215
+ <main>
216
+ <div class="header">
217
+ <div class="title-area" id="header-content">
218
+ <h2>Design System</h2>
219
+ <p>Visualizing your project architecture</p>
220
+ </div>
221
+ <div class="indicator">
222
+ <div class="pulse"></div>
223
+ Connected
224
+ </div>
225
+ </div>
226
+ <div id="content"></div>
227
+ </main>
228
+
229
+ <script>
230
+ let ir = null;
231
+ let selectedId = 'overview';
232
+
233
+ async function refresh() {
234
+ try {
235
+ const res = await fetch('/api/ir');
236
+ ir = await res.json();
237
+ renderNav();
238
+ if (selectedId === 'overview') renderOverview();
239
+ } catch (e) {
240
+ console.error("Failed to fetch IR", e);
241
+ }
242
+ }
243
+
244
+ function renderNav() {
245
+ const container = document.getElementById('nav-apps');
246
+ container.innerHTML = '';
247
+ ir.apps.forEach(app => {
248
+ const section = document.createElement('div');
249
+ section.className = 'nav-section';
250
+ section.innerHTML = \`<div class="nav-label">\${app.name}</div>\`;
251
+
252
+ app.entities?.forEach(ent => {
253
+ const item = document.createElement('div');
254
+ item.className = 'nav-item';
255
+ item.innerHTML = '<span>📦</span> ' + ent.name;
256
+ item.onclick = (event) => { selectItem(ent.name, event); renderEntity(ent); };
257
+ section.appendChild(item);
258
+ });
259
+
260
+ app.pages?.forEach(page => {
261
+ const item = document.createElement('div');
262
+ item.className = 'nav-item';
263
+ item.innerHTML = '<span>📄</span> ' + page.name;
264
+ item.onclick = (event) => { selectItem(page.name, event); renderPage(page); };
265
+ section.appendChild(item);
266
+ });
267
+ container.appendChild(section);
268
+ });
269
+ }
270
+
271
+ function selectItem(name, event) {
272
+ selectedId = name;
273
+ document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
274
+ event.currentTarget.classList.add('active');
275
+ }
276
+
277
+ function renderOverview() {
278
+ document.getElementById('header-content').innerHTML = '<h2>Project Overview</h2><p>Summary of discovered resources</p>';
279
+ let totalEntities = 0;
280
+ let totalPages = 0;
281
+ ir.apps.forEach(a => { totalEntities += (a.entities?.length || 0); totalPages += (a.pages?.length || 0); });
282
+
283
+ const content = document.getElementById('content');
284
+ content.innerHTML = \`
285
+ <div class="stats-row">
286
+ <div class="stat-card">
287
+ <div class="stat-value">\${ir.apps.length}</div>
288
+ <div class="stat-label">Applications</div>
289
+ </div>
290
+ <div class="stat-card">
291
+ <div class="stat-value">\${totalEntities}</div>
292
+ <div class="stat-label">Data Entities</div>
293
+ </div>
294
+ <div class="stat-card">
295
+ <div class="stat-value">\${totalPages}</div>
296
+ <div class="stat-label">UI Pages</div>
297
+ </div>
298
+ </div>
299
+ <div class="card">
300
+ <h3>Metadata</h3>
301
+ <table class="mono">
302
+ \${Object.entries(ir.apps[0]?.meta || {}).map(([k,v]) => \`
303
+ <tr>
304
+ <td style="color: var(--accent)">\${k}</td>
305
+ <td>\${JSON.stringify(v)}</td>
306
+ </tr>
307
+ \`).join('')}
308
+ </table>
309
+ </div>
310
+ \`;
311
+ }
312
+
313
+ function renderEntity(ent) {
314
+ document.getElementById('header-content').innerHTML = \`<h2>\${ent.name}</h2><p>Entity Model & API Operations</p>\`;
315
+ const content = document.getElementById('content');
316
+ content.innerHTML = \`
317
+ <div class="grid">
318
+ <div class="card">
319
+ <h3>Definition</h3>
320
+ <table>
321
+ <thead><tr><th>Field</th><th>Type</th></tr></thead>
322
+ <tbody>
323
+ \${Object.entries(ent.model || {}).map(([k,v]) => \`
324
+ <tr>
325
+ <td><strong>\${k}</strong></td>
326
+ <td class="mono" style="color: var(--accent)">\${v}</td>
327
+ </tr>
328
+ \`).join('')}
329
+ </tbody>
330
+ </table>
331
+ </div>
332
+ <div class="card">
333
+ <h3>Target Operations</h3>
334
+ <div style="display: flex; flex-wrap: wrap; gap: 8px">
335
+ \${ent.operations.map(op => \`<span class="badge">\${op.kind.toUpperCase()}: \${op.name}</span>\`).join('')}
336
+ </div>
337
+ </div>
338
+ </div>
339
+ \`;
340
+ }
341
+
342
+ function renderPage(page) {
343
+ document.getElementById('header-content').innerHTML = \`<h2>\${page.name}</h2><p>Route: <span class="mono">\${page.path}</span></p>\`;
344
+ const content = document.getElementById('content');
345
+ content.innerHTML = \`
346
+ <div class="card">
347
+ <h3>Component Tree</h3>
348
+ <div class="grid">
349
+ \${page.components.map(comp => \`
350
+ <div class="stat-card" style="background: rgba(255,255,255,0.02)">
351
+ <div style="display: flex; justify-content: space-between; align-items: center">
352
+ <strong>\${comp.name}</strong>
353
+ \${comp.entityRef ? '<span class="badge">Bound</span>' : ''}
354
+ </div>
355
+ \${comp.entityRef ? \`<div class="stat-label">Entity: \${comp.entityRef}</div>\` : ''}
356
+ </div>
357
+ \`).join('')}
358
+ </div>
359
+ </div>
360
+ \`;
361
+ }
362
+
363
+ refresh();
364
+ setInterval(refresh, 5000);
365
+ </script>
366
+ </body>
367
+ </html>
368
+ `;
369
+ }
package/dist/cli.js CHANGED
@@ -46,6 +46,21 @@ Examples:
46
46
  console.log("irgen 0.1.0");
47
47
  process.exit(0);
48
48
  }
49
+ if (process.argv[2] === "check") {
50
+ const { runCheck } = await import("./cli/check.js");
51
+ await runCheck(process.argv.slice(3));
52
+ process.exit(0);
53
+ }
54
+ if (process.argv[2] === "studio") {
55
+ const { runStudio } = await import("./cli/studio.js");
56
+ await runStudio(process.argv.slice(3));
57
+ return; // Keep alive as server
58
+ }
59
+ if (process.argv[2] === "init") {
60
+ const { runInit } = await import("./cli/init.js");
61
+ await runInit(process.argv.slice(3));
62
+ process.exit(0);
63
+ }
49
64
  try {
50
65
  const { register } = await import("tsx/esm/api");
51
66
  register();
@@ -220,7 +220,9 @@ export async function loadFrontendDsl(entry) {
220
220
  // reset
221
221
  _global.__IR_CURRENT_FRONTEND = null;
222
222
  try {
223
- await import(url);
223
+ // Add cache buster to force re-execution if file is already in module cache
224
+ const cacheBuster = `?cb=${Date.now()}`;
225
+ await import(url + cacheBuster);
224
226
  }
225
227
  catch (err) {
226
228
  // Some environments (tsx ESM loader) may fail resolving .ts imports via file URL.
@@ -92,7 +92,9 @@ export async function loadDsl(entry) {
92
92
  // reset
93
93
  _global.__IR_CURRENT_BACKEND = null;
94
94
  try {
95
- await import(url);
95
+ // Add cache buster to force re-execution if file is already in module cache
96
+ const cacheBuster = `?cb=${Date.now()}`;
97
+ await import(url + cacheBuster);
96
98
  }
97
99
  catch (err) {
98
100
  const errMessage = err instanceof Error ? err.message : String(err);
@@ -0,0 +1,7 @@
1
+ import { DeclBundle } from "../ir/decl/index.js";
2
+ export interface ValidationMessage {
3
+ type: "error" | "warning";
4
+ message: string;
5
+ location?: string;
6
+ }
7
+ export declare function validateSemantics(bundle: DeclBundle): ValidationMessage[];