@mounaji_npm/cli 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/bin/cli.js +42 -0
- package/bin/create.js +410 -0
- package/package.json +24 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mounaji CLI — entry point
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* mounaji create <name> Scaffold a new SaaS app
|
|
7
|
+
* mounaji add <module> Add a module page to an existing app
|
|
8
|
+
* mounaji tokens export Export current tokens as CSS/JSON
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const [,, command, ...args] = process.argv;
|
|
12
|
+
|
|
13
|
+
switch (command) {
|
|
14
|
+
case 'create':
|
|
15
|
+
process.argv = [...process.argv.slice(0, 2), ...args];
|
|
16
|
+
import('./create.js');
|
|
17
|
+
break;
|
|
18
|
+
|
|
19
|
+
case 'add':
|
|
20
|
+
console.log('\n@mounaji_npm/cli add — coming soon\n');
|
|
21
|
+
console.log('For now, manually add a module manifest to mn-config.js');
|
|
22
|
+
console.log('and create the page file in your app/ or src/pages/ directory.\n');
|
|
23
|
+
break;
|
|
24
|
+
|
|
25
|
+
case 'tokens':
|
|
26
|
+
console.log('\n@mounaji_npm/cli tokens — coming soon\n');
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
default:
|
|
30
|
+
console.log(`
|
|
31
|
+
@mounaji_npm/cli v0.1.0
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
npx @mounaji_npm/cli create <project-name> Scaffold a new SaaS app
|
|
35
|
+
npx @mounaji_npm/cli add <module-id> Add a module to an existing app (soon)
|
|
36
|
+
npx @mounaji_npm/cli tokens export Export design tokens (soon)
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
npx @mounaji_npm/cli create my-saas-app
|
|
40
|
+
create-mounaji-app my-saas-app
|
|
41
|
+
`);
|
|
42
|
+
}
|
package/bin/create.js
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-mounaji-app
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @mounaji_npm/cli create my-app
|
|
7
|
+
*
|
|
8
|
+
* Local linked binary:
|
|
9
|
+
* create-mounaji-app my-app
|
|
10
|
+
*
|
|
11
|
+
* Interactive prompts:
|
|
12
|
+
* 1. Project name
|
|
13
|
+
* 2. Framework (Next.js App Router | Vite + React)
|
|
14
|
+
* 3. Modules to include (multiselect)
|
|
15
|
+
* 4. Include admin controls / DevToolbar?
|
|
16
|
+
* 5. Auth provider (Firebase | Supabase | None)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
20
|
+
import { join, resolve } from 'path';
|
|
21
|
+
import { execSync } from 'child_process';
|
|
22
|
+
|
|
23
|
+
// We avoid hard-coding a dependency on 'prompts' for the scaffold itself
|
|
24
|
+
// so it works immediately without install. Use readline-based prompts instead.
|
|
25
|
+
import { createInterface } from 'readline';
|
|
26
|
+
|
|
27
|
+
const CYAN = '\x1b[36m';
|
|
28
|
+
const GREEN = '\x1b[32m';
|
|
29
|
+
const YELLOW = '\x1b[33m';
|
|
30
|
+
const BOLD = '\x1b[1m';
|
|
31
|
+
const RESET = '\x1b[0m';
|
|
32
|
+
|
|
33
|
+
const ALL_MODULES = [
|
|
34
|
+
{ id: 'home', label: 'Home', pkg: null },
|
|
35
|
+
{ id: 'dashboard', label: 'Dashboard', pkg: '@mounaji_npm/dashboard' },
|
|
36
|
+
{ id: 'chat', label: 'Chat', pkg: '@mounaji_npm/chat' },
|
|
37
|
+
{ id: 'assistants', label: 'Assistants', pkg: '@mounaji_npm/assistant' },
|
|
38
|
+
{ id: 'knowledge-base', label: 'Knowledge Base', pkg: '@mounaji_npm/knowledge-base' },
|
|
39
|
+
{ id: 'tasks', label: 'Tasks', pkg: null },
|
|
40
|
+
{ id: 'connections', label: 'Connections', pkg: null },
|
|
41
|
+
{ id: 'settings', label: 'Settings', pkg: null },
|
|
42
|
+
{ id: 'inventory', label: 'Inventory', pkg: null },
|
|
43
|
+
{ id: 'contacts', label: 'Contacts', pkg: null },
|
|
44
|
+
{ id: 'workflows', label: 'Workflows', pkg: null },
|
|
45
|
+
{ id: 'platforms', label: 'Platforms', pkg: null },
|
|
46
|
+
{ id: 'pricing', label: 'Pricing', pkg: null },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
async function prompt(question) {
|
|
50
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
51
|
+
return new Promise(resolve => {
|
|
52
|
+
rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function multiSelect(items, message) {
|
|
57
|
+
console.log(`\n${CYAN}${message}${RESET}`);
|
|
58
|
+
items.forEach((item, i) => console.log(` ${YELLOW}${i + 1}${RESET}. ${item.label}`));
|
|
59
|
+
console.log(` ${YELLOW}a${RESET}. All modules`);
|
|
60
|
+
const answer = await prompt(`\nEnter numbers separated by commas (e.g. 1,2,3) or 'a' for all: `);
|
|
61
|
+
if (answer.toLowerCase() === 'a') return items;
|
|
62
|
+
const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < items.length);
|
|
63
|
+
return indices.map(i => items[i]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const args = process.argv.slice(2);
|
|
68
|
+
let projectName = args[0];
|
|
69
|
+
|
|
70
|
+
console.log(`\n${BOLD}${CYAN}◆ create-mounaji-app${RESET}\n`);
|
|
71
|
+
console.log('Scaffolds a full SaaS app powered by @mounaji_npm/* components.\n');
|
|
72
|
+
|
|
73
|
+
if (!projectName) {
|
|
74
|
+
projectName = await prompt(`${CYAN}Project name${RESET} › `);
|
|
75
|
+
}
|
|
76
|
+
if (!projectName) { console.error('Project name is required.'); process.exit(1); }
|
|
77
|
+
|
|
78
|
+
const framework = await prompt(`${CYAN}Framework${RESET} [nextjs/vite] (default: nextjs) › `);
|
|
79
|
+
const useNextjs = !framework || framework.toLowerCase().startsWith('n');
|
|
80
|
+
|
|
81
|
+
const selectedModules = await multiSelect(
|
|
82
|
+
ALL_MODULES,
|
|
83
|
+
'Select modules to include:'
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const adminAnswer = await prompt(`\n${CYAN}Include DevToolbar + TokenEditor?${RESET} [y/n] (default: y) › `);
|
|
87
|
+
const includeAdmin = !adminAnswer || adminAnswer.toLowerCase() !== 'n';
|
|
88
|
+
|
|
89
|
+
// Derive output directory
|
|
90
|
+
const outDir = resolve(process.cwd(), projectName);
|
|
91
|
+
if (existsSync(outDir)) {
|
|
92
|
+
console.error(`\n${YELLOW}Directory "${projectName}" already exists.${RESET}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`\n${CYAN}Creating project...${RESET}`);
|
|
97
|
+
mkdirSync(outDir, { recursive: true });
|
|
98
|
+
|
|
99
|
+
// Derive package deps
|
|
100
|
+
const corePkgs = ['@mounaji_npm/tokens', '@mounaji_npm/ui', '@mounaji_npm/saas-template'];
|
|
101
|
+
const modulePkgs = [...new Set(selectedModules.map(m => m.pkg).filter(Boolean))];
|
|
102
|
+
if (includeAdmin) corePkgs.push('@mounaji_npm/admin-controls');
|
|
103
|
+
|
|
104
|
+
if (useNextjs) {
|
|
105
|
+
writeNextjsApp(outDir, projectName, selectedModules, includeAdmin, [...corePkgs, ...modulePkgs]);
|
|
106
|
+
} else {
|
|
107
|
+
writeViteApp(outDir, projectName, selectedModules, includeAdmin, [...corePkgs, ...modulePkgs]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`\n${GREEN}✓ Project created at ${outDir}${RESET}`);
|
|
111
|
+
console.log(`\n${BOLD}Next steps:${RESET}`);
|
|
112
|
+
console.log(` cd ${projectName}`);
|
|
113
|
+
console.log(` npm install`);
|
|
114
|
+
console.log(` npm run dev\n`);
|
|
115
|
+
console.log(`${CYAN}Docs: https://github.com/mounaji-studio/mounaji-platform/tree/main/npm_components${RESET}\n`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Next.js scaffolder ───────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
function writeNextjsApp(outDir, name, modules, includeAdmin, deps) {
|
|
121
|
+
// package.json
|
|
122
|
+
writeJSON(join(outDir, 'package.json'), {
|
|
123
|
+
name,
|
|
124
|
+
version: '0.1.0',
|
|
125
|
+
private: true,
|
|
126
|
+
scripts: {
|
|
127
|
+
dev: 'next dev',
|
|
128
|
+
build: 'next build',
|
|
129
|
+
start: 'next start',
|
|
130
|
+
lint: 'next lint',
|
|
131
|
+
},
|
|
132
|
+
dependencies: Object.fromEntries([
|
|
133
|
+
['next', '^15.0.0'],
|
|
134
|
+
['react', '^19.0.0'],
|
|
135
|
+
['react-dom', '^19.0.0'],
|
|
136
|
+
...deps.map(d => [d, 'latest']),
|
|
137
|
+
]),
|
|
138
|
+
devDependencies: {
|
|
139
|
+
eslint: '^9.0.0',
|
|
140
|
+
'eslint-config-next': '^15.0.0',
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// next.config.js
|
|
145
|
+
writeFile(join(outDir, 'next.config.js'), `/** @type {import('next').NextConfig} */\nconst nextConfig = {};\nexport default nextConfig;\n`);
|
|
146
|
+
|
|
147
|
+
// app/layout.js
|
|
148
|
+
mkdirSync(join(outDir, 'app'), { recursive: true });
|
|
149
|
+
writeFile(join(outDir, 'app', 'layout.js'), generateNextLayout(modules, includeAdmin, name));
|
|
150
|
+
|
|
151
|
+
// app/page.js (home)
|
|
152
|
+
writeFile(join(outDir, 'app', 'page.js'), generateHomePage(name));
|
|
153
|
+
|
|
154
|
+
// Selected module pages
|
|
155
|
+
modules.filter(m => m.id !== 'home').forEach(m => {
|
|
156
|
+
const dir = join(outDir, 'app', m.id);
|
|
157
|
+
mkdirSync(dir, { recursive: true });
|
|
158
|
+
writeFile(join(dir, 'page.js'), generateModulePage(m));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// mn-config.js
|
|
162
|
+
writeFile(join(outDir, 'mn-config.js'), generateMnConfig(modules, name));
|
|
163
|
+
|
|
164
|
+
// .env.local
|
|
165
|
+
writeFile(join(outDir, '.env.local'), [
|
|
166
|
+
'# Backend API',
|
|
167
|
+
'NEXT_PUBLIC_BACKEND_URL=http://localhost:5000',
|
|
168
|
+
'',
|
|
169
|
+
'# Firebase (optional)',
|
|
170
|
+
'# NEXT_PUBLIC_FIREBASE_API_KEY=',
|
|
171
|
+
'# NEXT_PUBLIC_FIREBASE_PROJECT_ID=',
|
|
172
|
+
].join('\n'));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Vite scaffolder ──────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
function writeViteApp(outDir, name, modules, includeAdmin, deps) {
|
|
178
|
+
writeJSON(join(outDir, 'package.json'), {
|
|
179
|
+
name,
|
|
180
|
+
version: '0.1.0',
|
|
181
|
+
private: true,
|
|
182
|
+
type: 'module',
|
|
183
|
+
scripts: { dev: 'vite', build: 'vite build', preview: 'vite preview' },
|
|
184
|
+
dependencies: Object.fromEntries([
|
|
185
|
+
['react', '^19.0.0'],
|
|
186
|
+
['react-dom', '^19.0.0'],
|
|
187
|
+
['react-router-dom', '^6.0.0'],
|
|
188
|
+
...deps.map(d => [d, 'latest']),
|
|
189
|
+
]),
|
|
190
|
+
devDependencies: {
|
|
191
|
+
'@vitejs/plugin-react': '^4.3.4',
|
|
192
|
+
vite: '^6.0.0',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
writeFile(join(outDir, 'vite.config.js'), `import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nexport default defineConfig({ plugins: [react()] });\n`);
|
|
197
|
+
|
|
198
|
+
mkdirSync(join(outDir, 'src'), { recursive: true });
|
|
199
|
+
writeFile(join(outDir, 'index.html'), generateViteHtml(name));
|
|
200
|
+
writeFile(join(outDir, 'src', 'main.jsx'), generateViteMain(modules, includeAdmin, name));
|
|
201
|
+
writeFile(join(outDir, 'src', 'App.jsx'), generateViteApp(modules, name));
|
|
202
|
+
writeFile(join(outDir, 'mn-config.js'), generateMnConfig(modules, name));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── Template generators ──────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
function generateNextLayout(modules, includeAdmin, name) {
|
|
208
|
+
const moduleImports = modules.map(m => ` ${toConstName(m.id)}_MODULE`).join(',\n');
|
|
209
|
+
return `'use client';
|
|
210
|
+
/**
|
|
211
|
+
* Root layout — generated by create-mounaji-app
|
|
212
|
+
* Edit mn-config.js to add/remove modules, change branding, or customize tokens.
|
|
213
|
+
*/
|
|
214
|
+
import { AppShell } from '@mounaji_npm/saas-template';
|
|
215
|
+
import {
|
|
216
|
+
${moduleImports},
|
|
217
|
+
} from '@mounaji_npm/saas-template/modules';
|
|
218
|
+
${includeAdmin ? "import { DevToolbar } from '@mounaji_npm/admin-controls';" : ''}
|
|
219
|
+
import { usePathname } from 'next/navigation';
|
|
220
|
+
import Link from 'next/link';
|
|
221
|
+
import { MN_CONFIG } from '../mn-config.js';
|
|
222
|
+
|
|
223
|
+
const MODULES = [
|
|
224
|
+
${modules.map(m => ` ${toConstName(m.id)}_MODULE,`).join('\n')}
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
function RootLayoutInner({ children }) {
|
|
228
|
+
const pathname = usePathname();
|
|
229
|
+
return (
|
|
230
|
+
<AppShell
|
|
231
|
+
modules={MODULES}
|
|
232
|
+
activePath={pathname}
|
|
233
|
+
LinkComponent={Link}
|
|
234
|
+
logo={<span style={{ fontWeight: 700, fontSize: '0.9375rem', color: 'var(--mn-text-primary-dark, #F0F4FF)' }}>{MN_CONFIG.name}</span>}
|
|
235
|
+
tokens={MN_CONFIG.tokens}
|
|
236
|
+
>
|
|
237
|
+
{children}
|
|
238
|
+
${includeAdmin ? '{process.env.NODE_ENV !== \'production\' && <DevToolbar />}' : ''}
|
|
239
|
+
</AppShell>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export default function RootLayout({ children }) {
|
|
244
|
+
return (
|
|
245
|
+
<html lang="en" suppressHydrationWarning>
|
|
246
|
+
<body style={{ margin: 0 }}>
|
|
247
|
+
<RootLayoutInner>{children}</RootLayoutInner>
|
|
248
|
+
</body>
|
|
249
|
+
</html>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function generateHomePage(name) {
|
|
256
|
+
return `export default function HomePage() {
|
|
257
|
+
return (
|
|
258
|
+
<div style={{ padding: 32 }}>
|
|
259
|
+
<h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700, color: 'var(--mn-text-primary-dark, #F0F4FF)' }}>
|
|
260
|
+
Welcome to ${name}
|
|
261
|
+
</h1>
|
|
262
|
+
<p style={{ color: 'var(--mn-text-secondary-dark, #94A3B8)', marginTop: 8 }}>
|
|
263
|
+
Your SaaS platform is ready. Start building.
|
|
264
|
+
</p>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function generateModulePage(m) {
|
|
272
|
+
const compName = toPascalCase(m.id) + 'Page';
|
|
273
|
+
return `// ${m.label} page — edit this file to build out the ${m.label} module
|
|
274
|
+
export default function ${compName}() {
|
|
275
|
+
return (
|
|
276
|
+
<div style={{ padding: 32, fontFamily: 'var(--mn-font-family, inherit)' }}>
|
|
277
|
+
<h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700, color: 'var(--mn-text-primary-dark, #F0F4FF)' }}>
|
|
278
|
+
${m.label}
|
|
279
|
+
</h1>
|
|
280
|
+
<p style={{ color: 'var(--mn-text-secondary-dark, #94A3B8)', marginTop: 8 }}>
|
|
281
|
+
Build your ${m.label} page here.
|
|
282
|
+
</p>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function generateMnConfig(modules, name) {
|
|
290
|
+
return `/**
|
|
291
|
+
* mn-config.js — Mounaji Platform Configuration
|
|
292
|
+
*
|
|
293
|
+
* Edit this file to:
|
|
294
|
+
* - Change branding (name, logo)
|
|
295
|
+
* - Add/remove nav modules
|
|
296
|
+
* - Override design tokens
|
|
297
|
+
* - Add custom page modules
|
|
298
|
+
*
|
|
299
|
+
* Adding a new page module:
|
|
300
|
+
* 1. Create app/my-page/page.js (Next.js) or src/pages/MyPage.jsx (Vite)
|
|
301
|
+
* 2. Add an entry to CUSTOM_MODULES below
|
|
302
|
+
* 3. Import and add it to AppShell's modules prop in layout.js
|
|
303
|
+
*/
|
|
304
|
+
|
|
305
|
+
export const MN_CONFIG = {
|
|
306
|
+
// ── Branding ──────────────────────────────────────────────────────────────
|
|
307
|
+
name: '${name}',
|
|
308
|
+
logoUrl: null, // set to '/logo.png' or a URL
|
|
309
|
+
|
|
310
|
+
// ── Design Token Overrides ───────────────────────────────────────────────
|
|
311
|
+
// These override @mounaji_npm/tokens DEFAULT_TOKENS.
|
|
312
|
+
// All values are injected as CSS variables at runtime.
|
|
313
|
+
tokens: {
|
|
314
|
+
// colorPrimary: '#7C3AED', // change brand color
|
|
315
|
+
// radiusLg: '1rem', // rounder corners
|
|
316
|
+
// fontFamily: '"Outfit", sans-serif',
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// ── Custom page modules ───────────────────────────────────────────────────
|
|
320
|
+
// Add entries here, then import and add to modules array in layout.js
|
|
321
|
+
customModules: [
|
|
322
|
+
// {
|
|
323
|
+
// id: 'analytics',
|
|
324
|
+
// label: 'Analytics',
|
|
325
|
+
// icon: '📊',
|
|
326
|
+
// path: '/analytics',
|
|
327
|
+
// section: 'Workspace',
|
|
328
|
+
// order: 10,
|
|
329
|
+
// },
|
|
330
|
+
],
|
|
331
|
+
};
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function generateViteHtml(name) {
|
|
336
|
+
return `<!DOCTYPE html>
|
|
337
|
+
<html lang="en">
|
|
338
|
+
<head>
|
|
339
|
+
<meta charset="UTF-8" />
|
|
340
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
341
|
+
<title>${name}</title>
|
|
342
|
+
</head>
|
|
343
|
+
<body style="margin:0">
|
|
344
|
+
<div id="root"></div>
|
|
345
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
346
|
+
</body>
|
|
347
|
+
</html>
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function generateViteMain(modules, includeAdmin, name) {
|
|
352
|
+
return `import { StrictMode } from 'react';
|
|
353
|
+
import { createRoot } from 'react-dom/client';
|
|
354
|
+
import App from './App.jsx';
|
|
355
|
+
|
|
356
|
+
createRoot(document.getElementById('root')).render(
|
|
357
|
+
<StrictMode>
|
|
358
|
+
<App />
|
|
359
|
+
</StrictMode>
|
|
360
|
+
);
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function generateViteApp(modules, name) {
|
|
365
|
+
const routes = modules.map(m => ` { path: '${m.id === 'home' ? '/' : '/' + m.id}', element: <div style={{padding:32}}><h1 style={{color:'var(--mn-text-primary-dark,#F0F4FF)'}}>${m.label}</h1></div> }`).join(',\n');
|
|
366
|
+
return `import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
|
|
367
|
+
import { AppShell } from '@mounaji_npm/saas-template';
|
|
368
|
+
import { ${modules.map(m => toConstName(m.id) + '_MODULE').join(', ')} } from '@mounaji_npm/saas-template/modules';
|
|
369
|
+
import { MN_CONFIG } from '../mn-config.js';
|
|
370
|
+
|
|
371
|
+
const MODULES = [${modules.map(m => toConstName(m.id) + '_MODULE').join(', ')}];
|
|
372
|
+
|
|
373
|
+
function AppInner() {
|
|
374
|
+
const { pathname } = useLocation();
|
|
375
|
+
return (
|
|
376
|
+
<AppShell modules={MODULES} activePath={pathname} tokens={MN_CONFIG.tokens}
|
|
377
|
+
logo={<span style={{fontWeight:700,color:'var(--mn-text-primary-dark,#F0F4FF)'}}>{MN_CONFIG.name}</span>}>
|
|
378
|
+
<Routes>
|
|
379
|
+
${routes}
|
|
380
|
+
</Routes>
|
|
381
|
+
</AppShell>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export default function App() {
|
|
386
|
+
return <BrowserRouter><AppInner /></BrowserRouter>;
|
|
387
|
+
}
|
|
388
|
+
`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ─── Utils ────────────────────────────────────────────────────────────────────
|
|
392
|
+
|
|
393
|
+
function toConstName(id) {
|
|
394
|
+
return id.toUpperCase().replace(/-/g, '_');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function toPascalCase(id) {
|
|
398
|
+
return id.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function writeFile(path, content) {
|
|
402
|
+
writeFileSync(path, content, 'utf8');
|
|
403
|
+
console.log(` ${GREEN}+${RESET} ${path.replace(process.cwd(), '.')}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function writeJSON(path, obj) {
|
|
407
|
+
writeFile(path, JSON.stringify(obj, null, 2) + '\n');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
main().catch(err => { console.error(err); process.exit(1); });
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mounaji_npm/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to scaffold a new Mounaji SaaS app — full Next.js template with selectable modules",
|
|
5
|
+
"keywords": ["cli", "scaffold", "saas", "nextjs", "mounaji", "create-app"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org/"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"create-mounaji-app": "./bin/create.js",
|
|
14
|
+
"mounaji": "./bin/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"files": ["bin", "templates"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node bin/create.js --help"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"prompts": "^2.4.2",
|
|
22
|
+
"kleur": "^4.1.5"
|
|
23
|
+
}
|
|
24
|
+
}
|