create-nextjs-stack 0.1.9 → 0.2.0

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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/create-nextjs-stack.svg?style=flat-square)](https://www.npmjs.com/package/create-nextjs-stack)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/create-nextjs-stack.svg?style=flat-square)](https://www.npmjs.com/package/create-nextjs-stack)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
8
- [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen?style=flat-square)](https://nodejs.org/)
8
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen?style=flat-square)](https://nodejs.org/)
9
9
 
10
10
  **A zero-config CLI to scaffold production-ready Next.js applications.**
11
11
  Choose between a marketing landing page, a Supabase admin panel, or both — in one command.
@@ -336,11 +336,6 @@ NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co
336
336
  NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsIn...
337
337
  SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsIn... # Never expose this client-side!
338
338
 
339
- # Supabase project meta (used by Supabase CLI / migrations)
340
- SUPABASE_PROJECT_ID=xxxxxxxxxxxx
341
- SUPABASE_PROJECT_NAME=my-project
342
- SUPABASE_DATABASE_PASSWORD=your-db-password
343
-
344
339
  # Used to verify on-demand revalidation requests
345
340
  REVALIDATION_SECRET=a-random-secret-string
346
341
 
@@ -349,7 +344,6 @@ REVALIDATION_SECRET=a-random-secret-string
349
344
  NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name
350
345
  CLOUDINARY_API_KEY=000000000000000
351
346
  CLOUDINARY_API_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
352
- CLOUDINARY_ID=your-cloud-name # Same as CLOUD_NAME, for SDK compat
353
347
  CLOUDINARY_URL=cloudinary://API_KEY:API_SECRET@CLOUD_NAME
354
348
 
355
349
  # ─── Site ──────────────────────────────────────────────────────────────────────
@@ -382,7 +376,7 @@ CLOUDINARY_URL=cloudinary://API_KEY:API_SECRET@CLOUD_NAME
382
376
 
383
377
  | Tool | Minimum Version |
384
378
  | ------- | --------------- |
385
- | Node.js | 18.x |
379
+ | Node.js | 20.x |
386
380
  | npm | 9.x |
387
381
 
388
382
  ### Available Scripts
@@ -497,7 +491,7 @@ A: Yes — create two separate Vercel projects pointing to the same repository,
497
491
 
498
492
  ## 🤝 Contributing
499
493
 
500
- Contributions, bug reports, and feature requests are welcome!
494
+ Contributions, bug reports, and feature requests are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines.
501
495
 
502
496
  1. [Open an issue](https://github.com/mburakaltiparmak/create-nextjs-stack/issues) to discuss what you'd like to change.
503
497
  2. Fork the repository and create a feature branch:
package/bin/cli.js CHANGED
@@ -6,10 +6,39 @@ const { program } = require("commander");
6
6
  const prompts = require("prompts");
7
7
  const chalk = require("chalk");
8
8
  const ora = require("ora");
9
+ const { execSync } = require("child_process");
10
+
11
+ function detectPackageManager() {
12
+ const userAgent = process.env.npm_config_user_agent || "";
13
+ if (userAgent.startsWith("yarn")) return "yarn";
14
+ if (userAgent.startsWith("pnpm")) return "pnpm";
15
+ if (userAgent.startsWith("bun")) return "bun";
16
+ return "npm";
17
+ }
18
+
19
+ function isPackageManagerAvailable(pm) {
20
+ try {
21
+ execSync(`${pm} --version`, { stdio: "ignore" });
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
9
27
 
10
28
  // Read version from package.json
11
29
  const packageJson = require("../package.json");
12
30
 
31
+ process.on("SIGINT", () => {
32
+ console.log(chalk.red("\n\nOperation cancelled."));
33
+ process.exit(1);
34
+ });
35
+ const updateNotifier = require("update-notifier");
36
+
37
+ const notifier = updateNotifier({ pkg: packageJson, updateCheckInterval: 1000 * 60 * 60 * 24 });
38
+ if (notifier.update) {
39
+ notifier.notify();
40
+ }
41
+
13
42
  program
14
43
  .name("create-nextjs-stack")
15
44
  .version(packageJson.version, "-v, --version", "Output the current version")
@@ -17,6 +46,17 @@ program
17
46
  .argument("[project-directory]", "Directory to create the project in")
18
47
  .option("-t, --template <type>", "Template type: web, admin, or full-stack")
19
48
  .action(async (projectDirectory, options) => {
49
+ const validTemplates = ["web", "admin", "full-stack"];
50
+ if (options.template && !validTemplates.includes(options.template)) {
51
+ console.log(
52
+ chalk.red(
53
+ `\nInvalid template: "${options.template}"\n` +
54
+ `Valid options: ${validTemplates.join(", ")}\n`
55
+ )
56
+ );
57
+ process.exit(1);
58
+ }
59
+
20
60
  let targetDir = projectDirectory;
21
61
 
22
62
  // 1. Get Project Name / Directory
@@ -162,24 +202,122 @@ program
162
202
 
163
203
  spinner.succeed("Scaffolding complete!");
164
204
 
165
- console.log(`\nSuccess! Created project at ${root}\n`);
205
+ // Skip install prompts if testing
206
+ if (process.env.NODE_ENV !== "test" && process.env.VITEST !== "true") {
207
+ const detectedPm = detectPackageManager();
166
208
 
167
- if (templateType === "full-stack") {
209
+ const { packageManager } = await prompts({
210
+ type: "select",
211
+ name: "packageManager",
212
+ message: "Which package manager would you like to use?",
213
+ choices: [
214
+ { title: "npm", value: "npm" },
215
+ { title: "yarn", value: "yarn" },
216
+ { title: "pnpm", value: "pnpm" },
217
+ { title: "bun", value: "bun" },
218
+ ].filter((choice) => isPackageManagerAvailable(choice.value)),
219
+ initial: ["npm", "yarn", "pnpm", "bun"].indexOf(detectedPm) >= 0 ? ["npm", "yarn", "pnpm", "bun"].indexOf(detectedPm) : 0,
220
+ });
221
+
222
+ if (!packageManager) {
223
+ console.log(chalk.red("\nOperation cancelled."));
224
+ process.exit(1);
225
+ }
226
+
227
+ const { shouldInstall } = await prompts({
228
+ type: "confirm",
229
+ name: "shouldInstall",
230
+ message: `Install dependencies with ${packageManager}?`,
231
+ initial: true,
232
+ });
233
+
234
+ if (shouldInstall === undefined) {
235
+ console.log(chalk.red("\nOperation cancelled."));
236
+ process.exit(1);
237
+ }
238
+
239
+ if (shouldInstall) {
240
+ const installTargets =
241
+ templateType === "full-stack"
242
+ ? [path.join(root, "web"), path.join(root, "admin")]
243
+ : [root];
244
+
245
+ for (const target of installTargets) {
246
+ const dirName = path.basename(target);
247
+ const installSpinner = ora(
248
+ `Installing dependencies in ${dirName}...`,
249
+ ).start();
250
+
251
+ try {
252
+ const installCmd =
253
+ packageManager === "yarn" ? "yarn" : `${packageManager} install`;
254
+
255
+ execSync(installCmd, {
256
+ cwd: target,
257
+ stdio: "pipe",
258
+ });
259
+
260
+ installSpinner.succeed(`Dependencies installed in ${dirName}`);
261
+ } catch (error) {
262
+ installSpinner.fail(`Failed to install dependencies in ${dirName}`);
263
+ console.log(
264
+ chalk.yellow(
265
+ ` You can install manually: cd ${dirName} && ${packageManager} install`,
266
+ ),
267
+ );
268
+ }
269
+ }
270
+ }
271
+
272
+ console.log(`\n${chalk.green("Success!")} Created project at ${root}\n`);
168
273
  console.log("Next steps:");
169
274
  console.log(chalk.cyan(` cd ${appName}`));
170
- console.log(" Then go to either web or admin folder:");
171
- console.log(chalk.cyan(` cd web`));
172
- console.log(chalk.cyan(` npm install`));
173
- console.log(chalk.cyan(` npm run dev`));
275
+
276
+ if (templateType === "full-stack") {
277
+ console.log(" Then go to either web or admin folder:");
278
+ console.log(chalk.cyan(` cd web`));
279
+ if (!shouldInstall) {
280
+ console.log(chalk.cyan(` ${packageManager || "npm"} install`));
281
+ }
282
+ console.log(chalk.cyan(` ${packageManager || "npm"} run dev`));
283
+ } else {
284
+ if (!shouldInstall) {
285
+ console.log(chalk.cyan(` ${packageManager || "npm"} install`));
286
+ }
287
+ console.log(chalk.cyan(` ${packageManager || "npm"} run dev`));
288
+ }
174
289
  } else {
290
+ // Fallback for tests
291
+ console.log(`\nSuccess! Created project at ${root}\n`);
175
292
  console.log("Next steps:");
176
293
  console.log(chalk.cyan(` cd ${appName}`));
177
- console.log(chalk.cyan(` npm install`));
178
- console.log(chalk.cyan(` npm run dev`));
294
+ if (templateType === "full-stack") {
295
+ console.log(" Then go to either web or admin folder:");
296
+ console.log(chalk.cyan(` cd web`));
297
+ console.log(chalk.cyan(` npm install`));
298
+ console.log(chalk.cyan(` npm run dev`));
299
+ } else {
300
+ console.log(chalk.cyan(` npm install`));
301
+ console.log(chalk.cyan(` npm run dev`));
302
+ }
179
303
  }
180
304
  } catch (error) {
181
305
  spinner.fail("Error scaffolding project.");
182
- console.error(error);
306
+
307
+ if (error.code === "EACCES") {
308
+ console.error(
309
+ chalk.red("\nPermission denied. Try running with elevated privileges.")
310
+ );
311
+ } else if (error.code === "ENOSPC") {
312
+ console.error(chalk.red("\nNo disk space available."));
313
+ } else {
314
+ console.error(chalk.red(`\n${error.message || error}`));
315
+ }
316
+
317
+ if (process.env.DEBUG) {
318
+ console.error("\nFull error:", error);
319
+ }
320
+
183
321
  process.exit(1);
184
322
  }
185
323
  });
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "create-nextjs-stack",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "CLI to scaffold Next.js Landing Page and Supabase Admin Panel",
5
+ "type": "commonjs",
5
6
  "private": false,
6
7
  "bin": {
7
8
  "create-nextjs-stack": "./bin/cli.js"
@@ -10,26 +11,40 @@
10
11
  "bin",
11
12
  "templates"
12
13
  ],
14
+ "exports": {
15
+ ".": "./bin/cli.js"
16
+ },
13
17
  "scripts": {
14
- "test": "vitest run"
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "test:coverage": "vitest run --coverage",
21
+ "test:verbose": "vitest run --reporter=verbose"
15
22
  },
16
23
  "dependencies": {
17
24
  "chalk": "^4.1.2",
18
25
  "commander": "^14.0.3",
19
26
  "fs-extra": "^11.3.3",
20
27
  "ora": "^5.4.1",
21
- "prompts": "^2.4.2"
28
+ "prompts": "^2.4.2",
29
+ "update-notifier": "^5.1.0"
22
30
  },
23
31
  "engines": {
24
- "node": ">=18.0.0"
32
+ "node": ">=20.0.0"
25
33
  },
26
34
  "keywords": [
27
35
  "nextjs",
36
+ "next",
28
37
  "starter",
29
38
  "template",
39
+ "scaffold",
40
+ "cli",
41
+ "create",
30
42
  "supabase",
31
43
  "admin",
32
- "cli"
44
+ "tailwind",
45
+ "typescript",
46
+ "react",
47
+ "boilerplate"
33
48
  ],
34
49
  "author": "Mehmet Burak Altıparmak <mburakaltiparmak@gmail.com>",
35
50
  "license": "MIT",
@@ -44,6 +59,7 @@
44
59
  "devDependencies": {
45
60
  "@types/fs-extra": "^11.0.4",
46
61
  "@types/node": "^25.2.3",
62
+ "@vitest/coverage-v8": "^4.0.18",
47
63
  "execa": "^9.6.1",
48
64
  "vitest": "^4.0.18"
49
65
  }
@@ -2,6 +2,9 @@
2
2
  "name": "supabase-admin-template",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "engines": {
6
+ "node": ">=20.0.0"
7
+ },
5
8
  "scripts": {
6
9
  "dev": "next dev",
7
10
  "build": "next build",
@@ -16,12 +19,11 @@
16
19
  "clsx": "^2.1.1",
17
20
  "lucide-react": "^0.563.0",
18
21
  "next": "^16.1.6",
19
- "react": "^19.2.3",
20
- "react-dom": "^19.2.3",
22
+ "react": "^19.2.4",
23
+ "react-dom": "^19.2.4",
21
24
  "react-hook-form": "^7.71.1",
22
25
  "react-redux": "^9.2.0",
23
26
  "react-toastify": "^11.0.3",
24
- "redux-logger": "^3.0.6",
25
27
  "tailwind-merge": "^3.5.0"
26
28
  },
27
29
  "devDependencies": {
@@ -29,7 +31,6 @@
29
31
  "@types/node": "^20",
30
32
  "@types/react": "^19",
31
33
  "@types/react-dom": "^19",
32
- "@types/redux-logger": "^3.0.13",
33
34
  "babel-plugin-react-compiler": "1.0.0",
34
35
  "eslint": "^9",
35
36
  "eslint-config-next": "^16.1.6",
@@ -1,13 +1,8 @@
1
1
  import { configureStore } from '@reduxjs/toolkit';
2
- import logger from 'redux-logger';
3
2
  import rootReducer from './reducers';
4
3
 
5
4
  const store = configureStore({
6
5
  reducer: rootReducer,
7
- middleware: (getDefaultMiddleware) =>
8
- process.env.NODE_ENV !== 'production'
9
- ? getDefaultMiddleware().concat(logger)
10
- : getDefaultMiddleware(),
11
6
  });
12
7
 
13
8
  // Inferred types — export and use via store/hooks.ts
@@ -1,25 +1,24 @@
1
- # Email Service (Resend)
2
- RESEND_API_KEY=your_resend_api_key_here
3
- RESEND_FROM_EMAIL=your_resend_from_email_here
4
-
5
- # Supabase Configuration
6
- SUPABASE_DATABASE_PASSWORD=your_supabase_database_password_here
7
- SUPABASE_PROJECT_NAME=your_supabase_project_name_here
8
- SUPABASE_PROJECT_ID=your_supabase_project_id_here
9
- NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here
10
- SUPABASE_PUBLISHABLE_KEY=your_supabase_publishable_key_here
11
- SUPABASE_SECRET_KEY=your_supabase_secret_key_here
1
+ # ─── Supabase ──────────────────────────────────────────────────────────────────
2
+ # https://supabase.com → Project Settings → API
3
+ NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co
12
4
  NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here
13
5
  SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key_here
14
- REVALIDATION_SECRET=your_revalidation_secret_here
15
6
 
16
- # Cloudinary Configuration
7
+ # ─── Email (Resend) ───────────────────────────────────────────────────────────
8
+ # https://resend.com → API Keys
9
+ RESEND_API_KEY=your_resend_api_key_here
10
+ RESEND_FROM_EMAIL=hello@yourdomain.com
11
+
12
+ # ─── Cloudinary ────────────────────────────────────────────────────────────────
13
+ # https://cloudinary.com → Dashboard
14
+ NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name_here
17
15
  CLOUDINARY_API_KEY=your_cloudinary_api_key_here
18
16
  CLOUDINARY_API_SECRET=your_cloudinary_api_secret_here
19
- CLOUDINARY_ID=your_cloud_name_here
20
- NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name_here
21
17
  CLOUDINARY_URL=cloudinary://your_api_key:your_api_secret@your_cloud_name
22
18
 
23
- # Site Configuration
19
+ # ─── Site ──────────────────────────────────────────────────────────────────────
24
20
  NEXT_PUBLIC_SITE_URL=http://localhost:3000
25
- NEXT_PUBLIC_GA_ID=your_google_analytics_id_here
21
+ NEXT_PUBLIC_GA_ID=your_google_analytics_id_here
22
+
23
+ # ─── Revalidation ─────────────────────────────────────────────────────────────
24
+ REVALIDATION_SECRET=your_random_secret_string_here
@@ -3,6 +3,9 @@
3
3
  "version": "0.1.0",
4
4
  "description": "Next.js Landing Page Starter",
5
5
  "private": true,
6
+ "engines": {
7
+ "node": ">=20.0.0"
8
+ },
6
9
  "scripts": {
7
10
  "dev": "next dev --turbopack",
8
11
  "build": "next build --turbopack",
@@ -26,10 +29,9 @@
26
29
  "react-hook-form": "^7.70.0",
27
30
  "react-icons": "^5.5.0",
28
31
  "react-redux": "^9.2.0",
29
- "redux-logger": "^3.0.6",
30
32
  "react-toastify": "^11.0.5",
31
33
  "resend": "^6.6.0",
32
- "zod": "^4.3.5"
34
+ "zod": "^3.25.0"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@eslint/eslintrc": "^3",
@@ -37,7 +39,6 @@
37
39
  "@types/node": "^20",
38
40
  "@types/react": "^19",
39
41
  "@types/react-dom": "^19",
40
- "@types/redux-logger": "^3.0.13",
41
42
  "eslint": "^9",
42
43
  "eslint-config-next": "^16.1.6",
43
44
  "tailwindcss": "^4",
@@ -1,13 +1,8 @@
1
1
  import { configureStore } from '@reduxjs/toolkit';
2
- import logger from 'redux-logger';
3
2
  import rootReducer from './reducers';
4
3
 
5
4
  const store = configureStore({
6
5
  reducer: rootReducer,
7
- middleware: (getDefaultMiddleware) =>
8
- process.env.NODE_ENV !== 'production'
9
- ? getDefaultMiddleware().concat(logger)
10
- : getDefaultMiddleware(),
11
6
  });
12
7
 
13
8
  // Inferred types — export and use via store/hooks.ts