create-esa-stack 0.1.0 → 0.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.
Files changed (2) hide show
  1. package/dist/cli.js +23 -476
  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
  }
@@ -458,24 +455,6 @@ class x {
458
455
  }
459
456
  }
460
457
  }
461
-
462
- class BD extends x {
463
- get cursor() {
464
- return this.value ? 0 : 1;
465
- }
466
- get _value() {
467
- return this.cursor === 0;
468
- }
469
- constructor(u) {
470
- super(u, false), this.value = !!u.initialValue, this.on("value", () => {
471
- this.value = this._value;
472
- }), this.on("confirm", (F) => {
473
- this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = F, this.state = "submit", this.close();
474
- }), this.on("cursor", () => {
475
- this.value = !this.value;
476
- });
477
- }
478
- }
479
458
  var TD = Object.defineProperty;
480
459
  var jD = (e, u, F) => (u in e) ? TD(e, u, { enumerable: true, configurable: true, writable: true, value: F }) : e[u] = F;
481
460
  var MD = (e, u, F) => (jD(e, typeof u != "symbol" ? u + "" : u, F), F);
@@ -584,38 +563,6 @@ ${import_picocolors2.default.cyan($2)}
584
563
  `;
585
564
  }
586
565
  } }).prompt();
587
- var ce = (s) => {
588
- const n = s.active ?? "Yes", t = s.inactive ?? "No";
589
- return new BD({ active: n, inactive: t, initialValue: s.initialValue ?? true, render() {
590
- const i = `${import_picocolors2.default.gray(a2)}
591
- ${y2(this.state)} ${s.message}
592
- `, r2 = this.value ? n : t;
593
- switch (this.state) {
594
- case "submit":
595
- return `${i}${import_picocolors2.default.gray(a2)} ${import_picocolors2.default.dim(r2)}`;
596
- case "cancel":
597
- return `${i}${import_picocolors2.default.gray(a2)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r2))}
598
- ${import_picocolors2.default.gray(a2)}`;
599
- default:
600
- return `${i}${import_picocolors2.default.cyan(a2)} ${this.value ? `${import_picocolors2.default.green(I2)} ${n}` : `${import_picocolors2.default.dim(T2)} ${import_picocolors2.default.dim(n)}`} ${import_picocolors2.default.dim("/")} ${this.value ? `${import_picocolors2.default.dim(T2)} ${import_picocolors2.default.dim(t)}` : `${import_picocolors2.default.green(I2)} ${t}`}
601
- ${import_picocolors2.default.cyan($2)}
602
- `;
603
- }
604
- } }).prompt();
605
- };
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
566
  var he = (s = "") => {
620
567
  process.stdout.write(`${import_picocolors2.default.gray($2)} ${import_picocolors2.default.red(s)}
621
568
 
@@ -631,29 +578,6 @@ ${import_picocolors2.default.gray($2)} ${s}
631
578
 
632
579
  `);
633
580
  };
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
581
  var _2 = () => {
658
582
  const s = C ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], n = C ? 80 : 120;
659
583
  let t, i, r2 = false, o = "";
@@ -682,414 +606,37 @@ var _2 = () => {
682
606
  o = g2 ?? o;
683
607
  } };
684
608
  };
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
609
 
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
-
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");
610
+ // src/cli.ts
611
+ import { execSync } from "node:child_process";
612
+ import { existsSync } from "node:fs";
613
+ import { join } from "node:path";
614
+ async function main() {
615
+ pe("Welcome to ESA Stack Generator");
955
616
  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
- }
617
+ message: "What is the name of your project?",
618
+ placeholder: "my-app",
619
+ validate(value) {
620
+ if (value.length === 0)
621
+ return "Project name is required";
622
+ if (existsSync(join(process.cwd(), value)))
623
+ return "Directory already exists";
962
624
  }
963
625
  });
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;
977
- }
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
- });
985
- if (lD(reactQuery)) {
986
- he("Operation cancelled");
987
- return null;
988
- }
989
- const shadcn = await ce({
990
- message: "Add shadcn/ui component library?",
991
- initialValue: true
992
- });
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;
626
+ if (typeof projectName === "symbol") {
627
+ he("Operation cancelled.");
628
+ process.exit(0);
1004
629
  }
1005
- const integrations = {
1006
- reactQuery,
1007
- shadcn,
1008
- resend
1009
- };
1010
630
  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() {
631
+ s.start("Scaffolding project...");
632
+ 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
633
  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
- }
1073
- }
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);
634
+ s.stop("Starting scaffolding...");
635
+ execSync(command, { stdio: "inherit" });
636
+ ge(`Successfully created ${projectName}!`);
1091
637
  } catch (error) {
1092
- v2.error(error instanceof Error ? error.message : "An unexpected error occurred");
638
+ s.stop("Failed to scaffold project.");
639
+ console.error(error);
1093
640
  process.exit(1);
1094
641
  }
1095
642
  }
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.1",
4
4
  "description": "CLI tool to scaffold Next.js projects with ESA's preferred tech stack",
5
5
  "type": "module",
6
6
  "bin": {