create-next-shadcn-kit 0.1.1 → 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
@@ -57,8 +57,9 @@ npx create-next-shadcn-kit my-app --pnpm
57
57
 
58
58
  ## Requirements
59
59
 
60
- - Node.js **18.17+**
60
+ - Node.js **20+** (Next.js 16 requires it)
61
61
  - Network access (to fetch `create-next-app` and `shadcn`)
62
+ - Works with **npm, pnpm, yarn, bun**. All are tested end-to-end.
62
63
 
63
64
  ## Development
64
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-next-shadcn-kit",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Create a Next.js app with shadcn/ui pre-integrated — zero config.",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -36,10 +36,11 @@
36
36
  "LICENSE"
37
37
  ],
38
38
  "engines": {
39
- "node": ">=18.17.0"
39
+ "node": ">=20.0.0"
40
40
  },
41
41
  "scripts": {
42
42
  "start": "node bin/index.js",
43
+ "test": "node --test",
43
44
  "test:local": "node bin/index.js test-app --yes"
44
45
  },
45
46
  "dependencies": {
package/src/index.js CHANGED
@@ -47,8 +47,8 @@ function devCommand(pm) {
47
47
 
48
48
  function assertNodeVersion() {
49
49
  const major = Number(process.versions.node.split(".")[0]);
50
- if (major < 18) {
51
- throw new Error(`Node.js 18.17+ is required. You are running ${process.versions.node}.`);
50
+ if (major < 20) {
51
+ throw new Error(`Node.js 20+ is required (Next.js 16 requires it). You are running ${process.versions.node}.`);
52
52
  }
53
53
  }
54
54
 
package/src/prompts.js CHANGED
@@ -28,7 +28,20 @@ const onCancel = () => {
28
28
  process.exit(1);
29
29
  };
30
30
 
31
+ function assertValidName(name) {
32
+ if (!isValidProjectName(name)) {
33
+ throw new Error(
34
+ `Invalid project name: "${name}". Use letters, numbers, dashes, dots, ` +
35
+ `tildes or underscores (optionally an npm scope) — no path separators.`
36
+ );
37
+ }
38
+ }
39
+
31
40
  export async function promptConfig(args) {
41
+ // A name passed on the CLI bypasses the interactive prompt's validation,
42
+ // so guard it here before it ever reaches path.resolve()/spawn().
43
+ if (args.projectName !== undefined) assertValidName(args.projectName);
44
+
32
45
  if (args.yes) {
33
46
  return {
34
47
  projectName: args.projectName || "my-app",
package/src/scaffold.js CHANGED
@@ -66,6 +66,15 @@ function storeDirFor(projectPath, config) {
66
66
  return config.srcDir ? path.join(projectPath, "src", "store") : path.join(projectPath, "store");
67
67
  }
68
68
 
69
+ // Turn a tsconfig-style import alias pattern (e.g. "@/*", "~/*") into the
70
+ // prefix used in import statements ("@", "~"). Falls back to "@" if empty.
71
+ export function aliasPrefixFor(importAlias) {
72
+ const prefix = String(importAlias || "@/*")
73
+ .replace(/\*$/, "")
74
+ .replace(/\/$/, "");
75
+ return prefix || "@";
76
+ }
77
+
69
78
  async function setupRedux(projectPath, config) {
70
79
  console.log();
71
80
  console.log(pc.cyan("◆") + " Setting up Redux Toolkit...");
@@ -170,11 +179,12 @@ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
170
179
  }
171
180
 
172
181
  const appDir = appDirFor(projectPath, config);
182
+ const storeImport = `${aliasPrefixFor(config.importAlias)}/store`;
173
183
  const providers = ts
174
184
  ? `"use client";
175
185
 
176
186
  import { Provider } from "react-redux";
177
- import { store } from "@/store";
187
+ import { store } from "${storeImport}";
178
188
 
179
189
  export function Providers({ children }: { children: React.ReactNode }) {
180
190
  return <Provider store={store}>{children}</Provider>;
@@ -183,7 +193,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
183
193
  : `"use client";
184
194
 
185
195
  import { Provider } from "react-redux";
186
- import { store } from "@/store";
196
+ import { store } from "${storeImport}";
187
197
 
188
198
  export function Providers({ children }) {
189
199
  return <Provider store={store}>{children}</Provider>;
@@ -367,16 +377,6 @@ function installerFor(pm, dev = true) {
367
377
  }
368
378
  }
369
379
 
370
- function runnerFor(pm) {
371
- switch (pm) {
372
- case "pnpm":
373
- return { cmd: "pnpm", args: ["dlx"] };
374
- case "yarn":
375
- return { cmd: "yarn", args: ["dlx"] };
376
- case "bun":
377
- return { cmd: "bunx", args: [] };
378
- case "npm":
379
- default:
380
- return { cmd: "npx", args: [] };
381
- }
380
+ function runnerFor(_pm) {
381
+ return { cmd: "npx", args: [] };
382
382
  }
package/src/utils.js CHANGED
@@ -1,5 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
2
 
3
+ // Flags that take a value via the space form (e.g. `--state redux`). Every
4
+ // other `--flag` is treated as a boolean so it never swallows the following
5
+ // positional argument (e.g. `--pnpm my-app` keeps "my-app" as the project name).
6
+ const VALUE_FLAGS = new Set(["state"]);
7
+
3
8
  export function parseArgs(argv) {
4
9
  const positional = [];
5
10
  const flags = {};
@@ -13,7 +18,7 @@ export function parseArgs(argv) {
13
18
  } else {
14
19
  const key = arg.slice(2);
15
20
  const next = argv[i + 1];
16
- if (next && !next.startsWith("-")) {
21
+ if (VALUE_FLAGS.has(key) && next !== undefined && !next.startsWith("-")) {
17
22
  flags[key] = next;
18
23
  i++;
19
24
  } else {