@flexireact/core 4.0.0 → 4.1.1

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/cli/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import fs2 from "fs";
5
5
  import path2 from "path";
6
6
  import { fileURLToPath, pathToFileURL } from "url";
7
- import { spawn } from "child_process";
7
+ import { execSync, spawn } from "child_process";
8
8
  import pc2 from "picocolors";
9
9
  import prompts2 from "prompts";
10
10
  import ora from "ora";
@@ -15,10 +15,10 @@ import path from "path";
15
15
  import pc from "picocolors";
16
16
  import prompts from "prompts";
17
17
  var log = {
18
- info: (msg) => console.log(`${pc.cyan("\u2139")} ${msg}`),
19
- success: (msg) => console.log(`${pc.green("\u2713")} ${msg}`),
20
- warn: (msg) => console.log(`${pc.yellow("\u26A0")} ${pc.yellow(msg)}`),
21
- error: (msg) => console.log(`${pc.red("\u2717")} ${pc.red(msg)}`),
18
+ info: (msg) => console.log(` ${pc.cyan("\u2139")} ${msg}`),
19
+ success: (msg) => console.log(` ${pc.green("\u2714")} ${msg}`),
20
+ warn: (msg) => console.log(` ${pc.yellow("\u26A0")} ${pc.yellow(msg)}`),
21
+ error: (msg) => console.log(` ${pc.red("\u2716")} ${pc.red(msg)}`),
22
22
  blank: () => console.log("")
23
23
  };
