create-esa-stack 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 (2) hide show
  1. package/dist/cli.js +36 -435
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -367,9 +367,6 @@ function hD(e, u) {
367
367
  return s;
368
368
  }
369
369
  var V = Symbol("clack:cancel");
370
- function lD(e) {
371
- return e === V;
372
- }
373
370
  function v(e, u) {
374
371
  e.isTTY && e.setRawMode(u);
375
372
  }
@@ -603,19 +600,6 @@ ${import_picocolors2.default.cyan($2)}
603
600
  }
604
601
  } }).prompt();
605
602
  };
606
- var R2 = (s) => s.replace(ye(), "");
607
- var me = (s = "", n = "") => {
608
- const t = `
609
- ${s}
610
- `.split(`
611
- `), i = R2(n).length, r2 = Math.max(t.reduce((c2, l2) => (l2 = R2(l2), l2.length > c2 ? l2.length : c2), 0), i) + 2, o = t.map((c2) => `${import_picocolors2.default.gray(a2)} ${import_picocolors2.default.dim(c2)}${" ".repeat(r2 - R2(c2).length)}${import_picocolors2.default.gray(a2)}`).join(`
612
- `);
613
- process.stdout.write(`${import_picocolors2.default.gray(a2)}
614
- ${import_picocolors2.default.green(M2)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(G.repeat(Math.max(r2 - i - 1, 1)) + H)}
615
- ${o}
616
- ${import_picocolors2.default.gray(ee + G.repeat(r2 + 2) + te)}
617
- `);
618
- };
619
603
  var he = (s = "") => {
620
604
  process.stdout.write(`${import_picocolors2.default.gray($2)} ${import_picocolors2.default.red(s)}
621
605
 
@@ -631,29 +615,6 @@ ${import_picocolors2.default.gray($2)} ${s}
631
615
 
632
616
  `);
633
617
  };
634
- var v2 = { message: (s = "", { symbol: n = import_picocolors2.default.gray(a2) } = {}) => {
635
- const t = [`${import_picocolors2.default.gray(a2)}`];
636
- if (s) {
637
- const [i, ...r2] = s.split(`
638
- `);
639
- t.push(`${n} ${i}`, ...r2.map((o) => `${import_picocolors2.default.gray(a2)} ${o}`));
640
- }
641
- process.stdout.write(`${t.join(`
642
- `)}
643
- `);
644
- }, info: (s) => {
645
- v2.message(s, { symbol: import_picocolors2.default.blue(se) });
646
- }, success: (s) => {
647
- v2.message(s, { symbol: import_picocolors2.default.green(re) });
648
- }, step: (s) => {
649
- v2.message(s, { symbol: import_picocolors2.default.green(M2) });
650
- }, warn: (s) => {
651
- v2.message(s, { symbol: import_picocolors2.default.yellow(ie) });
652
- }, warning: (s) => {
653
- v2.warn(s);
654
- }, error: (s) => {
655
- v2.message(s, { symbol: import_picocolors2.default.red(ne) });
656
- } };
657
618
  var _2 = () => {
658
619
  const s = C ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], n = C ? 80 : 120;
659
620
  let t, i, r2 = false, o = "";
@@ -682,414 +643,54 @@ var _2 = () => {
682
643
  o = g2 ?? o;
683
644
  } };
684
645
  };
