create-middag-ui 0.10.3 → 0.11.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/cli.js CHANGED
@@ -37,6 +37,9 @@ import {
37
37
  scaffoldFreeApp,
38
38
  scaffoldFreeAdapters,
39
39
  scaffoldDevShell,
40
+ scaffoldHostEntry,
41
+ scaffoldHostViteConfig,
42
+ scaffoldHostThemeCSS,
40
43
  } from "./lib/scaffold.js";
41
44
  import { runNpmInstall } from "./lib/install.js";
42
45
  import { log, success, heading, blank, info } from "./lib/ui.js";
@@ -116,7 +119,7 @@ if (!dirCreated) {
116
119
 
117
120
  heading(5, TOTAL_STEPS, "Scaffolding config files");
118
121
 
119
- scaffoldPackageJson(targetDir, host, cwd, registryPath);
122
+ scaffoldPackageJson(targetDir, host, cwd, registryPath, hostKey);
120
123
  scaffoldTsconfig(targetDir);
121
124
  scaffoldViteConfig(targetDir, host, registryPath);
122
125
  scaffoldEslintConfig(targetDir);
@@ -157,6 +160,11 @@ if (isPro) {
157
160
  success("FREE: generated DevShell + local Inertia adapters");
158
161
  }
159
162
 
163
+ // Host-specific production files (entry, vite config, theme CSS)
164
+ scaffoldHostEntry(targetDir, hostKey);
165
+ scaffoldHostViteConfig(targetDir, hostKey, host);
166
+ scaffoldHostThemeCSS(targetDir, hostKey, host);
167
+
160
168
  // ── Step 9: npm install ──────────────────────────────────────────────────
161
169
 
162
170
  heading(9, TOTAL_STEPS, "Installing dependencies");
@@ -173,6 +181,10 @@ heading(10, TOTAL_STEPS, "Done!");
173
181
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
174
182
  blank();
175
183
 
184
+ // Determine host-specific build script name for display
185
+ const hostBuildScript = hostKey === "wordpress" ? "build:wp" : hostKey === "moodle" ? "build:moodle" : "build:host";
186
+ const hostWatchScript = hostKey === "wordpress" ? "watch:wp" : hostKey === "moodle" ? "watch:moodle" : "watch:host";
187
+
176
188
  if (installOk) {
177
189
  log(`MIDDAG React UI ready in ${dirName}/ (${elapsed}s)\n`);
178
190
  console.log(" Start developing:");
@@ -194,6 +206,11 @@ console.log(" src/pages/settings.ts \u2190 advanced: tabbed_panel + fo
194
206
  console.log(" src/blocks/hello-block.tsx \u2190 custom block example (rename me!)");
195
207
  console.log(" src/app.tsx \u2190 hash-based page router");
196
208
 
209
+ blank();
210
+ console.log(` Production build for ${host.name}:`);
211
+ console.log(` npm run ${hostBuildScript} \u2192 build for ${host.name}`);
212
+ console.log(` npm run ${hostWatchScript} \u2192 rebuild on change`);
213
+
197
214
  blank();
198
215
  console.log(` Integrate with your ${host.name} plugin:`);
199
216
  console.log(" 1. Import { ContractPage } from '@middag-io/react'");
package/lib/detect.js CHANGED
@@ -1,11 +1,19 @@
1
1
  /**
2
2
  * detect.js — Host detection (Moodle / WordPress / Custom).
3
3
  *
4
- * Checks for marker files in the working directory.
4
+ * Detection strategy:
5
+ * - Moodle plugin: cwd has version.php with $plugin->component
6
+ * - WordPress: wp-config.php found in ancestor dirs (plugin/theme is 3+ levels deep)
7
+ * - Moodle root: ancestor has version.php WITHOUT $plugin->component
8
+ *
9
+ * Walks up to MAX_DEPTH ancestor directories from cwd.
5
10
  */
6
11
 
7
- import { existsSync } from "node:fs";
8
- import { join } from "node:path";
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { join, dirname } from "node:path";
14
+
15
+ /** Max ancestor levels to walk when searching for platform markers. */
16
+ const MAX_DEPTH = 5;
9
17
 
10
18
  export const HOSTS = {
11
19
  moodle: { name: "Moodle", detect: "version.php", port: 5174, headerHeight: 50 },
@@ -14,16 +22,74 @@ export const HOSTS = {
14
22
  };
15
23
 
16
24
  /**
17
- * Detect host platform by checking for marker files.
25
+ * Check if a version.php file contains $plugin->component (Moodle plugin marker).
26
+ *
27
+ * @param {string} filePath - Path to version.php
28
+ * @returns {boolean}
29
+ */
30
+ function isMoodlePluginVersion(filePath) {
31
+ try {
32
+ const content = readFileSync(filePath, "utf-8");
33
+ return /\$plugin\s*->\s*component\s*=/.test(content);
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Check if a version.php file is a Moodle root version.php.
41
+ * Root has $version but NOT $plugin->component.
42
+ *
43
+ * @param {string} filePath - Path to version.php
44
+ * @returns {boolean}
45
+ */
46
+ function isMoodleRootVersion(filePath) {
47
+ try {
48
+ const content = readFileSync(filePath, "utf-8");
49
+ if (/\$plugin\s*->\s*component\s*=/.test(content)) return false;
50
+ // Moodle root has both $version and $release — distinguishes from generic version.php
51
+ return /\$version\s*=/.test(content) && /\$release\s*=/.test(content);
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Detect host platform by checking cwd and ancestor directories.
59
+ *
60
+ * Priority:
61
+ * 1. Moodle plugin — cwd has version.php with $plugin->component
62
+ * 2. WordPress — wp-config.php in any ancestor (up to MAX_DEPTH)
63
+ * 3. Moodle root — version.php without $plugin->component in any ancestor
18
64
  *
19
65
  * @param {string} cwd - Directory to check
20
66
  * @returns {string|null} Host key ('moodle', 'wordpress') or null
21
67
  */
22
68
  export function detectHost(cwd) {
23
- for (const [key, host] of Object.entries(HOSTS)) {
24
- if (host.detect && existsSync(join(cwd, host.detect))) {
25
- return key;
69
+ // 1. Moodle plugin version.php in cwd with $plugin->component
70
+ const cwdVersion = join(cwd, "version.php");
71
+ if (existsSync(cwdVersion) && isMoodlePluginVersion(cwdVersion)) {
72
+ return "moodle";
73
+ }
74
+
75
+ // 2. Walk ancestors for WordPress (wp-config.php) or Moodle root (version.php)
76
+ let dir = cwd;
77
+ for (let i = 0; i < MAX_DEPTH; i++) {
78
+ const parent = dirname(dir);
79
+ if (parent === dir) break;
80
+ dir = parent;
81
+
82
+ // WordPress — wp-config.php
83
+ if (existsSync(join(dir, "wp-config.php"))) {
84
+ return "wordpress";
85
+ }
86
+
87
+ // Moodle root — version.php without $plugin->component
88
+ const versionFile = join(dir, "version.php");
89
+ if (existsSync(versionFile) && isMoodleRootVersion(versionFile)) {
90
+ return "moodle";
26
91
  }
27
92
  }
93
+
28
94
  return null;
29
95
  }
package/lib/scaffold.js CHANGED
@@ -77,8 +77,9 @@ export function createTargetDir(targetDir) {
77
77
  */
78
78
  /**
79
79
  * @param {string} registryPath - "github" (PRO) or "public" (FREE)
80
+ * @param {string} [hostKey] - 'wordpress' | 'moodle' | 'custom' (adds host build scripts)
80
81
  */
81
- export function scaffoldPackageJson(targetDir, host, cwd, registryPath) {
82
+ export function scaffoldPackageJson(targetDir, host, cwd, registryPath, hostKey) {
82
83
  const filePath = join(targetDir, "package.json");
83
84
  if (skipIfExists(filePath, "package.json")) return;
84
85
 
@@ -93,19 +94,33 @@ export function scaffoldPackageJson(targetDir, host, cwd, registryPath) {
93
94
  deps["sonner"] = "^2.0.0";
94
95
  }
95
96
 
97
+ const scripts = {
98
+ dev: "vite",
99
+ build: "vite build",
100
+ typecheck: "tsc --noEmit",
101
+ lint: "eslint .",
102
+ "lint:fix": "eslint . --fix",
103
+ format: 'prettier --write "src/**/*.{ts,tsx,css}"',
104
+ "format:check": 'prettier --check "src/**/*.{ts,tsx,css}"',
105
+ };
106
+
107
+ // Add host-specific build/watch scripts
108
+ if (hostKey === "wordpress") {
109
+ scripts["build:wp"] = "vite build --config vite.config.wordpress.ts";
110
+ scripts["watch:wp"] = "vite build --config vite.config.wordpress.ts --watch";
111
+ } else if (hostKey === "moodle") {
112
+ scripts["build:moodle"] = "vite build --config vite.config.moodle.ts";
113
+ scripts["watch:moodle"] = "vite build --config vite.config.moodle.ts --watch";
114
+ } else if (hostKey === "custom") {
115
+ scripts["build:host"] = "vite build --config vite.config.custom.ts";
116
+ scripts["watch:host"] = "vite build --config vite.config.custom.ts --watch";
117
+ }
118
+
96
119
  const pkg = {
97
120
  name: `${projectName}-ui`,
98
121
  private: true,
99
122
  type: "module",
100
- scripts: {
101
- dev: "vite",
102
- build: "vite build",
103
- typecheck: "tsc --noEmit",
104
- lint: "eslint .",
105
- "lint:fix": "eslint . --fix",
106
- format: 'prettier --write "src/**/*.{ts,tsx,css}"',
107
- "format:check": 'prettier --check "src/**/*.{ts,tsx,css}"',
108
- },
123
+ scripts,
109
124
  dependencies: deps,
110
125
  devDependencies: {
111
126
  "@types/react": "^19.0.0",
@@ -185,6 +200,9 @@ import { resolve } from "path";
185
200
  export default defineConfig({
186
201
  plugins: [react()],
187
202
  server: { port: ${host.port} },
203
+ optimizeDeps: {
204
+ include: ["@middag-io/react", "@middag-io/react/mock"],
205
+ },
188
206
  resolve: {
189
207
  alias: {
190
208
  "@/": resolve(__dirname, "src") + "/",
@@ -1231,6 +1249,301 @@ export function App() {
1231
1249
  }
1232
1250
  }
1233
1251
 
1252
+ // ── Host-specific production files ─────────────────────────────────────
1253
+
1254
+ /**
1255
+ * Scaffold production entry point: src/entry-{hostKey}.tsx.
1256
+ * Uses real createInertiaApp — no mocks.
1257
+ *
1258
+ * @param {string} targetDir - Absolute path to UI dir
1259
+ * @param {string} hostKey - 'wordpress' | 'moodle' | 'custom'
1260
+ */
1261
+ export function scaffoldHostEntry(targetDir, hostKey) {
1262
+ ensureDir(join(targetDir, "src"));
1263
+
1264
+ const filePath = join(targetDir, "src", `entry-${hostKey}.tsx`);
1265
+ const label = `src/entry-${hostKey}.tsx`;
1266
+ if (skipIfExists(filePath, label)) return;
1267
+
1268
+ // Host-specific setup and post-mount code
1269
+ let setupCode = "";
1270
+ let postMountCode = "";
1271
+
1272
+ if (hostKey === "wordpress") {
1273
+ setupCode = ` document.body.classList.add("middag-active");`;
1274
+ postMountCode = `
1275
+ // Relocate WP admin notices into the product shell content area
1276
+ const noticeContainer = document.createElement("div");
1277
+ noticeContainer.className = "middag-wp-notices";
1278
+ const observer = new MutationObserver(() => {
1279
+ const content = document.querySelector(".product-shell__content");
1280
+ if (!content) return;
1281
+ const notices = document.querySelectorAll(
1282
+ "#wpbody-content > .notice, #wpbody-content > .update-nag, #wpbody-content > .updated, #wpbody-content > .error",
1283
+ );
1284
+ if (notices.length > 0) {
1285
+ notices.forEach((n) => noticeContainer.appendChild(n));
1286
+ if (!noticeContainer.parentElement) {
1287
+ content.prepend(noticeContainer);
1288
+ }
1289
+ }
1290
+ });
1291
+ observer.observe(document.body, { childList: true, subtree: true });`;
1292
+ } else if (hostKey === "moodle") {
1293
+ setupCode = ` document.body.classList.add("middag-active");`;
1294
+ }
1295
+
1296
+ const content = `/**
1297
+ * Production entry point for ${hostKey === "wordpress" ? "WordPress" : hostKey === "moodle" ? "Moodle" : "custom host"}.
1298
+ *
1299
+ * Uses real createInertiaApp — the host platform (${hostKey === "wordpress" ? "WP" : hostKey === "moodle" ? "Moodle" : "your backend"})
1300
+ * serves the HTML and Inertia page props. This file is the build target
1301
+ * for \`npm run build:${hostKey === "custom" ? "host" : hostKey}\`.
1302
+ *
1303
+ * NOT used by \`npm run dev\` — that uses src/main.tsx with mock adapters.
1304
+ */
1305
+ import { createRoot } from "react-dom/client";
1306
+ import { createInertiaApp } from "@inertiajs/react";
1307
+ import {
1308
+ registerDefaults,
1309
+ registerShell,
1310
+ ContractPage,
1311
+ HostProductShell,
1312
+ I18nProvider,
1313
+ ProgressProvider,
1314
+ ptBR,
1315
+ } from "@middag-io/react";
1316
+ import type { PageContract } from "@middag-io/react";
1317
+ import "@middag-io/react/style.css";
1318
+ import "./theme.css";
1319
+
1320
+ registerDefaults();
1321
+ registerShell("product", HostProductShell);
1322
+
1323
+ createInertiaApp({
1324
+ id: "middag-app",
1325
+ resolve: () => {
1326
+ const Page = ({ contract }: { contract: PageContract }) => (
1327
+ <I18nProvider overrides={ptBR}>
1328
+ <ProgressProvider>
1329
+ <ContractPage contract={contract} />
1330
+ </ProgressProvider>
1331
+ </I18nProvider>
1332
+ );
1333
+ Page.displayName = "ContractPageWrapper";
1334
+ return Page;
1335
+ },
1336
+ setup({ el, App, props }) {
1337
+ el.classList.add("middag-root");
1338
+ ${setupCode}
1339
+ createRoot(el).render(<App {...props} />);
1340
+ ${postMountCode}
1341
+ },
1342
+ });
1343
+ `;
1344
+
1345
+ writeFile(filePath, content, label);
1346
+ }
1347
+
1348
+ /**
1349
+ * Scaffold host-specific Vite build config: vite.config.{hostKey}.ts.
1350
+ *
1351
+ * @param {string} targetDir - Absolute path to UI dir
1352
+ * @param {string} hostKey - 'wordpress' | 'moodle' | 'custom'
1353
+ * @param {object} host - HOSTS[hostKey] object
1354
+ */
1355
+ export function scaffoldHostViteConfig(targetDir, hostKey, host) {
1356
+ const filePath = join(targetDir, `vite.config.${hostKey}.ts`);
1357
+ const label = `vite.config.${hostKey}.ts`;
1358
+ if (skipIfExists(filePath, label)) return;
1359
+
1360
+ let outDir, formats, libName, fileName, extraRollup;
1361
+
1362
+ if (hostKey === "wordpress") {
1363
+ outDir = `resolve(__dirname, "../assets/dist")`;
1364
+ formats = `["iife"]`;
1365
+ libName = `"MiddagUI"`;
1366
+ fileName = `() => "app.js"`;
1367
+ extraRollup = `
1368
+ output: {
1369
+ assetFileNames: (assetInfo) => {
1370
+ if (assetInfo.name?.endsWith(".css")) return "style.css";
1371
+ return assetInfo.name || "[name]-[hash][extname]";
1372
+ },
1373
+ },`;
1374
+ } else if (hostKey === "moodle") {
1375
+ outDir = `resolve(__dirname, "../amd/build")`;
1376
+ formats = `["iife"]`;
1377
+ libName = `"MiddagUI"`;
1378
+ fileName = `() => "app.js"`;
1379
+ extraRollup = `
1380
+ output: {
1381
+ assetFileNames: (assetInfo) => {
1382
+ if (assetInfo.name?.endsWith(".css")) return "style.css";
1383
+ return assetInfo.name || "[name]-[hash][extname]";
1384
+ },
1385
+ },`;
1386
+ } else {
1387
+ outDir = `resolve(__dirname, "../dist")`;
1388
+ formats = `["es"]`;
1389
+ libName = `"MiddagUI"`;
1390
+ fileName = `() => "app.js"`;
1391
+ extraRollup = `
1392
+ output: {
1393
+ assetFileNames: (assetInfo) => {
1394
+ if (assetInfo.name?.endsWith(".css")) return "style.css";
1395
+ return assetInfo.name || "[name]-[hash][extname]";
1396
+ },
1397
+ },`;
1398
+ }
1399
+
1400
+ const content = `/**
1401
+ * Vite build config for ${host.name} — production build target.
1402
+ *
1403
+ * Usage:
1404
+ * npm run build:${hostKey === "custom" ? "host" : hostKey} \u2192 single build
1405
+ * npm run watch:${hostKey === "custom" ? "host" : hostKey} \u2192 rebuild on change
1406
+ *
1407
+ * This config builds src/entry-${hostKey}.tsx into a${hostKey === "custom" ? "n ESM" : "n IIFE"} bundle.
1408
+ * The dev server (\`npm run dev\`) uses vite.config.ts instead.
1409
+ */
1410
+ import { defineConfig } from "vite";
1411
+ import react from "@vitejs/plugin-react";
1412
+ import { resolve } from "path";
1413
+
1414
+ export default defineConfig({
1415
+ plugins: [react()],
1416
+ define: { "process.env.NODE_ENV": JSON.stringify("production") },
1417
+ resolve: { alias: { "@/": resolve(__dirname, "src") + "/" } },
1418
+ build: {
1419
+ outDir: ${outDir},
1420
+ emptyOutDir: true,
1421
+ lib: {
1422
+ entry: resolve(__dirname, "src/entry-${hostKey}.tsx"),
1423
+ formats: ${formats},
1424
+ name: ${libName},
1425
+ fileName: ${fileName},
1426
+ },
1427
+ cssCodeSplit: false,
1428
+ rollupOptions: {${extraRollup}
1429
+ },
1430
+ },
1431
+ });
1432
+ `;
1433
+
1434
+ writeFile(filePath, content, label);
1435
+ }
1436
+
1437
+ /**
1438
+ * Append host-specific CSS to src/theme.css.
1439
+ * If theme.css doesn't exist yet, it will be created by scaffoldDemoFiles.
1440
+ *
1441
+ * @param {string} targetDir - Absolute path to UI dir
1442
+ * @param {string} hostKey - 'wordpress' | 'moodle' | 'custom'
1443
+ * @param {object} host - HOSTS[hostKey] object
1444
+ */
1445
+ export function scaffoldHostThemeCSS(targetDir, hostKey, host) {
1446
+ const themePath = join(targetDir, "src", "theme.css");
1447
+
1448
+ let hostSection = "";
1449
+
1450
+ if (hostKey === "wordpress") {
1451
+ hostSection = `
1452
+
1453
+ /* \u2500\u2500 WordPress admin integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1454
+ * Active when MIDDAG mounts inside wp-admin (body.middag-active).
1455
+ */
1456
+
1457
+ body.middag-active #wpbody-content {
1458
+ padding-bottom: 0;
1459
+ }
1460
+
1461
+ body.middag-active [data-slot="sidebar-container"] {
1462
+ left: 160px !important;
1463
+ }
1464
+
1465
+ body.folded.middag-active [data-slot="sidebar-container"] {
1466
+ left: 36px !important;
1467
+ }
1468
+
1469
+ @media screen and (max-width: 782px) {
1470
+ body.middag-active [data-slot="sidebar-container"] {
1471
+ left: 0 !important;
1472
+ }
1473
+ }
1474
+
1475
+ body.middag-active #wpbody-content > .notice,
1476
+ body.middag-active #wpbody-content > .update-nag,
1477
+ body.middag-active #wpbody-content > .updated,
1478
+ body.middag-active #wpbody-content > .error {
1479
+ display: none !important;
1480
+ }
1481
+
1482
+ .middag-wp-notices {
1483
+ padding: 0.75rem 1.5rem 0;
1484
+ }
1485
+
1486
+ .middag-wp-notices .notice {
1487
+ display: block !important;
1488
+ margin: 0 0 0.5rem;
1489
+ border-radius: var(--radius, 0.5rem);
1490
+ }
1491
+
1492
+ body.middag-active #wpfooter {
1493
+ display: none !important;
1494
+ }
1495
+
1496
+ body.middag-active {
1497
+ --host-header-height: 32px;
1498
+ }
1499
+
1500
+ @media screen and (max-width: 782px) {
1501
+ body.middag-active {
1502
+ --host-header-height: 46px;
1503
+ }
1504
+ }`;
1505
+ } else if (hostKey === "moodle") {
1506
+ hostSection = `
1507
+
1508
+ /* \u2500\u2500 Moodle Boost integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1509
+ * Active when MIDDAG mounts inside Moodle admin (body.middag-active).
1510
+ */
1511
+
1512
+ body.middag-active {
1513
+ --host-header-height: 50px;
1514
+ }
1515
+
1516
+ body.middag-active [data-slot="sidebar-container"] {
1517
+ left: 0 !important;
1518
+ }`;
1519
+ } else {
1520
+ hostSection = `
1521
+
1522
+ /* \u2500\u2500 Host integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1523
+ * Set --host-header-height and --host-sidebar-width as needed.
1524
+ */
1525
+
1526
+ :root {
1527
+ --host-header-height: 0px;
1528
+ --host-sidebar-width: 0px;
1529
+ }`;
1530
+ }
1531
+
1532
+ // If theme.css exists, append; otherwise create with host section only
1533
+ if (existsSync(themePath)) {
1534
+ try {
1535
+ const existing = readFileSync(themePath, "utf-8");
1536
+ writeFileSync(themePath, existing + hostSection + "\n");
1537
+ success(`Appended ${host.name} integration CSS to src/theme.css`);
1538
+ } catch (err) {
1539
+ error(`Failed to append to src/theme.css: ${err.message}`);
1540
+ }
1541
+ } else {
1542
+ ensureDir(join(targetDir, "src"));
1543
+ writeFile(themePath, hostSection.trimStart() + "\n", "src/theme.css (host integration)");
1544
+ }
1545
+ }
1546
+
1234
1547
  // ── LEGACY (kept for backward compat, delegates to FREE) ────────────────
1235
1548
 
1236
1549
  /** @deprecated Use scaffoldFreeApp + scaffoldFreeAdapters instead */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-middag-ui",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "Bootstrap a MIDDAG React UI layer in your Moodle or WordPress plugin",
6
6
  "bin": {