create-next-style-guide 0.1.4 → 0.1.6
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/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/index.js +74 -19
- package/dist/index.js.map +1 -1
- package/package.json +16 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alidanial7
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nextjs/nextjs-original.svg" width="72" alt="Next.js" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Next Style Guide</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Ship an opinionated Next.js app in minutes</strong><br/>
|
|
9
|
+
App Router · route-colocated _components/ · Zod/Yup · Cursor rules · docs that match the code
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/create-next-style-guide"><img src="https://img.shields.io/npm/v/create-next-style-guide?style=for-the-badge&label=npm&color=CB3837" alt="npm" /></a>
|
|
14
|
+
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node-%3E%3D20-339933?style=for-the-badge&logo=nodedotjs&logoColor=white" alt="Node" /></a>
|
|
15
|
+
<a href="https://nextjs.org/"><img src="https://img.shields.io/badge/Next.js-16-black?style=for-the-badge&logo=nextdotjs&logoColor=white" alt="Next.js" /></a>
|
|
16
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-2ea44f?style=for-the-badge" alt="License" /></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://img.shields.io/badge/pnpm-workspace-F69220?style=flat-square&logo=pnpm&logoColor=white" alt="pnpm" />
|
|
21
|
+
<a href="https://www.npmjs.com/package/create-next-style-guide"><img src="https://img.shields.io/badge/CLI-create--next--style--guide-111827?style=flat-square" alt="CLI" /></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
Opinionated **Next.js** scaffold CLI: lean App Router layout, feature installers, agent rules, and docs that match what you enabled.
|
|
27
|
+
|
|
28
|
+
> Toggle options at create time — unused files and rules are not left behind.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✨ Why this template
|
|
33
|
+
|
|
34
|
+
| | | |
|
|
35
|
+
|:---:|:---|:---|
|
|
36
|
+
| 🏗️ | **App-colocated layout** | Thin routes · `_components/` per route · shared `components/` + `lib/` |
|
|
37
|
+
| 🎛️ | **Feature installers** | Flip options; prune what you did not pick |
|
|
38
|
+
| 🤖 | **Agent-ready** | Slim `AGENTS.md` + glob-scoped `.cursor/rules/` |
|
|
39
|
+
| 📚 | **Docs that match the code** | Generated `docs/style-guide/` |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🚀 Quick start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm create next-style-guide
|
|
47
|
+
# or
|
|
48
|
+
npx create-next-style-guide@latest
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Interactive prompts, or skip with a preset:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx create-next-style-guide@latest my-app --yes --preset api-app
|
|
55
|
+
npx create-next-style-guide@latest shop --yes --preset marketing
|
|
56
|
+
npx create-next-style-guide@latest admin --yes --preset dashboard
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Presets
|
|
60
|
+
|
|
61
|
+
| Preset | Focus |
|
|
62
|
+
|:---|:---|
|
|
63
|
+
| `minimal` | Base App Router only |
|
|
64
|
+
| `api-app` | Tailwind · shadcn:starter · dark · JWT · Query · Zustand · endpoints · Vitest · CI |
|
|
65
|
+
| `marketing` | Tailwind · shadcn:minimal · dark · SEO · i18n · Vercel · Vitest · CI |
|
|
66
|
+
| `dashboard` | Tailwind · shadcn:starter · dark · Query · Zustand (no endpoints) · Vitest · CI |
|
|
67
|
+
| `custom` | Answer every prompt (default in interactive mode) |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 📦 What you get
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
my-app/
|
|
75
|
+
├── src/
|
|
76
|
+
│ ├── app/ # App Router + _components/
|
|
77
|
+
│ ├── components/ # shared UI (≥2 routes)
|
|
78
|
+
│ ├── lib/ # env + helpers
|
|
79
|
+
│ ├── utils/ · providers/ · hooks/ · types/ · constants/
|
|
80
|
+
├── docs/style-guide/
|
|
81
|
+
├── AGENTS.md
|
|
82
|
+
├── .cursor/rules/
|
|
83
|
+
└── …
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Options
|
|
87
|
+
|
|
88
|
+
| | Choice |
|
|
89
|
+
|:---:|:---|
|
|
90
|
+
| 🎛️ | Preset: `minimal` / `api-app` / `marketing` / `dashboard` / `custom` |
|
|
91
|
+
| 📦 | Package manager: `pnpm` / `npm` / `yarn` |
|
|
92
|
+
| 🎨 | Styling: `none` / `tailwind` / `mui` |
|
|
93
|
+
| 🧩 | shadcn (Tailwind only): `minimal` (button) / `starter` (button · input · card · dialog) |
|
|
94
|
+
| ✅ | Validation: `zod` / `yup` |
|
|
95
|
+
| 🔑 | Auth: `none` / `jwt` / `authjs` |
|
|
96
|
+
| 🗃️ | Client store: `none` / `zustand` / `redux` |
|
|
97
|
+
| 🌙 | Dark mode (Tailwind + next-themes) |
|
|
98
|
+
| ✨ | Icons: `none` / `lucide` / `tabler` |
|
|
99
|
+
| 📡 | TanStack Query |
|
|
100
|
+
| 🔌 | OpenAPI endpoints codegen |
|
|
101
|
+
| 📨 | API envelope unwrap |
|
|
102
|
+
| 🔎 | SEO (public-routes, sitemap, robots) |
|
|
103
|
+
| 🌐 | i18n (next-intl) |
|
|
104
|
+
| 🧪 | Vitest (+ optional Playwright) |
|
|
105
|
+
| 🧹 | ESLint / Prettier / Husky |
|
|
106
|
+
| 🚀 | Deploy: `none` / `vercel` / `docker` |
|
|
107
|
+
| 🤖 | GitHub Actions CI |
|
|
108
|
+
|
|
109
|
+
Flags: `--preset`, `--pm`, `--store`, `--styling`, `--shadcn`, `--shadcn-set`, `--deploy`, `--dark-mode`, `--validation`, `--auth`, `--seo`, `--i18n`, `--testing`, `--playwright`, `--api-envelope`, `--icons`, … (+ `--no-*`).
|
|
110
|
+
|
|
111
|
+
Default scaffold is a **neutral base** (no endpoints/envelope/store). Use presets for common stacks.
|
|
112
|
+
|
|
113
|
+
Upgrade an existing app’s agent surface:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npx create-next-style-guide upgrade --diff .
|
|
117
|
+
npx create-next-style-guide upgrade --apply .
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Walkthrough: [`docs/examples/api-app-crud.md`](docs/examples/api-app-crud.md).
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🗂️ Repo layout
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
next-style-guide/
|
|
128
|
+
├── cli/ # create-next-style-guide
|
|
129
|
+
│ ├── src/ # prompts, scaffold, installers
|
|
130
|
+
│ ├── template/ # base app
|
|
131
|
+
│ └── addons/ # optional feature packs
|
|
132
|
+
├── scripts/smoke.sh
|
|
133
|
+
├── AGENTS.md
|
|
134
|
+
└── VERSION
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 🛠️ Contributors
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pnpm install
|
|
143
|
+
pnpm typecheck
|
|
144
|
+
pnpm smoke # uses rg if available, else grep
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Local CLI without publishing:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pnpm --filter create-next-style-guide build
|
|
151
|
+
node cli/dist/index.js my-app --yes --no-install
|
|
152
|
+
# or
|
|
153
|
+
pnpm --filter create-next-style-guide dev
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
See [`AGENTS.md`](AGENTS.md).
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 🚢 Release (npm via GitHub Actions)
|
|
161
|
+
|
|
162
|
+
Latest on npm: [`create-next-style-guide`](https://www.npmjs.com/package/create-next-style-guide) — version tracked in [`VERSION`](VERSION).
|
|
163
|
+
|
|
164
|
+
Pushing a version tag publishes the package.
|
|
165
|
+
|
|
166
|
+
1. **Secret once:** GitHub repo → Settings → Secrets and variables → Actions → `NPM_TOKEN`
|
|
167
|
+
Create a [granular npm token](https://www.npmjs.com/settings/~/tokens) with **Read and write** + **Bypass 2FA** for publish.
|
|
168
|
+
2. Commit your changes on `main`.
|
|
169
|
+
3. Tag and push (semver after current `VERSION`):
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# example: VERSION is 0.1.4 → next tag
|
|
173
|
+
git tag v0.1.5
|
|
174
|
+
git push origin v0.1.5
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Workflow [`.github/workflows/publish.yml`](.github/workflows/publish.yml) runs typecheck + smoke, publishes, then commits `VERSION` + root/`cli` `package.json` back to `main`. On pack/publish, root `README.md` + `LICENSE` are copied into `cli/` so the [npm package page](https://www.npmjs.com/package/create-next-style-guide) shows the same docs.
|
|
178
|
+
|
|
179
|
+
Tag format: `v` + semver (`v0.1.5`, `v1.0.0-beta.1`, …).
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 🤝 Contributing
|
|
184
|
+
|
|
185
|
+
Issues and PRs → [github.com/alidanial7/next-style-guide](https://github.com/alidanial7/next-style-guide)
|
|
186
|
+
|
|
187
|
+
## 📄 License
|
|
188
|
+
|
|
189
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -530,9 +530,10 @@ async function runPrompts(initialName) {
|
|
|
530
530
|
placeholder: "my-app",
|
|
531
531
|
initialValue: initialName ?? "my-app",
|
|
532
532
|
validate(value) {
|
|
533
|
-
|
|
533
|
+
const next = value ?? "";
|
|
534
|
+
if (!next.trim()) return "Name is required.";
|
|
534
535
|
try {
|
|
535
|
-
slugify(
|
|
536
|
+
slugify(next);
|
|
536
537
|
} catch (error) {
|
|
537
538
|
return error instanceof Error ? error.message : "Invalid name.";
|
|
538
539
|
}
|
|
@@ -626,10 +627,34 @@ async function runPrompts(initialName) {
|
|
|
626
627
|
// src/scaffold.ts
|
|
627
628
|
import fs28 from "fs-extra";
|
|
628
629
|
import path29 from "path";
|
|
630
|
+
import { execFileSync } from "child_process";
|
|
629
631
|
import * as p2 from "@clack/prompts";
|
|
630
|
-
import { execSync } from "child_process";
|
|
631
632
|
import color2 from "picocolors";
|
|
632
633
|
|
|
634
|
+
// src/helpers/run-package-install.ts
|
|
635
|
+
import { spawn } from "child_process";
|
|
636
|
+
function runPackageInstall(projectDir, pm) {
|
|
637
|
+
return new Promise((resolve, reject) => {
|
|
638
|
+
const child = spawn(pm.id, pm.installArgs, {
|
|
639
|
+
cwd: projectDir,
|
|
640
|
+
stdio: "inherit",
|
|
641
|
+
env: process.env,
|
|
642
|
+
shell: process.platform === "win32"
|
|
643
|
+
});
|
|
644
|
+
child.on("error", reject);
|
|
645
|
+
child.on("close", (code) => {
|
|
646
|
+
if (code === 0) {
|
|
647
|
+
resolve();
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
reject(new Error(`${pm.id} ${pm.installArgs.join(" ")} exited with ${code}`));
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
function yieldUi() {
|
|
655
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
656
|
+
}
|
|
657
|
+
|
|
633
658
|
// src/helpers/replace-tokens.ts
|
|
634
659
|
import fs from "fs-extra";
|
|
635
660
|
import path2 from "path";
|
|
@@ -1964,7 +1989,7 @@ var huskyInstaller = {
|
|
|
1964
1989
|
},
|
|
1965
1990
|
"devDependencies"
|
|
1966
1991
|
);
|
|
1967
|
-
await addScripts(projectDir, { prepare: "husky" });
|
|
1992
|
+
await addScripts(projectDir, { prepare: "husky || true" });
|
|
1968
1993
|
const tsHooks = [];
|
|
1969
1994
|
if (options.prettier) tsHooks.push("prettier --write");
|
|
1970
1995
|
if (options.eslint) tsHooks.push("eslint --fix");
|
|
@@ -3669,10 +3694,18 @@ var installers = [
|
|
|
3669
3694
|
appProvidersInstaller,
|
|
3670
3695
|
surfaceInstaller
|
|
3671
3696
|
];
|
|
3672
|
-
async function runInstallers(ctx) {
|
|
3697
|
+
async function runInstallers(ctx, onProgress) {
|
|
3698
|
+
const active = installers.filter(
|
|
3699
|
+
(installer) => installer.active(ctx.options)
|
|
3700
|
+
);
|
|
3673
3701
|
const ran = [];
|
|
3674
|
-
for (
|
|
3675
|
-
|
|
3702
|
+
for (let i = 0; i < active.length; i += 1) {
|
|
3703
|
+
const installer = active[i];
|
|
3704
|
+
await onProgress?.({
|
|
3705
|
+
id: installer.id,
|
|
3706
|
+
index: i + 1,
|
|
3707
|
+
total: active.length
|
|
3708
|
+
});
|
|
3676
3709
|
await installer.run(ctx);
|
|
3677
3710
|
ran.push(installer.id);
|
|
3678
3711
|
}
|
|
@@ -3693,6 +3726,7 @@ async function scaffold(options) {
|
|
|
3693
3726
|
}
|
|
3694
3727
|
const spin = p2.spinner();
|
|
3695
3728
|
spin.start("Copying template");
|
|
3729
|
+
await yieldUi();
|
|
3696
3730
|
if (!await fs28.pathExists(TEMPLATE_DIR)) {
|
|
3697
3731
|
spin.stop("Template missing");
|
|
3698
3732
|
throw new Error(`Template directory not found at ${TEMPLATE_DIR}`);
|
|
@@ -3709,30 +3743,51 @@ async function scaffold(options) {
|
|
|
3709
3743
|
await fs28.move(gitignoreSrc, gitignoreDest, { overwrite: true });
|
|
3710
3744
|
}
|
|
3711
3745
|
spin.message("Applying project tokens");
|
|
3746
|
+
await yieldUi();
|
|
3712
3747
|
await replaceTokensInTree(projectDir, {
|
|
3713
3748
|
__PROJECT_NAME__: options.name,
|
|
3714
3749
|
__PROJECT_SLUG__: options.slug,
|
|
3715
3750
|
__PACKAGE_MANAGER__: pm.id
|
|
3716
3751
|
});
|
|
3717
|
-
|
|
3718
|
-
|
|
3752
|
+
const ran = await runInstallers({ projectDir, options }, async (step) => {
|
|
3753
|
+
spin.message(`Installer (${step.index}/${step.total}): ${step.id}`);
|
|
3754
|
+
await yieldUi();
|
|
3755
|
+
});
|
|
3756
|
+
spin.stop(
|
|
3757
|
+
ran.length ? `Scaffold ready \xB7 ${ran.length} installer${ran.length === 1 ? "" : "s"}` : "Scaffold ready \xB7 base template"
|
|
3758
|
+
);
|
|
3719
3759
|
if (!options.noInstall) {
|
|
3720
|
-
|
|
3760
|
+
if (options.husky) {
|
|
3761
|
+
try {
|
|
3762
|
+
execFileSync("git", ["init"], {
|
|
3763
|
+
cwd: projectDir,
|
|
3764
|
+
stdio: "ignore"
|
|
3765
|
+
});
|
|
3766
|
+
} catch {
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
p2.log.step(
|
|
3770
|
+
`Installing dependencies with ${color2.cyan(pm.id)} \u2014 live output below\u2026`
|
|
3771
|
+
);
|
|
3721
3772
|
try {
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
});
|
|
3726
|
-
} catch {
|
|
3773
|
+
await runPackageInstall(projectDir, pm);
|
|
3774
|
+
p2.log.success("Dependencies installed");
|
|
3775
|
+
} catch (error) {
|
|
3727
3776
|
if (pm.id !== "npm") {
|
|
3728
|
-
|
|
3729
|
-
|
|
3777
|
+
p2.log.warn(`${pm.id} failed \u2014 retrying with npm\u2026`);
|
|
3778
|
+
const npm = getPackageManager("npm");
|
|
3779
|
+
await runPackageInstall(projectDir, npm);
|
|
3780
|
+
p2.log.success("Dependencies installed (npm)");
|
|
3730
3781
|
} else {
|
|
3731
|
-
|
|
3782
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3783
|
+
throw new Error(`Dependency install failed: ${message}`);
|
|
3732
3784
|
}
|
|
3733
3785
|
}
|
|
3786
|
+
} else {
|
|
3787
|
+
p2.log.info(
|
|
3788
|
+
color2.dim(`Skipped install \xB7 run ${pm.id} install in the project folder`)
|
|
3789
|
+
);
|
|
3734
3790
|
}
|
|
3735
|
-
spin.stop("Project ready");
|
|
3736
3791
|
if (ran.length) {
|
|
3737
3792
|
p2.log.info(`Installers: ${ran.join(", ")}`);
|
|
3738
3793
|
} else {
|