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.
- package/index.js +327 -0
- 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
|
+
}
|