@middag-io/react 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +349 -0
  2. package/bin/codemods/README.md +18 -0
  3. package/bin/codemods/v0.7.0.mjs +21 -0
  4. package/bin/middag-react.js +91 -10
  5. package/dist-lib/{ConditionTreeBlock-BGPYAImJ.js → ConditionTreeBlock-B24FzcsP.js} +39 -36
  6. package/dist-lib/{FormBuilderBlock-b_-QQaKn.js → FormBuilderBlock-BFUwXZ4d.js} +773 -772
  7. package/dist-lib/{MarkdownContent-BgwUdgoN.js → MarkdownContent-C7osjiEF.js} +1322 -1320
  8. package/dist-lib/{SentenceBuilderBlock-ClWjm0dX.js → SentenceBuilderBlock-BuT-5hKD.js} +39 -38
  9. package/dist-lib/app/LazyBlock.d.ts.map +1 -1
  10. package/dist-lib/app/providers/error-reporter.d.ts +66 -0
  11. package/dist-lib/app/providers/error-reporter.d.ts.map +1 -0
  12. package/dist-lib/app/providers/i18n-defaults.d.ts +12 -0
  13. package/dist-lib/app/providers/i18n-defaults.d.ts.map +1 -0
  14. package/dist-lib/app/providers/i18n.d.ts.map +1 -1
  15. package/dist-lib/base/blocks/ActionGridBlock.d.ts.map +1 -1
  16. package/dist-lib/base/blocks/ActivityTimelineBlock.d.ts.map +1 -1
  17. package/dist-lib/base/blocks/ConditionTreeBlock.d.ts.map +1 -1
  18. package/dist-lib/base/blocks/DenseTableBlock.d.ts.map +1 -1
  19. package/dist-lib/base/blocks/DetailPanelBlock.d.ts.map +1 -1
  20. package/dist-lib/base/blocks/EmptyStateBlock.d.ts.map +1 -1
  21. package/dist-lib/base/blocks/FormBuilderBlock.d.ts.map +1 -1
  22. package/dist-lib/base/blocks/FormPanelBlock.d.ts.map +1 -1
  23. package/dist-lib/base/blocks/SentenceBuilderBlock.d.ts.map +1 -1
  24. package/dist-lib/base/form/fields/MultiSelectField.d.ts.map +1 -1
  25. package/dist-lib/base/partials/ActionBar/index.d.ts.map +1 -1
  26. package/dist-lib/base/partials/ConfirmationDialog/index.d.ts.map +1 -1
  27. package/dist-lib/base/partials/DataTable/index.d.ts +1 -1
  28. package/dist-lib/base/partials/DataTable/index.d.ts.map +1 -1
  29. package/dist-lib/base/partials/FilterBar/index.d.ts.map +1 -1
  30. package/dist-lib/base/partials/FormSection/index.d.ts.map +1 -1
  31. package/dist-lib/base/partials/LogViewer/LogViewerToolbar.d.ts.map +1 -1
  32. package/dist-lib/base/partials/MarkdownContent/index.d.ts.map +1 -1
  33. package/dist-lib/base/partials/TimelineList/index.d.ts.map +1 -1
  34. package/dist-lib/base/shell/AdminShell.d.ts.map +1 -1
  35. package/dist-lib/base/shell/ProductShell.d.ts.map +1 -1
  36. package/dist-lib/base/shell/partials/CommandPalette.d.ts.map +1 -1
  37. package/dist-lib/base/shell/partials/HelpPanel.d.ts.map +1 -1
  38. package/dist-lib/base/shell/partials/InlineInspector.d.ts.map +1 -1
  39. package/dist-lib/base/shell/partials/NavErrorBoundary.d.ts +3 -11
  40. package/dist-lib/base/shell/partials/NavErrorBoundary.d.ts.map +1 -1
  41. package/dist-lib/base/shell/partials/OverlayScreen.d.ts.map +1 -1
  42. package/dist-lib/base/shell/partials/PageActionButton.d.ts.map +1 -1
  43. package/dist-lib/base/shell/partials/PageHeader.d.ts.map +1 -1
  44. package/dist-lib/base/shell/partials/SidebarNav.d.ts.map +1 -1
  45. package/dist-lib/contracts/page-contract-schema.d.ts +2 -14
  46. package/dist-lib/contracts/page-contract-schema.d.ts.map +1 -1
  47. package/dist-lib/contracts/page-contract.d.ts +10 -2
  48. package/dist-lib/contracts/page-contract.d.ts.map +1 -1
  49. package/dist-lib/i18n-zhMby9Ml.js +128 -0
  50. package/dist-lib/index.d.ts +7 -0
  51. package/dist-lib/index.d.ts.map +1 -1
  52. package/dist-lib/middag-react.css +1 -1
  53. package/dist-lib/middag-react.js +5176 -4790
  54. package/package.json +3 -2
  55. /package/dist-lib/{FlowEditorBlock-D3nx4re7.js → FlowEditorBlock-CHodeGBw.js} +0 -0
  56. /package/dist-lib/{dist-C3f2_wwS.js → dist-BDvVQ5m8.js} +0 -0
  57. /package/dist-lib/{index.min-BVLV21vp.js → index.min-Cno07_zY.js} +0 -0
