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 +1 -1
- package/package.json +3 -2
- package/src/index.js +15 -3
- package/src/prompts.js +13 -0
- package/src/scaffold.js +28 -2
- package/src/utils.js +6 -1
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 **
|
|
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.
|
|
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": ">=
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 "
|
|
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 "
|
|
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 {
|