gmoonc 0.0.3 → 0.0.4

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/dist/index.cjs CHANGED
@@ -244,20 +244,29 @@ function patchEntryCss(entrypointPath, dryRun) {
244
244
  return { success: true, backupPath: null };
245
245
  }
246
246
  const content = readFile(entrypointPath);
247
- if (content.includes("./gmoonc/styles/gmoonc.css") || content.includes("gmoonc/styles/gmoonc.css")) {
248
- logSuccess("CSS import already exists");
247
+ const hasThemeCss = content.includes("./gmoonc/styles/theme.css") || content.includes("gmoonc/styles/theme.css");
248
+ const hasGmooncCss = content.includes("./gmoonc/styles/gmoonc.css") || content.includes("gmoonc/styles/gmoonc.css");
249
+ if (hasThemeCss && hasGmooncCss) {
250
+ logSuccess("CSS imports already exist");
249
251
  return { success: true, backupPath: null };
250
252
  }
251
253
  const importRegex = /^import\s+.*$/gm;
252
254
  const imports = content.match(importRegex) || [];
253
255
  let newContent = content;
256
+ const cssImports = [];
257
+ if (!hasThemeCss) {
258
+ cssImports.push('import "./gmoonc/styles/theme.css";');
259
+ }
260
+ if (!hasGmooncCss) {
261
+ cssImports.push('import "./gmoonc/styles/gmoonc.css";');
262
+ }
254
263
  if (imports.length > 0) {
255
264
  const lastImport = imports[imports.length - 1];
256
265
  const lastImportIndex = content.lastIndexOf(lastImport);
257
266
  const insertIndex = lastImportIndex + lastImport.length;
258
- newContent = content.slice(0, insertIndex) + '\nimport "./gmoonc/styles/gmoonc.css";' + content.slice(insertIndex);
267
+ newContent = content.slice(0, insertIndex) + "\n" + cssImports.join("\n") + content.slice(insertIndex);
259
268
  } else {
260
- newContent = 'import "./gmoonc/styles/gmoonc.css";\n' + content;
269
+ newContent = cssImports.join("\n") + "\n" + content;
261
270
  }
262
271
  const backupPath = writeFileSafe(entrypointPath, newContent);
263
272
  if (backupPath) {
@@ -281,23 +290,58 @@ function extractRouteComponents(appContent) {
281
290
  const lines = appContent.split("\n");
282
291
  if (indexComponent) {
283
292
  for (const line of lines) {
284
- if (line.includes("import") && line.includes(indexComponent)) {
285
- indexImport = line.trim();
286
- break;
293
+ const trimmed = line.trim();
294
+ if (trimmed.startsWith("import")) {
295
+ const defaultImportMatch = trimmed.match(/import\s+(\w+)\s+from\s+["']([^"']+)["']/);
296
+ if (defaultImportMatch && defaultImportMatch[1] === indexComponent) {
297
+ indexImport = trimmed;
298
+ break;
299
+ }
300
+ const namedImportMatch = trimmed.match(/import\s+\{[^}]*\b(\w+)\b[^}]*\}\s+from\s+["']([^"']+)["']/);
301
+ if (namedImportMatch && namedImportMatch[1] === indexComponent) {
302
+ indexImport = trimmed;
303
+ break;
304
+ }
287
305
  }
288
306
  }
289
307
  }
290
308
  if (notFoundComponent) {
291
309
  for (const line of lines) {
292
- if (line.includes("import") && line.includes(notFoundComponent)) {
293
- notFoundImport = line.trim();
294
- break;
310
+ const trimmed = line.trim();
311
+ if (trimmed.startsWith("import")) {
312
+ const defaultImportMatch = trimmed.match(/import\s+(\w+)\s+from\s+["']([^"']+)["']/);
313
+ if (defaultImportMatch && defaultImportMatch[1] === notFoundComponent) {
314
+ notFoundImport = trimmed;
315
+ break;
316
+ }
317
+ const namedImportMatch = trimmed.match(/import\s+\{[^}]*\b(\w+)\b[^}]*\}\s+from\s+["']([^"']+)["']/);
318
+ if (namedImportMatch && namedImportMatch[1] === notFoundComponent) {
319
+ notFoundImport = trimmed;
320
+ break;
321
+ }
295
322
  }
296
323
  }
297
324
  }
298
325
  return { indexImport, notFoundImport, indexComponent, notFoundComponent };
299
326
  }