685
- function ye() {
686
- const s = ["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"].join("|");
687
- return new RegExp(s, "g");
688
- }
689
-
690
- // src/utils/process.ts
691
- import { spawn } from "child_process";
692
- async function executeCommand(command, args, options = {}) {
693
- return new Promise((resolve) => {
694
- const child = spawn(command, args, {
695
- cwd: options.cwd || process.cwd(),
696
- env: { ...process.env, ...options.env },
697
- stdio: "inherit"
698
- });
699
- let output = "";
700
- let errorOutput = "";
701
- child.on("close", (code) => {
702
- resolve({
703
- success: code === 0,
704
- code,
705
- output,
706
- error: errorOutput || undefined
707
- });
708
- });
709
- child.on("error", (error) => {
710
- resolve({
711
- success: false,
712
- code: null,
713
- output,
714
- error: error.message
715
- });
716
- });
717
- });
718
- }
719
- async function runCreateNextApp(projectName, options = {}) {
720
- const args = [
721
- "create-next-app@latest",
722
- projectName,
723
- "--ts",
724
- "--tailwind",
725
- "--biome",
726
- "--react-compiler",
727
- "--app",
728
- "--use-pnpm",
729
- "--turbopack",
730
- "--yes"
731
- ];
732
- return executeCommand("npx", args, options);
733
- }
734
-
735
- // src/generators/nextjs.ts
736
- async function generateNextJsProject(config) {
737
- const s = _2();
738
- s.start(`Creating Next.js project: ${config.projectName}...`);
739
- try {
740
- const result = await runCreateNextApp(config.projectName);
741
- if (result.success) {
742
- s.stop(`✓ Project created successfully!`);
743
- return true;
744
- } else {
745
- s.stop(`✗ Failed to create project`);
746
- if (result.error) {
747
- v2.error(result.error);
748
- }
749
- return false;
750
- }
751
- } catch (error) {
752
- s.stop(`✗ Failed to create project`);
753
- v2.error(error instanceof Error ? error.message : "Unknown error occurred");
754
- return false;
755
- }
756
- }
757
-
758
- // src/generators/react-query.ts
759
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
760
- import { join } from "path";
761
- async function setupReactQuery(config) {
762
- const s = _2();
763
- s.start("Setting up @tanstack/react-query...");
764
- try {
765
- const projectPath = config.projectPath;
766
- s.message("Installing @tanstack/react-query...");
767
- const installResult = await executeCommand("pnpm", ["add", "@tanstack/react-query"], { cwd: projectPath });
768
- if (!installResult.success) {
769
- s.stop("✗ Failed to install @tanstack/react-query");
770
- return false;
771
- }
772
- s.message("Creating RootProviders component...");
773
- const providersDir = join(projectPath, "src", "components", "templates", "RootProviders");
774
- if (!existsSync(providersDir)) {
775
- mkdirSync(providersDir, { recursive: true });
776
- }
777
- const providersPath = join(providersDir, "RootProviders.component.tsx");
778
- const providersTemplate = `'use client'
779
-
780
- // Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top
781
- import {
782
- isServer,
783
- QueryClient,
784
- QueryClientProvider,
785
- } from '@tanstack/react-query'
786
- import { useState } from 'react'
787
-
788
- function makeQueryClient() {
789
- return new QueryClient({
790
- defaultOptions: {
791
- queries: {
792
- // With SSR, we usually want to set some default staleTime
793
- // above 0 to avoid refetching immediately on the client
794
- staleTime: 60 * 1000,
795
- },
796
- },
797
- })
798
- }
799
-
800
- let browserQueryClient: QueryClient | undefined = undefined
801
-
802
- function getQueryClient() {
803
- if (isServer) {
804
- // Server: always make a new query client
805
- return makeQueryClient()
806
- } else {
807
- // Browser: make a new query client if we don't already have one
808
- // This is very important, so we don't re-make a new client if React
809
- // suspends during the initial render. This may not be needed if we
810
- // have a suspense boundary BELOW the creation of the query client
811
- if (!browserQueryClient) browserQueryClient = makeQueryClient()
812
- return browserQueryClient
813
- }
814
- }
815
-
816
- export default function RootProviders({ children }: { children: React.ReactNode }) {
817
- // NOTE: Avoid useState when initializing the query client if you don't
818
- // have a suspense boundary between this and the code that may
819
- // suspend because React will throw away the client on the initial
820
- // render if it suspends and there is no boundary
821
- const queryClient = getQueryClient()
822
-
823
- return (
824
- <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
825
- )
826
- }
827
- `;
828
- writeFileSync(providersPath, providersTemplate);
829
- s.message("Updating root layout...");
830
- const layoutPath = join(projectPath, "src", "app", "layout.tsx");
831
- if (existsSync(layoutPath)) {
832
- let layoutContent = readFileSync(layoutPath, "utf-8");
833
- const importStatement = `import RootProviders from "@/components/templates/RootProviders/RootProviders.component";
834
- `;
835
- if (!layoutContent.includes("RootProviders")) {
836
- const importRegex = /(import.*from.*;\n)+/;
837
- layoutContent = layoutContent.replace(importRegex, (match) => match + importStatement);
838
- layoutContent = layoutContent.replace(/(<body[^>]*>)([\s\S]*?)(<\/body>)/, (match, openTag, content, closeTag) => {
839
- if (content.includes("<RootProviders>")) {
840
- return match;
841
- }
842
- return `${openTag}
843
- <RootProviders>${content}</RootProviders>
844
- ${closeTag}`;
845
- });
846
- writeFileSync(layoutPath, layoutContent);
847
- }
848
- }
849
- s.stop("✓ React Query setup complete!");
850
- return true;
851
- } catch (error) {
852
- s.stop("✗ Failed to set up React Query");
853
- v2.error(error instanceof Error ? error.message : "Unknown error occurred");
854
- return false;
855
- }
856
- }
857
-
858
- // src/generators/resend.ts
859
- async function setupResend(config) {
860
- const s = _2();
861
- s.start("Setting up Resend...");
862
- try {
863
- const projectPath = config.projectPath;
864
- s.message("Installing resend package...");
865
- const result = await executeCommand("pnpm", ["add", "resend"], { cwd: projectPath });
866
- if (result.success) {
867
- s.stop("✓ Resend setup complete!");
868
- return true;
869
- } else {
870
- s.stop("✗ Failed to install Resend");
871
- if (result.error) {
872
- v2.error(result.error);
873
- }
874
- return false;
875
- }
876
- } catch (error) {
877
- s.stop("✗ Failed to set up Resend");
878
- v2.error(error instanceof Error ? error.message : "Unknown error occurred");
879
- return false;
880
- }
881
- }
882
646
 