package/README.md ADDED
@@ -0,0 +1,349 @@
1
+ # @middag-io/react
2
+
3
+ Shared React UI library for the MIDDAG ecosystem. Consumed by Moodle (`local_middag`) and WordPress host plugins via Inertia.js.
4
+
5
+ **[Live Demo](https://middag-react-mock.pages.dev)** | **[Documentation](https://ui-docs.middag.io)**
6
+
7
+ ## Stack
8
+
9
+ - React 19, TypeScript, Tailwind CSS v4
10
+ - Inertia.js (peer dependency -- all hosts use Inertia)
11
+ - ReUI components (Radix-based)
12
+ - TanStack Table, @xyflow/react, @dnd-kit
13
+
14
+ ## Install
15
+
16
+ ### Quick start (recommended)
17
+
18
+ ```bash
19
+ # Community (no auth needed)
20
+ npx create-middag-ui
21
+
22
+ # PRO (source maps, mock SPA, full exports)
23
+ npx @middag-io/create-middag-ui
24
+ ```
25
+
26
+ The wizard detects your host platform (Moodle/WordPress), configures authentication, and scaffolds a `ui/` directory with a working mock build.
27
+
28
+ ### Manual install
29
+
30
+ **npm public (no auth needed):**
31
+
32
+ ```bash
33
+ npm install @middag-io/react react react-dom @inertiajs/react @inertiajs/core
34
+ ```
35
+
36
+ **GitHub Packages (includes TypeScript source for IDE navigation):**
37
+
38
+ ```bash
39
+ # Add to global ~/.npmrc
40
+ echo "@middag-io:registry=https://npm.pkg.github.com" >> ~/.npmrc
41
+ echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc
42
+
43
+ npm install @middag-io/react react react-dom @inertiajs/react @inertiajs/core
44
+ ```
45
+
46
+ Create a token at [github.com/settings/tokens](https://github.com/settings/tokens) with `read:packages` scope. See [Authentication](https://ui-docs.middag.io/authentication) for details.
47
+
48
+ > **GitHub Packages access requires:** membership in the [middag-io](https://github.com/middag-io) organization on GitHub. The token alone is not sufficient — you must have read access to the `middag-io/middag-react` repository.
49
+
50
+ ### Community vs PRO
51
+
52
+ | Content | Community | PRO |
53
+ |---------|-----------|-----|
54
+ | ESM bundle (`dist-lib/`) | Yes | Yes |
55
+ | TypeScript declarations (`.d.ts`) | Yes | Yes |
56
+ | Declaration maps (`.d.ts.map`) | Yes | Yes |
57
+ | TypeScript source (`src/`) | No | Yes |
58
+ | Mock SPA extensible (`mock/`) | No | Yes |
59
+ | IDE "go to definition" into source | No | Yes |
60
+ | Authentication required | No | Yes |
61
+
62
+ Both editions deliver the same runtime code. Community is sufficient for building plugins. PRO additionally includes TypeScript source for IDE navigation and the extensible mock SPA.
63
+
64
+ ## Usage
65
+
66
+ ```tsx
67
+ import { ContractPage, registerDefaults } from '@middag-io/react';
68
+ import type { PageContract } from '@middag-io/react';
69
+
70
+ // Register core shells, layouts, and all 17 blocks
71
+ registerDefaults();
72
+
73
+ // Render a contract-driven page
74
+ <ContractPage contract={myContract} />
75
+ ```
76
+
77
+ ### Selective registration (IIFE consumers)
78
+
79
+ WordPress and other IIFE consumers should import only the blocks they need to avoid bundling heavy dependencies (@xyflow/react, @dnd-kit) via `inlineDynamicImports`:
80
+
81
+ ```tsx
82
+ import {
83
+ ContractPage,
84
+ registerShell, registerLayout, registerBlock,
85
+ AdminShell, StackLayout, DashboardLayout,
86
+ DenseTableBlock, MetricCardBlock, EmptyStateBlock,
87
+ } from '@middag-io/react';
88
+
89
+ registerShell('admin', AdminShell);
90
+ registerLayout('stack', StackLayout);
91
+ registerLayout('dashboard', DashboardLayout);
92
+ registerBlock('dense_table', DenseTableBlock);
93
+ registerBlock('metric_card', MetricCardBlock);
94
+ registerBlock('empty_state', EmptyStateBlock);
95
+ ```
96
+
97
+ ### Lazy block loading
98
+
99
+ Blocks can defer data loading until they mount (useful for tabbed pages):
100
+
101
+ ```tsx
102
+ // PHP sends block with empty data + lazyProp metadata:
103
+ {
104
+ type: 'dense_table',
105
+ key: 'invoices',
106
+ data: {},
107
+ meta: { lazyProp: 'invoices' }
108
+ }
109
+
110
+ // PHP also sends a top-level Inertia prop (initially null):
111
+ // 'invoices' => null
112
+
113
+ // When the block mounts, it auto-fetches via router.reload({ only: ['invoices'] })
114
+ // Radix Tabs unmounts inactive tabs, so lazy blocks only fetch when their tab activates
115
+ ```
116
+
117
+ ### ReUI components
118
+
119
+ Consumers can import ReUI primitives for custom components:
120
+
121
+ ```tsx
122
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@middag-io/react/reui/tabs.tsx';
123
+ import { Button } from '@middag-io/react/reui/button.tsx';
124
+ ```
125
+
126
+ ### Custom blocks
127
+
128
+ ```tsx
129
+ import { registerBlock, type BlockProps } from '@middag-io/react';
130
+
131
+ function ChartBlock({ block }: BlockProps<{ labels: string[]; values: number[] }>) {
132
+ return <div>{/* your chart */}</div>;
133
+ }
134
+
135
+ registerBlock('chart', ChartBlock);
136
+ ```
137
+
138
+ ### Contract validation
139
+
140
+ ```tsx
141
+ import { validatePageContract } from '@middag-io/react';
142
+
143
+ const errors = validatePageContract(contractFromBackend);
144
+ if (errors) {
145
+ console.error('Invalid contract:', errors);
146
+ }
147
+ ```
148
+
149
+ Zod schemas are exported for consumers who want to extend validation or generate JSON Schema for PHP consumers.
150
+
151
+ ### i18n with host-specific resolver
152
+
153
+ ```tsx
154
+ import { I18nProvider } from '@middag-io/react';
155
+
156
+ // Moodle: inject Moodle string resolver
157
+ <I18nProvider asyncResolver={moodleGetStrings}>
158
+ <App />
159
+ </I18nProvider>
160
+
161
+ // WordPress: inject WP i18n resolver
162
+ <I18nProvider asyncResolver={wpGetStrings}>
163
+ <App />
164
+ </I18nProvider>
165
+ ```
166
+
167
+ ## Architecture
168
+
169
+ ```
170
+ src/
171
+ app/ # ContractPage, registries, providers, LazyBlock
172
+ base/ # Shells, layouts, blocks, hooks, theme, partials
173
+ components/
174
+ reui/ # ReUI primitives (Radix-based, source of truth)
175
+ examples/ # ReUI component examples (synced from registry)
176
+ contracts/ # TypeScript types (PageContract, BlockData, etc.)
177
+ lib/ # Generic utilities and hooks
178
+ assets/ # Fonts, lottie animations
179
+ index.ts # Barrel export
180
+ mock/ # Dev playground (standalone SPA, no server needed)
181
+ scripts/ # Tooling (sync, doctor)
182
+ .githooks/ # Git hooks (pre-commit, commit-msg, pre-push)
183
+ .changeset/ # Changesets config for automated versioning
184
+ ```
185
+
186
+ ### What belongs here vs host plugins
187
+
188
+ | Here (@middag-io/react) | Host plugin (Moodle/WP) |
189
+ |-----------------------------|-------------------------------------|
190
+ | Shells, layouts, blocks | Extensions with host-specific pages |
191
+ | ContractPage renderer | Inertia server-side adapter |
192
+ | Theme system | License validation (server-side) |
193
+ | i18n provider (generic) | i18n resolver (host-specific) |
194
+ | Type contracts | API endpoints, DB queries |
195
+ | ReUI examples (reference) | Custom components |
196
+ | Mock build (dev playground) | AMD build (Moodle), ESM bundle (WP) |
197
+
198
+ ### Extension model
199
+
200
+ Extensions implement `ExtensionPage` (extends `PageContract`) for licensed features. The build gate excludes unlicensed extensions at compile time. Host plugins handle server-side license checks and only send contracts for active extensions.
201
+
202
+ ## Development
203
+
204
+ ```bash
205
+ # Check environment
206
+ npm run doctor
207
+
208
+ # Mock build -- standalone SPA, no server needed
209
+ npm run dev:mock # http://localhost:5174
210
+
211
+ # Typecheck
212
+ npm run typecheck
213
+
214
+ # Lint
215
+ npm run lint:fix
216
+
217
+ # Build ESM lib
218
+ npm run build
219
+ ```
220
+
221
+ ### Host switching (mock only)
222
+
223
+ The mock build includes a host switcher (M/W button in sidebar footer) to preview the UI inside Moodle Boost header or WordPress admin chrome.
224
+
225
+ ### ReUI component examples
226
+
227
+ Examples from the ReUI registry are synced to `src/components/examples/`. They serve as reference for component usage and are excluded from the published NPM package.
228
+
229
+ ```bash
230
+ # Sync all examples from ReUI registry (auto-discovers new components)
231
+ npm run sync:examples
232
+
233
+ # Preview what would be synced
234
+ npm run sync:examples -- --dry-run
235
+
236
+ # Check if new examples are available (used by CI)
237
+ npm run sync:examples -- --check
238
+ ```
239
+
240
+ A GitHub Action runs weekly (Mondays 8:00 UTC) to check for new examples and opens a PR automatically if updates are available.
241
+
242
+ ## Git Hooks
243
+
244
+ Configured via `.githooks/` (activated by `npm run prepare`):
245
+
246
+ | Hook | Trigger | Protection |
247
+ |--------------|--------------|--------------------------------------|
248
+ | `pre-commit` | `git commit` | Typecheck + lint on staged files |
249
+ | `commit-msg` | `git commit` | Enforces Conventional Commits format |
250
+ | `pre-push` | `git push` | Full typecheck + lib build |
251
+
252
+ ## Versioning
253
+
254
+ Uses [Changesets](https://github.com/changesets/changesets) for automated versioning and changelog with GitHub-linked entries.
255
+
256
+ ```bash
257
+ # After finishing a feature:
258
+ npx changeset # select patch/minor/major, write summary
259
+ git add . && git commit # include the .changeset/*.md file
260
+
261
+ # On merge to main:
262
+ # GitHub Action opens "Version Packages" PR
263
+ # Merge that PR to publish to GitHub Packages
264
+ ```
265
+
266
+ ## CLI
267
+
268
+ The package includes a CLI for bootstrapping and maintaining the UI layer in consumer projects:
269
+
270
+ | Command | Description |
271
+ |-----------------------------------------|-------------------------------------------------------------------------------|
272
+ | `npx create-middag-ui` | Bootstrap `ui/` (Community) |
273
+ | `npx @middag-io/create-middag-ui` | Bootstrap `ui/` (PRO) |
274
+ | `npx @middag-io/react doctor` | Validate consumer project setup (deps, configs, peer deps) |
275
+ | `npx @middag-io/react dev` | Start mock dev server from consumer project |
276
+ | `npx @middag-io/react add-block <type>` | Scaffold a new block type with component + mock factory |
277
+ | `npx @middag-io/react upgrade` | Check for updates and run codemods |
278
+
279
+ ## Scripts
280
+
281
+ | Command | Description |
282
+ |-------------------------|---------------------------------------|
283
+ | `npm run doctor` | Validate development environment |
284
+ | `npm run dev:mock` | Dev server with hot reload (mock SPA) |
285
+ | `npm run build` | Build ESM lib to `dist-lib/` |
286
+ | `npm run build:mock` | Build mock SPA to `dist-mock/` |
287
+ | `npm run typecheck` | TypeScript type check |
288
+ | `npm run lint` | ESLint check |
289
+ | `npm run lint:fix` | ESLint auto-fix |
290
+ | `npm run format` | Prettier format |
291
+ | `npm run format:check` | Prettier check |
292
+ | `npm run sync:examples` | Sync ReUI examples from registry |
293
+ | `npm run changeset` | Create a changeset for versioning |
294
+ | `npm run release` | Build + publish (used by CI) |
295
+
296
+ ## Peer Dependencies
297
+
298
+ - `react` ^19.0.0
299
+ - `react-dom` ^19.0.0
300
+ - `@inertiajs/react` ^2.0.0
301
+ - `@inertiajs/core` ^2.0.0
302
+
303
+ ## Mock Build Deployment (Cloudflare Pages)
304
+
305
+ The mock SPA is deployed automatically to Cloudflare Pages on every push to `main`.
306
+
307
+ **Live URL:** `https://middag-react-mock.pages.dev`
308
+
309
+ ### First-time setup
310
+
311
+ 1. Create the Cloudflare Pages project:
312
+ ```bash
313
+ npx wrangler pages project create middag-react-mock --production-branch main
314
+ ```
315
+
316
+ 2. Add repository secrets in GitHub (`Settings > Secrets and variables > Actions`):
317
+ - `CLOUDFLARE_API_TOKEN` -- API token with "Cloudflare Pages: Edit" permission
318
+ - `CLOUDFLARE_ACCOUNT_ID` -- your Cloudflare account ID (found in dashboard URL or Workers & Pages overview)
319
+
320
+ 3. Push to `main` -- the `deploy-mock.yml` workflow runs automatically.
321
+
322
+ ### Manual deploy (local)
323
+
324
+ ```bash
325
+ npm run build:mock
326
+ npx wrangler pages deploy dist-mock --project-name=middag-react-mock
327
+ ```
328
+
329
+ ## CI/CD
330
+
331
+ | Workflow | Trigger | What it does |
332
+ |--------------------------------|-------------------------------------------|----------------------------------------------------------------------|
333
+ | `publish.yml` | Push to main | Typecheck + Changesets publish to GitHub Packages + npm public |
334
+ | `publish-create-middag-ui.yml` | Push to main (packages/create-middag-ui/) | Publish to npm (Community) + GitHub Packages (PRO) |
335
+ | `deploy-mock.yml` | Push to main / manual | Build mock + deploy to Cloudflare Pages |
336
+ | `sync-examples.yml` | Monday 8:00 UTC / manual | Sync ReUI examples, opens PR if updates |
337
+
338
+ ### Required secrets
339
+
340
+ | Secret | Registry | Purpose |
341
+ |-------------------------|------------------|--------------------------------------------|
342
+ | `GITHUB_TOKEN` | GitHub Packages | Publish `@middag-io/react` (auto-provided) |
343
+ | `NPM_TOKEN` | npm (public) | Publish `@middag-io/react` + `create-middag-ui` |
344
+ | `CLOUDFLARE_API_TOKEN` | Cloudflare Pages | Deploy mock SPA |
345
+ | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Pages | Account identifier |
346
+
347
+ ## License
348
+
349
+ Private -- MIDDAG proprietary.
@@ -0,0 +1,18 @@
1
+ # Codemods
2
+
3
+ Automatic migration scripts for breaking changes between versions.
4
+
5
+ Each codemod is a `.mjs` file named `v{VERSION}.mjs` (e.g., `v0.7.0.mjs`).
6
+ The upgrade command runs all codemods between the current and target version.
7
+
8
+ ## Writing a codemod
9
+
10
+ Export a default function that receives the project root path:
11
+
12
+ ```js
13
+ export default async function migrate(projectRoot) {
14
+ // Read, transform, write files
15
+ }
16
+
17
+ export const description = "What this codemod does";
18
+ ```
@@ -0,0 +1,21 @@
1
+ /**
2
+ * v0.7.0 codemod: i18n defaults, error telemetry, mock exports, open contract types
3
+ *
4
+ * No breaking changes — inform user about new features.
5
+ */
6
+
7
+ /* global console */
8
+
9
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
+ export default async function migrate(_projectRoot) {
11
+ console.log([
12
+ " No breaking changes in v0.7.0",
13
+ " New features available:",
14
+ " - LIB_UI_DEFAULTS: override lib UI strings via I18nProvider",
15
+ " - ErrorReporterProvider: plug Sentry/New Relic for error telemetry",
16
+ " - @middag-io/react/mock: extend the mock SPA (GitHub Packages only)",
17
+ " - Custom shell/layout types: register your own shells and layouts",
18
+ ].join("\n"));
19
+ }
20
+
21
+ export const description = "i18n defaults, error telemetry, mock exports, open contract types";
@@ -15,8 +15,9 @@
15
15
  * For bootstrapping a new project, use: npx create-middag-ui
16
16
  */
17
17
 
18
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
- import { join } from "node:path";
18
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
19
+ import { join, dirname } from "node:path";
20
+ import { fileURLToPath } from "node:url";
20
21
  import { execSync } from "node:child_process";
21
22
 
22
23
  // ── Helpers ────────────────────────────────────────────────────────────────
@@ -170,7 +171,55 @@ export function create${pascalName.replace("Block", "")}Block(
170
171
  console.log(`\n Docs: https://docs.middag.io/guides/custom-blocks\n`);
171
172
  }
172
173
 
173
- function cmdUpgrade(cwd) {
174
+ /**
175
+ * Parse a semver string like "0.7.0" or "^0.6.0" into [major, minor, patch].
176
+ * Strips leading ^ ~ >= etc.
177
+ */
178
+ function parseSemver(v) {
179
+ const m = String(v).match(/(\d+)\.(\d+)\.(\d+)/);
180
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
181
+ }
182
+
183
+ /**
184
+ * Compare two semver tuples: -1 (a < b), 0 (a == b), 1 (a > b).
185
+ */
186
+ function compareSemver(a, b) {
187
+ for (let i = 0; i < 3; i++) {
188
+ if (a[i] < b[i]) return -1;
189
+ if (a[i] > b[i]) return 1;
190
+ }
191
+ return 0;
192
+ }
193
+
194
+ /**
195
+ * Discover codemod files in bin/codemods/ and return those whose version
196
+ * is > currentVer and <= targetVer, sorted ascending.
197
+ */
198
+ function discoverCodemods(currentVer, targetVer) {
199
+ const __filename = fileURLToPath(import.meta.url);
200
+ const __dirname = dirname(__filename);
201
+ const codemodsDir = join(__dirname, "codemods");
202
+
203
+ if (!existsSync(codemodsDir)) return [];
204
+
205
+ const files = readdirSync(codemodsDir).filter((f) => /^v[\d.]+\.mjs$/.test(f));
206
+ const candidates = [];
207
+
208
+ for (const file of files) {
209
+ const ver = parseSemver(file);
210
+ if (!ver) continue;
211
+ // Include codemods where: currentVer < codemodVer <= targetVer
212
+ if (compareSemver(ver, currentVer) > 0 && compareSemver(ver, targetVer) <= 0) {
213
+ candidates.push({ file, ver, path: join(codemodsDir, file) });
214
+ }
215
+ }
216
+
217
+ // Sort ascending by version
218
+ candidates.sort((a, b) => compareSemver(a.ver, b.ver));
219
+ return candidates;
220
+ }
221
+
222
+ async function cmdUpgrade(cwd) {
174
223
  log("Checking for updates...\n");
175
224
  const pkgPath = join(cwd, "package.json");
176
225
 
@@ -180,19 +229,51 @@ function cmdUpgrade(cwd) {
180
229
  }
181
230
 
182
231
  try {
183
- const result = execSync("npm view @middag-io/react version 2>/dev/null", { encoding: "utf8" }).trim();
232
+ const latest = execSync("npm view @middag-io/react version 2>/dev/null", { encoding: "utf8" }).trim();
184
233
  const currentPkg = JSON.parse(readFileSync(pkgPath, "utf8"));
185
- const current = currentPkg.dependencies?.["@middag-io/react"] || "unknown";
234
+ const currentRaw = currentPkg.dependencies?.["@middag-io/react"] || "unknown";
235
+
236
+ console.log(` Current: ${currentRaw}`);
237
+ console.log(` Latest: ${latest}`);
186
238
 
187
- console.log(` Current: ${current}`);
188
- console.log(` Latest: ${result}`);
239
+ const currentVer = parseSemver(currentRaw);
240
+ const targetVer = parseSemver(latest);
189
241
 
190
- if (current.includes(result)) {
242
+ if (currentRaw.includes(latest)) {
191
243
  success("Already up to date");
192
244
  } else {
193
245
  log("Updating...");
194
246
  run("npm install @middag-io/react@latest", { cwd });
195
- success(`Updated to ${result}`);
247
+ success(`Updated to ${latest}`);
248
+ }
249
+
250
+ // ── Codemods ────────────────────────────────────────────────────────
251
+ if (currentVer && targetVer && compareSemver(currentVer, targetVer) < 0) {
252
+ const codemods = discoverCodemods(currentVer, targetVer);
253
+
254
+ if (codemods.length > 0) {
255
+ console.log("");
256
+ log(`Running ${codemods.length} codemod(s)...\n`);
257
+
258
+ let applied = 0;
259
+ for (const cm of codemods) {
260
+ const mod = await import(cm.path);
261
+ const desc = mod.description || cm.file;
262
+ console.log(` \x1b[36m▸\x1b[0m ${cm.file}: ${desc}`);
263
+ try {
264
+ await mod.default(cwd);
265
+ applied++;
266
+ } catch (err) {
267
+ fail(`Codemod ${cm.file} failed: ${err.message}`);
268
+ }
269
+ }
270
+
271
+ console.log("");
272
+ success(`${applied}/${codemods.length} codemod(s) applied`);
273
+ } else {
274
+ console.log("");
275
+ success("No codemods needed for this upgrade");
276
+ }
196
277
  }
197
278
  } catch {
198
279
  warn("Could not check latest version (network or auth issue)");
@@ -222,7 +303,7 @@ switch (command) {
222
303
  cmdAddBlock(cwd, args[0]);
223
304
  break;
224
305
  case "upgrade":
225
- cmdUpgrade(cwd);
306
+ await cmdUpgrade(cwd);
226
307
  break;
227
308
  default:
228
309
  console.log(`
@@ -1,83 +1,86 @@
1
- import { jsx as e, jsxs as t } from "react/jsx-runtime";
1
+ import { n as e } from "./i18n-zhMby9Ml.js";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
2
3
  //#region src/base/blocks/ConditionTreeBlock.tsx
3
- function n({ logic: t }) {
4
- return /* @__PURE__ */ e("span", {
5
- className: t === "and" ? "text-primary inline-flex items-center rounded bg-[oklch(0.93_0.04_264)] px-2 py-0.5 text-xs font-bold uppercase" : "inline-flex items-center rounded bg-[oklch(0.95_0.04_75)] px-2 py-0.5 text-xs font-bold text-[oklch(0.55_0.16_75)] uppercase",
6
- children: t
4
+ function r({ logic: e }) {
5
+ return /* @__PURE__ */ t("span", {
6
+ className: e === "and" ? "text-primary inline-flex items-center rounded bg-[oklch(0.93_0.04_264)] px-2 py-0.5 text-xs font-bold uppercase" : "inline-flex items-center rounded bg-[oklch(0.95_0.04_75)] px-2 py-0.5 text-xs font-bold text-[oklch(0.55_0.16_75)] uppercase",
7
+ children: e
7
8
  });
8
9
  }
9
- function r({ rule: n }) {
10
- return /* @__PURE__ */ t("div", {
10
+ function i({ rule: r }) {
11
+ let { t: i } = e();
12
+ return /* @__PURE__ */ n("div", {
11
13
  className: "bg-background flex items-center gap-2 rounded-md p-2",
12
14
  children: [
13
- /* @__PURE__ */ e("span", {
15
+ /* @__PURE__ */ t("span", {
14
16
  className: "border-border bg-muted/50 inline-flex items-center rounded border px-2 py-1 text-sm font-medium",
15
- children: n.field
17
+ children: r.field
16
18
  }),
17
- /* @__PURE__ */ e("span", {
19
+ /* @__PURE__ */ t("span", {
18
20
  className: "text-muted-foreground text-sm",
19
- children: n.operator
21
+ children: r.operator
20
22
  }),
21
- /* @__PURE__ */ e("span", {
23
+ /* @__PURE__ */ t("span", {
22
24
  className: "border-border bg-muted/50 inline-flex items-center rounded border px-2 py-1 text-sm font-medium",
23
- children: n.value
25
+ children: r.value
24
26
  }),
25
- /* @__PURE__ */ e("button", {
27
+ /* @__PURE__ */ t("button", {
26
28
  type: "button",
27
29
  className: "text-muted-foreground hover:text-destructive ml-auto flex h-6 w-6 items-center justify-center rounded text-sm transition-colors",
28
30
  tabIndex: -1,
29
- "aria-label": "Remover regra",
31
+ "aria-label": i("middag.ui.condition_tree.remove_rule"),
30
32
  children: "×"
31
33
  })
32
34
  ]
33
35
  });
34
36
  }
35
- function i({ group: a, depth: o }) {
36
- return /* @__PURE__ */ t("div", {
37
+ function a({ group: o, depth: s }) {
38
+ let { t: c } = e();
39
+ return /* @__PURE__ */ n("div", {
37
40
  className: "bg-muted/30 space-y-2 rounded-lg border p-3",
38
- style: o > 0 ? { marginLeft: `${o * 16}px` } : void 0,
41
+ style: s > 0 ? { marginLeft: `${s * 16}px` } : void 0,
39
42
  children: [
40
- /* @__PURE__ */ t("div", {
43
+ /* @__PURE__ */ n("div", {
41
44
  className: "flex items-center gap-2",
42
- children: [/* @__PURE__ */ e(n, { logic: a.logic }), o > 0 && /* @__PURE__ */ e("button", {
45
+ children: [/* @__PURE__ */ t(r, { logic: o.logic }), s > 0 && /* @__PURE__ */ t("button", {
43
46
  type: "button",
44
47
  className: "text-muted-foreground hover:text-destructive ml-auto flex h-6 w-6 items-center justify-center rounded text-sm transition-colors",
45
48
  tabIndex: -1,
46
- "aria-label": "Remover grupo",
49
+ "aria-label": c("middag.ui.condition_tree.remove_group"),
47
50
  children: "×"
48
51
  })]
49
52
  }),
50
- a.rules.map((t) => /* @__PURE__ */ e(r, { rule: t }, t.id)),
51
- a.groups.map((t) => /* @__PURE__ */ e(i, {
52
- group: t,
53
- depth: o + 1
54
- }, t.id)),
55
- /* @__PURE__ */ t("div", {
53
+ o.rules.map((e) => /* @__PURE__ */ t(i, { rule: e }, e.id)),
54
+ o.groups.map((e) => /* @__PURE__ */ t(a, {
55
+ group: e,
56
+ depth: s + 1
57
+ }, e.id)),
58
+ /* @__PURE__ */ n("div", {
56
59
  className: "flex items-center gap-2 pt-1",
57
- children: [/* @__PURE__ */ e("button", {
60
+ children: [/* @__PURE__ */ t("button", {
58
61
  type: "button",
59
62
  className: "text-muted-foreground hover:border-primary hover:text-primary rounded-md border border-dashed px-3 py-1 text-xs transition-colors",
60
63
  tabIndex: -1,
61
- children: "+ Adicionar regra"
62
- }), /* @__PURE__ */ e("button", {
64
+ children: c("middag.ui.condition_tree.add_rule")
65
+ }), /* @__PURE__ */ t("button", {
63
66
  type: "button",
64
67
  className: "text-muted-foreground hover:border-primary hover:text-primary rounded-md border border-dashed px-3 py-1 text-xs transition-colors",
65
68
  tabIndex: -1,
66
- children: "+ Adicionar grupo"
69
+ children: c("middag.ui.condition_tree.add_group")
67
70
  })]
68
71
  })
69
72
  ]
70
73
  });
71
74
  }
72
- function a({ block: t }) {
73
- let { data: n } = t;
74
- return /* @__PURE__ */ e("div", {
75
+ function o({ block: e }) {
76
+ let { data: n } = e;
77
+ return /* @__PURE__ */ t("div", {
75
78
  className: "space-y-3",
76
- children: /* @__PURE__ */ e(i, {
79
+ children: /* @__PURE__ */ t(a, {
77
80
  group: n.root,
78
81
  depth: 0
79
82
  })
80
83
  });
81
84
  }
82
85
  //#endregion
83
- export { a as ConditionTreeBlock };
86
+ export { o as ConditionTreeBlock };