@mounaji_npm/cli 0.1.1 → 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.
- package/bin/cli.js +1 -1
- package/bin/create.js +275 -28
- package/package.json +1 -1
package/bin/cli.js
CHANGED
package/bin/create.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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 {
|
|
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
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
315
|
+
<h1 className="mn-page-title" style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>
|
|
260
316
|
Welcome to ${name}
|
|
261
317
|
</h1>
|
|
262
|
-
<p
|
|
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
|
|
339
|
+
<h1 className="mn-page-title" style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>
|
|
278
340
|
${m.label}
|
|
279
341
|
</h1>
|
|
280
|
-
<p
|
|
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 => `
|
|
366
|
-
return `import {
|
|
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
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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(), '.')}`);
|