883
- // src/generators/shadcn.ts
884
- async function setupShadcn(config) {
885
- const s = _2();
886
- s.start("Setting up shadcn/ui...");
887
- try {
888
- const projectPath = config.projectPath;
889
- s.message("Running shadcn init...");
890
- const result = await executeCommand("pnpm", ["dlx", "shadcn@latest", "init", "-y"], { cwd: projectPath });
891
- if (result.success) {
892
- s.stop("✓ shadcn/ui setup complete!");
893
- return true;
894
- } else {
895
- s.stop("✗ Failed to set up shadcn/ui");
896
- if (result.error) {
897
- v2.error(result.error);
898
- }
899
- return false;
900
- }
901
- } catch (error) {
902
- s.stop("✗ Failed to set up shadcn/ui");
903
- v2.error(error instanceof Error ? error.message : "Unknown error occurred");
904
- return false;
905
- }
906
- }
907
-
908
- // src/utils/file-system.ts
909
- import { join as join2, resolve } from "path";
910
- function resolvePath(path) {
911
- return resolve(process.cwd(), path);
912
- }
913
- function getProjectPath(projectName, targetDir) {
914
- if (targetDir) {
915
- return resolvePath(join2(targetDir, projectName));
916
- }
917
- return resolvePath(projectName);
918
- }
919
-
920
- // src/utils/validators.ts
921
- import { existsSync as existsSync2 } from "fs";
922
- function isValidProjectName(name) {
923
- const validNameRegex = /^(?![@._])[a-z0-9-_]+$/;
924
- if (!name || name.length === 0) {
925
- return false;
926
- }
927
- if (name.length > 214) {
928
- return false;
929
- }
930
- return validNameRegex.test(name);
931
- }
932
- function getProjectNameValidationMessage(name) {
933
- if (!name || name.length === 0) {
934
- return "Project name cannot be empty";
935
- }
936
- if (name.length > 214) {
937
- return "Project name must be 214 characters or less";
938
- }
939
- if (name.startsWith(".") || name.startsWith("_")) {
940
- return "Project name cannot start with . or _";
941
- }
942
- if (!/^[a-z0-9-_]+$/.test(name)) {
943
- return "Project name can only contain lowercase letters, numbers, hyphens, and underscores";
944
- }
945
- return "Invalid project name";
946
- }
947
- function directoryExists(path) {
948
- return existsSync2(path);
949
- }
950
-
951
- // src/prompts.ts
952
- async function promptForProjectConfig() {
953
- console.log("");
954
- pe("\uD83D\uDE80 Create ESA Stack");
647
+ // src/cli.ts
648
+ import { execSync } from "node:child_process";
649
+ import { existsSync } from "node:fs";
650
+ import { join } from "node:path";
651
+ async function main() {
652
+ pe("Welcome to ESA Stack Generator");
955
653
  const projectName = await ae({
956
- message: "What is your project name?",
957
- placeholder: "my-awesome-app",
958
- validate: (value) => {
959
- if (!isValidProjectName(value)) {
960
- return getProjectNameValidationMessage(value);
961
- }
962
- }
963
- });
964
- if (lD(projectName)) {
965
- he("Operation cancelled");
966
- return null;
967
- }
968
- const projectPath = getProjectPath(projectName);
969
- if (directoryExists(projectPath)) {
970
- const shouldContinue = await ce({
971
- message: `Directory "${projectName}" already exists. Continue anyway?`,
972
- initialValue: false
973
- });
974
- if (lD(shouldContinue) || !shouldContinue) {
975
- he("Operation cancelled");
976
- return null;
654
+ message: "What is the name of your project?",
655
+ placeholder: "my-app",
656
+ validate(value) {
657
+ if (value.length === 0)
658
+ return "Project name is required";
659
+ if (existsSync(join(process.cwd(), value)))
660
+ return "Directory already exists";
977
661
  }
978
- }
979
- console.log("");
980
- me("Select optional integrations to add to your project", "Optional Integrations");
981
- const reactQuery = await ce({
982
- message: "Add @tanstack/react-query for data fetching?",
983
- initialValue: true
984
662
  });
985
- if (lD(reactQuery)) {
986
- he("Operation cancelled");
987
- return null;
663
+ if (typeof projectName === "symbol") {
664
+ he("Operation cancelled.");
665
+ process.exit(0);
988
666
  }
989
- const shadcn = await ce({
990
- message: "Add shadcn/ui component library?",
667
+ const installShadcn = await ce({
668
+ message: "Do you want to install shadcn/ui?",
991
669
  initialValue: true
992
670
  });
993
- if (lD(shadcn)) {
994
- he("Operation cancelled");
995
- return null;
996
- }
997
- const resend = await ce({
998
- message: "Add Resend for email API?",
999
- initialValue: false
1000
- });
1001
- if (lD(resend)) {
1002
- he("Operation cancelled");
1003
- return null;
671
+ if (typeof installShadcn === "symbol") {
672
+ he("Operation cancelled.");
673
+ process.exit(0);
1004
674
  }
1005
- const integrations = {
1006
- reactQuery,
1007
- shadcn,
1008
- resend
1009
- };
1010
675
  const s = _2();
1011
- s.start("Preparing to create your project...");
1012
- await new Promise((resolve2) => setTimeout(resolve2, 500));
1013
- s.stop("Configuration ready!");
1014
- const integrationsText = [
1015
- integrations.reactQuery && " ✓ @tanstack/react-query",
1016
- integrations.shadcn && " ✓ shadcn/ui",
1017
- integrations.resend && " ✓ resend"
1018
- ].filter(Boolean).join(`
1019
- `);
1020
- console.log("");
1021
- me(`Project: ${projectName}
1022
- Path: ${projectPath}
1023
-
1024
- Default Tech Stack:
1025
- ✓ Next.js (App Router)
1026
- ✓ TypeScript
1027
- ✓ Tailwind CSS
1028
- ✓ Biome.js
1029
- ✓ React Compiler
1030
- ✓ Turbopack
1031
- ✓ pnpm${integrationsText ? `
1032
-
1033
- Optional Integrations:
1034
- ${integrationsText}` : ""}`, "Configuration");
1035
- const shouldProceed = await ce({
1036
- message: "Create project with this configuration?",
1037
- initialValue: true
1038
- });
1039
- if (lD(shouldProceed) || !shouldProceed) {
1040
- he("Operation cancelled");
1041
- return null;
1042
- }
1043
- return {
1044
- projectName,
1045
- projectPath,
1046
- integrations
1047
- };
1048
- }
1049
-
1050
- // src/cli.ts
1051
- async function main() {
676
+ s.start("Scaffolding project...");
677
+ const command = `npx create-next-app@latest ${projectName} --biome --ts --tailwind --react-compiler --app --src-dir --import-alias "@/*" --use-pnpm --turbopack --skip-install --yes`;
1052
678
  try {
1053
- const config = await promptForProjectConfig();
1054
- if (!config) {
1055
- process.exit(0);
1056
- }
1057
- const success = await generateNextJsProject(config);
1058
- if (!success) {
1059
- ge(" Project creation failed");
1060
- process.exit(1);
1061
- }
1062
- if (config.integrations.reactQuery) {
1063
- const reactQuerySuccess = await setupReactQuery(config);
1064
- if (!reactQuerySuccess) {
1065
- v2.warn("React Query setup failed, but project was created successfully");
1066
- }
1067
- }
1068
- if (config.integrations.shadcn) {
1069
- const shadcnSuccess = await setupShadcn(config);
1070
- if (!shadcnSuccess) {
1071
- v2.warn("shadcn/ui setup failed, but project was created successfully");
1072
- }
679
+ s.stop("Starting scaffolding...");
680
+ execSync(command, { stdio: "inherit" });
681
+ if (installShadcn) {
682
+ const projectPath = join(process.cwd(), projectName);
683
+ console.log(`
684
+ Setting up shadcn/ui...`);
685
+ console.log("Installing dependencies...");
686
+ execSync("pnpm install", { stdio: "inherit", cwd: projectPath });
687
+ console.log("Initializing shadcn/ui...");
688
+ execSync("npx shadcn@latest init", { stdio: "inherit", cwd: projectPath });
1073
689
  }
1074
- if (config.integrations.resend) {
1075
- const resendSuccess = await setupResend(config);
1076
- if (!resendSuccess) {
1077
- v2.warn("Resend setup failed, but project was created successfully");
1078
- }
1079
- }
1080
- console.log("");
1081
- ge("\uD83C\uDF89 Project created successfully!");
1082
- const nextSteps = [
1083
- `cd ${config.projectName}`,
1084
- config.integrations.reactQuery ? "# React Query is ready to use!" : "",
1085
- "pnpm dev"
1086
- ].filter(Boolean).join(`
1087
- `);
1088
- console.log("");
1089
- me(nextSteps, "Next steps");
1090
- process.exit(0);
690
+ ge(`Successfully created ${projectName}!`);
1091
691
  } catch (error) {
1092
- v2.error(error instanceof Error ? error.message : "An unexpected error occurred");
692
+ s.stop("Failed to scaffold project.");
693
+ console.error(error);
1093
694
  process.exit(1);
1094
695
  }
1095
696
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-esa-stack",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI tool to scaffold Next.js projects with ESA's preferred tech stack",
5
5
  "type": "module",
6
6
  "bin": {