create-nexora-next 0.4.7 → 0.4.9
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 +362 -37
- package/package.json +1 -1
- package/src/index.js +10 -1
- package/src/steps/03-base-files.js +12 -15
- package/src/steps/04-providers.js +1 -1
- package/src/steps/12-github.js +210 -0
- package/src/templates/files.js +2 -10
- package/src/utils/args.js +7 -2
package/README.md
CHANGED
|
@@ -1,46 +1,371 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
```
|
|
4
|
+
███╗ ██╗███████╗██╗ ██╗ ██████╗ ██████╗ █████╗
|
|
5
|
+
████╗ ██║██╔════╝╚██╗██╔╝██╔═══██╗██╔══██╗██╔══██╗
|
|
6
|
+
██╔██╗ ██║█████╗ ╚███╔╝ ██║ ██║██████╔╝███████║
|
|
7
|
+
██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║██╔══██╗██╔══██║
|
|
8
|
+
██║ ╚████║███████╗██╔╝ ██╗╚██████╔╝██║ ██║██║ ██║
|
|
9
|
+
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**The opinionated Next.js scaffold CLI by Rayan.**
|
|
13
|
+
One command. Full setup. Ship faster.
|
|
14
|
+
|
|
15
|
+
[](https://www.npmjs.com/package/create-nexora-next)
|
|
16
|
+
[](https://www.npmjs.com/package/create-nexora-next)
|
|
17
|
+
[](./LICENSE)
|
|
18
|
+
[](https://nodejs.org)
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
---
|
|
4
23
|
|
|
5
24
|
## Usage
|
|
6
25
|
|
|
7
26
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
# npm
|
|
28
|
+
npx create-nexora-next@latest
|
|
29
|
+
|
|
30
|
+
# pnpm
|
|
31
|
+
pnpm dlx create-nexora-next@latest
|
|
32
|
+
|
|
33
|
+
# bun
|
|
34
|
+
bunx create-nexora-next@latest
|
|
35
|
+
|
|
36
|
+
# yarn
|
|
37
|
+
yarn dlx create-nexora-next@latest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The CLI walks you through a short prompt sequence — pick your package manager, choose your features, and watch your project build itself. Every step is wrapped with error recovery — if something fails, the CLI pauses, shows you the exact error, and lets you retry without starting over.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Flag mode
|
|
45
|
+
|
|
46
|
+
Skip the prompts entirely by passing flags directly:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Full setup with bun, all features
|
|
50
|
+
npx create-nexora-next my-app --pm bun --all
|
|
51
|
+
|
|
52
|
+
# Pick specific features
|
|
53
|
+
npx create-nexora-next my-app --pm pnpm --i18n --query --theming --husky
|
|
54
|
+
|
|
55
|
+
# Disable specific features from --all
|
|
56
|
+
npx create-nexora-next my-app --pm bun --all --no-animations --no-auth
|
|
57
|
+
|
|
58
|
+
# Accept all defaults silently
|
|
59
|
+
npx create-nexora-next my-app -y
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Flag | Description |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `--pm <manager>` | Package manager — `npm` · `pnpm` · `bun` · `yarn` |
|
|
65
|
+
| `--all` | Enable every feature |
|
|
66
|
+
| `--i18n` | Localization via next-intl |
|
|
67
|
+
| `--query` | TanStack Query + persistence |
|
|
68
|
+
| `--theming` | Theming via next-themes |
|
|
69
|
+
| `--animations` | Motion + GSAP |
|
|
70
|
+
| `--auth` | Auth proxy + validators |
|
|
71
|
+
| `--axios` | Axios client/server instances |
|
|
72
|
+
| `--husky` | Husky pre-commit hooks |
|
|
73
|
+
| `--rc` / `--react-compiler` | React Compiler |
|
|
74
|
+
| `--github` | Connect a GitHub repository |
|
|
75
|
+
| `--no-theming` | Skip theming |
|
|
76
|
+
| `--no-husky` | Skip Husky |
|
|
77
|
+
| `--no-rc` | Skip React Compiler |
|
|
78
|
+
| `--no-github` | Skip GitHub setup |
|
|
79
|
+
| `-y` / `--yes` | Accept all defaults |
|
|
80
|
+
| `-v` / `--version` | Print version |
|
|
81
|
+
| `-h` / `--help` | Show help |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## What's always included
|
|
86
|
+
|
|
87
|
+
Every project gets this baseline — no questions asked:
|
|
88
|
+
|
|
89
|
+
| What | Details |
|
|
34
90
|
|---|---|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
91
|
+
| **Next.js 16+** | TypeScript · Tailwind CSS v4 · ESLint · App Router · `src/` dir · `~/` path alias |
|
|
92
|
+
| **shadcn/ui** | Initialised with defaults · `components.json` patched to `~/lib/utils/index.ts` · default `src/lib/utils.ts` removed |
|
|
93
|
+
| **Custom globals.css** | Squircle utilities · responsive container · skeleton animation · reduced motion |
|
|
94
|
+
| **Prettier** | `prettier-plugin-tailwindcss` for automatic class sorting |
|
|
95
|
+
| **Poppins font** | Via `next/font/google` with full weight range (200–800) |
|
|
96
|
+
| **Utility functions** | `cn()` · `toSlug()` · `createInitials()` · `normalizeEmail()` · `sanitizeName()` · `trycatch()` |
|
|
97
|
+
| **Constants** | `SITE_URL` · `TZ_COOKIE` · locale constants if i18n opted in |
|
|
98
|
+
| **SEO scaffold** | `src/lib/seo/index.ts` — locale-aware if i18n enabled |
|
|
99
|
+
| **Sitemap** | `src/app/sitemap.ts` pre-wired to `SITE_URL` |
|
|
100
|
+
| **ESLint config** | Clean flat config · Next.js core web vitals · TypeScript rules · TanStack plugin if query opted in |
|
|
101
|
+
| **Providers pattern** | `src/components/providers/index.tsx` — dynamically generated, only includes opted-in providers |
|
|
102
|
+
| **SileoToaster** | Toast notifications via `sileo` at `src/components/ui/sileo.tsx` |
|
|
103
|
+
| **nuqs** | Type-safe URL search params adapter |
|
|
104
|
+
| **nextjs-toploader** | Page transition progress bar |
|
|
105
|
+
| **Folder structure** | `hooks/` · `components/{atoms,molecules,organisms,primitives,adapters,providers,ui}/` |
|
|
106
|
+
| **Core deps** | `zustand` · `zod` · `react-hook-form` · `date-fns` · `@date-fns/tz` · `clsx` · `tailwind-merge` |
|
|
107
|
+
| **Dev deps** | `prettier` · `prettier-plugin-tailwindcss` · `babel-plugin-react-compiler` |
|
|
108
|
+
| **`type-check` script** | `tsc --pretty --noEmit` added to `package.json` |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Optional features
|
|
113
|
+
|
|
114
|
+
### 🌍 Localization (`next-intl`)
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
src/
|
|
118
|
+
├── i18n/
|
|
119
|
+
│ ├── routing.ts # locale routing config
|
|
120
|
+
│ ├── request.ts # server-side locale resolution + timezone
|
|
121
|
+
│ └── navigation.ts # typed Link, useRouter, usePathname
|
|
122
|
+
├── app/
|
|
123
|
+
│ ├── layout.tsx # replaced with passthrough shell
|
|
124
|
+
│ └── [locale]/
|
|
125
|
+
│ ├── layout.tsx # full root layout with Providers + fonts
|
|
126
|
+
│ └── page.tsx # localized home page
|
|
127
|
+
└── lib/utils/
|
|
128
|
+
└── locale-date-formats.ts
|
|
129
|
+
|
|
130
|
+
locales/
|
|
131
|
+
└── en.json # base translation keys
|
|
132
|
+
|
|
133
|
+
next-intl.ts # typed AppConfig declaration
|
|
134
|
+
next.config.ts # updated with createNextIntlPlugin
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- `LocaleProvider` wrapping `NextIntlClientProvider`
|
|
138
|
+
- `TzProvider` — auto-detects and persists device timezone via cookie
|
|
139
|
+
- Default `src/app/page.tsx` removed — `[locale]/page.tsx` takes over routing
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### ⚡ TanStack Query
|
|
144
|
+
|
|
145
|
+
Installs in the correct peer dependency order — `@tanstack/react-query` first, persist packages after:
|
|
146
|
+
|
|
147
|
+
- `@tanstack/react-query`
|
|
148
|
+
- `@tanstack/react-query-persist-client`
|
|
149
|
+
- `@tanstack/query-async-storage-persister`
|
|
150
|
+
- `@tanstack/eslint-plugin-query` (devDependency)
|
|
151
|
+
|
|
152
|
+
Pre-configured `QueryClientProvider` with:
|
|
153
|
+
- 5-minute stale time + gcTime
|
|
154
|
+
- 24-hour local storage persistence
|
|
155
|
+
- Mutation cache auto-invalidation via `meta.invalidateQueries`
|
|
156
|
+
- `refetchOnWindowFocus: false`, `retry: false`
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### 🎨 Theming (`next-themes`)
|
|
161
|
+
|
|
162
|
+
`ThemeProvider` with system default, class attribute, and `disableTransitionOnChange`.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### 🔐 Auth
|
|
167
|
+
|
|
168
|
+
- Proxy chain middleware at `src/proxy.ts`
|
|
169
|
+
- `withProxyChain` + `authProxy` stub ready to implement
|
|
170
|
+
- `src/lib/validators/index.ts` — `EmailValidator`, `PasswordValidator`, `NameValidator`, `OTPValidator` and more
|
|
171
|
+
- `src/lib/validators/auth.ts` — `LoginSchema`, `RegisterSchema` with zod
|
|
172
|
+
- `src/apis/{client,server}/index.ts`
|
|
173
|
+
- `cookies-next` installed
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### 🎭 Animations
|
|
178
|
+
|
|
179
|
+
- `motion` (Framer Motion v11+)
|
|
180
|
+
- `gsap`
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### 🔄 Axios
|
|
185
|
+
|
|
186
|
+
- `axios` installed
|
|
187
|
+
- `src/lib/axios/axios.client.ts` — client instance with credentials
|
|
188
|
+
- `src/lib/axios/axios.server.ts` — server instance
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### 🐶 Husky pre-commit hooks
|
|
193
|
+
|
|
194
|
+
Runs three checks on every commit — fails fast with clear feedback:
|
|
195
|
+
|
|
196
|
+
```sh
|
|
197
|
+
🚀 Running pre-commit checks...
|
|
198
|
+
|
|
199
|
+
📝 Linting your code... → bun lint
|
|
200
|
+
✅ Lint passed!
|
|
201
|
+
|
|
202
|
+
🔍 Checking types... → bun type-check
|
|
203
|
+
✅ Types are solid!
|
|
204
|
+
|
|
205
|
+
🏗️ Building project... → bun run build
|
|
206
|
+
✅ Build successful!
|
|
207
|
+
|
|
208
|
+
🎉 All checks passed! Committing...
|
|
209
|
+
⏰ Commit time: 2026-03-21 14:30:00
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### ⚛️ React Compiler
|
|
215
|
+
|
|
216
|
+
`babel-plugin-react-compiler` always installed as a devDependency. If opted in, `reactCompiler: true` is added to `next.config.ts`.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### 🐙 GitHub repository
|
|
221
|
+
|
|
222
|
+
Connects your project to GitHub automatically using the GitHub API — no manual `git remote` setup needed.
|
|
223
|
+
|
|
224
|
+
**Before using this feature, generate a Classic PAT:**
|
|
225
|
+
|
|
226
|
+
> ⚠️ Use a **Classic** Personal Access Token — not fine-grained.
|
|
227
|
+
> Fine-grained tokens are scoped to existing repositories, so they cannot grant access to repos that don't exist yet. A Classic PAT with the `repo` scope works for both creating new repos and connecting existing ones.
|
|
228
|
+
|
|
229
|
+
1. Go to [github.com/settings/tokens/new](https://github.com/settings/tokens/new)
|
|
230
|
+
2. Set **Token type** to **Classic**
|
|
231
|
+
3. Tick the **`repo`** scope — that's all you need
|
|
232
|
+
4. Set an expiration (90 days recommended)
|
|
233
|
+
5. Copy the token — it starts with `gh`
|
|
234
|
+
|
|
235
|
+
**What the CLI does with it:**
|
|
236
|
+
|
|
237
|
+
1. Prompts for your Classic PAT — masked input
|
|
238
|
+
2. Verifies it against the GitHub API immediately
|
|
239
|
+
3. If invalid — shows the error and asks **"Try a different token?"** — loops until valid or skipped
|
|
240
|
+
4. Choose to **create a new repo** or **pick an existing one**
|
|
241
|
+
5. If creating — set a name (defaults to project name), optional description, visibility (private by default)
|
|
242
|
+
6. If picking — fetches your 100 most recently pushed repos to choose from
|
|
243
|
+
7. Adds remote origin via SSH, pushes `main`, creates and pushes `staging` branch
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
✓ Authenticated as bbrainttech
|
|
247
|
+
|
|
248
|
+
✔ What would you like to do? › Create a new repository
|
|
249
|
+
✔ Repository name: › my-app
|
|
250
|
+
✔ Repository description (optional): ›
|
|
251
|
+
✔ Make repository private? › yes
|
|
252
|
+
|
|
253
|
+
✓ Creating repo my-app on GitHub
|
|
254
|
+
✓ Creating initial commit
|
|
255
|
+
✓ Adding remote origin → git@github.com:bbrainttech/my-app.git
|
|
256
|
+
✓ Pushing main branch
|
|
257
|
+
✓ Creating staging branch
|
|
258
|
+
|
|
259
|
+
✓ Repository connected
|
|
260
|
+
git@github.com:bbrainttech/my-app.git
|
|
261
|
+
Branches: main · staging
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
If you skip or cancel at any point, the project is still fully built — GitHub setup is the last step and nothing else is affected.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Project structure
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
my-app/
|
|
272
|
+
├── locales/
|
|
273
|
+
│ └── en.json # (i18n)
|
|
274
|
+
├── src/
|
|
275
|
+
│ ├── app/
|
|
276
|
+
│ │ ├── [locale]/
|
|
277
|
+
│ │ │ ├── layout.tsx # (i18n) full root layout
|
|
278
|
+
│ │ │ └── page.tsx # (i18n) localized home
|
|
279
|
+
│ │ ├── layout.tsx # passthrough shell (i18n) or full layout
|
|
280
|
+
│ │ ├── globals.css
|
|
281
|
+
│ │ └── sitemap.ts
|
|
282
|
+
│ ├── i18n/ # (i18n)
|
|
283
|
+
│ │ ├── routing.ts
|
|
284
|
+
│ │ ├── request.ts
|
|
285
|
+
│ │ └── navigation.ts
|
|
286
|
+
│ ├── apis/ # (auth)
|
|
287
|
+
│ │ ├── client/index.ts
|
|
288
|
+
│ │ └── server/index.ts
|
|
289
|
+
│ ├── components/
|
|
290
|
+
│ │ ├── adapters/nuqs.adapter.tsx
|
|
291
|
+
│ │ ├── atoms/
|
|
292
|
+
│ │ ├── molecules/
|
|
293
|
+
│ │ ├── organisms/
|
|
294
|
+
│ │ ├── primitives/
|
|
295
|
+
│ │ ├── providers/
|
|
296
|
+
│ │ │ ├── index.tsx # dynamic — opted-in providers only
|
|
297
|
+
│ │ │ ├── theme.provider.tsx # (theming)
|
|
298
|
+
│ │ │ ├── locale.provider.tsx # (i18n)
|
|
299
|
+
│ │ │ ├── tz.provider.tsx # (i18n)
|
|
300
|
+
│ │ │ └── query-client.provider.tsx # (query)
|
|
301
|
+
│ │ └── ui/
|
|
302
|
+
│ │ └── sileo.tsx
|
|
303
|
+
│ ├── constants/index.ts
|
|
304
|
+
│ ├── hooks/
|
|
305
|
+
│ ├── lib/
|
|
306
|
+
│ │ ├── axios/ # (axios)
|
|
307
|
+
│ │ │ ├── axios.client.ts
|
|
308
|
+
│ │ │ └── axios.server.ts
|
|
309
|
+
│ │ ├── fonts.ts
|
|
310
|
+
│ │ ├── proxy/ # (auth or i18n)
|
|
311
|
+
│ │ │ ├── index.ts
|
|
312
|
+
│ │ │ └── auth.ts # (auth)
|
|
313
|
+
│ │ ├── seo/index.ts
|
|
314
|
+
│ │ ├── utils/
|
|
315
|
+
│ │ │ ├── index.ts
|
|
316
|
+
│ │ │ └── locale-date-formats.ts # (i18n)
|
|
317
|
+
│ │ └── validators/ # (auth)
|
|
318
|
+
│ │ ├── index.ts
|
|
319
|
+
│ │ └── auth.ts
|
|
320
|
+
│ └── proxy.ts # (i18n or auth)
|
|
321
|
+
├── .env.local
|
|
322
|
+
├── .prettierrc
|
|
323
|
+
├── .husky/pre-commit # (husky)
|
|
324
|
+
├── components.json
|
|
325
|
+
├── eslint.config.mjs
|
|
326
|
+
├── next-intl.ts # (i18n)
|
|
327
|
+
└── next.config.ts
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Error recovery
|
|
333
|
+
|
|
334
|
+
Every step in the CLI is wrapped with automatic retry-on-failure. If a step throws — network issue, permission error, missing binary — the CLI:
|
|
335
|
+
|
|
336
|
+
1. Shows the exact error message
|
|
337
|
+
2. Asks **"Ready to retry?"**
|
|
338
|
+
3. Loops until success or you choose to abort
|
|
339
|
+
|
|
340
|
+
For GitHub token errors specifically, the retry loop re-prompts for a fresh token rather than restarting the whole step.
|
|
341
|
+
|
|
342
|
+
---
|
|
42
343
|
|
|
43
344
|
## Requirements
|
|
44
345
|
|
|
45
|
-
- Node.js 18+
|
|
46
|
-
- npm
|
|
346
|
+
- **Node.js** 18+
|
|
347
|
+
- **Package manager** — npm · pnpm · bun · yarn
|
|
348
|
+
- **GitHub Classic PAT** — only if using the GitHub feature (`repo` scope, not fine-grained)
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Part of the Nexora family
|
|
353
|
+
|
|
354
|
+
| Package | Stack | Status |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| `create-nexora-next` | Next.js 16+ | ✅ Available |
|
|
357
|
+
| `create-nexora-go` | Go | 🔜 Coming soon |
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Author
|
|
362
|
+
|
|
363
|
+
**Chiatiah Rayan** · [rayanstudio.dev](https://rayanstudio.dev) · [@bbrainttech](https://github.com/bbrainttech)
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
<div align="center">
|
|
368
|
+
|
|
369
|
+
Made with 🔥 by Rayan · MIT License
|
|
370
|
+
|
|
371
|
+
</div>
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { stepInstallDeps } from './steps/08-install-deps.js'
|
|
|
20
20
|
import { stepSetupHusky } from './steps/09-husky.js'
|
|
21
21
|
import { stepSetupAxios } from './steps/10-axios.js'
|
|
22
22
|
import { stepPatchPackageJson } from './steps/11-patch-pkg.js'
|
|
23
|
+
import { stepSetupGithub } from './steps/12-github.js'
|
|
23
24
|
|
|
24
25
|
function bail(msg) {
|
|
25
26
|
p.cancel(pc.red(msg))
|
|
@@ -73,7 +74,7 @@ function printSummary(opts, projectName, pm) {
|
|
|
73
74
|
p.log.success(pc.bold(pc.green(` 🚀 ${projectName} is ready!`)))
|
|
74
75
|
console.log()
|
|
75
76
|
console.log(pc.bold(' What was set up:'))
|
|
76
|
-
console.log(row('Next.js
|
|
77
|
+
console.log(row('Next.js 16+ + TypeScript + Tailwind + ESLint', true))
|
|
77
78
|
console.log(row('shadcn/ui + custom globals.css utilities', true))
|
|
78
79
|
console.log(row('Prettier + tailwind class sorting', true))
|
|
79
80
|
console.log(row('Poppins font + path alias ~/', true))
|
|
@@ -85,6 +86,7 @@ function printSummary(opts, projectName, pm) {
|
|
|
85
86
|
console.log(row('Axios client + server instances', opts.axios))
|
|
86
87
|
console.log(row('Husky pre-commit hooks', opts.husky))
|
|
87
88
|
console.log(row('React Compiler', opts.reactCompiler))
|
|
89
|
+
console.log(row('GitHub repository connected', opts.github))
|
|
88
90
|
console.log()
|
|
89
91
|
console.log(pc.bold(' Next steps:'))
|
|
90
92
|
console.log(` ${pc.cyan('cd')} ${projectName}`)
|
|
@@ -177,6 +179,7 @@ async function main() {
|
|
|
177
179
|
const axios = await ask(parsed.axios, () => p.confirm({ message: 'Set up axios client/server instances?', initialValue: false }))
|
|
178
180
|
const husky = await ask(parsed.husky, () => p.confirm({ message: 'Configure Husky pre-commit hooks?', initialValue: true }))
|
|
179
181
|
const reactCompiler = await ask(parsed.reactCompiler, () => p.confirm({ message: 'Enable React Compiler? (babel-plugin-react-compiler)', initialValue: true }))
|
|
182
|
+
const github = await ask(parsed.github, () => p.confirm({ message: 'Connect a GitHub repository?', initialValue: true }))
|
|
180
183
|
|
|
181
184
|
const features = [
|
|
182
185
|
i18n && 'i18n',
|
|
@@ -187,6 +190,7 @@ async function main() {
|
|
|
187
190
|
axios && 'axios',
|
|
188
191
|
husky && 'husky',
|
|
189
192
|
reactCompiler && 'react-compiler',
|
|
193
|
+
github && 'github',
|
|
190
194
|
].filter(Boolean)
|
|
191
195
|
|
|
192
196
|
console.log()
|
|
@@ -213,6 +217,7 @@ async function main() {
|
|
|
213
217
|
axios: Boolean(axios),
|
|
214
218
|
husky: Boolean(husky),
|
|
215
219
|
reactCompiler: Boolean(reactCompiler),
|
|
220
|
+
github: Boolean(github),
|
|
216
221
|
}
|
|
217
222
|
|
|
218
223
|
await stepCreateNextApp(name, pm, targetDir)
|
|
@@ -232,6 +237,10 @@ async function main() {
|
|
|
232
237
|
|
|
233
238
|
if (opts.husky) await stepSetupHusky(targetDir, pm)
|
|
234
239
|
|
|
240
|
+
if (opts.github) {
|
|
241
|
+
await stepSetupGithub(targetDir, name)
|
|
242
|
+
}
|
|
243
|
+
|
|
235
244
|
printSummary(opts, name, pm)
|
|
236
245
|
p.outro(pc.bgGreen(pc.black(' Done! ')))
|
|
237
246
|
}
|
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
1
|
import fs from 'fs'
|
|
3
|
-
import
|
|
4
|
-
import { safeStep } from '../utils/safe-step.js'
|
|
2
|
+
import path from 'path'
|
|
5
3
|
import {
|
|
4
|
+
constantsTs,
|
|
6
5
|
envLocal,
|
|
7
|
-
|
|
6
|
+
eslintConfigMjs,
|
|
8
7
|
fontsTs,
|
|
9
|
-
|
|
10
|
-
utilsWithAxiosTs,
|
|
11
|
-
constantsTs,
|
|
12
|
-
seoTs,
|
|
13
|
-
seoWithLocaleTs,
|
|
14
|
-
sileoUiTsx,
|
|
15
|
-
sileoProviderTsx,
|
|
8
|
+
localeLayoutTs,
|
|
16
9
|
nuqsAdapterTsx,
|
|
10
|
+
prettierRc,
|
|
17
11
|
rootLayoutI18nTs,
|
|
18
12
|
rootLayoutTs,
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
seoTs,
|
|
14
|
+
seoWithLocaleTs,
|
|
15
|
+
sileoUiTsx,
|
|
21
16
|
sitemapTs,
|
|
17
|
+
utilsTs,
|
|
18
|
+
utilsWithAxiosTs
|
|
22
19
|
} from '../templates/files.js'
|
|
20
|
+
import { safeStep } from '../utils/safe-step.js'
|
|
21
|
+
import { mkdir, writeFile } from '../utils/writer.js'
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* @param {string} targetDir
|
|
@@ -56,8 +55,6 @@ export async function stepWriteBaseFiles(targetDir, opts) {
|
|
|
56
55
|
// src/components/ui/sileo.tsx
|
|
57
56
|
writeFile(p('src', 'components', 'ui', 'sileo.tsx'), sileoUiTsx)
|
|
58
57
|
|
|
59
|
-
// src/components/providers/sileo.provider.tsx
|
|
60
|
-
writeFile(p('src', 'components', 'providers', 'sileo.provider.tsx'), sileoProviderTsx)
|
|
61
58
|
|
|
62
59
|
// src/components/adapters/nuqs.adapter.tsx
|
|
63
60
|
writeFile(p('src', 'components', 'adapters', 'nuqs.adapter.tsx'), nuqsAdapterTsx)
|
|
@@ -17,7 +17,7 @@ export async function stepWriteProviders(targetDir, opts) {
|
|
|
17
17
|
await safeStep("Generating providers", () => {
|
|
18
18
|
const p = (...parts) => path.join(targetDir, ...parts);
|
|
19
19
|
|
|
20
|
-
// Dynamic providers/index.
|
|
20
|
+
// Dynamic providers/index.tsx
|
|
21
21
|
writeFile(
|
|
22
22
|
p("src", "components", "providers", "index.tsx"),
|
|
23
23
|
providersIndexTsx(opts),
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import * as p from '@clack/prompts'
|
|
3
|
+
import pc from 'picocolors'
|
|
4
|
+
import { run, runSilent } from '../utils/runner.js'
|
|
5
|
+
import { safeStep } from '../utils/safe-step.js'
|
|
6
|
+
|
|
7
|
+
const GH_API = 'https://api.github.com'
|
|
8
|
+
|
|
9
|
+
// ─── GitHub API helpers ───────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
async function ghFetch(endpoint, token, options = {}) {
|
|
12
|
+
const res = await fetch(`${GH_API}${endpoint}`, {
|
|
13
|
+
...options,
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: `Bearer ${token}`,
|
|
16
|
+
Accept: 'application/vnd.github+json',
|
|
17
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
...options.headers,
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
const data = await res.json()
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw new Error(data.message || `GitHub API error ${res.status}`)
|
|
25
|
+
}
|
|
26
|
+
return data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getAuthenticatedUser(token) {
|
|
30
|
+
return ghFetch('/user', token)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function listUserRepos(token) {
|
|
34
|
+
// Fetch up to 100 most recently pushed repos
|
|
35
|
+
return ghFetch('/user/repos?per_page=100&sort=pushed&affiliation=owner', token)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function createRepo(token, { name, description, isPrivate }) {
|
|
39
|
+
return ghFetch('/user/repos', token, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
name,
|
|
43
|
+
description: description || '',
|
|
44
|
+
private: isPrivate,
|
|
45
|
+
auto_init: false,
|
|
46
|
+
}),
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Git helpers ──────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function gitRun(cmd, cwd) {
|
|
53
|
+
run(cmd, cwd)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Main step ────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} targetDir
|
|
60
|
+
* @param {string} projectName
|
|
61
|
+
*/
|
|
62
|
+
export async function stepSetupGithub(targetDir, projectName) {
|
|
63
|
+
p.log.step(pc.bold(pc.cyan('\n GitHub repository setup')))
|
|
64
|
+
|
|
65
|
+
// ── Ask for PAT (with retry loop on invalid token) ───────────────────────
|
|
66
|
+
let token
|
|
67
|
+
let ghUser
|
|
68
|
+
|
|
69
|
+
while (true) {
|
|
70
|
+
const input = await p.password({
|
|
71
|
+
message: 'Enter your GitHub Personal Access Token (PAT):',
|
|
72
|
+
validate: (v) => {
|
|
73
|
+
if (!v || !v.trim()) return 'Token is required.'
|
|
74
|
+
if (!v.trim().startsWith('gh')) return 'PAT should start with "gh" — check your token.'
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
if (p.isCancel(input)) { p.log.warn('GitHub setup skipped.'); return }
|
|
78
|
+
|
|
79
|
+
const verifySpinner = p.spinner()
|
|
80
|
+
verifySpinner.start('Verifying token with GitHub...')
|
|
81
|
+
try {
|
|
82
|
+
ghUser = await getAuthenticatedUser(input.trim())
|
|
83
|
+
verifySpinner.stop(pc.green(`✓ Authenticated as ${pc.bold(ghUser.login)}`))
|
|
84
|
+
token = input.trim()
|
|
85
|
+
break
|
|
86
|
+
} catch (err) {
|
|
87
|
+
verifySpinner.stop(pc.red('✗ Token verification failed'))
|
|
88
|
+
p.log.error(pc.red(` ${err.message}`))
|
|
89
|
+
p.log.warn(pc.yellow(' Generate a valid PAT at https://github.com/settings/tokens (repo scope required)'))
|
|
90
|
+
|
|
91
|
+
const retry = await p.confirm({
|
|
92
|
+
message: 'Try a different token?',
|
|
93
|
+
initialValue: true,
|
|
94
|
+
})
|
|
95
|
+
if (p.isCancel(retry) || !retry) {
|
|
96
|
+
p.log.warn('GitHub setup skipped — you can add the remote manually later.')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
// loop — prompt again
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Create new or pick existing ────────────────────────────────────────────
|
|
104
|
+
const repoAction = await p.select({
|
|
105
|
+
message: 'What would you like to do?',
|
|
106
|
+
options: [
|
|
107
|
+
{ value: 'create', label: 'Create a new repository' },
|
|
108
|
+
{ value: 'existing', label: 'Use an existing repository' },
|
|
109
|
+
],
|
|
110
|
+
})
|
|
111
|
+
if (p.isCancel(repoAction)) { p.log.warn('GitHub setup skipped.'); return }
|
|
112
|
+
|
|
113
|
+
let sshUrl
|
|
114
|
+
|
|
115
|
+
// ── CREATE NEW REPO ────────────────────────────────────────────────────────
|
|
116
|
+
if (repoAction === 'create') {
|
|
117
|
+
const repoName = await p.text({
|
|
118
|
+
message: 'Repository name:',
|
|
119
|
+
initialValue: projectName,
|
|
120
|
+
validate: (v) => {
|
|
121
|
+
if (!v || !v.trim()) return 'Repository name is required.'
|
|
122
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(v.trim()))
|
|
123
|
+
return 'Only letters, numbers, hyphens, underscores and dots allowed.'
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
if (p.isCancel(repoName)) { p.log.warn('GitHub setup skipped.'); return }
|
|
127
|
+
|
|
128
|
+
const repoDesc = await p.text({
|
|
129
|
+
message: 'Repository description (optional):',
|
|
130
|
+
placeholder: 'Leave blank to skip',
|
|
131
|
+
})
|
|
132
|
+
if (p.isCancel(repoDesc)) { p.log.warn('GitHub setup skipped.'); return }
|
|
133
|
+
|
|
134
|
+
const isPrivate = await p.confirm({
|
|
135
|
+
message: 'Make repository private?',
|
|
136
|
+
initialValue: true,
|
|
137
|
+
})
|
|
138
|
+
if (p.isCancel(isPrivate)) { p.log.warn('GitHub setup skipped.'); return }
|
|
139
|
+
|
|
140
|
+
let newRepo
|
|
141
|
+
await safeStep(`Creating repo ${pc.cyan(repoName.trim())} on GitHub`, async () => {
|
|
142
|
+
newRepo = await createRepo(token.trim(), {
|
|
143
|
+
name: repoName.trim(),
|
|
144
|
+
description: typeof repoDesc === 'string' ? repoDesc.trim() : '',
|
|
145
|
+
isPrivate: Boolean(isPrivate),
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
sshUrl = newRepo.ssh_url
|
|
150
|
+
|
|
151
|
+
// ── PICK EXISTING REPO ─────────────────────────────────────────────────────
|
|
152
|
+
} else {
|
|
153
|
+
let repos = []
|
|
154
|
+
const fetchSpinner = p.spinner()
|
|
155
|
+
fetchSpinner.start('Fetching your repositories...')
|
|
156
|
+
try {
|
|
157
|
+
repos = await listUserRepos(token.trim())
|
|
158
|
+
fetchSpinner.stop(pc.green(`✓ Found ${repos.length} repositories`))
|
|
159
|
+
} catch (err) {
|
|
160
|
+
fetchSpinner.stop(pc.red('✗ Failed to fetch repositories'))
|
|
161
|
+
p.log.error(pc.red(err.message))
|
|
162
|
+
p.log.warn('GitHub setup skipped.')
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (repos.length === 0) {
|
|
167
|
+
p.log.warn('No repositories found on your account. Try creating one instead.')
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const picked = await p.select({
|
|
172
|
+
message: 'Select a repository:',
|
|
173
|
+
options: repos.map((r) => ({
|
|
174
|
+
value: r.ssh_url,
|
|
175
|
+
label: r.full_name,
|
|
176
|
+
hint: r.private ? 'private' : 'public',
|
|
177
|
+
})),
|
|
178
|
+
})
|
|
179
|
+
if (p.isCancel(picked)) { p.log.warn('GitHub setup skipped.'); return }
|
|
180
|
+
|
|
181
|
+
sshUrl = picked
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Git init + remote + staging branch ────────────────────────────────────
|
|
185
|
+
await safeStep('Creating initial commit', () => {
|
|
186
|
+
gitRun('git add -A', targetDir)
|
|
187
|
+
gitRun('git commit -m "chore: initial commit from create-nexora-next"', targetDir)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
await safeStep(`Adding remote origin → ${pc.cyan(sshUrl)}`, () => {
|
|
191
|
+
gitRun(`git remote add origin ${sshUrl}`, targetDir)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
await safeStep('Pushing main branch', () => {
|
|
195
|
+
gitRun('git branch -M main', targetDir)
|
|
196
|
+
gitRun('git push -u origin main', targetDir)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
await safeStep('Creating staging branch', () => {
|
|
200
|
+
gitRun('git checkout -b staging', targetDir)
|
|
201
|
+
gitRun('git push -u origin staging', targetDir)
|
|
202
|
+
gitRun('git checkout main', targetDir)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
p.log.success(
|
|
206
|
+
pc.green(`\n ✓ Repository connected`) +
|
|
207
|
+
pc.dim(`\n ${sshUrl}`) +
|
|
208
|
+
pc.dim(`\n Branches: main · staging`)
|
|
209
|
+
)
|
|
210
|
+
}
|
package/src/templates/files.js
CHANGED
|
@@ -299,14 +299,6 @@ const SileoToaster = () => {
|
|
|
299
299
|
export { SileoToaster }
|
|
300
300
|
`
|
|
301
301
|
|
|
302
|
-
// ─── src/components/providers/sileo.provider.tsx ─────────────────────────────
|
|
303
|
-
export const sileoProviderTsx = `// Sileo root provider — wrap app-level providers here as needed
|
|
304
|
-
import type { PropsWithChildren } from 'react'
|
|
305
|
-
|
|
306
|
-
export default ({ children }: PropsWithChildren) => {
|
|
307
|
-
return <>{children}</>
|
|
308
|
-
}
|
|
309
|
-
`
|
|
310
302
|
|
|
311
303
|
// ─── src/components/providers/index.tsx (dynamic) ─────────────────────────────
|
|
312
304
|
export const providersIndexTsx = (opts) => {
|
|
@@ -601,9 +593,9 @@ export const proxyAuthTs = `import { NextFetchEvent, NextRequest, NextResponse }
|
|
|
601
593
|
import type { ContextualProxy, ProxyContext } from '.'
|
|
602
594
|
|
|
603
595
|
export const authProxy: ContextualProxy = async (
|
|
604
|
-
|
|
596
|
+
_req: NextRequest,
|
|
605
597
|
_event: NextFetchEvent,
|
|
606
|
-
|
|
598
|
+
_ctx: ProxyContext,
|
|
607
599
|
) => {
|
|
608
600
|
// TODO: implement auth checks — verify session/token, set ctx.role, redirect if needed
|
|
609
601
|
return NextResponse.next()
|
package/src/utils/args.js
CHANGED
|
@@ -11,9 +11,11 @@ const FLAGS = {
|
|
|
11
11
|
'--husky': { key: 'husky' },
|
|
12
12
|
'--rc': { key: 'reactCompiler' },
|
|
13
13
|
'--react-compiler': { key: 'reactCompiler' },
|
|
14
|
+
'--github': { key: 'github' },
|
|
14
15
|
'--no-theming': { key: 'theming', value: false },
|
|
15
16
|
'--no-husky': { key: 'husky', value: false },
|
|
16
17
|
'--no-rc': { key: 'reactCompiler', value: false },
|
|
18
|
+
'--no-github': { key: 'github', value: false },
|
|
17
19
|
'--pm': { key: 'pm', takesValue: true },
|
|
18
20
|
'-y': { key: 'yes' },
|
|
19
21
|
'--yes': { key: 'yes' },
|
|
@@ -40,6 +42,7 @@ export function parseArgs(argv) {
|
|
|
40
42
|
axios: null,
|
|
41
43
|
husky: null,
|
|
42
44
|
reactCompiler: null,
|
|
45
|
+
github: null,
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
let i = 0
|
|
@@ -62,7 +65,7 @@ export function parseArgs(argv) {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
if (result.all) {
|
|
65
|
-
for (const key of ['i18n','query','theming','animations','auth','axios','husky','reactCompiler']) {
|
|
68
|
+
for (const key of ['i18n','query','theming','animations','auth','axios','husky','reactCompiler','github']) {
|
|
66
69
|
if (result[key] === null) result[key] = true
|
|
67
70
|
}
|
|
68
71
|
}
|
|
@@ -80,7 +83,7 @@ export function hasAnyFlag(parsed) {
|
|
|
80
83
|
parsed.all ||
|
|
81
84
|
parsed.name !== null ||
|
|
82
85
|
parsed.pm !== null ||
|
|
83
|
-
['i18n','query','theming','animations','auth','axios','husky','reactCompiler']
|
|
86
|
+
['i18n','query','theming','animations','auth','axios','husky','reactCompiler','github']
|
|
84
87
|
.some(k => parsed[k] !== null)
|
|
85
88
|
)
|
|
86
89
|
}
|
|
@@ -103,9 +106,11 @@ export function printHelp(version) {
|
|
|
103
106
|
--axios Axios client/server instances
|
|
104
107
|
--husky Husky pre-commit hooks
|
|
105
108
|
--rc / --react-compiler React Compiler
|
|
109
|
+
--github Connect a GitHub repository
|
|
106
110
|
--no-theming Skip theming
|
|
107
111
|
--no-husky Skip Husky
|
|
108
112
|
--no-rc Skip React Compiler
|
|
113
|
+
--no-github Skip GitHub setup
|
|
109
114
|
-y / --yes Accept all defaults (no prompts)
|
|
110
115
|
-v / --version Print version
|
|
111
116
|
-h / --help Show this help
|