create-next-shadcn-kit 0.1.2 → 0.3.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,7 +57,7 @@ npx create-next-shadcn-kit my-app --pnpm
57
57
 
58
58
  ## Requirements
59
59
 
60
- - Node.js **20+** (Next.js 16 requires it)
60
+ - Node.js **22+** (Next.js 16 plus the pnpm/lint-staged toolchain require it)
61
61
  - Network access (to fetch `create-next-app` and `shadcn`)
62
62
  - Works with **npm, pnpm, yarn, bun**. All are tested end-to-end.
63
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-next-shadcn-kit",
3
- "version": "0.1.2",
3
+ "version": "0.3.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": ">=20.0.0"
39
+ "node": ">=22.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
@@ -45,10 +45,22 @@ function devCommand(pm) {
45
45
  return `${pm} dev`;
46
46
  }
47
47
 
48
+ // Minimum Node supported by the generated project's toolchain. Husky's
49
+ // lint-staged pulls in listr2 (Node >=22.13) and pnpm 11's binary needs
50
+ // node:sqlite (Node >=22.13), so the practical floor is Node 22.
51
+ export const MIN_NODE_MAJOR = 22;
52
+
53
+ export function isSupportedNode(version) {
54
+ const major = Number(String(version).replace(/^v/, "").split(".")[0]);
55
+ return Number.isFinite(major) && major >= MIN_NODE_MAJOR;
56
+ }
57
+
48
58
  function assertNodeVersion() {
49
- const major = Number(process.versions.node.split(".")[0]);
50
- if (major < 20) {
51
- throw new Error(`Node.js 20+ is required (Next.js 16 requires it). You are running ${process.versions.node}.`);
59
+ if (!isSupportedNode(process.versions.node)) {
60
+ throw new Error(
61
+ `Node.js ${MIN_NODE_MAJOR}+ is required (Next.js 16 and the pnpm/lint-staged toolchain need it). ` +
62
+ `You are running ${process.versions.node}.`
63
+ );
52
64
  }
53
65
  }
54
66
 
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
@@ -7,6 +7,14 @@ export async function scaffold(config) {
7
7
  const cwd = process.cwd();
8
8
  const projectPath = path.resolve(cwd, config.projectName);
9
9
 
10
+ // pnpm 10+/11 defaults strictDepBuilds=true, so unapproved dependency build
11
+ // scripts (e.g. sharp, unrs-resolver pulled in by shadcn) abort the install
12
+ // in a non-interactive run. Downgrade it to a warning for all child pnpm
13
+ // processes; respect an explicit override if the user already set one.
14
+ if (config.packageManager === "pnpm" && process.env.pnpm_config_strict_dep_builds === undefined) {
15
+ process.env.pnpm_config_strict_dep_builds = "false";
16
+ }
17
+
10
18
  if (fs.existsSync(projectPath) && fs.readdirSync(projectPath).length > 0) {
11
19
  throw new Error(`Directory "${config.projectName}" already exists and is not empty.`);
12
20
  }
@@ -66,6 +74,15 @@ function storeDirFor(projectPath, config) {
66
74
  return config.srcDir ? path.join(projectPath, "src", "store") : path.join(projectPath, "store");
67
75
  }
68
76
 
77
+ // Turn a tsconfig-style import alias pattern (e.g. "@/*", "~/*") into the
78
+ // prefix used in import statements ("@", "~"). Falls back to "@" if empty.
79
+ export function aliasPrefixFor(importAlias) {
80
+ const prefix = String(importAlias || "@/*")
81
+ .replace(/\*$/, "")
82
+ .replace(/\/$/, "");
83
+ return prefix || "@";
84
+ }
85
+
69
86
  async function setupRedux(projectPath, config) {
70
87
  console.log();
71
88
  console.log(pc.cyan("◆") + " Setting up Redux Toolkit...");
@@ -170,11 +187,12 @@ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
170
187
  }
171
188
 
172
189
  const appDir = appDirFor(projectPath, config);
190
+ const storeImport = `${aliasPrefixFor(config.importAlias)}/store`;
173
191
  const providers = ts
174
192
  ? `"use client";
175
193
 
176
194
  import { Provider } from "react-redux";
177
- import { store } from "@/store";
195
+ import { store } from "${storeImport}";
178
196
 
179
197
  export function Providers({ children }: { children: React.ReactNode }) {
180
198
  return <Provider store={store}>{children}</Provider>;
@@ -183,7 +201,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
183
201
  : `"use client";
184
202
 
185
203
  import { Provider } from "react-redux";
186
- import { store } from "@/store";
204
+ import { store } from "${storeImport}";
187
205
 
188
206
  export function Providers({ children }) {
189
207
  return <Provider store={store}>{children}</Provider>;
@@ -294,6 +312,14 @@ async function setupHusky(projectPath, config) {
294
312
  delete pkg["lint-staged"];
295
313
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
296
314
 
315
+ // Husky needs this project to be its own git repository. create-next-app
316
+ // skips git init when nested inside an existing repo, and rolls it back if it
317
+ // can't make the initial commit (e.g. no git identity configured). Ensure one
318
+ // so the hook setup below doesn't fail.
319
+ if (!fs.existsSync(path.join(projectPath, ".git"))) {
320
+ await run("git", ["init"], { cwd: projectPath });
321
+ }
322
+
297
323
  const runner = runnerFor(config.packageManager);
298
324
  await run(runner.cmd, [...runner.args, "husky", "install"], {
299
325
  cwd: projectPath,
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 {