create-middag-ui 0.10.2 → 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 +23 -2
- package/lib/detect.js +73 -7
- package/lib/scaffold.js +423 -19
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
scaffoldPackageJson,
|
|
29
29
|
scaffoldTsconfig,
|
|
30
30
|
scaffoldViteConfig,
|
|
31
|
+
scaffoldEslintConfig,
|
|
32
|
+
scaffoldPrettierConfig,
|
|
31
33
|
scaffoldIndexHtml,
|
|
32
34
|
scaffoldDemoFiles,
|
|
33
35
|
scaffoldPageExamples,
|
|
@@ -35,6 +37,9 @@ import {
|
|
|
35
37
|
scaffoldFreeApp,
|
|
36
38
|
scaffoldFreeAdapters,
|
|
37
39
|
scaffoldDevShell,
|
|
40
|
+
scaffoldHostEntry,
|
|
41
|
+
scaffoldHostViteConfig,
|
|
42
|
+
scaffoldHostThemeCSS,
|
|
38
43
|
} from "./lib/scaffold.js";
|
|
39
44
|
import { runNpmInstall } from "./lib/install.js";
|
|
40
45
|
import { log, success, heading, blank, info } from "./lib/ui.js";
|
|
@@ -114,9 +119,11 @@ if (!dirCreated) {
|
|
|
114
119
|
|
|
115
120
|
heading(5, TOTAL_STEPS, "Scaffolding config files");
|
|
116
121
|
|
|
117
|
-
scaffoldPackageJson(targetDir, host, cwd, registryPath);
|
|
122
|
+
scaffoldPackageJson(targetDir, host, cwd, registryPath, hostKey);
|
|
118
123
|
scaffoldTsconfig(targetDir);
|
|
119
124
|
scaffoldViteConfig(targetDir, host, registryPath);
|
|
125
|
+
scaffoldEslintConfig(targetDir);
|
|
126
|
+
scaffoldPrettierConfig(targetDir);
|
|
120
127
|
scaffoldIndexHtml(targetDir);
|
|
121
128
|
|
|
122
129
|
// ── Step 6: Scaffold ~/.npmrc (GitHub path only) ─────────────────────────
|
|
@@ -145,7 +152,7 @@ scaffoldPageExamples(targetDir);
|
|
|
145
152
|
|
|
146
153
|
if (isPro) {
|
|
147
154
|
scaffoldProApp(targetDir);
|
|
148
|
-
success("PRO: using MockProductShell
|
|
155
|
+
success("PRO: using MockProductShell from @middag-io/react/mock");
|
|
149
156
|
} else {
|
|
150
157
|
scaffoldFreeAdapters(targetDir);
|
|
151
158
|
scaffoldDevShell(targetDir);
|
|
@@ -153,6 +160,11 @@ if (isPro) {
|
|
|
153
160
|
success("FREE: generated DevShell + local Inertia adapters");
|
|
154
161
|
}
|
|
155
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
|
+
|
|
156
168
|
// ── Step 9: npm install ──────────────────────────────────────────────────
|
|
157
169
|
|
|
158
170
|
heading(9, TOTAL_STEPS, "Installing dependencies");
|
|
@@ -169,6 +181,10 @@ heading(10, TOTAL_STEPS, "Done!");
|
|
|
169
181
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
170
182
|
blank();
|
|
171
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
|
+
|
|
172
188
|
if (installOk) {
|
|
173
189
|
log(`MIDDAG React UI ready in ${dirName}/ (${elapsed}s)\n`);
|
|
174
190
|
console.log(" Start developing:");
|
|
@@ -190,6 +206,11 @@ console.log(" src/pages/settings.ts \u2190 advanced: tabbed_panel + fo
|
|
|
190
206
|
console.log(" src/blocks/hello-block.tsx \u2190 custom block example (rename me!)");
|
|
191
207
|
console.log(" src/app.tsx \u2190 hash-based page router");
|
|
192
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
|
+
|
|
193
214
|
blank();
|
|
194
215
|
console.log(` Integrate with your ${host.name} plugin:`);
|
|
195
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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,17 +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
|
-
},
|
|
123
|
+
scripts,
|
|
107
124
|
dependencies: deps,
|
|
108
125
|
devDependencies: {
|
|
109
126
|
"@types/react": "^19.0.0",
|
|
@@ -115,6 +132,14 @@ export function scaffoldPackageJson(targetDir, host, cwd, registryPath) {
|
|
|
115
132
|
typescript: "^5.7.0",
|
|
116
133
|
vite: "^6.0.0",
|
|
117
134
|
"@vitejs/plugin-react": "^4.0.0",
|
|
135
|
+
"@eslint/js": "^9.0.0",
|
|
136
|
+
"typescript-eslint": "^8.0.0",
|
|
137
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
138
|
+
"eslint-plugin-react-refresh": "^0.5.0",
|
|
139
|
+
"eslint-config-prettier": "^10.0.0",
|
|
140
|
+
eslint: "^9.0.0",
|
|
141
|
+
prettier: "^3.0.0",
|
|
142
|
+
"prettier-plugin-tailwindcss": "^0.6.0",
|
|
118
143
|
},
|
|
119
144
|
};
|
|
120
145
|
|
|
@@ -157,16 +182,9 @@ export function scaffoldViteConfig(targetDir, host, registryPath) {
|
|
|
157
182
|
const filePath = join(targetDir, "vite.config.ts");
|
|
158
183
|
if (skipIfExists(filePath, "vite.config.ts")) return;
|
|
159
184
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
? "// PRO: Inertia mocks from @middag-io/react/mock"
|
|
185
|
+
const adapterComment = registryPath === "github"
|
|
186
|
+
? "// PRO: Inertia mocks re-exported from @middag-io/react/mock (same context)"
|
|
163
187
|
: "// FREE: Inertia mocks from local adapters";
|
|
164
|
-
const adapterReact = isPro
|
|
165
|
-
? 'resolve(__dirname, "node_modules/@middag-io/react/mock/adapters/inertia-react.ts")'
|
|
166
|
-
: 'resolve(__dirname, "src/adapters/inertia-react.ts")';
|
|
167
|
-
const adapterCore = isPro
|
|
168
|
-
? 'resolve(__dirname, "node_modules/@middag-io/react/mock/adapters/inertia-core.ts")'
|
|
169
|
-
: 'resolve(__dirname, "src/adapters/inertia-core.ts")';
|
|
170
188
|
|
|
171
189
|
const content = `/**
|
|
172
190
|
* Vite config \u2014 used by \`npm run dev\` and \`npm run build\`.
|
|
@@ -182,12 +200,15 @@ import { resolve } from "path";
|
|
|
182
200
|
export default defineConfig({
|
|
183
201
|
plugins: [react()],
|
|
184
202
|
server: { port: ${host.port} },
|
|
203
|
+
optimizeDeps: {
|
|
204
|
+
include: ["@middag-io/react", "@middag-io/react/mock"],
|
|
205
|
+
},
|
|
185
206
|
resolve: {
|
|
186
207
|
alias: {
|
|
187
208
|
"@/": resolve(__dirname, "src") + "/",
|
|
188
209
|
${adapterComment}
|
|
189
|
-
"@inertiajs/react":
|
|
190
|
-
"@inertiajs/core":
|
|
210
|
+
"@inertiajs/react": resolve(__dirname, "src/adapters/inertia-react.ts"),
|
|
211
|
+
"@inertiajs/core": resolve(__dirname, "src/adapters/inertia-core.ts"),
|
|
191
212
|
},
|
|
192
213
|
},
|
|
193
214
|
});
|
|
@@ -196,6 +217,70 @@ export default defineConfig({
|
|
|
196
217
|
writeFile(filePath, content, "vite.config.ts");
|
|
197
218
|
}
|
|
198
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Scaffold eslint.config.js.
|
|
222
|
+
*/
|
|
223
|
+
export function scaffoldEslintConfig(targetDir) {
|
|
224
|
+
const filePath = join(targetDir, "eslint.config.js");
|
|
225
|
+
if (skipIfExists(filePath, "eslint.config.js")) return;
|
|
226
|
+
|
|
227
|
+
writeFile(
|
|
228
|
+
filePath,
|
|
229
|
+
`import js from "@eslint/js";
|
|
230
|
+
import tseslint from "typescript-eslint";
|
|
231
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
232
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
233
|
+
import prettierConfig from "eslint-config-prettier";
|
|
234
|
+
|
|
235
|
+
export default tseslint.config(
|
|
236
|
+
{ ignores: ["dist/", "node_modules/"] },
|
|
237
|
+
js.configs.recommended,
|
|
238
|
+
...tseslint.configs.recommended,
|
|
239
|
+
{
|
|
240
|
+
files: ["**/*.{ts,tsx}"],
|
|
241
|
+
plugins: {
|
|
242
|
+
"react-hooks": reactHooks,
|
|
243
|
+
"react-refresh": reactRefresh,
|
|
244
|
+
},
|
|
245
|
+
rules: {
|
|
246
|
+
...reactHooks.configs.recommended.rules,
|
|
247
|
+
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
|
248
|
+
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
249
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
prettierConfig,
|
|
253
|
+
);
|
|
254
|
+
`,
|
|
255
|
+
"eslint.config.js",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Scaffold .prettierrc.
|
|
261
|
+
*/
|
|
262
|
+
export function scaffoldPrettierConfig(targetDir) {
|
|
263
|
+
const filePath = join(targetDir, ".prettierrc");
|
|
264
|
+
if (skipIfExists(filePath, ".prettierrc")) return;
|
|
265
|
+
|
|
266
|
+
writeFile(
|
|
267
|
+
filePath,
|
|
268
|
+
JSON.stringify(
|
|
269
|
+
{
|
|
270
|
+
semi: true,
|
|
271
|
+
singleQuote: false,
|
|
272
|
+
tabWidth: 2,
|
|
273
|
+
trailingComma: "all",
|
|
274
|
+
printWidth: 100,
|
|
275
|
+
plugins: ["prettier-plugin-tailwindcss"],
|
|
276
|
+
},
|
|
277
|
+
null,
|
|
278
|
+
2,
|
|
279
|
+
) + "\n",
|
|
280
|
+
".prettierrc",
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
199
284
|
/**
|
|
200
285
|
* Scaffold index.html at project root.
|
|
201
286
|
*/
|
|
@@ -803,6 +888,30 @@ export const settingsContract: PageContract = {
|
|
|
803
888
|
*/
|
|
804
889
|
export function scaffoldProApp(targetDir) {
|
|
805
890
|
ensureDir(join(targetDir, "src"));
|
|
891
|
+
ensureDir(join(targetDir, "src", "adapters"));
|
|
892
|
+
|
|
893
|
+
// Inertia adapter shims — re-export from pre-built mock bundle
|
|
894
|
+
// so usePage() shares the same MockPageContext as MockPageProvider.
|
|
895
|
+
const adapterReactPath = join(targetDir, "src", "adapters", "inertia-react.ts");
|
|
896
|
+
if (!skipIfExists(adapterReactPath, "src/adapters/inertia-react.ts")) {
|
|
897
|
+
writeFile(adapterReactPath, `/**
|
|
898
|
+
* Mock @inertiajs/react — re-exports from pre-built @middag-io/react/mock.
|
|
899
|
+
*
|
|
900
|
+
* This ensures usePage() reads from the same MockPageContext as
|
|
901
|
+
* MockPageProvider (both live in the pre-built ESM bundle).
|
|
902
|
+
*/
|
|
903
|
+
export { usePage, Head, Link, router } from "@middag-io/react/mock";
|
|
904
|
+
`, "src/adapters/inertia-react.ts");
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const adapterCorePath = join(targetDir, "src", "adapters", "inertia-core.ts");
|
|
908
|
+
if (!skipIfExists(adapterCorePath, "src/adapters/inertia-core.ts")) {
|
|
909
|
+
writeFile(adapterCorePath, `/**
|
|
910
|
+
* Mock @inertiajs/core — re-exports from pre-built @middag-io/react/mock.
|
|
911
|
+
*/
|
|
912
|
+
export { router, setMockNavigate } from "@middag-io/react/mock";
|
|
913
|
+
`, "src/adapters/inertia-core.ts");
|
|
914
|
+
}
|
|
806
915
|
|
|
807
916
|
const mainPath = join(targetDir, "src", "main.tsx");
|
|
808
917
|
if (!skipIfExists(mainPath, "src/main.tsx")) {
|
|
@@ -1140,6 +1249,301 @@ export function App() {
|
|
|
1140
1249
|
}
|
|
1141
1250
|
}
|
|
1142
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
|
+
|
|
1143
1547
|
// ── LEGACY (kept for backward compat, delegates to FREE) ────────────────
|
|
1144
1548
|
|
|
1145
1549
|
/** @deprecated Use scaffoldFreeApp + scaffoldFreeAdapters instead */
|