@mounaji_npm/cli 0.1.0 → 0.1.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.
Files changed (3) hide show
  1. package/bin/cli.js +2 -2
  2. package/bin/create.js +277 -30
  3. package/package.json +14 -5
package/bin/cli.js CHANGED
@@ -28,7 +28,7 @@ switch (command) {
28
28
 
29
29
  default:
30
30
  console.log(`
31
- @mounaji_npm/cli v0.1.0
31
+ @mounaji_npm/cli v0.1.2
32
32
 
33
33
  Usage:
34
34
  npx @mounaji_npm/cli create <project-name> Scaffold a new SaaS app
@@ -37,6 +37,6 @@ Usage:
37
37
 
38
38
  Examples:
39
39
  npx @mounaji_npm/cli create my-saas-app
40
- create-mounaji-app my-saas-app
40
+ mounaji create my-saas-app
41
41
  `);
42
42
  }
package/bin/create.js CHANGED
@@ -5,8 +5,8 @@
5
5
  * Usage:
6
6
  * npx @mounaji_npm/cli create my-app
7
7
  *
8
- * Local linked binary:
9
- * create-mounaji-app my-app
8
+ * Local installed binary:
9
+ * mounaji create my-app
10
10
  *
11
11
  * Interactive prompts:
12
12
  * 1. Project name
@@ -46,6 +46,18 @@ const ALL_MODULES = [
46
46
  { id: 'pricing', label: 'Pricing', pkg: null },
47
47
  ];
48
48
 
