create-modern-react 2.3.5 → 2.3.7

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/bin/index.js CHANGED
@@ -9,10 +9,11 @@ program
9
9
  .description(
10
10
  'Create production-ready React + TypeScript + Tailwind applications in seconds'
11
11
  )
12
- .version('2.0.0')
12
+ .version('2.3.5')
13
13
  .argument('[project-name]', 'name of the project')
14
14
  .option('--skip-install', 'skip dependency installation')
15
15
  .option('--skip-git', 'skip git initialization')
16
+ .option('--no-scripts', 'skip all lifecycle scripts (skills extraction + npm scripts)')
16
17
  .action(async (projectName, options) => {
17
18
  try {
18
19
  await createProject(projectName, options);
package/lib/install.js CHANGED
@@ -69,27 +69,33 @@ async function getAvailablePackageManager(preferredManager) {
69
69
  }
70
70
 
71
71
  // Get install command and args for package manager
72
- function getInstallCommand(packageManager) {
72
+ function getInstallCommand(packageManager, noScripts = false) {
73
+ const ignoreScriptsFlag = noScripts ? ['--ignore-scripts'] : [];
74
+
73
75
  switch (packageManager) {
74
76
  case 'yarn':
75
- return { command: 'yarn', args: ['install'] };
77
+ return { command: 'yarn', args: ['install', ...ignoreScriptsFlag] };
76
78
  case 'pnpm':
77
- return { command: 'pnpm', args: ['install'] };
79
+ return { command: 'pnpm', args: ['install', ...ignoreScriptsFlag] };
78
80
  case 'npm':
79
81
  default:
80
- return { command: 'npm', args: ['install'] };
82
+ return { command: 'npm', args: ['install', ...ignoreScriptsFlag] };
81
83
  }
82
84
  }
83
85
 
84
86
  async function installDependencies(config) {
85
- const { projectPath, packageManager: preferredManager, projectName } = config;
87
+ const { projectPath, packageManager: preferredManager, projectName, noScripts } = config;
86
88
 
87
89
  console.log(chalk.blue('\nšŸ“¦ Installing dependencies...'));
88
90
 
89
91
  try {
90
92
  // Get the best available package manager
91
93
  const availableManager = await getAvailablePackageManager(preferredManager);
92
- const { command, args } = getInstallCommand(availableManager);
94
+ const { command, args } = getInstallCommand(availableManager, noScripts);
95
+
96
+ if (noScripts) {
97
+ console.log(chalk.yellow('āš ļø Running with --ignore-scripts (lifecycle scripts disabled)'));
98
+ }
93
99
 
94
100
  // Update config with actual package manager used
95
101
  config.actualPackageManager = availableManager;
package/lib/prompts.js CHANGED
@@ -163,7 +163,9 @@ async function createProject(projectName, options) {
163
163
  useHusky: optionalFeatures.includes('husky'),
164
164
  // Flags
165
165
  initGit,
166
- installDeps
166
+ installDeps,
167
+ // Security: skip lifecycle scripts when --no-scripts is passed
168
+ noScripts: options.scripts === false
167
169
  };
168
170
 
169
171
  // ─────────────────────────────────────────────────────────────
@@ -178,7 +180,11 @@ async function createProject(projectName, options) {
178
180
  console.log(chalk.cyan('│') + chalk.gray(` ${config.useAntd ? 'Ant Design v5' : 'Shadcn/ui components'} `) + chalk.cyan('│'));
179
181
  console.log(chalk.cyan('│') + chalk.gray(' Wouter routing + Axios ') + chalk.cyan('│'));
180
182
  console.log(chalk.cyan('│') + chalk.gray(' Lucide icons + ESLint + Prettier ') + chalk.cyan('│'));
181
- console.log(chalk.cyan('│') + chalk.magenta(' šŸ¤– Claude Code AI Skills ') + chalk.cyan('│'));
183
+ if (config.noScripts) {
184
+ console.log(chalk.cyan('│') + chalk.yellow(' šŸ”’ Claude Code AI Skills (skipped) ') + chalk.cyan('│'));
185
+ } else {
186
+ console.log(chalk.cyan('│') + chalk.magenta(' šŸ¤– Claude Code AI Skills ') + chalk.cyan('│'));
187
+ }
182
188
  console.log(chalk.cyan('ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤'));
183
189
  console.log(chalk.cyan('│') + chalk.white(' Optional Features: ') + chalk.cyan('│'));
184
190
  console.log(chalk.cyan('│') + ` Redux Toolkit: ${config.useRedux ? chalk.green('āœ“') : chalk.gray('āœ—')} ` + chalk.cyan('│'));
@@ -204,6 +210,14 @@ async function createProject(projectName, options) {
204
210
  const runCmd = pm === 'npm' ? 'npm run' : pm;
205
211
 
206
212
  console.log(chalk.green.bold(`\nāœ… Project "${projectName}" created successfully!\n`));
213
+
214
+ // Show note if --no-scripts was used
215
+ if (config.noScripts) {
216
+ console.log(chalk.yellow('ā„¹ļø Note: --no-scripts was used\n'));
217
+ console.log(chalk.gray(' • Claude Code AI skills were not included'));
218
+ console.log(chalk.gray(' • Dependency lifecycle scripts were skipped\n'));
219
+ }
220
+
207
221
  console.log(chalk.white('Next steps:\n'));
208
222
  console.log(chalk.gray(` cd ${projectName}`));
209
223
  if (!installDeps) {
package/lib/setup.js CHANGED
@@ -20,9 +20,16 @@ async function setupProject(config) {
20
20
  const templatePath = path.join(__dirname, '../templates/base');
21
21
  await fs.copy(templatePath, projectPath);
22
22
 
23
- // Step 2.5: Extract Claude Code AI skills from archive
24
- console.log(chalk.gray(' Including Claude Code AI skills (.claude/skills)...'));
25
- await extractSkillsArchive(projectPath);
23
+ // Step 2.5: Extract Claude Code AI skills from archive (unless --no-scripts)
24
+ if (config.noScripts) {
25
+ console.log(chalk.gray(' Skipping Claude Code AI skills (--no-scripts)...'));
26
+ // Remove the archive since we're not extracting it
27
+ const archivePath = path.join(projectPath, '.claude/skills.tar.gz');
28
+ await fs.remove(archivePath);
29
+ } else {
30
+ console.log(chalk.gray(' Including Claude Code AI skills (.claude/skills)...'));
31
+ await extractSkillsArchive(projectPath);
32
+ }
26
33
 
27
34
  // Step 3: Handle Antd vs Shadcn/ui
28
35
  if (config.useAntd) {
@@ -31,6 +38,8 @@ async function setupProject(config) {
31
38
  await fs.remove(path.join(projectPath, 'components.json'));
32
39
  await copyOptionalTemplate('antd', projectPath);
33
40
  await updateProvidersForAntd(projectPath);
41
+ // Replace components that use ~/components/ui with antd-compatible versions
42
+ await replaceComponentsForAntd(projectPath);
34
43
  }
35
44
 
36
45
  // Step 4: Copy optional feature templates
@@ -38,6 +47,10 @@ async function setupProject(config) {
38
47
  console.log(chalk.gray(' Adding Redux Toolkit + Redux Persist...'));
39
48
  await copyOptionalTemplate('redux', projectPath);
40
49
  await updateProvidersForRedux(projectPath, config.useAntd);
50
+ // If antd is selected, replace redux provider with antd-compatible version
51
+ if (config.useAntd) {
52
+ await replaceReduxProviderForAntd(projectPath);
53
+ }
41
54
  }
42
55
 
43
56
  if (config.useForms) {
@@ -203,6 +216,50 @@ export { ThemeProvider, useTheme } from './theme-provider';
203
216
  await fs.ensureDir(path.join(projectPath, 'src/styles'));
204
217
  }
205
218
 
219
+ /**
220
+ * Replace components that import from ~/components/ui with antd-compatible versions
221
+ * These files use shadcn Button, Card, Skeleton which are removed when antd is selected
222
+ */
223
+ async function replaceComponentsForAntd(projectPath) {
224
+ const replacementsPath = path.join(
225
+ __dirname,
226
+ '../templates/optional/antd-replacements'
227
+ );
228
+
229
+ // Files to replace: [source in antd-replacements, target in project]
230
+ const filesToReplace = [
231
+ ['components/layout/error-boundary.tsx', 'src/components/layout/error-boundary.tsx'],
232
+ ['routes/index.tsx', 'src/routes/index.tsx'],
233
+ ['screens/home/index.tsx', 'src/screens/home/index.tsx'],
234
+ ['screens/not-found/index.tsx', 'src/screens/not-found/index.tsx'],
235
+ ];
236
+
237
+ for (const [source, target] of filesToReplace) {
238
+ const sourcePath = path.join(replacementsPath, source);
239
+ const targetPath = path.join(projectPath, target);
240
+
241
+ if (await fs.pathExists(sourcePath)) {
242
+ await fs.copy(sourcePath, targetPath);
243
+ }
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Replace Redux provider with antd-compatible version
249
+ * The base provider imports Skeleton from ~/components/ui which is removed for antd
250
+ */
251
+ async function replaceReduxProviderForAntd(projectPath) {
252
+ const antdProviderPath = path.join(
253
+ __dirname,
254
+ '../templates/optional/antd-replacements/redux/provider.tsx'
255
+ );
256
+ const targetPath = path.join(projectPath, 'src/redux/provider.tsx');
257
+
258
+ if (await fs.pathExists(antdProviderPath)) {
259
+ await fs.copy(antdProviderPath, targetPath);
260
+ }
261
+ }
262
+
206
263
  /**
207
264
  * Update providers/index.tsx to include Redux Provider
208
265
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-modern-react",
3
- "version": "2.3.5",
3
+ "version": "2.3.7",
4
4
  "description": "Create production-ready React + TypeScript + Tailwind applications in seconds",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
@@ -0,0 +1,63 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ import { AlertTriangle, RefreshCw } from 'lucide-react';
3
+ import { Button } from 'antd';
4
+
5
+ interface Props {
6
+ children: ReactNode;
7
+ fallback?: ReactNode;
8
+ }
9
+
10
+ interface State {
11
+ hasError: boolean;
12
+ error: Error | null;
13
+ }
14
+
15
+ export class ErrorBoundary extends Component<Props, State> {
16
+ public state: State = {
17
+ hasError: false,
18
+ error: null,
19
+ };
20
+
21
+ public static getDerivedStateFromError(error: Error): State {
22
+ return { hasError: true, error };
23
+ }
24
+
25
+ public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
26
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
27
+ }
28
+
29
+ private handleRetry = () => {
30
+ this.setState({ hasError: false, error: null });
31
+ };
32
+
33
+ public render() {
34
+ if (this.state.hasError) {
35
+ if (this.props.fallback) {
36
+ return this.props.fallback;
37
+ }
38
+
39
+ return (
40
+ <div className="flex min-h-[400px] flex-col items-center justify-center gap-4 p-8">
41
+ <div className="flex h-16 w-16 items-center justify-center rounded-full bg-red-50 dark:bg-red-950">
42
+ <AlertTriangle className="h-8 w-8 text-red-500" />
43
+ </div>
44
+ <div className="text-center">
45
+ <h2 className="text-xl font-semibold">Something went wrong</h2>
46
+ <p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
47
+ {this.state.error?.message || 'An unexpected error occurred'}
48
+ </p>
49
+ </div>
50
+ <Button
51
+ onClick={this.handleRetry}
52
+ icon={<RefreshCw className="h-4 w-4" />}
53
+ className="mt-4"
54
+ >
55
+ Try again
56
+ </Button>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ return this.props.children;
62
+ }
63
+ }
@@ -0,0 +1,33 @@
1
+ import { Provider } from 'react-redux';
2
+ import { PersistGate } from 'redux-persist/integration/react';
3
+ import { store, persistor } from './store';
4
+ import { Skeleton } from 'antd';
5
+
6
+ interface ReduxProviderProps {
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ function LoadingFallback() {
11
+ return (
12
+ <div className="flex min-h-screen items-center justify-center">
13
+ <div className="flex flex-col items-center gap-4">
14
+ <Skeleton.Avatar active size={48} shape="circle" />
15
+ <Skeleton.Input active size="small" style={{ width: 128 }} />
16
+ </div>
17
+ </div>
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Redux Provider with persistence
23
+ * Wraps the application with Redux store and persist gate
24
+ */
25
+ export function ReduxProvider({ children }: ReduxProviderProps) {
26
+ return (
27
+ <Provider store={store}>
28
+ <PersistGate loading={<LoadingFallback />} persistor={persistor}>
29
+ {children}
30
+ </PersistGate>
31
+ </Provider>
32
+ );
33
+ }
@@ -0,0 +1,40 @@
1
+ import { Suspense } from 'react';
2
+ import { Route, Switch } from 'wouter';
3
+ import { routes } from './routes';
4
+ import { Skeleton } from 'antd';
5
+
6
+ /**
7
+ * Loading fallback component
8
+ */
9
+ function RouteLoading() {
10
+ return (
11
+ <div className="flex min-h-screen items-center justify-center">
12
+ <div className="flex flex-col items-center gap-4">
13
+ <Skeleton.Avatar active size={48} shape="circle" />
14
+ <Skeleton.Input active size="small" style={{ width: 128 }} />
15
+ </div>
16
+ </div>
17
+ );
18
+ }
19
+
20
+ /**
21
+ * Application router using Wouter
22
+ * - Lightweight (2KB) alternative to React Router
23
+ * - Supports lazy loading with React.Suspense
24
+ * - Simple API with <Route> and <Switch>
25
+ */
26
+ export function AppRouter() {
27
+ return (
28
+ <Suspense fallback={<RouteLoading />}>
29
+ <Switch>
30
+ {routes.map(({ path, component: Component }) => (
31
+ <Route key={path} path={path}>
32
+ <Component />
33
+ </Route>
34
+ ))}
35
+ </Switch>
36
+ </Suspense>
37
+ );
38
+ }
39
+
40
+ export { routes } from './routes';
@@ -0,0 +1,227 @@
1
+ import { useState } from 'react';
2
+ import { Moon, Sun, Github, Zap, Bot, ExternalLink } from 'lucide-react';
3
+ import { Button, Card } from 'antd';
4
+ import { useTheme } from '~/providers';
5
+
6
+ export default function Home() {
7
+ const { theme, setTheme, resolvedTheme } = useTheme();
8
+ const [count, setCount] = useState(0);
9
+
10
+ const toggleTheme = () => {
11
+ setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
12
+ };
13
+
14
+ return (
15
+ <div className="flex min-h-screen flex-col items-center justify-center p-8">
16
+ <div className="w-full max-w-2xl space-y-8">
17
+ {/* Header */}
18
+ <div className="text-center">
19
+ <div className="mb-4 flex items-center justify-between">
20
+ <div className="w-40" />
21
+ <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
22
+ <Zap className="h-8 w-8" />
23
+ </div>
24
+ <Button
25
+ type="default"
26
+ href="https://github.com/abhay-rana/create-modern-react"
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ icon={<Github className="h-4 w-4" />}
30
+ >
31
+ View CLI on GitHub
32
+ </Button>
33
+ </div>
34
+ <h1 className="text-4xl font-bold tracking-tight">
35
+ create-modern-react
36
+ </h1>
37
+ <p className="mt-2 text-muted-foreground">
38
+ Production-ready React + TypeScript + Tailwind in seconds
39
+ </p>
40
+ <div className="mt-4">
41
+ <code className="rounded-md bg-muted px-3 py-1.5 font-mono text-sm">
42
+ npx create-modern-react my-app
43
+ </code>
44
+ </div>
45
+ </div>
46
+
47
+ {/* Counter Card */}
48
+ <Card
49
+ title={
50
+ <div className="flex items-center justify-between">
51
+ <span>Interactive Counter</span>
52
+ <Button
53
+ type="text"
54
+ shape="circle"
55
+ onClick={toggleTheme}
56
+ aria-label="Toggle theme"
57
+ icon={
58
+ resolvedTheme === 'dark' ? (
59
+ <Sun className="h-5 w-5" />
60
+ ) : (
61
+ <Moon className="h-5 w-5" />
62
+ )
63
+ }
64
+ />
65
+ </div>
66
+ }
67
+ >
68
+ <div className="space-y-4">
69
+ <div className="flex items-center justify-center gap-4">
70
+ <Button size="large" onClick={() => setCount((c) => c - 1)}>
71
+ -
72
+ </Button>
73
+ <span className="min-w-[4rem] text-center text-4xl font-bold tabular-nums">
74
+ {count}
75
+ </span>
76
+ <Button size="large" onClick={() => setCount((c) => c + 1)}>
77
+ +
78
+ </Button>
79
+ </div>
80
+ <p className="text-center text-sm text-muted-foreground">
81
+ Click the buttons to update the count
82
+ </p>
83
+ </div>
84
+ </Card>
85
+
86
+ {/* Features */}
87
+ <div className="grid gap-4 sm:grid-cols-2">
88
+ <FeatureCard
89
+ title="Vite + SWC"
90
+ description="Lightning fast builds with Hot Module Replacement"
91
+ />
92
+ <FeatureCard
93
+ title="TypeScript"
94
+ description="Full type safety with strict mode enabled"
95
+ />
96
+ <FeatureCard
97
+ title="Tailwind CSS"
98
+ description="Utility-first CSS with dark mode support"
99
+ />
100
+ <FeatureCard
101
+ title="Ant Design"
102
+ description="Enterprise-class UI components"
103
+ />
104
+ </div>
105
+
106
+ {/* AI Skills Highlight */}
107
+ <div className="rounded-lg border bg-gradient-to-r from-primary/5 to-primary/10 p-4">
108
+ <div className="flex items-start gap-3">
109
+ <Bot className="h-5 w-5 text-primary mt-0.5" />
110
+ <div>
111
+ <h3 className="font-semibold">Claude Code AI Skills</h3>
112
+ <p className="mt-1 text-sm text-muted-foreground">
113
+ 8 pre-configured skills for React best practices, UI/UX design, browser testing, and spec refinement
114
+ </p>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ {/* Projects Showcase */}
120
+ <div className="space-y-4">
121
+ <h2 className="text-center text-2xl font-bold">
122
+ Production Projects created using this boilerplate
123
+ </h2>
124
+ <div className="grid gap-6 sm:grid-cols-2">
125
+ <ProjectCard
126
+ category="AI/Career"
127
+ title="ResumeFreePro"
128
+ description="AI-Powered Resume Builder"
129
+ designStyle="Modern + Glassmorphism"
130
+ url="https://resumefreepro.com?utm_source=starter-template&utm_medium=landing-page&utm_campaign=create-modern-react-demo"
131
+ previewUrl="https://storage.resumefreepro.com/media-files/resumefreepro.webp"
132
+ />
133
+ <ProjectCard
134
+ category="E-Pharmacy"
135
+ title="HealthMug"
136
+ description="Online Pharmacy Platform"
137
+ designStyle="Clean + Professional"
138
+ url="https://healthmug.com?utm_source=starter-template&utm_medium=landing-page&utm_campaign=create-modern-react-demo"
139
+ previewUrl="https://storage.resumefreepro.com/media-files/healthmug.webp"
140
+ />
141
+ </div>
142
+ </div>
143
+
144
+ <p className="text-center text-xs text-muted-foreground">
145
+ Current theme: {theme} (resolved: {resolvedTheme})
146
+ </p>
147
+ </div>
148
+ </div>
149
+ );
150
+ }
151
+
152
+ function FeatureCard({
153
+ title,
154
+ description,
155
+ }: {
156
+ title: string;
157
+ description: string;
158
+ }) {
159
+ return (
160
+ <div className="rounded-lg border bg-card p-4 text-card-foreground">
161
+ <h3 className="font-semibold">{title}</h3>
162
+ <p className="mt-1 text-sm text-muted-foreground">{description}</p>
163
+ </div>
164
+ );
165
+ }
166
+
167
+ function ProjectCard({
168
+ category,
169
+ title,
170
+ description,
171
+ designStyle,
172
+ url,
173
+ previewUrl,
174
+ }: {
175
+ category: string;
176
+ title: string;
177
+ description: string;
178
+ designStyle: string;
179
+ url: string;
180
+ previewUrl: string;
181
+ }) {
182
+ return (
183
+ <a
184
+ href={url}
185
+ target="_blank"
186
+ rel="noopener noreferrer"
187
+ className="group block"
188
+ >
189
+ <Card
190
+ hoverable
191
+ className="overflow-hidden"
192
+ cover={
193
+ <div className="relative h-48 overflow-hidden bg-muted">
194
+ <img
195
+ src={previewUrl}
196
+ alt={`${title} preview`}
197
+ className="h-full w-full object-cover object-top transition-transform duration-300 group-hover:scale-105"
198
+ />
199
+ {/* Overlay on hover */}
200
+ <div className="absolute inset-0 flex items-center justify-center bg-black/60 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
201
+ <div className="text-center text-white">
202
+ <ExternalLink className="mx-auto h-8 w-8" />
203
+ <p className="mt-2 text-sm font-medium">View Demo</p>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ }
208
+ >
209
+ <div className="space-y-3">
210
+ {/* Category Badge */}
211
+ <span className="inline-block rounded-md bg-primary/10 px-2 py-1 text-xs font-medium text-primary">
212
+ {category}
213
+ </span>
214
+
215
+ {/* Title & Description */}
216
+ <div>
217
+ <h3 className="font-bold">{title}</h3>
218
+ <p className="mt-1 text-sm text-muted-foreground">{description}</p>
219
+ </div>
220
+
221
+ {/* Design Style */}
222
+ <p className="text-xs text-muted-foreground">{designStyle}</p>
223
+ </div>
224
+ </Card>
225
+ </a>
226
+ );
227
+ }
@@ -0,0 +1,30 @@
1
+ import { Link } from 'wouter';
2
+ import { Home, ArrowLeft } from 'lucide-react';
3
+ import { Button } from 'antd';
4
+
5
+ export default function NotFound() {
6
+ return (
7
+ <div className="flex min-h-screen flex-col items-center justify-center p-8">
8
+ <div className="text-center">
9
+ <h1 className="text-9xl font-bold text-muted-foreground/20">404</h1>
10
+ <h2 className="mt-4 text-2xl font-semibold">Page not found</h2>
11
+ <p className="mt-2 text-muted-foreground">
12
+ Sorry, we couldn't find the page you're looking for.
13
+ </p>
14
+ <div className="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
15
+ <Link href="/">
16
+ <Button type="primary" icon={<Home className="h-4 w-4" />}>
17
+ Go home
18
+ </Button>
19
+ </Link>
20
+ <Button
21
+ icon={<ArrowLeft className="h-4 w-4" />}
22
+ onClick={() => window.history.back()}
23
+ >
24
+ Go back
25
+ </Button>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ );
30
+ }