@aditokmo/react-setup-cli 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.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
3
3
  import path from 'path';
4
4
  import { execSync } from 'child_process';
5
5
  import { askQuestions } from './questions.js';
6
- import { copyTemplate, patchViteConfig, finalizeViteConfig, patchAppFile, finalizeAppFile } from './utils.js';
6
+ import { copyTemplate, patchViteConfig, finalizeViteConfig, patchAppFile, finalizeAppFile, detectPackageManager } from './utils.js';
7
7
  import { collectDependencies } from './installers.js';
8
8
  import { fileURLToPath } from 'url';
9
9
  const __filename = fileURLToPath(import.meta.url);
@@ -11,8 +11,11 @@ const __dirname = path.dirname(__filename);
11
11
  async function main() {
12
12
  console.log('⚛️ Welcome to React CLI Setup by github/aditokmo');
13
13
  let projectDir = '';
14
+ const originalDirectory = process.cwd();
14
15
  try {
15
16
  const answers = await askQuestions();
17
+ const packageManager = detectPackageManager();
18
+ const installAction = packageManager === 'yarn' ? 'add' : 'install';
16
19
  projectDir = path.join(process.cwd(), answers.projectName);
17
20
  const templateRoot = path.join(__dirname, '../templates');
18
21
  const appFilePath = path.join(projectDir, 'src', 'App.tsx');
@@ -29,7 +32,8 @@ async function main() {
29
32
  }
30
33
  // State Management
31
34
  if (answers.reactQuery) {
32
- copyTemplate(path.join(templateRoot, 'state', 'react-query', 'src'), path.join(projectDir, 'src/providers'));
35
+ copyTemplate(path.join(templateRoot, 'state', 'react-query', 'src', 'provider'), path.join(projectDir, 'src/providers'));
36
+ copyTemplate(path.join(templateRoot, 'state', 'react-query', 'src', 'hook'), path.join(projectDir, 'src/modules/auth/hooks'));
33
37
  patchAppFile(appFilePath, "import { ReactQueryProvider } from './providers'", "<ReactQueryProvider>", "</ReactQueryProvider>");
34
38
  }
35
39
  if (answers.globalState === 'zustand') {
@@ -50,15 +54,17 @@ async function main() {
50
54
  }
51
55
  finalizeAppFile(appFilePath);
52
56
  finalizeViteConfig(viteConfigPath);
53
- const { dependency, devDependency, cmd } = collectDependencies(answers);
57
+ const { dependency, devDependency, cmd } = collectDependencies(answers, packageManager);
54
58
  process.chdir(projectDir);
59
+ console.log(`📦 Initializing ${packageManager} project...`);
60
+ execSync(`${packageManager} install`, { stdio: 'inherit' });
55
61
  if (dependency.length) {
56
62
  console.log('📦 Installing dependencies...');
57
- execSync(`pnpm add ${dependency.join(' ')}`, { stdio: 'inherit' });
63
+ execSync(`${packageManager} ${installAction} ${dependency.join(' ')}`, { stdio: 'inherit' });
58
64
  }
59
65
  if (devDependency.length) {
60
66
  console.log('📦 Installing dev dependencies...');
61
- execSync(`pnpm add -D ${devDependency.join(' ')}`, { stdio: 'inherit' });
67
+ execSync(`${packageManager} ${installAction} -D ${devDependency.join(' ')}`, { stdio: 'inherit' });
62
68
  }
63
69
  // Extra cmds like shadcn
64
70
  for (const command of cmd) {
@@ -66,11 +72,12 @@ async function main() {
66
72
  execSync(command, { stdio: 'inherit' });
67
73
  }
68
74
  console.log('✅ Project setup completed');
69
- console.log(`👉 cd ${answers.projectName} && pnpm run dev`);
75
+ console.log(`👉 cd ${answers.projectName} && ${packageManager} run dev`);
70
76
  }
71
77
  catch (error) {
72
78
  console.error('\n❌ Error while creating a project:');
73
79
  console.error(`👉 ${error.message}`);
80
+ process.chdir(originalDirectory);
74
81
  if (projectDir && fs.existsSync(projectDir)) {
75
82
  console.log('🧹 Cleaning... Deleting failed project installation.');
76
83
  fs.removeSync(projectDir);
@@ -1,19 +1,26 @@
1
1
  import { installers } from './mapper.js';
2
- import { axiosInstaller, shadcnInstaller } from './packages.js';
3
- export function collectDependencies(answers) {
2
+ import { axiosInstaller } from './packages.js';
3
+ export function collectDependencies(answers, packageManager) {
4
4
  const dependency = new Set();
5
5
  const devDependency = new Set();
6
6
  const cmd = [];
7
+ const dlx = packageManager === 'npm' ? 'npx --yes' : `${packageManager} dlx --yes`;
7
8
  // Default base packages
8
9
  axiosInstaller.dependency?.forEach(d => dependency.add(d));
9
10
  // Packages from answers
10
11
  Object.entries(answers).forEach(([key, value]) => {
12
+ if (Array.isArray(value))
13
+ return;
11
14
  const installer = installers[value === true ? key : value];
12
15
  installer?.dependency?.forEach(d => dependency.add(d));
13
16
  installer?.devDependency?.forEach(d => devDependency.add(d));
14
17
  });
15
18
  if (answers.shadcn && answers.style === 'tailwind') {
16
- cmd.push(...shadcnInstaller.cmd);
19
+ cmd.push(`${dlx} shadcn@latest init -d`);
20
+ if (answers.shadcnComponents && answers.shadcnComponents.length > 0) {
21
+ const componentsStr = answers.shadcnComponents.join(' ');
22
+ cmd.push(`${dlx} shadcn@latest add ${componentsStr} -y -o`);
23
+ }
17
24
  }
18
25
  return {
19
26
  dependency: Array.from(dependency),
package/dist/mapper.js CHANGED
@@ -1,4 +1,4 @@
1
- import { fontAwesomeIconsInstaller, reactFormHookInstaller, reactHotToastInstaller, reactIconsInstaller, reactQueryInstaller, reactRouterInstaller, reactToastifyInstaller, sonnerInstaller, tailwindInstaller, tanstackFormInstaller, tanstackRouterInstaller, yupInstaller, zodInstaller, zustandInstaller } from './packages.js';
1
+ import { fontAwesomeIconsInstaller, phosphorIconsInstaller, reactFormHookInstaller, reactHotToastInstaller, reactIconsInstaller, reactQueryInstaller, reactRouterInstaller, reactToastifyInstaller, sonnerInstaller, tailwindInstaller, tanstackFormInstaller, tanstackRouterInstaller, yupInstaller, zodInstaller, zustandInstaller } from './packages.js';
2
2
  export const installers = {
3
3
  'reactQuery': reactQueryInstaller,
4
4
  'zustand': zustandInstaller,
@@ -7,6 +7,7 @@ export const installers = {
7
7
  'tailwind': tailwindInstaller,
8
8
  'react-icons': reactIconsInstaller,
9
9
  'font-awesome': fontAwesomeIconsInstaller,
10
+ 'phosphor-icons': phosphorIconsInstaller,
10
11
  'react-hot-toast': reactHotToastInstaller,
11
12
  'react-toastify': reactToastifyInstaller,
12
13
  'sonner': sonnerInstaller,
package/dist/packages.js CHANGED
@@ -18,6 +18,9 @@ export const reactIconsInstaller = {
18
18
  export const fontAwesomeIconsInstaller = {
19
19
  dependency: ['@fortawesome/fontawesome-svg-core', '@fortawesome/react-fontawesome', '@fortawesome/free-solid-svg-icons', '@fortawesome/free-regular-svg-icons', '@fortawesome/free-brands-svg-icons'],
20
20
  };
21
+ export const phosphorIconsInstaller = {
22
+ dependency: ['phosphor-react'],
23
+ };
21
24
  // Toast
22
25
  export const reactHotToastInstaller = {
23
26
  dependency: ['react-hot-toast'],
@@ -28,10 +31,6 @@ export const reactToastifyInstaller = {
28
31
  export const sonnerInstaller = {
29
32
  dependency: ['sonner'],
30
33
  };
31
- // UI Libs
32
- export const shadcnInstaller = {
33
- cmd: ['pnpm dlx shadcn@latest init']
34
- };
35
34
  // Routers
36
35
  export const reactRouterInstaller = {
37
36
  dependency: ['react-router', 'react-router-dom'],
package/dist/questions.js CHANGED
@@ -1,4 +1,4 @@
1
- import { group, text, select, confirm, isCancel, cancel } from '@clack/prompts';
1
+ import { group, text, select, confirm, isCancel, cancel, multiselect } from '@clack/prompts';
2
2
  export async function askQuestions() {
3
3
  const results = await group({
4
4
  projectName: () => text({
@@ -18,11 +18,32 @@ export async function askQuestions() {
18
18
  return Promise.resolve(false);
19
19
  return confirm({ message: 'Include Shadcn UI?' });
20
20
  },
21
+ shadcnComponents: ({ results }) => {
22
+ if (!results.shadcn)
23
+ return Promise.resolve([]);
24
+ return multiselect({
25
+ message: 'Use space to select shadcn/ui components to install:',
26
+ options: [
27
+ { value: 'button', label: 'Button' },
28
+ { value: 'input', label: 'Input' },
29
+ { value: 'card', label: 'Card' },
30
+ { value: 'dialog', label: 'Dialog' },
31
+ { value: 'sheet', label: 'Sheet' },
32
+ { value: 'dropdown-menu', label: 'Dropdown Menu' },
33
+ { value: 'table', label: 'Table' },
34
+ { value: 'checkbox', label: 'Checkbox' },
35
+ { value: 'avatar', label: 'Avatar' },
36
+ { value: 'badge', label: 'Badge' },
37
+ ],
38
+ required: false,
39
+ });
40
+ },
21
41
  icons: () => select({
22
42
  message: 'Choose icon library:',
23
43
  options: [
24
44
  { value: 'react-icons', label: 'React Icons' },
25
45
  { value: 'font-awesome', label: 'Font Awesome' },
46
+ { value: 'phosphor-icons', label: 'Phosphor Icons' }
26
47
  ]
27
48
  }),
28
49
  toast: () => select({
@@ -71,6 +92,9 @@ export async function askQuestions() {
71
92
  isCancel(results.style) ||
72
93
  isCancel(results.router) ||
73
94
  isCancel(results.shadcn) ||
95
+ isCancel(results.shadcnComponents) ||
96
+ isCancel(results.icons) ||
97
+ isCancel(results.toast) ||
74
98
  isCancel(results.reactQuery)) {
75
99
  cancel('Setup cancelled.');
76
100
  process.exit(0);
@@ -78,5 +102,6 @@ export async function askQuestions() {
78
102
  return {
79
103
  ...results,
80
104
  shadcn: results.shadcn,
105
+ shadcnComponents: (results.shadcnComponents ?? []),
81
106
  };
82
107
  }
package/dist/utils.js CHANGED
@@ -72,3 +72,11 @@ export function finalizeAppFile(filePath) {
72
72
  .trim();
73
73
  fs.writeFileSync(filePath, content + '\n');
74
74
  }
75
+ export function detectPackageManager() {
76
+ const userAgent = process.env.npm_config_user_agent || '';
77
+ if (userAgent.includes('pnpm'))
78
+ return 'pnpm';
79
+ if (userAgent.includes('yarn'))
80
+ return 'yarn';
81
+ return 'npm';
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aditokmo/react-setup-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A fast React CLI to jumpstart your projects. It sets up your libraries and organizes a scalable folder structure so you can skip the configuration and go straight to coding.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,43 +1,55 @@
1
+ import { useState } from "react";
1
2
  import { getAPIErrorMessage } from "@/utils/api-error-handler";
2
- import { AuthService } from "../services"
3
- import { useMutation } from '@tanstack/react-query';
3
+ import { AuthService } from "../services";
4
4
 
5
5
  export const useAuth = () => {
6
- const login = useMutation({
7
- mutationFn: AuthService.login,
8
- onSuccess: (res) => {
9
- // Handle mutation success
10
- },
11
- onError: (error) => {
12
- const errorMessage = getAPIErrorMessage(error);
13
- console.error(errorMessage)
14
- // Display error message to user
15
- },
16
- });
6
+ const [isLoading, setIsLoading] = useState(false);
7
+ const [error, setError] = useState<string | null>(null);
17
8
 
18
- const register = useMutation({
19
- mutationFn: AuthService.register,
20
- onSuccess: (res) => {
21
- // Handle mutation success
22
- },
23
- onError: (error) => {
24
- const errorMessage = getAPIErrorMessage(error);
25
- console.error(errorMessage)
26
- // Display error message to user
27
- },
28
- });
9
+ const login = async (data: any) => {
10
+ setIsLoading(true);
11
+ setError(null);
12
+ try {
13
+ const res = await AuthService.login(data);
14
+ return res;
15
+ } catch (err) {
16
+ const errorMessage = getAPIErrorMessage(err);
17
+ setError(errorMessage);
18
+ console.error(errorMessage);
19
+ throw err;
20
+ } finally {
21
+ setIsLoading(false);
22
+ }
23
+ };
29
24
 
30
- const logout = useMutation({
31
- mutationFn: AuthService.logout,
32
- onSuccess: (res) => {
33
- // Handle mutation success
34
- },
35
- onError: (error) => {
36
- const errorMessage = getAPIErrorMessage(error);
37
- console.error(errorMessage)
38
- // Display error message to user
39
- },
40
- });
25
+ const register = async (data: any) => {
26
+ setIsLoading(true);
27
+ setError(null);
28
+ try {
29
+ const res = await AuthService.register(data);
30
+ return res;
31
+ } catch (err) {
32
+ const errorMessage = getAPIErrorMessage(err);
33
+ setError(errorMessage);
34
+ throw err;
35
+ } finally {
36
+ setIsLoading(false);
37
+ }
38
+ };
41
39
 
42
- return { login, register, logout }
43
- }
40
+ const logout = async () => {
41
+ try {
42
+ await AuthService.logout();
43
+ } catch (err) {
44
+ console.error(getAPIErrorMessage(err));
45
+ }
46
+ };
47
+
48
+ return {
49
+ login,
50
+ register,
51
+ logout,
52
+ isLoading,
53
+ error
54
+ };
55
+ };
@@ -1,9 +1,14 @@
1
- export function NotFound() {
1
+ import '@/styles/404.css';
2
+
3
+ export const NotFound = () => {
2
4
  return (
3
- <div className="flex flex-col items-center justify-center h-screen">
4
- <h1 className="text-4xl font-bold">404 - Page Not Found</h1>
5
- <p>Page you are looking, doesn't exist.</p>
6
- <a href="/" className="text-blue-500 underline mt-4">Go Back</a>
5
+ <div className="not-found-wrapper">
6
+ <div className="not-found-content">
7
+ <span>404</span>
8
+ <h1>Oops! Page Not Found</h1>
9
+ <p>The page you are looking for doesn't exist. Click button below to go to the homepage</p>
10
+ <a href="/">Back to Homepage</a>
11
+ </div>
7
12
  </div>
8
- )
9
- }
13
+ );
14
+ };
@@ -0,0 +1,105 @@
1
+ .not-found-wrapper {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ justify-content: center;
6
+ min-height: 100vh;
7
+ padding: 20px;
8
+ text-align: center;
9
+ font-family: "Poppins", sans-serif;
10
+ background: #e6edfc;
11
+ }
12
+
13
+ .not-found-content {
14
+ padding: 100px 70px;
15
+ background: #fff;
16
+ max-width: 700px;
17
+ width: 100%;
18
+ display: flex;
19
+ flex-direction: column;
20
+ justify-content: center;
21
+ align-items: center;
22
+ border-radius: 24px;
23
+ box-shadow: 0px 0px 14px -14px rgba(0, 0, 0, 0.75);
24
+ }
25
+
26
+ .not-found-content span {
27
+ color: #666;
28
+ font-weight: 700;
29
+ font-size: 6rem;
30
+ line-height: 1;
31
+ }
32
+
33
+ .not-found-content h1 {
34
+ font-size: 2.5rem;
35
+ font-weight: 600;
36
+ margin: 0;
37
+ color: #111;
38
+ }
39
+
40
+ .not-found-content p {
41
+ font-size: 1rem;
42
+ color: #666;
43
+ max-width: 360px;
44
+ width: 100%;
45
+ margin: 10px 0;
46
+ }
47
+
48
+ .not-found-content a {
49
+ font-size: 0.9rem;
50
+ color: #fff;
51
+ background-color: #37538a;
52
+ padding: 10px 30px;
53
+ margin-top: 20px;
54
+ border-radius: 10px;
55
+ transition: 0.2s;
56
+ cursor: pointer;
57
+ text-decoration: none;
58
+ display: inline-block;
59
+ }
60
+
61
+ .not-found-content a:hover {
62
+ background: #263c67;
63
+ }
64
+
65
+ @media (max-width: 768px) {
66
+ .not-found-content {
67
+ padding: 60px 40px;
68
+ max-width: 90%;
69
+ }
70
+
71
+ .not-found-content span {
72
+ font-size: 4.5rem;
73
+ margin-bottom: 20px;
74
+ }
75
+
76
+ .not-found-content h1 {
77
+ font-size: 2rem;
78
+ }
79
+ }
80
+
81
+ @media (max-width: 480px) {
82
+ .not-found-content {
83
+ padding: 40px 20px;
84
+ border-radius: 16px;
85
+ }
86
+
87
+ .not-found-content span {
88
+ font-size: 3.5rem;
89
+ margin-bottom: 20px;
90
+ }
91
+
92
+ .not-found-content h1 {
93
+ font-size: 1.5rem;
94
+ }
95
+
96
+ .not-found-content p {
97
+ font-size: 0.9rem;
98
+ }
99
+
100
+ .not-found-content a {
101
+ width: 100%;
102
+ box-sizing: border-box;
103
+ text-align: center;
104
+ }
105
+ }
@@ -0,0 +1,43 @@
1
+ import { getAPIErrorMessage } from "@/utils/api-error-handler";
2
+ import { AuthService } from "../services"
3
+ import { useMutation } from '@tanstack/react-query';
4
+
5
+ export const useAuth = () => {
6
+ const login = useMutation({
7
+ mutationFn: AuthService.login,
8
+ onSuccess: (res) => {
9
+ // Handle mutation success
10
+ },
11
+ onError: (error) => {
12
+ const errorMessage = getAPIErrorMessage(error);
13
+ console.error(errorMessage)
14
+ // Display error message to user
15
+ },
16
+ });
17
+
18
+ const register = useMutation({
19
+ mutationFn: AuthService.register,
20
+ onSuccess: (res) => {
21
+ // Handle mutation success
22
+ },
23
+ onError: (error) => {
24
+ const errorMessage = getAPIErrorMessage(error);
25
+ console.error(errorMessage)
26
+ // Display error message to user
27
+ },
28
+ });
29
+
30
+ const logout = useMutation({
31
+ mutationFn: AuthService.logout,
32
+ onSuccess: (res) => {
33
+ // Handle mutation success
34
+ },
35
+ onError: (error) => {
36
+ const errorMessage = getAPIErrorMessage(error);
37
+ console.error(errorMessage)
38
+ // Display error message to user
39
+ },
40
+ });
41
+
42
+ return { login, register, logout }
43
+ }
@@ -1,10 +1,17 @@
1
1
  import { useState } from "react";
2
- import { QueryClientProvider } from '@tanstack/react-query';
3
- import { QueryClient } from '@tanstack/react-query';
2
+ import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
4
3
  import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
5
4
 
6
5
  export function ReactQueryProvider({ children }: { children: React.ReactNode }) {
7
- const [queryClient] = useState(() => new QueryClient());
6
+ const [queryClient] = useState(() => new QueryClient({
7
+ defaultOptions: {
8
+ queries: {
9
+ refetchOnWindowFocus: false,
10
+ retry: 1,
11
+ staleTime: 5 * 1000,
12
+ },
13
+ },
14
+ }));
8
15
 
9
16
  return (
10
17
  <QueryClientProvider client={queryClient}>