49
+ const PACKAGE_VERSIONS = {
50
+ '@mounaji_npm/admin-controls': '^0.1.1',
51
+ '@mounaji_npm/assistant': '^0.1.1',
52
+ '@mounaji_npm/auth': '^0.1.2',
53
+ '@mounaji_npm/chat': '^0.1.1',
54
+ '@mounaji_npm/dashboard': '^0.1.1',
55
+ '@mounaji_npm/knowledge-base': '^0.1.1',
56
+ '@mounaji_npm/saas-template': '^0.1.2',
57
+ '@mounaji_npm/tokens': '^0.1.1',
58
+ '@mounaji_npm/ui': '^0.1.1',
59
+ };
60
+
49
61
  async function prompt(question) {
50
62
  const rl = createInterface({ input: process.stdin, output: process.stdout });
51
63
  return new Promise(resolve => {
@@ -97,7 +109,7 @@ async function main() {
97
109
  mkdirSync(outDir, { recursive: true });
98
110
 
99
111
  // Derive package deps
100
- const corePkgs = ['@mounaji_npm/tokens', '@mounaji_npm/ui', '@mounaji_npm/saas-template'];
112
+ const corePkgs = ['@mounaji_npm/tokens', '@mounaji_npm/ui', '@mounaji_npm/saas-template', '@mounaji_npm/auth'];
101
113
  const modulePkgs = [...new Set(selectedModules.map(m => m.pkg).filter(Boolean))];
102
114
  if (includeAdmin) corePkgs.push('@mounaji_npm/admin-controls');
103
115
 
@@ -123,6 +135,7 @@ function writeNextjsApp(outDir, name, modules, includeAdmin, deps) {
123
135
  name,
124
136
  version: '0.1.0',
125
137
  private: true,
138
+ type: 'module',
126
139
  scripts: {
127
140
  dev: 'next dev',
128
141
  build: 'next build',
@@ -133,7 +146,7 @@ function writeNextjsApp(outDir, name, modules, includeAdmin, deps) {
133
146
  ['next', '^15.0.0'],
134
147
  ['react', '^19.0.0'],
135
148
  ['react-dom', '^19.0.0'],
136
- ...deps.map(d => [d, 'latest']),
149
+ ...deps.map(d => [d, resolveDepVersion(d)]),
137
150
  ]),
138
151
  devDependencies: {
139
152
  eslint: '^9.0.0',
@@ -147,6 +160,8 @@ function writeNextjsApp(outDir, name, modules, includeAdmin, deps) {
147
160
  // app/layout.js
148
161
  mkdirSync(join(outDir, 'app'), { recursive: true });
149
162
  writeFile(join(outDir, 'app', 'layout.js'), generateNextLayout(modules, includeAdmin, name));
163
+ mkdirSync(join(outDir, 'lib'), { recursive: true });
164
+ writeFile(join(outDir, 'lib', 'demoAuthAdapter.js'), generateDemoAuthAdapter());
150
165
 
151
166
  // app/page.js (home)
152
167
  writeFile(join(outDir, 'app', 'page.js'), generateHomePage(name));
@@ -185,7 +200,7 @@ function writeViteApp(outDir, name, modules, includeAdmin, deps) {
185
200
  ['react', '^19.0.0'],
186
201
  ['react-dom', '^19.0.0'],
187
202
  ['react-router-dom', '^6.0.0'],
188
- ...deps.map(d => [d, 'latest']),
203
+ ...deps.map(d => [d, resolveDepVersion(d)]),
189
204
  ]),
190
205
  devDependencies: {
191
206
  '@vitejs/plugin-react': '^4.3.4',
@@ -196,9 +211,11 @@ function writeViteApp(outDir, name, modules, includeAdmin, deps) {
196
211
  writeFile(join(outDir, 'vite.config.js'), `import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nexport default defineConfig({ plugins: [react()] });\n`);
197
212
 
198
213
  mkdirSync(join(outDir, 'src'), { recursive: true });
214
+ mkdirSync(join(outDir, 'src', 'lib'), { recursive: true });
199
215
  writeFile(join(outDir, 'index.html'), generateViteHtml(name));
200
216
  writeFile(join(outDir, 'src', 'main.jsx'), generateViteMain(modules, includeAdmin, name));
201
- writeFile(join(outDir, 'src', 'App.jsx'), generateViteApp(modules, name));
217
+ writeFile(join(outDir, 'src', 'App.jsx'), generateViteApp(modules, includeAdmin, name));
218
+ writeFile(join(outDir, 'src', 'lib', 'demoAuthAdapter.js'), generateDemoAuthAdapter());
202
219
  writeFile(join(outDir, 'mn-config.js'), generateMnConfig(modules, name));
203
220
  }
204
221
 
@@ -210,33 +227,72 @@ function generateNextLayout(modules, includeAdmin, name) {
210
227
  /**
211
228
  * Root layout — generated by create-mounaji-app
212
229
  * Edit mn-config.js to add/remove modules, change branding, or customize tokens.
230
+ * Replace demoAuthAdapter with a real provider such as
231
+ * createFirebaseAdapter from @mounaji_npm/auth/firebase when ready.
213
232
  */
233
+ import { useEffect, useState } from 'react';
214
234
  import { AppShell } from '@mounaji_npm/saas-template';
215
235
  import {
216
236
  ${moduleImports},
217
237
  } from '@mounaji_npm/saas-template/modules';
218
238
  ${includeAdmin ? "import { DevToolbar } from '@mounaji_npm/admin-controls';" : ''}
219
- import { usePathname } from 'next/navigation';
239
+ import { AuthProvider, LoginButton } from '@mounaji_npm/auth';
240
+ import { usePathname, useRouter } from 'next/navigation';
220
241
  import Link from 'next/link';
221
242
  import { MN_CONFIG } from '../mn-config.js';
243
+ import { createDemoAuthAdapter } from '../lib/demoAuthAdapter.js';
222
244
 
223
245
  const MODULES = [
224
246
  ${modules.map(m => ` ${toConstName(m.id)}_MODULE,`).join('\n')}
225
247
  ];
226
248
 
249
+ const THEME_STORAGE_KEY = 'mn_theme_mode';
250
+
227
251
  function RootLayoutInner({ children }) {
228
252
  const pathname = usePathname();
253
+ const router = useRouter();
254
+ const [adapter] = useState(() => createDemoAuthAdapter());
255
+ const [isDark, setIsDark] = useState(true);
256
+
257
+ useEffect(() => {
258
+ const savedTheme = window.localStorage.getItem(THEME_STORAGE_KEY);
259
+ if (savedTheme === 'light') {
260
+ setIsDark(false);
261
+ }
262
+ }, []);
263
+
264
+ useEffect(() => {
265
+ window.localStorage.setItem(THEME_STORAGE_KEY, isDark ? 'dark' : 'light');
266
+ }, [isDark]);
267
+
229
268
  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>
269
+ <AuthProvider adapter={adapter}>
270
+ <AppShell
271
+ modules={MODULES}
272
+ activePath={pathname}
273
+ LinkComponent={Link}
274
+ logo={(
275
+ <span
276
+ style={{
277
+ fontWeight: 700,
278
+ fontSize: '0.9375rem',
279
+ color: isDark
280
+ ? 'var(--mn-text-primary-dark, #F0F4FF)'
281
+ : 'var(--mn-text-primary-light, #1C1915)',
282
+ }}
283
+ >
284
+ {MN_CONFIG.name}
285
+ </span>
286
+ )}
287
+ tokens={MN_CONFIG.tokens}
288
+ isDark={isDark}
289
+ onThemeToggle={() => setIsDark((value) => !value)}
290
+ topNavRight={<LoginButton isDark={isDark} onNavigate={(path) => router.push(path)} />}
291
+ >
292
+ {children}
293
+ ${includeAdmin ? '{process.env.NODE_ENV !== \'production\' && <DevToolbar />}' : ''}
294
+ </AppShell>
295
+ </AuthProvider>
240
296
  );
241
297
  }
242
298
 
@@ -256,12 +312,18 @@ function generateHomePage(name) {
256
312
  return `export default function HomePage() {
257
313
  return (
258
314
  <div style={{ padding: 32 }}>
259
- <h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700, color: 'var(--mn-text-primary-dark, #F0F4FF)' }}>
315
+ <h1 className="mn-page-title" style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>
260
316
  Welcome to ${name}
261
317
  </h1>
262
- <p style={{ color: 'var(--mn-text-secondary-dark, #94A3B8)', marginTop: 8 }}>
318
+ <p className="mn-page-copy" style={{ marginTop: 8 }}>
263
319
  Your SaaS platform is ready. Start building.
264
320
  </p>
321
+ <style>{\`
322
+ .mn-page-title { color: var(--mn-text-primary-dark, #F0F4FF); }
323
+ .mn-page-copy { color: var(--mn-text-secondary-dark, #94A3B8); }
324
+ [data-mn-theme="light"] .mn-page-title { color: var(--mn-text-primary-light, #1C1915); }
325
+ [data-mn-theme="light"] .mn-page-copy { color: var(--mn-text-secondary-light, #47413C); }
326
+ \`}</style>
265
327
  </div>
266
328
  );
267
329
  }
@@ -274,12 +336,18 @@ function generateModulePage(m) {
274
336
  export default function ${compName}() {
275
337
  return (
276
338
  <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)' }}>
339
+ <h1 className="mn-page-title" style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>
278
340
  ${m.label}
279
341
  </h1>
280
- <p style={{ color: 'var(--mn-text-secondary-dark, #94A3B8)', marginTop: 8 }}>
342
+ <p className="mn-page-copy" style={{ marginTop: 8 }}>
281
343
  Build your ${m.label} page here.
282
344
  </p>
345
+ <style>{\`
346
+ .mn-page-title { color: var(--mn-text-primary-dark, #F0F4FF); }
347
+ .mn-page-copy { color: var(--mn-text-secondary-dark, #94A3B8); }
348
+ [data-mn-theme="light"] .mn-page-title { color: var(--mn-text-primary-light, #1C1915); }
349
+ [data-mn-theme="light"] .mn-page-copy { color: var(--mn-text-secondary-light, #47413C); }
350
+ \`}</style>
283
351
  </div>
284
352
  );
285
353
  }
@@ -361,24 +429,93 @@ createRoot(document.getElementById('root')).render(
361
429
  `;
362
430
  }
363
431
 
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';
432
+ function generateViteApp(modules, includeAdmin, name) {
433
+ const routes = modules.map(m => ` { path: '${m.id === 'home' ? '/' : '/' + m.id}', title: '${m.label}', description: '${m.id === 'home' ? 'Your SaaS platform is ready. Start building.' : `Build your ${m.label} page here.`}' }`).join(',\n');
434
+ return `import { useEffect, useState } from 'react';
435
+ import { BrowserRouter, Routes, Route, useLocation, useNavigate } from 'react-router-dom';
367
436
  import { AppShell } from '@mounaji_npm/saas-template';
368
437
  import { ${modules.map(m => toConstName(m.id) + '_MODULE').join(', ')} } from '@mounaji_npm/saas-template/modules';
438
+ import { AuthProvider, LoginButton } from '@mounaji_npm/auth';
439
+ ${includeAdmin ? "import { DevToolbar } from '@mounaji_npm/admin-controls';" : ''}
369
440
  import { MN_CONFIG } from '../mn-config.js';
441
+ import { createDemoAuthAdapter } from './lib/demoAuthAdapter.js';
370
442
 
371
443
  const MODULES = [${modules.map(m => toConstName(m.id) + '_MODULE').join(', ')}];
444
+ const ROUTES = [
445
+ ${routes}
446
+ ];
447
+ const THEME_STORAGE_KEY = 'mn_theme_mode';
448
+
449
+ function PageFrame({ title, description }) {
450
+ return (
451
+ <div style={{ padding: 32, fontFamily: 'var(--mn-font-family, inherit)' }}>
452
+ <h1 className="mn-page-title" style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>
453
+ {title}
454
+ </h1>
455
+ <p className="mn-page-copy" style={{ marginTop: 8 }}>
456
+ {description}
457
+ </p>
458
+ <style>{\`
459
+ .mn-page-title { color: var(--mn-text-primary-dark, #F0F4FF); }
460
+ .mn-page-copy { color: var(--mn-text-secondary-dark, #94A3B8); }
461
+ [data-mn-theme="light"] .mn-page-title { color: var(--mn-text-primary-light, #1C1915); }
462
+ [data-mn-theme="light"] .mn-page-copy { color: var(--mn-text-secondary-light, #47413C); }
463
+ \`}</style>
464
+ </div>
465
+ );
466
+ }
372
467
 
373
468
  function AppInner() {
374
469
  const { pathname } = useLocation();
470
+ const navigate = useNavigate();
471
+ const [adapter] = useState(() => createDemoAuthAdapter());
472
+ const [isDark, setIsDark] = useState(true);
473
+
474
+ useEffect(() => {
475
+ const savedTheme = window.localStorage.getItem(THEME_STORAGE_KEY);
476
+ if (savedTheme === 'light') {
477
+ setIsDark(false);
478
+ }
479
+ }, []);
480
+
481
+ useEffect(() => {
482
+ window.localStorage.setItem(THEME_STORAGE_KEY, isDark ? 'dark' : 'light');
483
+ }, [isDark]);
484
+
375
485
  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>
486
+ <AuthProvider adapter={adapter}>
487
+ <AppShell
488
+ modules={MODULES}
489
+ activePath={pathname}
490
+ tokens={MN_CONFIG.tokens}
491
+ isDark={isDark}
492
+ onThemeToggle={() => setIsDark((value) => !value)}
493
+ topNavRight={<LoginButton isDark={isDark} onNavigate={(path) => navigate(path)} />}
494
+ logo={
495
+ <span
496
+ style={{
497
+ fontWeight: 700,
498
+ color: isDark
499
+ ? 'var(--mn-text-primary-dark, #F0F4FF)'
500
+ : 'var(--mn-text-primary-light, #1C1915)',
501
+ }}
502
+ >
503
+ {MN_CONFIG.name}
504
+ </span>
505
+ }
506
+ >
507
+ <Routes>
508
+ {ROUTES.map((route) => (
509
+ <Route
510
+ key={route.path}
511
+ path={route.path}
512
+ element={<PageFrame title={route.title} description={route.description} />}
513
+ />
514
+ ))}
515
+ </Routes>
516
+ ${includeAdmin ? '{import.meta.env.DEV && <DevToolbar />}' : ''}
517
+ </AppShell>
518
+ </AuthProvider>
382
519
  );
383
520
  }
384
521
 
@@ -388,6 +525,112 @@ export default function App() {
388
525
  `;
389
526
  }
390
527
 
528
+ function generateDemoAuthAdapter() {
529
+ return `const USER_STORAGE_KEY = 'mn_demo_auth_user';
530
+ const TOKEN_STORAGE_KEY = 'mn_demo_auth_token';
531
+
532
+ export function createDemoAuthAdapter() {
533
+ const listeners = new Set();
534
+
535
+ function readSession() {
536
+ if (typeof window === 'undefined') {
537
+ return { user: null, token: null };
538
+ }
539
+
540
+ const token = window.localStorage.getItem(TOKEN_STORAGE_KEY);
541
+ const rawUser = window.localStorage.getItem(USER_STORAGE_KEY);
542
+
543
+ if (!token || !rawUser) {
544
+ return { user: null, token: null };
545
+ }
546
+
547
+ try {
548
+ return { user: JSON.parse(rawUser), token };
549
+ } catch {
550
+ window.localStorage.removeItem(USER_STORAGE_KEY);
551
+ window.localStorage.removeItem(TOKEN_STORAGE_KEY);
552
+ return { user: null, token: null };
553
+ }
554
+ }
555
+
556
+ function emit() {
557
+ const { user, token } = readSession();
558
+ listeners.forEach((callback) => callback(user, token));
559
+ }
560
+
561
+ return {
562
+ async login({ method = 'google' } = {}) {
563
+ if (typeof window === 'undefined') {
564
+ return null;
565
+ }
566
+
567
+ const user = {
568
+ id: 'demo-user',
569
+ name: 'Demo User',
570
+ email: 'demo@mounaji.app',
571
+ avatar: null,
572
+ roles: { admin: true },
573
+ raw: {
574
+ method,
575
+ source: 'demo-auth-adapter',
576
+ },
577
+ };
578
+
579
+ window.localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(user));
580
+ window.localStorage.setItem(TOKEN_STORAGE_KEY, 'demo-token');
581
+ emit();
582
+ return user;
583
+ },
584
+
585
+ async logout() {
586
+ if (typeof window === 'undefined') {
587
+ return;
588
+ }
589
+
590
+ window.localStorage.removeItem(USER_STORAGE_KEY);
591
+ window.localStorage.removeItem(TOKEN_STORAGE_KEY);
592
+ emit();
593
+ },
594
+
595
+ async getToken() {
596
+ if (typeof window === 'undefined') {
597
+ return null;
598
+ }
599
+
600
+ return window.localStorage.getItem(TOKEN_STORAGE_KEY);
601
+ },
602
+
603
+ onAuthChange(callback) {
604
+ listeners.add(callback);
605
+
606
+ const handleStorage = () => {
607
+ const { user, token } = readSession();
608
+ callback(user, token);
609
+ };
610
+
611
+ if (typeof window !== 'undefined') {
612
+ window.addEventListener('storage', handleStorage);
613
+ }
614
+
615
+ Promise.resolve().then(() => {
616
+ const { user, token } = readSession();
617
+ callback(user, token);
618
+ });
619
+
620
+ return () => {
621
+ listeners.delete(callback);
622
+ if (typeof window !== 'undefined') {
623
+ window.removeEventListener('storage', handleStorage);
624
+ }
625
+ };
626
+ },
627
+
628
+ provider: 'demo',
629
+ };
630
+ }
631
+ `;
632
+ }
633
+
391
634
  // ─── Utils ────────────────────────────────────────────────────────────────────
392
635
 
393
636
  function toConstName(id) {
@@ -398,6 +641,10 @@ function toPascalCase(id) {
398
641
  return id.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('');
399
642
  }
400
643
 
644
+ function resolveDepVersion(dep) {
645
+ return PACKAGE_VERSIONS[dep] ?? 'latest';
646
+ }
647
+
401
648
  function writeFile(path, content) {
402
649
  writeFileSync(path, content, 'utf8');
403
650
  console.log(` ${GREEN}+${RESET} ${path.replace(process.cwd(), '.')}`);
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@mounaji_npm/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
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"],
5
+ "keywords": [
6
+ "cli",
7
+ "scaffold",
8
+ "saas",
9
+ "nextjs",
10
+ "mounaji",
11
+ "create-app"
12
+ ],
6
13
  "license": "MIT",
7
14
  "type": "module",
8
15
  "publishConfig": {
@@ -10,10 +17,12 @@
10
17
  "registry": "https://registry.npmjs.org/"
11
18
  },
12
19
  "bin": {
13
- "create-mounaji-app": "./bin/create.js",
14
- "mounaji": "./bin/cli.js"
20
+ "mounaji": "bin/cli.js"
15
21
  },
16
- "files": ["bin", "templates"],
22
+ "files": [
23
+ "bin",
24
+ "templates"
25
+ ],
17
26
  "scripts": {
18
27
  "test": "node bin/create.js --help"
19
28
  },