300
- function generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, dryRun) {
327
+ function convertImportToRelative(importLine, appRoutesPath, appPath) {
328
+ const pathMatch = importLine.match(/from\s+["']([^"']+)["']/);
329
+ if (!pathMatch) {
330
+ return importLine;
331
+ }
332
+ const originalPath = pathMatch[1];
333
+ if (originalPath.startsWith("./") || originalPath.startsWith("../")) {
334
+ const appDir = appPath.substring(0, appPath.lastIndexOf("/"));
335
+ const appRoutesDir = appRoutesPath.substring(0, appRoutesPath.lastIndexOf("/"));
336
+ const resolvedPath = (0, import_path5.join)(appDir, originalPath);
337
+ const relativePath = (0, import_path5.relative)(appRoutesDir, resolvedPath);
338
+ const normalizedPath = relativePath.replace(/\\/g, "/");
339
+ const finalPath = normalizedPath.startsWith(".") ? normalizedPath : "./" + normalizedPath;
340
+ return importLine.replace(pathMatch[1], finalPath);
341
+ }
342
+ return importLine;
343
+ }
344
+ function generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, appPath, dryRun) {
301
345
  const appRoutesPath = (0, import_path5.join)(consumerDir, "src/gmoonc/router/AppRoutes.tsx");
302
346
  if (dryRun) {
303
347
  logInfo(`[DRY RUN] Would generate ${appRoutesPath}`);
@@ -313,19 +357,21 @@ function generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, i
313
357
  routes.push(` { path: "*", element: <${notFoundComponent} /> }`);
314
358
  }
315
359
  const imports = [
316
- "import { useRoutes } from 'react-router-dom';",
360
+ "import { useRoutes, type RouteObject } from 'react-router-dom';",
317
361
  "import { createGmooncRoutes } from './createGmooncRoutes';"
318
362
  ];
319
363
  if (indexImport) {
320
- imports.push(indexImport);
364
+ const convertedImport = convertImportToRelative(indexImport, appRoutesPath, appPath);
365
+ imports.push(convertedImport);
321
366
  }
322
367
  if (notFoundImport) {
323
- imports.push(notFoundImport);
368
+ const convertedImport = convertImportToRelative(notFoundImport, appRoutesPath, appPath);
369
+ imports.push(convertedImport);
324
370
  }
325
371
  const content = `${imports.join("\n")}
326
372
 
327
373
  export function GMooncAppRoutes({ basePath = "${basePath}" }: { basePath?: string }) {
328
- const allRoutes = [
374
+ const allRoutes: RouteObject[] = [
329
375
  ${routes.join(",\n")}
330
376
  ];
331
377
 
@@ -380,10 +426,54 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
380
426
  logInfo(`[DRY RUN] Would patch ${appPath}`);
381
427
  return { success: true, backupPath: null };
382
428
  }
383
- generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, false);
429
+ generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, appPath, false);
384
430
  const backupPath = writeFileSafe(appPath, "");
385
- const importLine = `import { GMooncAppRoutes } from "./gmoonc/router/AppRoutes";`;
386
431
  const lines = appContent.split("\n");
432
+ let hasBrowserRouterImport = false;
433
+ let reactRouterImportIndex = -1;
434
+ for (let i = 0; i < lines.length; i++) {
435
+ const line = lines[i].trim();
436
+ if (line.includes("from") && line.includes("react-router-dom")) {
437
+ reactRouterImportIndex = i;
438
+ if (line.includes("BrowserRouter")) {
439
+ hasBrowserRouterImport = true;
440
+ }
441
+ }
442
+ }
443
+ if (!hasBrowserRouterImport) {
444
+ if (reactRouterImportIndex >= 0) {
445
+ const existingLine = lines[reactRouterImportIndex];
446
+ if (existingLine.includes("{") && existingLine.includes("}")) {
447
+ const updatedLine = existingLine.replace(/\{([^}]+)\}/, (match, imports) => {
448
+ const importList = imports.split(",").map((s) => s.trim());
449
+ if (!importList.includes("BrowserRouter")) {
450
+ importList.push("BrowserRouter");
451
+ }
452
+ return `{ ${importList.join(", ")} }`;
453
+ });
454
+ lines[reactRouterImportIndex] = updatedLine;
455
+ } else {
456
+ lines.splice(reactRouterImportIndex + 1, 0, `import { BrowserRouter } from "react-router-dom";`);
457
+ }
458
+ } else {
459
+ const importLine2 = `import { BrowserRouter } from "react-router-dom";`;
460
+ let lastImportIndex2 = -1;
461
+ for (let i = 0; i < lines.length; i++) {
462
+ const line = lines[i].trim();
463
+ if (line.startsWith("import ") || line.startsWith("import{") || line.startsWith("import ")) {
464
+ lastImportIndex2 = i;
465
+ } else if (line && lastImportIndex2 >= 0 && !line.startsWith("//") && !line.startsWith("/*")) {
466
+ break;
467
+ }
468
+ }
469
+ if (lastImportIndex2 >= 0) {
470
+ lines.splice(lastImportIndex2 + 1, 0, importLine2);
471
+ } else {
472
+ lines.unshift(importLine2);
473
+ }
474
+ }
475
+ }
476
+ const importLine = `import { GMooncAppRoutes } from "./gmoonc/router/AppRoutes";`;
387
477
  let lastImportIndex = -1;
388
478
  for (let i = 0; i < lines.length; i++) {
389
479
  const line = lines[i].trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gmoonc",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Goalmoon Ctrl (gmoonc): Complete dashboard installer for React projects",
5
5
  "license": "MIT",
6
6
  "homepage": "https://gmoonc.com",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "type": "module",
16
16
  "bin": {
17
- "gmoonc": "./dist/cli.cjs"
17
+ "gmoonc": "./dist/index.cjs"
18
18
  },
19
19
  "files": [
20
20
  "dist",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "scripts": {
27
27
  "sync:templates": "node scripts/sync-templates.mjs",
28
- "build": "tsup src/cli/index.ts --format cjs --no-splitting --out-dir dist && node -e \"const fs=require('fs');const f='dist/cli.js';const cjs='dist/cli.cjs';if(fs.existsSync(f)){fs.renameSync(f,cjs);fs.writeFileSync(cjs,'#!/usr/bin/env node\\n'+fs.readFileSync(cjs))}\"",
28
+ "build": "tsup src/cli/index.ts --format cjs --no-splitting --out-dir dist && node -e \"const fs=require('fs');const path=require('path');const distDir='dist';const files=fs.readdirSync(distDir);const indexFile=files.find(f=>f.startsWith('index')&&f.endsWith('.js'));if(indexFile){const oldPath=path.join(distDir,indexFile);const newPath=path.join(distDir,'index.cjs');fs.renameSync(oldPath,newPath);const content=fs.readFileSync(newPath,'utf8');if(!content.startsWith('#!/usr/bin/env node')){fs.writeFileSync(newPath,'#!/usr/bin/env node\\n'+content)}}\"",
29
29
  "clean": "rimraf dist",
30
30
  "prepublishOnly": "npm run clean && npm run build",
31
31
  "postinstall": "node scripts/block-global-install.cjs"
@@ -1,4 +1,4 @@
1
- import { type MenuItem as CoreMenuItem } from '../../core/core';
1
+ import { type MenuItem as CoreMenuItem } from '../../core/types';
2
2
 
3
3
  /**
4
4
  * Extended MenuItem interface with submenu support
@@ -1,4 +1,4 @@
1
- import { defineConfig } from '../core/core';
1
+ import { defineConfig } from '../core/types';
2
2
  import { createDefaultMenu } from '../app/menu/defaultMenu';
3
3
 
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback } from 'react';
2
2
  import { Outlet, useLocation, useNavigate, Link } from 'react-router-dom';
3
- import { GmooncShell } from '../ui/ui';
3
+ import { GmooncShell } from '../ui/shell';
4
4
  import { defaultConfig } from '../config/defaultConfig';
5
5
  import { GMooncSessionProvider, useGMooncSession } from '../session/GMooncSessionContext';
6
6
  import { GMooncUserProfile } from '../components/GMooncUserProfile';
@@ -24,33 +24,36 @@ function GMooncAppLayoutInner() {
24
24
  }, [navigate]);
25
25
 
26
26
  return (
27
- <GmooncShell
28
- config={defaultConfig}
29
- roles={roles}
30
- activePath={location.pathname}
31
- onNavigate={(path) => navigate(path)}
32
- headerRight={
33
- <GMooncUserProfile
34
- onAccount={handleAccount}
35
- onAbout={handleAbout}
36
- onLogoutRequest={handleLogout}
37
- />
38
- }
39
- renderLink={({ path, label, isActive, onClick }) => (
40
- <Link
41
- to={path}
42
- onClick={onClick}
43
- style={{
44
- textDecoration: isActive ? 'underline' : 'none',
45
- color: 'inherit'
46
- }}
47
- >
48
- {label}
49
- </Link>
50
- )}
51
- >
52
- <Outlet />
53
- </GmooncShell>
27
+ <div className="gmoonc-root">
28
+ <GmooncShell
29
+ config={defaultConfig}
30
+ roles={roles}
31
+ activePath={location.pathname}
32
+ onNavigate={(path) => navigate(path)}
33
+ headerRight={
34
+ <GMooncUserProfile
35
+ onAccount={handleAccount}
36
+ onAbout={handleAbout}
37
+ onLogoutRequest={handleLogout}
38
+ />
39
+ }
40
+ renderLink={({ path, label, isActive, onClick, className, children }) => (
41
+ <Link
42
+ to={path}
43
+ onClick={onClick}
44
+ className={className}
45
+ style={{
46
+ textDecoration: isActive ? 'underline' : 'none',
47
+ color: 'inherit'
48
+ }}
49
+ >
50
+ {children || label}
51
+ </Link>
52
+ )}
53
+ >
54
+ <Outlet />
55
+ </GmooncShell>
56
+ </div>
54
57
  );
55
58
  }
56
59
 
@@ -0,0 +1,101 @@
1
+ /*
2
+ * Goalmoon Ctrl (gmoonc) Theme Variables
3
+ *
4
+ * This file contains CSS custom properties (variables) that define the visual theme
5
+ * of the gmoonc dashboard. You can customize these values to match your brand.
6
+ *
7
+ * All variables are scoped under .gmoonc-root to avoid conflicts with your app.
8
+ */
9
+
10
+ .gmoonc-root {
11
+ /* Colors - Background */
12
+ --gmoonc-color-bg: #293047;
13
+ --gmoonc-color-bg-light: #eaf0f5;
14
+ --gmoonc-color-bg-gradient-start: #eaf0f5;
15
+ --gmoonc-color-bg-gradient-end: #dbe2ea;
16
+
17
+ /* Colors - Surface */
18
+ --gmoonc-color-surface: #374161;
19
+ --gmoonc-color-surface-2: #3F4A6E;
20
+ --gmoonc-color-surface-3: #6374AD;
21
+ --gmoonc-color-surface-light: #f8f9fa;
22
+ --gmoonc-color-surface-white: #ffffff;
23
+
24
+ /* Colors - Text */
25
+ --gmoonc-color-text: #eaf0f5;
26
+ --gmoonc-color-text-primary: #374161;
27
+ --gmoonc-color-text-secondary: #6c757d;
28
+ --gmoonc-color-text-muted: #dbe2ea;
29
+ --gmoonc-color-text-white: #ffffff;
30
+
31
+ /* Colors - Primary */
32
+ --gmoonc-color-primary: #879FED;
33
+ --gmoonc-color-primary-2: #6374AD;
34
+ --gmoonc-color-primary-dark: #374161;
35
+
36
+ /* Colors - Accent */
37
+ --gmoonc-color-accent: #71b399;
38
+
39
+ /* Colors - Borders */
40
+ --gmoonc-color-border: #dbe2ea;
41
+ --gmoonc-color-border-light: #e9ecef;
42
+ --gmoonc-color-border-dark: #1d2436;
43
+
44
+ /* Colors - States */
45
+ --gmoonc-color-error: #dc2626;
46
+ --gmoonc-color-error-bg: #fef2f2;
47
+ --gmoonc-color-error-border: #fecaca;
48
+ --gmoonc-color-success: #166534;
49
+ --gmoonc-color-success-bg: #f0fdf4;
50
+ --gmoonc-color-success-border: #bbf7d0;
51
+ --gmoonc-color-disabled: #6c757d;
52
+ --gmoonc-color-disabled-bg: #f8f9fa;
53
+
54
+ /* Spacing */
55
+ --gmoonc-gap: 12px;
56
+ --gmoonc-gap-sm: 8px;
57
+ --gmoonc-gap-md: 16px;
58
+ --gmoonc-gap-lg: 20px;
59
+ --gmoonc-gap-xl: 30px;
60
+
61
+ /* Border Radius */
62
+ --gmoonc-radius: 12px;
63
+ --gmoonc-radius-sm: 8px;
64
+ --gmoonc-radius-md: 10px;
65
+ --gmoonc-radius-lg: 16px;
66
+
67
+ /* Typography */
68
+ --gmoonc-font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
69
+ --gmoonc-font-weight-title: 700;
70
+ --gmoonc-font-weight-heading: 600;
71
+ --gmoonc-font-weight-body: 400;
72
+ --gmoonc-font-weight-medium: 500;
73
+
74
+ /* Font Sizes */
75
+ --gmoonc-font-size-xs: 12px;
76
+ --gmoonc-font-size-sm: 14px;
77
+ --gmoonc-font-size-base: 16px;
78
+ --gmoonc-font-size-lg: 18px;
79
+ --gmoonc-font-size-xl: 24px;
80
+ --gmoonc-font-size-2xl: 28px;
81
+ --gmoonc-font-size-3xl: 32px;
82
+
83
+ /* Shadows */
84
+ --gmoonc-shadow-sm: 0 4px 6px rgba(55, 65, 97, 0.1);
85
+ --gmoonc-shadow-md: 0 4px 20px rgba(55, 65, 97, 0.1);
86
+ --gmoonc-shadow-lg: 0 8px 32px rgba(55, 65, 97, 0.15);
87
+ --gmoonc-shadow-header: 0 4px 20px rgba(55, 65, 97, 0.3);
88
+ --gmoonc-shadow-menu-header: 0 8px 16px rgba(0, 0, 0, 0.22);
89
+
90
+ /* Transitions */
91
+ --gmoonc-transition: 0.3s ease;
92
+ --gmoonc-transition-fast: 0.2s ease;
93
+
94
+ /* Z-index */
95
+ --gmoonc-z-header: 1000;
96
+ --gmoonc-z-profile: 1001;
97
+ --gmoonc-z-dropdown: 1002;
98
+ --gmoonc-z-hamburger: 1002;
99
+ --gmoonc-z-backdrop: 1050;
100
+ --gmoonc-z-menu: 1100;
101
+ }
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useMemo, useEffect } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
- import type { MenuItem as CoreMenuItem } from '@gmoonc/core';
3
+ import type { MenuItem as CoreMenuItem } from '../core/types';
4
4
  import './styles.css';
5
5
 
6
6
  /**
@@ -36,6 +36,8 @@ export interface GmooncMenuProps {
36
36
  label: string;
37
37
  isActive: boolean;
38
38
  onClick?: () => void;
39
+ className?: string;
40
+ children?: React.ReactNode;
39
41
  }) => React.ReactNode;
40
42
  /** Whether menu is open (for mobile/tablet) */
41
43
  isOpen?: boolean;
@@ -392,7 +394,8 @@ export function GmooncMenu({
392
394
  path: item.path,
393
395
  label: item.label,
394
396
  isActive: isItemActive,
395
- onClick: handleItemClick
397
+ onClick: handleItemClick,
398
+ className: `gmoonc-menu-link ${hasSubmenu ? 'has-submenu' : ''} ${isSubmenuOpen ? 'submenu-open' : ''} ${isItemActive ? 'active' : ''}`
396
399
  })
397
400
  ) : (
398
401
  <button
@@ -439,7 +442,8 @@ export function GmooncMenu({
439
442
  path: subItem.path,
440
443
  label: subItem.label,
441
444
  isActive: isSubActive,
442
- onClick: handleSubItemClick
445
+ onClick: handleSubItemClick,
446
+ className: `gmoonc-submenu-link ${isSubActive ? 'active' : ''}`
443
447
  }) : (
444
448
  <button
445
449
  type="button"
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useCallback } from 'react';
2
- import type { GmooncConfig } from '@gmoonc/core';
2
+ import type { GmooncConfig } from '../core/types';
3
3
  import { GmooncHeader } from './header';
4
4
  import { GmooncSidebar } from './sidebar';
5
5
  import { GmooncMenu, type GmooncMenuItem } from './menu';