create-middag-ui 0.26.0 → 0.26.2

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"`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-middag-ui",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "type": "module",
5
5
  "description": "Bootstrap a MIDDAG React UI layer in your Moodle or WordPress plugin",
6
6
  "scripts": {