24
24
  var templates = {
@@ -516,72 +516,383 @@ async function generateSpecialFile(type, cwd) {
516
516
  log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
517
517
  }
518
518
  function listGenerators() {
519
- console.log(`
520
- ${pc.bold("Available Generators:")}
519
+ console.log(` ${pc.bold("Available Generators")}`);
520
+ log.blank();
521
+ console.log(` ${pc.cyan("page")} ${pc.dim("Create a new page")}`);
522
+ console.log(` ${pc.cyan("layout")} ${pc.dim("Create a layout wrapper")}`);
523
+ console.log(` ${pc.cyan("component")} ${pc.dim("Create a React component")}`);
524
+ console.log(` ${pc.cyan("hook")} ${pc.dim("Create a custom hook")}`);
525
+ console.log(` ${pc.cyan("api")} ${pc.dim("Create an API route")}`);
526
+ console.log(` ${pc.cyan("action")} ${pc.dim("Create a server action")}`);
527
+ console.log(` ${pc.cyan("middleware")} ${pc.dim("Create request middleware")}`);
528
+ log.blank();
529
+ console.log(` ${pc.bold("Usage")}`);
530
+ console.log(` $ flexi g <type> [name]`);
531
+ log.blank();
532
+ }
521
533
 
522
- ${pc.cyan("page")} Create a new page (app/ or pages/)
523
- ${pc.cyan("layout")} Create a layout wrapper
524
- ${pc.cyan("component")} Create a React component
525
- ${pc.cyan("hook")} Create a custom hook
526
- ${pc.cyan("api")} Create an API route
527
- ${pc.cyan("action")} Create a server action
528
- ${pc.cyan("middleware")} Create request middleware
529
- ${pc.cyan("context")} Create a React context
530
- ${pc.cyan("loading")} Create a loading component
531
- ${pc.cyan("error")} Create an error boundary
532
- ${pc.cyan("not-found")} Create a 404 page
534
+ // packages/create-flexireact/src/templates/default.ts
535
+ function defaultTemplate(projectName, options = {}) {
536
+ return {
537
+ "package.json": JSON.stringify({
538
+ name: projectName,
539
+ version: "1.0.0",
540
+ private: true,
541
+ type: "module",
542
+ scripts: {
543
+ dev: "flexireact dev",
544
+ build: "flexireact build",
545
+ start: "flexireact start"
546
+ },
547
+ dependencies: {
548
+ "react": "^19.2.1",
549
+ "react-dom": "^19.2.1",
550
+ "@flexireact/core": "^4.1.0"
551
+ },
552
+ devDependencies: {
553
+ "@types/react": "^19.0.0",
554
+ "@types/react-dom": "^19.0.0",
555
+ "typescript": "^5.7.0"
556
+ }
557
+ }, null, 2),
558
+ "tsconfig.json": JSON.stringify({
559
+ compilerOptions: {
560
+ target: "ES2022",
561
+ lib: ["DOM", "DOM.Iterable", "ES2022"],
562
+ module: "ESNext",
563
+ moduleResolution: "bundler",
564
+ jsx: "react-jsx",
565
+ strict: true,
566
+ skipLibCheck: true,
567
+ noEmit: true,
568
+ baseUrl: ".",
569
+ paths: { "@/*": ["./*"] }
570
+ },
571
+ include: ["**/*.ts", "**/*.tsx"],
572
+ exclude: ["node_modules", ".flexi"]
573
+ }, null, 2),
574
+ "flexireact.config.js": `export default {
575
+ server: { port: 3000 },
576
+ styles: ['/styles.css'],
577
+ };
578
+ `,
579
+ "pages/index.tsx": `import React from 'react';
533
580
 
534
- ${pc.bold("Usage:")}
535
- ${pc.dim("$")} flexi generate ${pc.cyan("<type>")} ${pc.dim("[name]")}
536
- ${pc.dim("$")} flexi g ${pc.cyan("<type>")} ${pc.dim("[name]")}
581
+ export default function HomePage() {
582
+ return (
583
+ <div style={styles.container}>
584
+ <div style={styles.card}>
585
+ <div style={styles.logo}>F</div>
586
+ <h1 style={styles.title}>Welcome to FlexiReact</h1>
587
+ <p style={styles.subtitle}>
588
+ Edit <code style={styles.code}>pages/index.tsx</code> to get started
589
+ </p>
590
+ <div style={styles.links}>
591
+ <a href="https://github.com/aspect-dev/flexireact" style={styles.link}>GitHub</a>
592
+ <a href="/about" style={styles.link}>About</a>
593
+ </div>
594
+ </div>
595
+ </div>
596
+ );
597
+ }
537
598
 
538
- ${pc.bold("Examples:")}
539
- ${pc.dim("$")} flexi g page dashboard
540
- ${pc.dim("$")} flexi g component Button
541
- ${pc.dim("$")} flexi g hook auth
542
- ${pc.dim("$")} flexi g api users
543
- `);
599
+ const styles: Record<string, React.CSSProperties> = {
600
+ container: {
601
+ minHeight: '100vh',
602
+ display: 'flex',
603
+ alignItems: 'center',
604
+ justifyContent: 'center',
605
+ background: 'linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%)',
606
+ fontFamily: 'system-ui, -apple-system, sans-serif',
607
+ },
608
+ card: {
609
+ textAlign: 'center',
610
+ padding: '60px 40px',
611
+ background: 'rgba(255, 255, 255, 0.03)',
612
+ borderRadius: '24px',
613
+ border: '1px solid rgba(255, 255, 255, 0.1)',
614
+ maxWidth: '500px',
615
+ },
616
+ logo: {
617
+ width: '80px',
618
+ height: '80px',
619
+ background: 'linear-gradient(135deg, #00FF9C 0%, #00D68F 100%)',
620
+ borderRadius: '20px',
621
+ display: 'flex',
622
+ alignItems: 'center',
623
+ justifyContent: 'center',
624
+ fontSize: '40px',
625
+ fontWeight: 900,
626
+ color: '#000',
627
+ margin: '0 auto 30px',
628
+ },
629
+ title: { fontSize: '2.5rem', fontWeight: 700, color: '#fff', margin: '0 0 16px' },
630
+ subtitle: { fontSize: '1.1rem', color: '#888', margin: '0 0 32px' },
631
+ code: { background: 'rgba(0, 255, 156, 0.1)', color: '#00FF9C', padding: '4px 8px', borderRadius: '6px' },
632
+ links: { display: 'flex', gap: '16px', justifyContent: 'center' },
633
+ link: { color: '#00FF9C', textDecoration: 'none', padding: '12px 24px', borderRadius: '12px', background: 'rgba(0, 255, 156, 0.1)' },
634
+ };
635
+ `,
636
+ "pages/about.tsx": `import React from 'react';
637
+
638
+ export default function AboutPage() {
639
+ return (
640
+ <div style={{ padding: 40, fontFamily: 'system-ui', background: '#0a0a0a', color: '#fff', minHeight: '100vh' }}>
641
+ <a href="/" style={{ color: '#00FF9C', textDecoration: 'none' }}>Back</a>
642
+ <h1 style={{ marginTop: 40 }}>About</h1>
643
+ <p style={{ color: '#888' }}>FlexiReact - Modern React Framework</p>
644
+ </div>
645
+ );
646
+ }
647
+ `,
648
+ "pages/api/hello.ts": `export async function GET() {
649
+ return Response.json({ message: 'Hello from FlexiReact!' });
650
+ }
651
+ `,
652
+ "public/styles.css": `* { box-sizing: border-box; margin: 0; padding: 0; }
653
+ `,
654
+ "public/favicon.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="20" fill="#0a0a0a"/><text x="50" y="68" font-family="system-ui" font-size="50" font-weight="900" fill="#00FF9C" text-anchor="middle">F</text></svg>`,
655
+ ".gitignore": `node_modules/
656
+ .flexi/
657
+ dist/
658
+ .env
659
+ `
660
+ };
661
+ }
662
+
663
+ // packages/create-flexireact/src/templates/minimal.ts
664
+ function minimalTemplate(projectName, options = {}) {
665
+ return {
666
+ "package.json": JSON.stringify({
667
+ name: projectName,
668
+ version: "1.0.0",
669
+ private: true,
670
+ type: "module",
671
+ scripts: {
672
+ dev: "flexireact dev",
673
+ build: "flexireact build",
674
+ start: "flexireact start"
675
+ },
676
+ dependencies: {
677
+ "react": "^19.2.1",
678
+ "react-dom": "^19.2.1",
679
+ "@flexireact/core": "^4.1.0"
680
+ },
681
+ devDependencies: {
682
+ "@types/react": "^19.0.0",
683
+ "@types/react-dom": "^19.0.0",
684
+ "typescript": "^5.7.0"
685
+ }
686
+ }, null, 2),
687
+ "tsconfig.json": JSON.stringify({
688
+ compilerOptions: {
689
+ target: "ES2022",
690
+ lib: ["DOM", "ES2022"],
691
+ module: "ESNext",
692
+ moduleResolution: "bundler",
693
+ jsx: "react-jsx",
694
+ strict: true,
695
+ skipLibCheck: true,
696
+ noEmit: true
697
+ },
698
+ include: ["**/*.ts", "**/*.tsx"],
699
+ exclude: ["node_modules"]
700
+ }, null, 2),
701
+ "flexireact.config.js": `export default { server: { port: 3000 } };
702
+ `,
703
+ "pages/index.tsx": `import React from 'react';
704
+
705
+ export default function Home() {
706
+ return (
707
+ <div style={{ padding: 40, fontFamily: 'system-ui', background: '#0a0a0a', color: '#fff', minHeight: '100vh' }}>
708
+ <h1 style={{ color: '#00FF9C' }}>FlexiReact</h1>
709
+ <p>Edit pages/index.tsx to start</p>
710
+ </div>
711
+ );
712
+ }
713
+ `,
714
+ ".gitignore": `node_modules/
715
+ .flexi/
716
+ `
717
+ };
718
+ }
719
+
720
+ // packages/create-flexireact/src/templates/app-router.ts
721
+ function appRouterTemplate(projectName) {
722
+ return {
723
+ "package.json": JSON.stringify({
724
+ name: projectName,
725
+ version: "1.0.0",
726
+ private: true,
727
+ type: "module",
728
+ scripts: {
729
+ dev: "flexireact dev",
730
+ build: "flexireact build",
731
+ start: "flexireact start"
732
+ },
733
+ dependencies: {
734
+ "react": "^19.2.1",
735
+ "react-dom": "^19.2.1",
736
+ "@flexireact/core": "^4.1.0"
737
+ },
738
+ devDependencies: {
739
+ "@types/react": "^19.0.0",
740
+ "@types/react-dom": "^19.0.0",
741
+ "typescript": "^5.7.0"
742
+ }
743
+ }, null, 2),
744
+ "tsconfig.json": JSON.stringify({
745
+ compilerOptions: {
746
+ target: "ES2022",
747
+ lib: ["DOM", "DOM.Iterable", "ES2022"],
748
+ module: "ESNext",
749
+ moduleResolution: "bundler",
750
+ jsx: "react-jsx",
751
+ strict: true,
752
+ skipLibCheck: true,
753
+ noEmit: true,
754
+ baseUrl: ".",
755
+ paths: { "@/*": ["./*"] }
756
+ },
757
+ include: ["**/*.ts", "**/*.tsx"],
758
+ exclude: ["node_modules", ".flexi"]
759
+ }, null, 2),
760
+ "flexireact.config.js": `export default {
761
+ server: { port: 3000 },
762
+ appDir: 'app',
763
+ };
764
+ `,
765
+ "app/layout.tsx": `import React from 'react';
766
+
767
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
768
+ return (
769
+ <html lang="en">
770
+ <head>
771
+ <meta charSet="UTF-8" />
772
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
773
+ <title>FlexiReact App</title>
774
+ <style>{\`
775
+ * { box-sizing: border-box; margin: 0; padding: 0; }
776
+ body { font-family: system-ui, sans-serif; background: #0a0a0a; color: #fff; }
777
+ \`}</style>
778
+ </head>
779
+ <body>{children}</body>
780
+ </html>
781
+ );
782
+ }
783
+ `,
784
+ "app/page.tsx": `import React from 'react';
785
+
786
+ export default function HomePage() {
787
+ return (
788
+ <main style={styles.main}>
789
+ <div style={styles.logo}>F</div>
790
+ <h1 style={styles.title}>FlexiReact App Router</h1>
791
+ <p style={styles.text}>Next.js style routing with app/ directory</p>
792
+ <div style={styles.links}>
793
+ <a href="/dashboard" style={styles.link}>Dashboard \u2192</a>
794
+ </div>
795
+ </main>
796
+ );
797
+ }
798
+
799
+ const styles: Record<string, React.CSSProperties> = {
800
+ main: {
801
+ minHeight: '100vh',
802
+ display: 'flex',
803
+ flexDirection: 'column',
804
+ alignItems: 'center',
805
+ justifyContent: 'center',
806
+ textAlign: 'center',
807
+ padding: '20px',
808
+ },
809
+ logo: {
810
+ width: '80px',
811
+ height: '80px',
812
+ background: '#00FF9C',
813
+ borderRadius: '20px',
814
+ display: 'flex',
815
+ alignItems: 'center',
816
+ justifyContent: 'center',
817
+ fontSize: '40px',
818
+ fontWeight: 900,
819
+ color: '#000',
820
+ marginBottom: '30px',
821
+ },
822
+ title: {
823
+ fontSize: '2.5rem',
824
+ marginBottom: '16px',
825
+ },
826
+ text: {
827
+ color: '#888',
828
+ marginBottom: '32px',
829
+ },
830
+ links: {
831
+ display: 'flex',
832
+ gap: '16px',
833
+ },
834
+ link: {
835
+ color: '#00FF9C',
836
+ textDecoration: 'none',
837
+ padding: '12px 24px',
838
+ borderRadius: '12px',
839
+ background: 'rgba(0, 255, 156, 0.1)',
840
+ },
841
+ };
842
+ `,
843
+ "app/dashboard/page.tsx": `import React from 'react';
844
+
845
+ export default function DashboardPage() {
846
+ return (
847
+ <div style={{ padding: 40 }}>
848
+ <a href="/" style={{ color: '#00FF9C', textDecoration: 'none' }}>\u2190 Back</a>
849
+ <h1 style={{ marginTop: 30, marginBottom: 20 }}>Dashboard</h1>
850
+ <p style={{ color: '#888' }}>This is a nested route: app/dashboard/page.tsx</p>
851
+ </div>
852
+ );
853
+ }
854
+ `,
855
+ "app/api/hello/route.ts": `export async function GET() {
856
+ return Response.json({ message: 'Hello from App Router API!' });
857
+ }
858
+ `,
859
+ ".gitignore": `node_modules/
860
+ .flexi/
861
+ dist/
862
+ .env
863
+ `
864
+ };
865
+ }
866
+
867
+ // packages/create-flexireact/src/templates/index.ts
868
+ function getTemplateFiles(templateKey, projectName, options = {}) {
869
+ switch (templateKey) {
870
+ case "minimal":
871
+ return minimalTemplate(projectName, options);
872
+ case "app-router":
873
+ return appRouterTemplate(projectName);
874
+ default:
875
+ return defaultTemplate(projectName, options);
876
+ }
544
877
  }
545
878
 
546
879
  // cli/index.ts
547
880
  var __filename2 = fileURLToPath(import.meta.url);
548
881
  var __dirname2 = path2.dirname(__filename2);
549
- var VERSION = "3.0.0";
882
+ var VERSION = "4.1.0";
550
883
  var LOGO = `
551
- ${pc2.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
552
- ${pc2.cyan("\u2551")} ${pc2.cyan("\u2551")}
553
- ${pc2.cyan("\u2551")} ${pc2.bold(pc2.magenta("\u26A1"))} ${pc2.bold(pc2.white("F L E X I R E A C T"))} ${pc2.cyan("\u2551")}
554
- ${pc2.cyan("\u2551")} ${pc2.cyan("\u2551")}
555
- ${pc2.cyan("\u2551")} ${pc2.dim("The Modern React Framework")} ${pc2.cyan("\u2551")}
556
- ${pc2.cyan("\u2551")} ${pc2.dim("TypeScript \u2022 Tailwind \u2022 SSR \u2022 Islands")} ${pc2.cyan("\u2551")}
557
- ${pc2.cyan("\u2551")} ${pc2.cyan("\u2551")}
558
- ${pc2.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
884
+ ${pc2.white("\u25B2")} ${pc2.bold("FlexiReact")} ${pc2.dim(VERSION)}
559
885
  `;
560
- var MINI_LOGO = `${pc2.magenta("\u26A1")} ${pc2.bold("FlexiReact")}`;
886
+ var MINI_LOGO = `${pc2.white("\u25B2")} ${pc2.bold("FlexiReact")}`;
561
887
  var log2 = {
562
- info: (msg) => console.log(`${pc2.cyan("\u2139")} ${msg}`),
563
- success: (msg) => console.log(`${pc2.green("\u2713")} ${msg}`),
564
- warn: (msg) => console.log(`${pc2.yellow("\u26A0")} ${pc2.yellow(msg)}`),
565
- error: (msg) => console.log(`${pc2.red("\u2717")} ${pc2.red(msg)}`),
566
- step: (num, total, msg) => console.log(`${pc2.dim(`[${num}/${total}]`)} ${msg}`),
888
+ info: (msg) => console.log(` ${pc2.cyan("\u2139")} ${msg}`),
889
+ success: (msg) => console.log(` ${pc2.green("\u2714")} ${msg}`),
890
+ warn: (msg) => console.log(` ${pc2.yellow("\u26A0")} ${pc2.yellow(msg)}`),
891
+ error: (msg) => console.log(` ${pc2.red("\u2716")} ${pc2.red(msg)}`),
892
+ step: (msg) => console.log(` ${pc2.dim("\u25CB")} ${msg}`),
567
893
  blank: () => console.log(""),
568
- divider: () => console.log(pc2.dim("\u2500".repeat(60)))
894
+ divider: () => console.log(pc2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"))
569
895
  };
570
- function copyDirectory(src, dest) {
571
- if (!fs2.existsSync(dest)) {
572
- fs2.mkdirSync(dest, { recursive: true });
573
- }
574
- const entries = fs2.readdirSync(src, { withFileTypes: true });
575
- for (const entry of entries) {
576
- const srcPath = path2.join(src, entry.name);
577
- const destPath = path2.join(dest, entry.name);
578
- if (entry.isDirectory()) {
579
- copyDirectory(srcPath, destPath);
580
- } else {
581
- fs2.copyFileSync(srcPath, destPath);
582
- }
583
- }
584
- }
585
896
  async function runCommand(cmd, cwd) {
586
897
  return new Promise((resolve, reject) => {
587
898
  const child = spawn(cmd, {
@@ -622,638 +933,73 @@ async function createProject(projectName) {
622
933
  name: "template",
623
934
  message: "Select a template:",
624
935
  choices: [
625
- { title: "\u{1F680} Default (Tailwind + shadcn/ui)", value: "default" },
626
- { title: "\u{1F49A} FlexiUI (Landing page + @flexireact/flexi-ui)", value: "flexi-ui" },
627
- { title: "\u{1F4E6} Minimal (Clean slate)", value: "minimal" }
936
+ { title: `\u26A1 Default ${pc2.dim("(Pages routing)")}`, value: "default" },
937
+ { title: `\u{1F4C2} App Router ${pc2.dim("(Next.js style)")}`, value: "app-router" },
938
+ { title: `\u{1F4E6} Minimal ${pc2.dim("(Bare starter)")}`, value: "minimal" }
628
939
  ],
629
940
  initial: 0
630
- },
631
- {
632
- type: "toggle",
633
- name: "typescript",
634
- message: "Use TypeScript?",
635
- initial: true,
636
- active: "Yes",
637
- inactive: "No"
638
941
  }
639
942
  ]);
640
943
  if (options.template === void 0) process.exit(1);
641
944
  log2.blank();
642
- log2.divider();
643
- log2.blank();
644
- const totalSteps = options.template === "default" ? 6 : options.template === "flexi-ui" ? 5 : 4;
945
+ const totalSteps = 5;
645
946
  let currentStep = 0;
646
947
  currentStep++;
647
- log2.step(currentStep, totalSteps, "Creating project directory...");
948
+ log2.step("Creating project directory...");
648
949
  fs2.mkdirSync(projectPath, { recursive: true });
649
- log2.success(`Created ${pc2.cyan(name)}/`);
650
950
  currentStep++;
651
- log2.step(currentStep, totalSteps, "Setting up project structure...");
652
- const templateName = options.template;
653
- const templatePath = path2.resolve(__dirname2, "..", "templates", templateName);
654
- if (fs2.existsSync(templatePath)) {
655
- copyDirectory(templatePath, projectPath);
656
- log2.success("Project structure created");
657
- } else {
658
- await createDefaultTemplate(projectPath, name, options.typescript);
659
- log2.success("Project structure created");
951
+ log2.step("Generating files...");
952
+ try {
953
+ const files = getTemplateFiles(options.template, name, {});
954
+ for (const [filePath, content] of Object.entries(files)) {
955
+ const fullPath = path2.join(projectPath, filePath);
956
+ const dir = path2.dirname(fullPath);
957
+ if (!fs2.existsSync(dir)) {
958
+ fs2.mkdirSync(dir, { recursive: true });
959
+ }
960
+ fs2.writeFileSync(fullPath, content);
961
+ }
962
+ } catch (err) {
963
+ log2.error(`Failed to generate template: ${err.message}`);
964
+ process.exit(1);
660
965
  }
661
966
  currentStep++;
662
- log2.step(currentStep, totalSteps, "Configuring project...");
663
- const packageJsonPath = path2.join(projectPath, "package.json");
664
- if (fs2.existsSync(packageJsonPath)) {
665
- const pkg = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
666
- pkg.name = name;
667
- fs2.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
967
+ log2.step("Initializing git...");
968
+ try {
969
+ execSync("git init", { cwd: projectPath, stdio: "ignore" });
970
+ execSync("git add .", { cwd: projectPath, stdio: "ignore" });
971
+ execSync('git commit -m "Initial commit from FlexiReact CLI"', { cwd: projectPath, stdio: "ignore" });
972
+ } catch {
668
973
  }
669
- log2.success("Project configured");
670
974
  currentStep++;
671
- log2.step(currentStep, totalSteps, "Installing dependencies...");
672
- const spinner = ora({ text: "Installing packages...", color: "cyan" }).start();
975
+ log2.step("Installing dependencies...");
976
+ const spinner = ora({ text: "Installing packages...", color: "cyan", indent: 2 }).start();
673
977
  try {
674
978
  await runCommand("npm install", projectPath);
675
- spinner.succeed("Dependencies installed");
979
+ spinner.stop();
980
+ log2.success("Dependencies installed");
676
981
  } catch {
677
- spinner.fail("Failed to install dependencies");
678
- log2.warn('Run "npm install" manually in the project directory');
982
+ spinner.stop();
983
+ log2.warn('Run "npm install" manually');
679
984
  }
680
985
  currentStep++;
681
- log2.step(currentStep, totalSteps, "Linking FlexiReact...");
682
- const linkSpinner = ora({ text: "Linking framework...", color: "cyan" }).start();
986
+ log2.step("Linking FlexiReact...");
683
987
  try {
684
988
  const frameworkRoot = path2.resolve(__dirname2, "..");
685
989
  await runCommand(`npm link "${frameworkRoot}"`, projectPath);
686
- linkSpinner.succeed("FlexiReact linked");
687
990
  } catch {
688
- linkSpinner.fail("Failed to link FlexiReact");
689
991
  log2.warn('Run "npm link flexireact" manually');
690
992
  }
691
- if (options.template === "default") {
692
- currentStep++;
693
- log2.step(currentStep, totalSteps, "Setting up shadcn/ui components...");
694
- log2.success("shadcn/ui configured");
695
- }
696
- log2.blank();
697
- log2.divider();
698
- log2.blank();
699
- console.log(` ${pc2.green("\u2728")} ${pc2.bold("Success!")} Your FlexiReact app is ready.`);
700
- log2.blank();
701
- console.log(` ${pc2.dim("$")} ${pc2.cyan(`cd ${name}`)}`);
702
- console.log(` ${pc2.dim("$")} ${pc2.cyan("npm run dev")}`);
703
993
  log2.blank();
704
- console.log(` ${pc2.dim("Then open")} ${pc2.cyan("http://localhost:3000")} ${pc2.dim("in your browser.")}`);
994
+ console.log(` ${pc2.green("\u2714")} ${pc2.bold("Success!")} Created ${name} at ${projectPath}`);
705
995
  log2.blank();
706
- console.log(` ${pc2.dim("Documentation:")} ${pc2.cyan("https://github.com/flexireact/flexireact")}`);
996
+ console.log(` Next steps:`);
997
+ console.log(` ${pc2.dim("1.")} ${pc2.cyan(`cd ${name}`)}`);
998
+ console.log(` ${pc2.dim("2.")} ${pc2.cyan("npm run dev")}`);
707
999
  log2.blank();
708
1000
  }
709
- async function createDefaultTemplate(projectPath, name, useTypeScript) {
710
- const ext = useTypeScript ? "tsx" : "jsx";
711
- const configExt = useTypeScript ? "ts" : "js";
712
- const dirs = [
713
- "app/components",
714
- "app/styles",
715
- "pages/api",
716
- "public"
717
- ];
718
- for (const dir of dirs) {
719
- fs2.mkdirSync(path2.join(projectPath, dir), { recursive: true });
720
- }
721
- const packageJson = {
722
- name,
723
- version: "0.1.0",
724
- private: true,
725
- type: "module",
726
- scripts: {
727
- dev: "flexi dev",
728
- build: "flexi build",
729
- start: "flexi start",
730
- doctor: "flexi doctor"
731
- },
732
- dependencies: {
733
- react: "^19.0.0",
734
- "react-dom": "^19.0.0",
735
- "class-variance-authority": "^0.7.0",
736
- clsx: "^2.1.1",
737
- "tailwind-merge": "^2.5.5",
738
- "lucide-react": "^0.468.0"
739
- },
740
- devDependencies: {
741
- tailwindcss: "^3.4.16",
742
- postcss: "^8.4.49",
743
- autoprefixer: "^10.4.20",
744
- ...useTypeScript ? {
745
- typescript: "^5.7.2",
746
- "@types/react": "^19.0.0",
747
- "@types/react-dom": "^19.0.0",
748
- "@types/node": "^22.10.1"
749
- } : {}
750
- }
751
- };
752
- fs2.writeFileSync(
753
- path2.join(projectPath, "package.json"),
754
- JSON.stringify(packageJson, null, 2)
755
- );
756
- if (useTypeScript) {
757
- const tsconfig = {
758
- compilerOptions: {
759
- target: "ES2022",
760
- lib: ["dom", "dom.iterable", "ES2022"],
761
- allowJs: true,
762
- skipLibCheck: true,
763
- strict: true,
764
- noEmit: true,
765
- esModuleInterop: true,
766
- module: "ESNext",
767
- moduleResolution: "bundler",
768
- resolveJsonModule: true,
769
- isolatedModules: true,
770
- jsx: "react-jsx",
771
- baseUrl: ".",
772
- paths: {
773
- "@/*": ["./*"],
774
- "@/components/*": ["./app/components/*"]
775
- }
776
- },
777
- include: ["**/*.ts", "**/*.tsx"],
778
- exclude: ["node_modules"]
779
- };
780
- fs2.writeFileSync(
781
- path2.join(projectPath, "tsconfig.json"),
782
- JSON.stringify(tsconfig, null, 2)
783
- );
784
- }
785
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */
786
- export default {
787
- content: [
788
- './app/**/*.{js,ts,jsx,tsx}',
789
- './pages/**/*.{js,ts,jsx,tsx}',
790
- './components/**/*.{js,ts,jsx,tsx}',
791
- ],
792
- darkMode: 'class',
793
- theme: {
794
- extend: {
795
- colors: {
796
- border: 'hsl(var(--border))',
797
- background: 'hsl(var(--background))',
798
- foreground: 'hsl(var(--foreground))',
799
- primary: {
800
- DEFAULT: 'hsl(var(--primary))',
801
- foreground: 'hsl(var(--primary-foreground))',
802
- },
803
- secondary: {
804
- DEFAULT: 'hsl(var(--secondary))',
805
- foreground: 'hsl(var(--secondary-foreground))',
806
- },
807
- muted: {
808
- DEFAULT: 'hsl(var(--muted))',
809
- foreground: 'hsl(var(--muted-foreground))',
810
- },
811
- accent: {
812
- DEFAULT: 'hsl(var(--accent))',
813
- foreground: 'hsl(var(--accent-foreground))',
814
- },
815
- },
816
- borderRadius: {
817
- lg: 'var(--radius)',
818
- md: 'calc(var(--radius) - 2px)',
819
- sm: 'calc(var(--radius) - 4px)',
820
- },
821
- },
822
- },
823
- plugins: [],
824
- };
825
- `;
826
- fs2.writeFileSync(path2.join(projectPath, "tailwind.config.js"), tailwindConfig);
827
- const postcssConfig = `export default {
828
- plugins: {
829
- tailwindcss: {},
830
- autoprefixer: {},
831
- },
832
- };
833
- `;
834
- fs2.writeFileSync(path2.join(projectPath, "postcss.config.js"), postcssConfig);
835
- const globalsCss = `@tailwind base;
836
- @tailwind components;
837
- @tailwind utilities;
838
-
839
- @layer base {
840
- :root {
841
- --background: 222 47% 11%;
842
- --foreground: 210 40% 98%;
843
- --primary: 263 70% 50%;
844
- --primary-foreground: 210 40% 98%;
845
- --secondary: 217 33% 17%;
846
- --secondary-foreground: 210 40% 98%;
847
- --muted: 217 33% 17%;
848
- --muted-foreground: 215 20% 65%;
849
- --accent: 263 70% 50%;
850
- --accent-foreground: 210 40% 98%;
851
- --border: 217 33% 17%;
852
- --radius: 0.5rem;
853
- }
854
- }
855
-
856
- @layer base {
857
- * {
858
- @apply border-border;
859
- }
860
- body {
861
- @apply bg-background text-foreground antialiased;
862
- font-feature-settings: "rlig" 1, "calt" 1;
863
- }
864
- }
865
- `;
866
- fs2.writeFileSync(path2.join(projectPath, "app/styles/globals.css"), globalsCss);
867
- const flexiConfig = `export default {
868
- server: {
869
- port: 3000,
870
- host: 'localhost'
871
- },
872
- islands: {
873
- enabled: true
874
- },
875
- rsc: {
876
- enabled: true
877
- }
878
- };
879
- `;
880
- fs2.writeFileSync(path2.join(projectPath, `flexireact.config.${configExt}`), flexiConfig);
881
- await createComponents(projectPath, ext);
882
- await createPages(projectPath, ext);
883
- }
884
- async function createComponents(projectPath, ext) {
885
- const buttonComponent = `import React from 'react';
886
- import { cva, type VariantProps } from 'class-variance-authority';
887
- import { clsx } from 'clsx';
888
- import { twMerge } from 'tailwind-merge';
889
-
890
- function cn(...inputs${ext === "tsx" ? ": (string | undefined)[]" : ""}) {
891
- return twMerge(clsx(inputs));
892
- }
893
-
894
- const buttonVariants = cva(
895
- 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
896
- {
897
- variants: {
898
- variant: {
899
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
900
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
901
- outline: 'border border-border bg-transparent hover:bg-accent hover:text-accent-foreground',
902
- ghost: 'hover:bg-accent hover:text-accent-foreground',
903
- },
904
- size: {
905
- default: 'h-10 px-4 py-2',
906
- sm: 'h-9 rounded-md px-3',
907
- lg: 'h-11 rounded-md px-8',
908
- icon: 'h-10 w-10',
909
- },
910
- },
911
- defaultVariants: {
912
- variant: 'default',
913
- size: 'default',
914
- },
915
- }
916
- );
917
-
918
- ${ext === "tsx" ? `interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}` : ""}
919
-
920
- export function Button({ className, variant, size, ...props }${ext === "tsx" ? ": ButtonProps" : ""}) {
921
- return (
922
- <button
923
- className={cn(buttonVariants({ variant, size, className }))}
924
- {...props}
925
- />
926
- );
927
- }
928
- `;
929
- fs2.writeFileSync(path2.join(projectPath, `app/components/Button.${ext}`), buttonComponent);
930
- const cardComponent = `import React from 'react';
931
-
932
- ${ext === "tsx" ? `interface CardProps {
933
- children: React.ReactNode;
934
- className?: string;
935
- }` : ""}
936
-
937
- export function Card({ children, className = '' }${ext === "tsx" ? ": CardProps" : ""}) {
938
- return (
939
- <div className={\`rounded-lg border border-border bg-secondary/50 p-6 backdrop-blur-sm \${className}\`}>
940
- {children}
941
- </div>
942
- );
943
- }
944
-
945
- export function CardHeader({ children, className = '' }${ext === "tsx" ? ": CardProps" : ""}) {
946
- return <div className={\`mb-4 \${className}\`}>{children}</div>;
947
- }
948
-
949
- export function CardTitle({ children, className = '' }${ext === "tsx" ? ": CardProps" : ""}) {
950
- return <h3 className={\`text-xl font-semibold \${className}\`}>{children}</h3>;
951
- }
952
-
953
- export function CardDescription({ children, className = '' }${ext === "tsx" ? ": CardProps" : ""}) {
954
- return <p className={\`text-muted-foreground \${className}\`}>{children}</p>;
955
- }
956
-
957
- export function CardContent({ children, className = '' }${ext === "tsx" ? ": CardProps" : ""}) {
958
- return <div className={className}>{children}</div>;
959
- }
960
- `;
961
- fs2.writeFileSync(path2.join(projectPath, `app/components/Card.${ext}`), cardComponent);
962
- const navbarComponent = `import React from 'react';
963
-
964
- export function Navbar() {
965
- return (
966
- <nav className="fixed top-0 left-0 right-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
967
- <div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4">
968
- <a href="/" className="flex items-center gap-2 text-xl font-bold">
969
- <span className="text-2xl">\u26A1</span>
970
- <span className="bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
971
- FlexiReact
972
- </span>
973
- </a>
974
-
975
- <div className="flex items-center gap-6">
976
- <a href="/" className="text-muted-foreground hover:text-foreground transition-colors">
977
- Home
978
- </a>
979
- <a href="/docs" className="text-muted-foreground hover:text-foreground transition-colors">
980
- Docs
981
- </a>
982
- <a href="/api/hello" className="text-muted-foreground hover:text-foreground transition-colors">
983
- API
984
- </a>
985
- <a
986
- href="https://github.com/flexireact/flexireact"
987
- target="_blank"
988
- rel="noopener noreferrer"
989
- className="text-muted-foreground hover:text-foreground transition-colors"
990
- >
991
- GitHub
992
- </a>
993
- </div>
994
- </div>
995
- </nav>
996
- );
997
- }
998
- `;
999
- fs2.writeFileSync(path2.join(projectPath, `app/components/Navbar.${ext}`), navbarComponent);
1000
- }
1001
- async function createPages(projectPath, ext) {
1002
- const homePage = `import React from 'react';
1003
-
1004
- export const title = 'FlexiReact - The Modern React Framework';
1005
-
1006
- const features = [
1007
- { icon: '\u26A1', title: 'Lightning Fast', desc: 'Powered by esbuild for instant builds.' },
1008
- { icon: '\u{1F4D8}', title: 'TypeScript', desc: 'First-class TypeScript support.' },
1009
- { icon: '\u{1F3DD}\uFE0F', title: 'Islands', desc: 'Partial hydration for minimal JS.' },
1010
- { icon: '\u{1F4C1}', title: 'File Routing', desc: 'Create a file, get a route.' },
1011
- { icon: '\u{1F50C}', title: 'API Routes', desc: 'Build your API alongside frontend.' },
1012
- { icon: '\u{1F680}', title: 'SSR/SSG', desc: 'Server rendering and static generation.' },
1013
- ];
1014
-
1015
- export default function HomePage() {
1016
- return (
1017
- <div style={styles.container}>
1018
- {/* Navbar */}
1019
- <nav style={styles.nav}>
1020
- <a href="/" style={styles.logo}>
1021
- <svg style={{ width: 32, height: 32 }} viewBox="0 0 200 200" fill="none">
1022
- <defs>
1023
- <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
1024
- <stop offset="0%" stopColor="#61DAFB"/>
1025
- <stop offset="100%" stopColor="#21A1F1"/>
1026
- </linearGradient>
1027
- </defs>
1028
- <circle cx="100" cy="100" r="12" fill="url(#g)"/>
1029
- <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#g)" strokeWidth="6" transform="rotate(-30 100 100)"/>
1030
- <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#g)" strokeWidth="6" transform="rotate(30 100 100)"/>
1031
- <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#g)" strokeWidth="6" transform="rotate(90 100 100)"/>
1032
- </svg>
1033
- <span style={styles.logoText}>FlexiReact</span>
1034
- </a>
1035
- <div style={styles.navLinks}>
1036
- <a href="/" style={styles.navLink}>Home</a>
1037
- <a href="/api/hello" style={styles.navLink}>API</a>
1038
- </div>
1039
- </nav>
1040
-
1041
- {/* Hero */}
1042
- <section style={styles.hero}>
1043
- <svg style={{ width: 120, height: 120, marginBottom: 24 }} viewBox="0 0 200 200" fill="none">
1044
- <defs>
1045
- <linearGradient id="hero" x1="0%" y1="0%" x2="100%" y2="100%">
1046
- <stop offset="0%" stopColor="#61DAFB"/>
1047
- <stop offset="100%" stopColor="#21A1F1"/>
1048
- </linearGradient>
1049
- </defs>
1050
- <circle cx="100" cy="100" r="12" fill="url(#hero)"/>
1051
- <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#hero)" strokeWidth="6" transform="rotate(-30 100 100)"/>
1052
- <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#hero)" strokeWidth="6" transform="rotate(30 100 100)"/>
1053
- <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#hero)" strokeWidth="6" transform="rotate(90 100 100)"/>
1054
- <circle cx="28" cy="70" r="8" fill="url(#hero)"/>
1055
- <circle cx="172" cy="130" r="8" fill="url(#hero)"/>
1056
- <circle cx="100" cy="20" r="8" fill="url(#hero)"/>
1057
- </svg>
1058
-
1059
- <div style={styles.badge}>\u{1F680} v2.1 \u2014 TypeScript & Islands</div>
1060
-
1061
- <h1 style={styles.title}>
1062
- Build faster with<br/>
1063
- <span style={styles.titleGradient}>FlexiReact</span>
1064
- </h1>
1065
-
1066
- <p style={styles.subtitle}>
1067
- The modern React framework with SSR, SSG, Islands architecture,<br/>
1068
- and file-based routing. Simple and powerful.
1069
- </p>
1070
-
1071
- <div style={styles.buttons}>
1072
- <a href="/docs" style={styles.primaryBtn}>Get Started \u2192</a>
1073
- <a href="/api/hello" style={styles.secondaryBtn}>View API</a>
1074
- </div>
1075
- </section>
1076
-
1077
- {/* Features */}
1078
- <section style={styles.features}>
1079
- <h2 style={styles.featuresTitle}>Everything you need</h2>
1080
- <div style={styles.grid}>
1081
- {features.map((f, i) => (
1082
- <div key={i} style={styles.card}>
1083
- <div style={styles.cardIcon}>{f.icon}</div>
1084
- <h3 style={styles.cardTitle}>{f.title}</h3>
1085
- <p style={styles.cardDesc}>{f.desc}</p>
1086
- </div>
1087
- ))}
1088
- </div>
1089
- </section>
1090
-
1091
- {/* Footer */}
1092
- <footer style={styles.footer}>
1093
- Built with \u2764\uFE0F using FlexiReact
1094
- </footer>
1095
- </div>
1096
- );
1097
- }
1098
-
1099
- const styles = {
1100
- container: {
1101
- minHeight: '100vh',
1102
- background: 'linear-gradient(180deg, #0f172a 0%, #1e293b 100%)',
1103
- color: '#f8fafc',
1104
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1105
- },
1106
- nav: {
1107
- position: 'fixed',
1108
- top: 0,
1109
- left: 0,
1110
- right: 0,
1111
- height: 64,
1112
- display: 'flex',
1113
- alignItems: 'center',
1114
- justifyContent: 'space-between',
1115
- padding: '0 24px',
1116
- background: 'rgba(15, 23, 42, 0.8)',
1117
- backdropFilter: 'blur(12px)',
1118
- borderBottom: '1px solid rgba(255,255,255,0.1)',
1119
- zIndex: 100,
1120
- },
1121
- logo: {
1122
- display: 'flex',
1123
- alignItems: 'center',
1124
- gap: 8,
1125
- textDecoration: 'none',
1126
- color: '#f8fafc',
1127
- },
1128
- logoText: {
1129
- fontSize: 20,
1130
- fontWeight: 700,
1131
- background: 'linear-gradient(90deg, #61DAFB, #21A1F1)',
1132
- WebkitBackgroundClip: 'text',
1133
- WebkitTextFillColor: 'transparent',
1134
- },
1135
- navLinks: { display: 'flex', gap: 24 },
1136
- navLink: { color: '#94a3b8', textDecoration: 'none', fontSize: 14 },
1137
- hero: {
1138
- display: 'flex',
1139
- flexDirection: 'column',
1140
- alignItems: 'center',
1141
- justifyContent: 'center',
1142
- textAlign: 'center',
1143
- padding: '140px 24px 80px',
1144
- },
1145
- badge: {
1146
- background: 'rgba(99, 102, 241, 0.2)',
1147
- border: '1px solid rgba(99, 102, 241, 0.3)',
1148
- borderRadius: 9999,
1149
- padding: '8px 16px',
1150
- fontSize: 14,
1151
- marginBottom: 24,
1152
- },
1153
- title: {
1154
- fontSize: 'clamp(2.5rem, 8vw, 4.5rem)',
1155
- fontWeight: 800,
1156
- lineHeight: 1.1,
1157
- marginBottom: 24,
1158
- },
1159
- titleGradient: {
1160
- background: 'linear-gradient(90deg, #61DAFB, #a78bfa, #61DAFB)',
1161
- WebkitBackgroundClip: 'text',
1162
- WebkitTextFillColor: 'transparent',
1163
- },
1164
- subtitle: {
1165
- fontSize: 18,
1166
- color: '#94a3b8',
1167
- maxWidth: 600,
1168
- lineHeight: 1.6,
1169
- marginBottom: 32,
1170
- },
1171
- buttons: { display: 'flex', gap: 16, flexWrap: 'wrap', justifyContent: 'center' },
1172
- primaryBtn: {
1173
- background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
1174
- color: '#fff',
1175
- padding: '14px 28px',
1176
- borderRadius: 12,
1177
- textDecoration: 'none',
1178
- fontWeight: 600,
1179
- boxShadow: '0 4px 20px rgba(99, 102, 241, 0.4)',
1180
- },
1181
- secondaryBtn: {
1182
- background: 'transparent',
1183
- color: '#f8fafc',
1184
- padding: '14px 28px',
1185
- borderRadius: 12,
1186
- textDecoration: 'none',
1187
- fontWeight: 600,
1188
- border: '1px solid rgba(255,255,255,0.2)',
1189
- },
1190
- features: {
1191
- padding: '80px 24px',
1192
- maxWidth: 1200,
1193
- margin: '0 auto',
1194
- },
1195
- featuresTitle: {
1196
- fontSize: 32,
1197
- fontWeight: 700,
1198
- textAlign: 'center',
1199
- marginBottom: 48,
1200
- },
1201
- grid: {
1202
- display: 'grid',
1203
- gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
1204
- gap: 24,
1205
- },
1206
- card: {
1207
- background: 'rgba(255,255,255,0.05)',
1208
- border: '1px solid rgba(255,255,255,0.1)',
1209
- borderRadius: 16,
1210
- padding: 24,
1211
- },
1212
- cardIcon: { fontSize: 32, marginBottom: 12 },
1213
- cardTitle: { fontSize: 18, fontWeight: 600, marginBottom: 8 },
1214
- cardDesc: { fontSize: 14, color: '#94a3b8', lineHeight: 1.5 },
1215
- footer: {
1216
- textAlign: 'center',
1217
- padding: 32,
1218
- color: '#64748b',
1219
- borderTop: '1px solid rgba(255,255,255,0.1)',
1220
- },
1221
- };
1222
- `;
1223
- fs2.writeFileSync(path2.join(projectPath, "pages/index.jsx"), homePage);
1224
- const apiRoute = `/**
1225
- * API Route: /api/hello
1226
- */
1227
-
1228
- export function get(req${ext === "tsx" ? ": any" : ""}, res${ext === "tsx" ? ": any" : ""}) {
1229
- res.json({
1230
- message: 'Hello from FlexiReact API! \u{1F680}',
1231
- timestamp: new Date().toISOString(),
1232
- framework: 'FlexiReact v3.1'
1233
- });
1234
- }
1235
-
1236
- export function post(req${ext === "tsx" ? ": any" : ""}, res${ext === "tsx" ? ": any" : ""}) {
1237
- const { name } = req.body || {};
1238
- res.json({
1239
- message: \`Hello, \${name || 'World'}!\`,
1240
- timestamp: new Date().toISOString()
1241
- });
1242
- }
1243
- `;
1244
- fs2.writeFileSync(path2.join(projectPath, `pages/api/hello.${ext === "tsx" ? "ts" : "js"}`), apiRoute);
1245
- fs2.writeFileSync(path2.join(projectPath, "public/.gitkeep"), "");
1246
- }
1247
1001
  async function runDev() {
1248
- console.log(`
1249
- ${pc2.cyan(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")}
1250
- ${pc2.cyan(" \u2502")} ${pc2.cyan("\u2502")}
1251
- ${pc2.cyan(" \u2502")} ${pc2.bold(pc2.cyan("\u269B"))} ${pc2.bold(pc2.white("F L E X I R E A C T"))} ${pc2.cyan("\u2502")}
1252
- ${pc2.cyan(" \u2502")} ${pc2.cyan("\u2502")}
1253
- ${pc2.cyan(" \u2502")} ${pc2.dim("The Modern React Framework")} ${pc2.cyan("\u2502")}
1254
- ${pc2.cyan(" \u2502")} ${pc2.cyan("\u2502")}
1255
- ${pc2.cyan(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")}
1256
- `);
1002
+ console.log("");
1257
1003
  const isBuilt = __dirname2.includes("dist");
1258
1004
  const ext = isBuilt ? "js" : "ts";
1259
1005
  const startDevDir = isBuilt ? path2.join(__dirname2, "..", "core") : path2.join(__dirname2, "..", "core");
@@ -1307,30 +1053,29 @@ async function runBuild(options = {}) {
1307
1053
  mode: "production",
1308
1054
  analyze: options.analyze
1309
1055
  });
1310
- spinner.succeed("Build complete!");
1056
+ spinner.stop();
1057
+ log2.success("Build complete");
1311
1058
  log2.blank();
1312
- log2.success(`Output: ${pc2.cyan(".flexi/")}`);
1059
+ console.log(` ${pc2.dim("Output in")} ${pc2.cyan(".flexi/")}`);
1313
1060
  if (options.analyze && result?.analysis) {
1314
1061
  log2.blank();
1315
- log2.info("\u{1F4CA} Bundle Analysis:");
1062
+ console.log(` ${pc2.bold("Bundle Analysis")}`);
1316
1063
  log2.blank();
1317
1064
  const analysis = result.analysis;
1318
1065
  const sorted = Object.entries(analysis.files || {}).sort((a, b) => b[1].size - a[1].size);
1319
- console.log(pc2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1320
- console.log(` ${pc2.bold("File")}${" ".repeat(35)}${pc2.bold("Size")}`);
1321
- console.log(pc2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1066
+ console.log(` ${pc2.dim("File")} ${pc2.dim("Size")} ${pc2.dim("Gzip")}`);
1067
+ console.log(pc2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1322
1068
  for (const [file, info] of sorted.slice(0, 15)) {
1323
- const name = file.length > 35 ? "..." + file.slice(-32) : file;
1069
+ const name = file.length > 40 ? "..." + file.slice(-37) : file;
1324
1070
  const size = formatBytes(info.size);
1325
- const gzip = info.gzipSize ? pc2.dim(` (${formatBytes(info.gzipSize)} gzip)`) : "";
1326
- console.log(` ${name.padEnd(38)} ${pc2.cyan(size)}${gzip}`);
1327
- }
1328
- console.log(pc2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1329
- console.log(` ${pc2.bold("Total:")}${" ".repeat(31)} ${pc2.green(formatBytes(analysis.totalSize || 0))}`);
1330
- if (analysis.totalGzipSize) {
1331
- console.log(` ${pc2.dim("Gzipped:")}${" ".repeat(29)} ${pc2.dim(formatBytes(analysis.totalGzipSize))}`);
1071
+ const gzip = info.gzipSize ? formatBytes(info.gzipSize) : "-";
1072
+ console.log(` ${name.padEnd(43)} ${size.padEnd(12)} ${pc2.dim(gzip)}`);
1332
1073
  }
1333
1074
  log2.blank();
1075
+ const total = formatBytes(analysis.totalSize || 0);
1076
+ const totalGzip = analysis.totalGzipSize ? formatBytes(analysis.totalGzipSize) : "-";
1077
+ console.log(` ${pc2.dim("Total:")} ${pc2.bold(total)} ${pc2.dim(`(Gzip: ${totalGzip})`)}`);
1078
+ log2.blank();
1334
1079
  }
1335
1080
  } catch (error) {
1336
1081
  spinner.fail("Build failed");
@@ -1371,9 +1116,8 @@ async function runStart() {
1371
1116
  process.on("SIGTERM", () => child.kill("SIGTERM"));
1372
1117
  }
1373
1118
  async function runDoctor() {
1374
- console.log(MINI_LOGO);
1375
1119
  log2.blank();
1376
- log2.info("Checking your project...");
1120
+ console.log(` ${pc2.bold("Health Check")}`);
1377
1121
  log2.blank();
1378
1122
  const checks = [];
1379
1123
  const projectRoot = process.cwd();
@@ -1436,7 +1180,7 @@ async function runDoctor() {
1436
1180
  icon = "\u25CB";
1437
1181
  color = pc2.cyan;
1438
1182
  }
1439
- console.log(` ${color(icon)} ${check.name}: ${pc2.dim(check.message)}`);
1183
+ console.log(` ${color(icon)} ${check.name.padEnd(20)} ${pc2.dim(check.message)}`);
1440
1184
  }
1441
1185
  log2.blank();
1442
1186
  if (hasErrors) {
@@ -1450,28 +1194,19 @@ async function runDoctor() {
1450
1194
  }
1451
1195
  function showHelp() {
1452
1196
  console.log(LOGO);
1453
- console.log(` ${pc2.bold("Usage:")}`);
1454
- console.log(` ${pc2.cyan("flexi")} ${pc2.dim("<command>")} ${pc2.dim("[options]")}`);
1455
- log2.blank();
1456
- console.log(` ${pc2.bold("Commands:")}`);
1457
- console.log(` ${pc2.cyan("create")} ${pc2.dim("<name>")} Create a new FlexiReact project`);
1458
- console.log(` ${pc2.cyan("dev")} Start development server`);
1459
- console.log(` ${pc2.cyan("build")} Build for production`);
1460
- console.log(` ${pc2.cyan("start")} Start production server`);
1461
- console.log(` ${pc2.cyan("generate")} ${pc2.dim("<type>")} Generate component/page/hook/etc (alias: g)`);
1462
- console.log(` ${pc2.cyan("doctor")} Check project health`);
1463
- console.log(` ${pc2.cyan("help")} Show this help message`);
1197
+ console.log(` ${pc2.bold("Usage")}`);
1198
+ console.log(` $ flexi <command> [options]`);
1464
1199
  log2.blank();
1465
- console.log(` ${pc2.bold("Generate Types:")}`);
1466
- console.log(` ${pc2.dim("page, layout, component, hook, api, action, middleware, context")}`);
1467
- console.log(` ${pc2.dim("loading, error, not-found")}`);
1200
+ console.log(` ${pc2.bold("Commands")}`);
1201
+ console.log(` ${pc2.cyan("create")} ${pc2.dim("Create a new project")}`);
1202
+ console.log(` ${pc2.cyan("dev")} ${pc2.dim("Start development server")}`);
1203
+ console.log(` ${pc2.cyan("build")} ${pc2.dim("Build for production")}`);
1204
+ console.log(` ${pc2.cyan("start")} ${pc2.dim("Start production server")}`);
1205
+ console.log(` ${pc2.cyan("doctor")} ${pc2.dim("Check project health")}`);
1468
1206
  log2.blank();
1469
- console.log(` ${pc2.bold("Examples:")}`);
1470
- console.log(` ${pc2.dim("$")} flexi create my-app`);
1471
- console.log(` ${pc2.dim("$")} flexi dev`);
1472
- console.log(` ${pc2.dim("$")} flexi g component Button`);
1473
- console.log(` ${pc2.dim("$")} flexi g page dashboard`);
1474
- console.log(` ${pc2.dim("$")} flexi build --analyze`);
1207
+ console.log(` ${pc2.bold("Generators")}`);
1208
+ console.log(` $ flexi g <type> [name]`);
1209
+ console.log(` ${pc2.dim("Types:")} page, component, hook, api, action, middleware`);
1475
1210
  log2.blank();
1476
1211
  }
1477
1212
  async function main() {
@@ -1521,8 +1256,13 @@ async function main() {
1521
1256
  process.exit(command ? 1 : 0);
1522
1257
  }
1523
1258
  }
1524
- main().catch((error) => {
1525
- log2.error(error.message);
1526
- process.exit(1);
1527
- });
1259
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
1260
+ main().catch((error) => {
1261
+ log2.error(error.message);
1262
+ process.exit(1);
1263
+ });
1264
+ }
1265
+ export {
1266
+ createProject
1267
+ };
1528
1268
  //# sourceMappingURL=index.js.map