create-middag-ui 0.26.1 → 0.27.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
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  import { join } from "node:path";
23
- import { detectHost, detectMoodleComponent, HOSTS } from "./lib/detect.js";
23
+ import { detectHost, detectMoodleComponent, detectPhpHost, HOSTS } from "./lib/detect.js";
24
24
  import { ask, confirm, select } from "./lib/prompts.js";
25
25
  import { runTokenFlow } from "./lib/auth.js";
26
26
  import {
@@ -92,6 +92,32 @@ if (hostKey) {
92
92
 
93
93
  const host = HOSTS[hostKey];
94
94
 
95
+ // Custom host: probe for a PHP backend so the production build targets the
96
+ // PHP doc-root (public/build) with a base-href that keeps lazy chunks resolving.
97
+ let phpHost = null;
98
+ if (hostKey === "custom") {
99
+ const php = detectPhpHost(cwd);
100
+ if (php && php.docRoot) {
101
+ const fwLabel = php.framework ? ` (${php.framework})` : "";
102
+ if (nonInteractive) {
103
+ phpHost = php;
104
+ success(`PHP host detected${fwLabel} → build to public/build (--yes mode)`);
105
+ } else {
106
+ const docRel = php.docRoot.replace(cwd, ".");
107
+ const ok = await confirm(
108
+ `PHP backend detected${fwLabel}. Build assets into ${docRel}/build?`,
109
+ true,
110
+ );
111
+ if (ok) {
112
+ phpHost = php;
113
+ success("Production build will target the PHP doc-root (public/build)");
114
+ } else {
115
+ info("Keeping generic custom build (../dist)");
116
+ }
117
+ }
118
+ }
119
+ }
120
+
95
121
  // Moodle: detect frankenstyle component name (e.g. "local_middag", "mod_assign")
96
122
  let moodleComponent = null;
97
123
  if (hostKey === "moodle") {
@@ -249,7 +275,7 @@ if (isPro) {
249
275
 
250
276
  // Host-specific production files (entry, vite config, theme CSS)
251
277
  scaffoldHostEntry(targetDir, hostKey);
252
- scaffoldHostViteConfig(targetDir, hostKey, host, withLicensing);
278
+ scaffoldHostViteConfig(targetDir, hostKey, host, withLicensing, phpHost);
253
279
  scaffoldHostThemeCSS(targetDir, hostKey, host);
254
280
 
255
281
  // Moodle-specific: AMD plugin + Moodle adapters (ajax, strings, notification)
package/lib/detect.js CHANGED
@@ -113,3 +113,31 @@ export function detectHost(cwd) {
113
113
 
114
114
  return null;
115
115
  }
116
+
117
+ /**
118
+ * Detect a PHP host and its doc-root, walking cwd + ancestors.
119
+ *
120
+ * Used to refine the 'custom' host: a PHP backend's production build must
121
+ * target the doc-root (public/build) with a proper base-href so lazy chunks
122
+ * resolve. PHP marker priority: composer.json (universal) → public/ doc-root;
123
+ * bin/console (Symfony) / artisan (Laravel) only refine the framework hint.
124
+ *
125
+ * @param {string} cwd - Directory the scaffold runs in (ui/ goes under here)
126
+ * @returns {{ lang: 'php', framework: string|null, appRoot: string, docRoot: string|null } | null}
127
+ */
128
+ export function detectPhpHost(cwd) {
129
+ let dir = cwd;
130
+ for (let i = 0; i <= MAX_DEPTH; i++) {
131
+ if (existsSync(join(dir, "composer.json"))) {
132
+ const docRoot = existsSync(join(dir, "public")) ? join(dir, "public") : null;
133
+ let framework = null;
134
+ if (existsSync(join(dir, "artisan"))) framework = "laravel";
135
+ else if (existsSync(join(dir, "bin", "console"))) framework = "symfony";
136
+ return { lang: "php", framework, appRoot: dir, docRoot };
137
+ }
138
+ const parent = dirname(dir);
139
+ if (parent === dir) break;
140
+ dir = parent;
141
+ }
142
+ return null;
143
+ }
package/lib/scaffold.js CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
- import { basename, dirname, join } from "node:path";
11
+ import { basename, dirname, join, relative } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { error, success, warn } from "./ui.js";
14
14
 
@@ -1435,7 +1435,7 @@ ${postMountCode}
1435
1435
  * @param {object} host - HOSTS[hostKey] object
1436
1436
  * @param {boolean} withLicensing - Whether to emit @middag-io/licensing delivery manifest plugin
1437
1437
  */
1438
- export function scaffoldHostViteConfig(targetDir, hostKey, host, withLicensing = false) {
1438
+ export function scaffoldHostViteConfig(targetDir, hostKey, host, withLicensing = false, phpHost = null) {
1439
1439
  const filePath = join(targetDir, `vite.config.${hostKey}.ts`);
1440
1440
  const label = `vite.config.${hostKey}.ts`;
1441
1441
  if (skipIfExists(filePath, label)) return;
@@ -1524,10 +1524,58 @@ export default defineConfig({
1524
1524
  },
1525
1525
  },
1526
1526
  });
1527
+ `;
1528
+ writeFile(filePath, content, label);
1529
+ return;
1530
+ } else if (hostKey === "custom" && phpHost && phpHost.docRoot) {
1531
+ // PHP host: app-entry build into the doc-root. base:"/build/" scopes
1532
+ // lazy-chunk import URLs (heavy blocks: ChartPanel, FormPanel, DateField)
1533
+ // to where PHP serves /public/build — without it dynamic imports 404.
1534
+ // entry-custom.tsx self-executes (registerDefaults + createInertiaApp), so
1535
+ // this is an APP build (rollupOptions.input), NOT a library build.
1536
+ const docRootName = basename(phpHost.docRoot);
1537
+ const rel = relative(targetDir, join(phpHost.docRoot, "build")) || "../public/build";
1538
+ const outDirExpr = `resolve(__dirname, ${JSON.stringify(rel)})`;
1539
+ const content = `/**
1540
+ * Vite build config for the custom host (PHP) — production build target.
1541
+ *
1542
+ * Builds src/entry-custom.tsx as an APP entry (self-executes: registerDefaults
1543
+ * + createInertiaApp) so the bundle runs when the PHP shell loads
1544
+ * \`<script type="module" src="/build/app.js">\`. base:"/build/" scopes lazy
1545
+ * chunk import URLs to where the assets are served (${docRootName}/build).
1546
+ *
1547
+ * The dev server (\`npm run dev\`) uses vite.config.ts instead.
1548
+ */
1549
+ import { defineConfig } from "vite";
1550
+ import react from "@vitejs/plugin-react";
1551
+ ${licensingImport.trimEnd()}
1552
+ import { resolve } from "path";
1553
+
1554
+ export default defineConfig({
1555
+ plugins: [react()${licensingPluginEntry}],
1556
+ define: { "process.env.NODE_ENV": JSON.stringify("production") },
1557
+ resolve: { alias: { "@/": resolve(__dirname, "src") + "/" } },
1558
+ base: "/build/",
1559
+ build: {
1560
+ outDir: ${outDirExpr},
1561
+ emptyOutDir: true,
1562
+ cssCodeSplit: false,
1563
+ rollupOptions: {
1564
+ input: resolve(__dirname, "src/entry-custom.tsx"),
1565
+ output: {
1566
+ entryFileNames: "app.js",
1567
+ chunkFileNames: "[name]-[hash].js",
1568
+ assetFileNames: (assetInfo) =>
1569
+ assetInfo.name?.endsWith(".css") ? "style.css" : "[name]-[hash][extname]",
1570
+ },
1571
+ },
1572
+ },
1573
+ });
1527
1574
  `;
1528
1575
  writeFile(filePath, content, label);
1529
1576
  return;
1530
1577
  } else {
1578
+ // Generic custom (non-PHP) or PHP without a public/ doc-root — lib mode.
1531
1579
  outDir = `resolve(__dirname, "../dist")`;
1532
1580
  formats = `["es"]`;
1533
1581
  libName = `"MiddagUI"`;
@@ -2002,7 +2050,7 @@ function readTemplate(relativePath) {
2002
2050
  // ── Shared: register, page-resolver, route helper, demo page ────────────
2003
2051
 
2004
2052
  /**
2005
- * Scaffold FREE register: src/app/register.ts — the 12 standard blocks.
2053
+ * Scaffold FREE register: src/app/register.ts — the 13 standard blocks.
2006
2054
  */
2007
2055
  export function scaffoldFreeRegister(targetDir) {
2008
2056
  ensureDir(join(targetDir, "src", "app"));
@@ -5,7 +5,7 @@
5
5
  * Only available when installed from GitHub Packages.
6
6
  *
7
7
  * Generates the extended mock harness (mock/, src/app/register.ts)
8
- * with 19 blocks (12 standard + 7 premium), extracted navigation/data/entities/routes files,
8
+ * with 19 blocks (13 standard + 6 premium), extracted navigation/data/entities/routes files,
9
9
  * and the slim app.tsx that delegates to mock/.
10
10
  */
11
11
 
@@ -54,7 +54,7 @@ function skipIfExists(filePath, label) {
54
54
  // ── 1. PRO register (19 blocks) ────────────────────────────────────────
55
55
 
56
56
  /**
57
- * Scaffold `src/app/register.ts` — PRO version with 19 blocks (12 standard + 7 premium).
57
+ * Scaffold `src/app/register.ts` — PRO version with 19 blocks (13 standard + 6 premium).
58
58
  * Overrides any FREE register that may have been scaffolded.
59
59
  *
60
60
  * @param {string} targetDir - Absolute path to UI project root
@@ -12,8 +12,8 @@ import "./theme.css";
12
12
  import "@fontsource-variable/figtree";
13
13
  import { App } from "./app";
14
14
 
15
- // Dev mode: register the free engine defaults (12 standard blocks + fields +
16
- // icons + cells) plus the premium runtime (the 7 heavy blocks). Then override
15
+ // Dev mode: register the free engine defaults (13 standard blocks + fields +
16
+ // icons + cells) plus the premium runtime (the 6 heavy blocks). Then override
17
17
  // the "product" shell with the host-sim MockProductShell inherited from
18
18
  // @middag-io/react-demo (Moodle/WP chrome, host switcher, theme/locale toggles).
19
19
  // Dev-only — in production, entry-*.tsx uses the selective register from ./app/register.
@@ -1,13 +1,16 @@
1
1
  /**
2
2
  * register — selective registration for this plugin's UI (PRO).
3
3
  *
4
- * Registers the 12 standard blocks (plus shells, layouts, cell renderers, form
4
+ * Registers the 13 standard blocks (plus shells, layouts, cell renderers, form
5
5
  * fields and icons) from @middag-io/react, then calls registerProDefaults()
6
6
  * from @middag-io/react-pro to add the premium runtime: the rich ProductShell
7
- * + chrome panels and the 7 interactive blocks (form_panel, chart_panel,
8
- * kanban_board, flow_editor, form_builder, condition_tree, sentence_builder).
7
+ * + chrome panels and the 6 interactive blocks (chart_panel, flow_editor,
8
+ * condition_tree, sentence_builder, form_builder, kanban_board).
9
9
  *
10
- * Total: 19 blocks (12 standard + 7 premium).
10
+ * Note: `form_panel` is a standard Community block (registered below); only
11
+ * `form_builder` is premium.
12
+ *
13
+ * Total: 19 blocks (13 standard + 6 premium).
11
14
  *
12
15
  * Full catalog: https://docs.middag.io/blocks
13
16
  */
@@ -26,7 +29,7 @@ import {
26
29
  SidebarLayout,
27
30
  DashboardLayout,
28
31
  WizardLayout,
29
- // Blocks (the 12 standard barrel exports)
32
+ // Blocks (the 13 standard barrel exports)
30
33
  DenseTableBlock,
31
34
  MetricCardBlock,
32
35
  EmptyStateBlock,
@@ -39,6 +42,7 @@ import {
39
42
  CardGridBlock,
40
43
  ActionGridBlock,
41
44
  LinkListBlock,
45
+ FormPanelBlock,
42
46
  } from "@middag-io/react";
43
47
  import { registerProDefaults } from "@middag-io/react-pro/runtime";
44
48
 
@@ -57,7 +61,7 @@ export function registerDefaults(): void {
57
61
  registerLayout("dashboard", DashboardLayout);
58
62
  registerLayout("wizard", WizardLayout);
59
63
 
60
- // Blocks — the 12 standard blocks from the barrel
64
+ // Blocks — the 13 standard blocks from the barrel
61
65
  registerBlock("dense_table", DenseTableBlock);
62
66
  registerBlock("metric_card", MetricCardBlock);
63
67
  registerBlock("empty_state", EmptyStateBlock);
@@ -70,6 +74,9 @@ export function registerDefaults(): void {
70
74
  registerBlock("card_grid", CardGridBlock);
71
75
  registerBlock("action_grid", ActionGridBlock);
72
76
  registerBlock("link_list", LinkListBlock);
77
+ // form_panel pulls react-hook-form + zod (lazy-loaded); it is a standard
78
+ // Community block — drop it if this bundle has no forms.
79
+ registerBlock("form_panel", FormPanelBlock);
73
80
 
74
81
  // Cell renderers (status, timestamp, link, boolean, etc.)
75
82
  registerDefaultCells();
@@ -80,7 +87,7 @@ export function registerDefaults(): void {
80
87
  // Icons (navigation, block, entity type icons)
81
88
  registerDefaultIcons();
82
89
 
83
- // Premium runtime — the rich ProductShell + chrome and the 7 interactive
90
+ // Premium runtime — the rich ProductShell + chrome and the 6 interactive
84
91
  // blocks. Ships in @middag-io/react-pro (GitHub Packages, PRO tier).
85
92
  registerProDefaults();
86
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-middag-ui",
3
- "version": "0.26.1",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "description": "Bootstrap a MIDDAG React UI layer in your Moodle or WordPress plugin",
6
6
  "scripts": {