create-middag-ui 0.1.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.
Files changed (2) hide show
  1. package/index.js +327 -0
  2. package/package.json +27 -0
package/index.js ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-middag-ui — Bootstrap a MIDDAG React UI layer.
5
+ *
6
+ * Published on public npm so `npx create-middag-ui` works without
7
+ * GitHub Packages authentication. This package handles:
8
+ * 1. Host detection (Moodle / WordPress / Custom)
9
+ * 2. ui/ directory scaffolding
10
+ * 3. .npmrc configuration for @middag-io scope
11
+ * 4. Hello-world PageContract generation
12
+ * 5. npm install (triggers @middag-io/react download from GitHub Packages)
13
+ *
14
+ * After init, the remaining CLI commands (doctor, dev, add-block, upgrade)
15
+ * are available via: npx @middag-io/react <command>
16
+ */
17
+
18
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { createInterface } from "node:readline";
21
+
22
+ const HOSTS = {
23
+ moodle: { name: "Moodle", detect: "version.php", port: 5174 },
24
+ wordpress: { name: "WordPress", detect: "wp-config.php", port: 5175 },
25
+ custom: { name: "Custom", detect: null, port: 5176 },
26
+ };
27
+
28
+ // ── Helpers ────────────────────────────────────────────────────────────────
29
+
30
+ function ask(question) {
31
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
32
+ return new Promise((r) =>
33
+ rl.question(question, (a) => {
34
+ rl.close();
35
+ r(a.trim());
36
+ }),
37
+ );
38
+ }
39
+
40
+ function log(msg) {
41
+ console.log(`\x1b[36mcreate-middag-ui\x1b[0m ${msg}`);
42
+ }
43
+ function success(msg) {
44
+ console.log(` \x1b[32m✓\x1b[0m ${msg}`);
45
+ }
46
+ function warn(msg) {
47
+ console.log(` \x1b[33m⚠\x1b[0m ${msg}`);
48
+ }
49
+
50
+ function detectHost(cwd) {
51
+ for (const [key, host] of Object.entries(HOSTS)) {
52
+ if (host.detect && existsSync(join(cwd, host.detect))) return key;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ // ── Main ───────────────────────────────────────────────────────────────────
58
+
59
+ const cwd = process.cwd();
60
+ const startTime = Date.now();
61
+
62
+ log("Initializing MIDDAG React UI in your project...\n");
63
+
64
+ // Step 1: Detect host
65
+ let host = detectHost(cwd);
66
+ if (host) {
67
+ success(`Detected host: ${HOSTS[host].name} (found ${HOSTS[host].detect})`);
68
+ } else {
69
+ log("Could not auto-detect host platform.");
70
+ console.log(" 1) Moodle");
71
+ console.log(" 2) WordPress");
72
+ console.log(" 3) Custom / Other");
73
+ const choice = await ask("\n Select platform [1-3]: ");
74
+ host =
75
+ ["moodle", "wordpress", "custom"][parseInt(choice, 10) - 1] || "custom";
76
+ success(`Selected: ${HOSTS[host].name}`);
77
+ }
78
+
79
+ const uiDir = join(cwd, "ui");
80
+
81
+ // Step 2: Create ui/ directory
82
+ if (existsSync(uiDir)) {
83
+ warn("ui/ directory already exists — skipping scaffold");
84
+ } else {
85
+ mkdirSync(uiDir, { recursive: true });
86
+ success("Created ui/");
87
+ }
88
+
89
+ // Step 3: Configure .npmrc with GitHub Packages registry
90
+ const npmrcPath = join(uiDir, ".npmrc");
91
+ if (!existsSync(npmrcPath)) {
92
+ writeFileSync(
93
+ npmrcPath,
94
+ "@middag-io:registry=https://npm.pkg.github.com\n",
95
+ );
96
+ success("Created .npmrc with GitHub Packages registry");
97
+ warn(
98
+ "Add your GitHub token: echo '//npm.pkg.github.com/:_authToken=YOUR_TOKEN' >> ui/.npmrc",
99
+ );
100
+ } else {
101
+ success(".npmrc already exists");
102
+ }
103
+
104
+ // Step 4: Create package.json
105
+ const pkgPath = join(uiDir, "package.json");
106
+ if (!existsSync(pkgPath)) {
107
+ const projectName = cwd.split("/").pop() || "project";
108
+ const pkg = {
109
+ name: `${projectName}-ui`,
110
+ private: true,
111
+ type: "module",
112
+ scripts: {
113
+ dev: "vite --config vite.mock.config.ts",
114
+ "dev:mock": "vite --config vite.mock.config.ts",
115
+ build: "vite build",
116
+ "build:mock": "vite build --config vite.mock.config.ts",
117
+ typecheck: "tsc --noEmit",
118
+ lint: "eslint .",
119
+ "lint:fix": "eslint . --fix",
120
+ },
121
+ dependencies: {
122
+ "@middag-io/react": "^0.1.0",
123
+ },
124
+ devDependencies: {
125
+ "@types/react": "^19.0.0",
126
+ "@types/react-dom": "^19.0.0",
127
+ react: "^19.0.0",
128
+ "react-dom": "^19.0.0",
129
+ "@inertiajs/react": "^2.0.0",
130
+ "@inertiajs/core": "^2.0.0",
131
+ typescript: "^5.7.0",
132
+ vite: "^6.0.0",
133
+ "@vitejs/plugin-react": "^4.0.0",
134
+ tailwindcss: "^4.0.0",
135
+ "@tailwindcss/vite": "^4.0.0",
136
+ },
137
+ };
138
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
139
+ success("Created package.json");
140
+ }
141
+
142
+ // Step 5: Create tsconfig.json
143
+ const tsconfigPath = join(uiDir, "tsconfig.json");
144
+ if (!existsSync(tsconfigPath)) {
145
+ const tsconfig = {
146
+ compilerOptions: {
147
+ target: "ES2022",
148
+ module: "ESNext",
149
+ moduleResolution: "bundler",
150
+ jsx: "react-jsx",
151
+ strict: true,
152
+ noUnusedLocals: true,
153
+ noUnusedParameters: true,
154
+ skipLibCheck: true,
155
+ paths: { "@/*": ["./src/*"], "@mock/*": ["./mock/*"] },
156
+ baseUrl: ".",
157
+ },
158
+ include: ["src", "mock"],
159
+ };
160
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
161
+ success("Created tsconfig.json");
162
+ }
163
+
164
+ // Step 6: Create hello-world contract
165
+ const mockDir = join(uiDir, "mock");
166
+ mkdirSync(join(uiDir, "src"), { recursive: true });
167
+ mkdirSync(mockDir, { recursive: true });
168
+
169
+ const helloContractPath = join(mockDir, "hello-contract.ts");
170
+ if (!existsSync(helloContractPath)) {
171
+ writeFileSync(
172
+ helloContractPath,
173
+ `import type { PageContract } from "@middag-io/react";
174
+
175
+ /**
176
+ * Hello World contract — a minimal PageContract to verify your setup.
177
+ *
178
+ * This is what your ${HOSTS[host].name} backend will send via Inertia.
179
+ * Replace this with real data from your server.
180
+ */
181
+ export const helloContract: PageContract = {
182
+ shell: "admin",
183
+ meta: {
184
+ title: "Hello MIDDAG",
185
+ breadcrumbs: [
186
+ { label: "Home", href: "/" },
187
+ { label: "Hello", href: "/hello" },
188
+ ],
189
+ },
190
+ layout: {
191
+ template: "stack",
192
+ regions: {
193
+ content: [
194
+ {
195
+ key: "welcome_metrics",
196
+ type: "metric_card",
197
+ data: {
198
+ title: "Setup Complete",
199
+ value: "1",
200
+ subtitle: "@middag-io/react is working",
201
+ trend: { direction: "up", value: "100%", label: "Ready" },
202
+ },
203
+ },
204
+ {
205
+ key: "hello_table",
206
+ type: "dense_table",
207
+ data: {
208
+ title: "Example Data",
209
+ columns: [
210
+ { key: "id", label: "ID", sortable: true },
211
+ { key: "name", label: "Name", sortable: true },
212
+ { key: "status", label: "Status" },
213
+ ],
214
+ rows: [
215
+ { id: 1, name: "First item", status: "Active" },
216
+ { id: 2, name: "Second item", status: "Draft" },
217
+ { id: 3, name: "Third item", status: "Active" },
218
+ ],
219
+ pagination: { current: 1, total: 1, perPage: 10, totalRows: 3 },
220
+ },
221
+ },
222
+ ],
223
+ },
224
+ },
225
+ };
226
+ `,
227
+ );
228
+ success("Created mock/hello-contract.ts (hello-world PageContract)");
229
+ }
230
+
231
+ // Step 7: Create mock entry point
232
+ const mockMainPath = join(mockDir, "main.tsx");
233
+ if (!existsSync(mockMainPath)) {
234
+ writeFileSync(
235
+ mockMainPath,
236
+ `import { StrictMode } from "react";
237
+ import { createRoot } from "react-dom/client";
238
+ import { ContractPage, registerDefaults } from "@middag-io/react";
239
+ import "@middag-io/react/style.css";
240
+ import "./tailwind.css";
241
+ import { helloContract } from "./hello-contract";
242
+
243
+ // Register all default shells, layouts, and blocks
244
+ registerDefaults();
245
+
246
+ createRoot(document.getElementById("root")!).render(
247
+ <StrictMode>
248
+ <ContractPage contract={helloContract} />
249
+ </StrictMode>,
250
+ );
251
+ `,
252
+ );
253
+ success("Created mock/main.tsx");
254
+ }
255
+
256
+ const mockIndexPath = join(mockDir, "index.html");
257
+ if (!existsSync(mockIndexPath)) {
258
+ writeFileSync(
259
+ mockIndexPath,
260
+ `<!doctype html>
261
+ <html lang="en">
262
+ <head>
263
+ <meta charset="UTF-8" />
264
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
265
+ <title>MIDDAG React UI — Mock</title>
266
+ </head>
267
+ <body>
268
+ <div id="root"></div>
269
+ <script type="module" src="./main.tsx"></script>
270
+ </body>
271
+ </html>
272
+ `,
273
+ );
274
+ success("Created mock/index.html");
275
+ }
276
+
277
+ // Step 8: Create vite mock config
278
+ const viteConfigPath = join(uiDir, "vite.mock.config.ts");
279
+ if (!existsSync(viteConfigPath)) {
280
+ writeFileSync(
281
+ viteConfigPath,
282
+ `import { defineConfig } from "vite";
283
+ import react from "@vitejs/plugin-react";
284
+ import tailwindcss from "@tailwindcss/vite";
285
+ import { resolve } from "path";
286
+
287
+ export default defineConfig({
288
+ plugins: [react(), tailwindcss()],
289
+ root: "mock",
290
+ server: { port: ${HOSTS[host].port} },
291
+ resolve: {
292
+ alias: {
293
+ "@/": resolve(__dirname, "src") + "/",
294
+ "@mock/": resolve(__dirname, "mock") + "/",
295
+ },
296
+ },
297
+ });
298
+ `,
299
+ );
300
+ success("Created vite.mock.config.ts");
301
+ }
302
+
303
+ // Step 9: Create tailwind CSS
304
+ const cssPath = join(mockDir, "tailwind.css");
305
+ if (!existsSync(cssPath)) {
306
+ writeFileSync(cssPath, '@import "tailwindcss";\n');
307
+ success("Created mock/tailwind.css");
308
+ }
309
+
310
+ // Summary
311
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
312
+ console.log("");
313
+ log(`Done in ${elapsed}s!\n`);
314
+ console.log(" Next steps:");
315
+ console.log(" cd ui");
316
+ console.log(" npm install");
317
+ console.log(" npm run dev:mock\n");
318
+ console.log(` Your mock will run at http://localhost:${HOSTS[host].port}`);
319
+ console.log("");
320
+ console.log(" After install, more commands become available:");
321
+ console.log(" npx @middag-io/react doctor # validate setup");
322
+ console.log(" npx @middag-io/react dev # start mock server");
323
+ console.log(" npx @middag-io/react add-block <t> # scaffold a block");
324
+ console.log(" npx @middag-io/react upgrade # check for updates");
325
+ console.log("");
326
+ console.log(" Docs: https://docs.middag.io");
327
+ console.log("");
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "create-middag-ui",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Bootstrap a MIDDAG React UI layer in your Moodle or WordPress plugin",
6
+ "bin": {
7
+ "create-middag-ui": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js"
11
+ ],
12
+ "keywords": [
13
+ "middag",
14
+ "react",
15
+ "moodle",
16
+ "wordpress",
17
+ "inertia",
18
+ "admin-ui",
19
+ "create"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/middag-io/middag-react.git",
24
+ "directory": "packages/create-middag-ui"
25
+ },
26
+ "license": "MIT"
27
+ }