create-nexora-next 0.4.8 → 0.4.10
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 +218 -75
- package/package.json +1 -1
- package/src/index.js +10 -1
- package/src/steps/12-github.js +210 -0
- package/src/templates/files.js +2 -2
- package/src/utils/args.js +7 -2
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
**The opinionated Next.js scaffold CLI by Rayan.**
|
|
12
|
+
**The opinionated Next.js scaffold CLI by Rayan.**
|
|
13
13
|
One command. Full setup. Ship faster.
|
|
14
14
|
|
|
15
15
|
[](https://www.npmjs.com/package/create-nexora-next)
|
|
@@ -37,92 +37,231 @@ bunx create-nexora-next@latest
|
|
|
37
37
|
yarn dlx create-nexora-next@latest
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
The CLI
|
|
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 |
|
|
41
82
|
|
|
42
83
|
---
|
|
43
84
|
|
|
44
85
|
## What's always included
|
|
45
86
|
|
|
46
|
-
Every project
|
|
87
|
+
Every project gets this baseline — no questions asked:
|
|
47
88
|
|
|
48
89
|
| What | Details |
|
|
49
90
|
|---|---|
|
|
50
|
-
| **Next.js 16
|
|
51
|
-
| **shadcn/ui** | Initialised with defaults · `components.json` patched to `~/lib/utils/index.ts` |
|
|
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 |
|
|
52
93
|
| **Custom globals.css** | Squircle utilities · responsive container · skeleton animation · reduced motion |
|
|
53
94
|
| **Prettier** | `prettier-plugin-tailwindcss` for automatic class sorting |
|
|
54
|
-
| **Poppins font** | Via `next/font/google` with full weight range |
|
|
95
|
+
| **Poppins font** | Via `next/font/google` with full weight range (200–800) |
|
|
55
96
|
| **Utility functions** | `cn()` · `toSlug()` · `createInitials()` · `normalizeEmail()` · `sanitizeName()` · `trycatch()` |
|
|
56
|
-
| **Constants** | `SITE_URL` · `TZ_COOKIE` · locale constants
|
|
97
|
+
| **Constants** | `SITE_URL` · `TZ_COOKIE` · locale constants if i18n opted in |
|
|
57
98
|
| **SEO scaffold** | `src/lib/seo/index.ts` — locale-aware if i18n enabled |
|
|
58
99
|
| **Sitemap** | `src/app/sitemap.ts` pre-wired to `SITE_URL` |
|
|
59
|
-
| **ESLint config** | Clean flat config
|
|
60
|
-
| **Providers pattern** |
|
|
61
|
-
| **SileoToaster** | Toast notifications via `sileo` |
|
|
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` |
|
|
62
103
|
| **nuqs** | Type-safe URL search params adapter |
|
|
63
104
|
| **nextjs-toploader** | Page transition progress bar |
|
|
64
105
|
| **Folder structure** | `hooks/` · `components/{atoms,molecules,organisms,primitives,adapters,providers,ui}/` |
|
|
65
106
|
| **Core deps** | `zustand` · `zod` · `react-hook-form` · `date-fns` · `@date-fns/tz` · `clsx` · `tailwind-merge` |
|
|
66
107
|
| **Dev deps** | `prettier` · `prettier-plugin-tailwindcss` · `babel-plugin-react-compiler` |
|
|
67
|
-
| **
|
|
108
|
+
| **`type-check` script** | `tsc --pretty --noEmit` added to `package.json` |
|
|
68
109
|
|
|
69
110
|
---
|
|
70
111
|
|
|
71
112
|
## Optional features
|
|
72
113
|
|
|
73
|
-
Prompted during setup — only what you need gets installed:
|
|
74
|
-
|
|
75
114
|
### 🌍 Localization (`next-intl`)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
---
|
|
85
142
|
|
|
86
143
|
### ⚡ TanStack Query
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- `@tanstack/eslint-plugin-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
|
+
---
|
|
94
159
|
|
|
95
160
|
### 🎨 Theming (`next-themes`)
|
|
96
|
-
|
|
161
|
+
|
|
162
|
+
`ThemeProvider` with system default, class attribute, and `disableTransitionOnChange`.
|
|
163
|
+
|
|
164
|
+
---
|
|
97
165
|
|
|
98
166
|
### 🔐 Auth
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
- `
|
|
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
|
|
102
172
|
- `src/apis/{client,server}/index.ts`
|
|
103
173
|
- `cookies-next` installed
|
|
104
174
|
|
|
175
|
+
---
|
|
176
|
+
|
|
105
177
|
### 🎭 Animations
|
|
106
|
-
|
|
178
|
+
|
|
179
|
+
- `motion` (Framer Motion v11+)
|
|
107
180
|
- `gsap`
|
|
108
181
|
|
|
182
|
+
---
|
|
183
|
+
|
|
109
184
|
### 🔄 Axios
|
|
185
|
+
|
|
110
186
|
- `axios` installed
|
|
111
|
-
- `src/lib/axios/axios.client.ts` —
|
|
112
|
-
- `src/lib/axios/axios.server.ts` —
|
|
187
|
+
- `src/lib/axios/axios.client.ts` — client instance with credentials
|
|
188
|
+
- `src/lib/axios/axios.server.ts` — server instance
|
|
189
|
+
|
|
190
|
+
---
|
|
113
191
|
|
|
114
192
|
### 🐶 Husky pre-commit hooks
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
120
210
|
```
|
|
121
|
-
|
|
211
|
+
|
|
212
|
+
---
|
|
122
213
|
|
|
123
214
|
### ⚛️ React Compiler
|
|
124
|
-
|
|
125
|
-
- `reactCompiler: true` added to `next.config.ts
|
|
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.
|
|
126
265
|
|
|
127
266
|
---
|
|
128
267
|
|
|
@@ -131,63 +270,60 @@ Fails fast with a clear error message. Shows timestamp on success.
|
|
|
131
270
|
```
|
|
132
271
|
my-app/
|
|
133
272
|
├── locales/
|
|
134
|
-
│ └── en.json
|
|
273
|
+
│ └── en.json # (i18n)
|
|
135
274
|
├── src/
|
|
136
275
|
│ ├── app/
|
|
137
276
|
│ │ ├── [locale]/
|
|
138
|
-
│ │ │ ├── layout.tsx
|
|
139
|
-
│ │ │ └── page.tsx
|
|
140
|
-
│ │ ├── layout.tsx
|
|
277
|
+
│ │ │ ├── layout.tsx # (i18n) full root layout
|
|
278
|
+
│ │ │ └── page.tsx # (i18n) localized home
|
|
279
|
+
│ │ ├── layout.tsx # passthrough shell (i18n) or full layout
|
|
141
280
|
│ │ ├── globals.css
|
|
142
281
|
│ │ └── sitemap.ts
|
|
143
|
-
│ ├── i18n/
|
|
282
|
+
│ ├── i18n/ # (i18n)
|
|
144
283
|
│ │ ├── routing.ts
|
|
145
284
|
│ │ ├── request.ts
|
|
146
285
|
│ │ └── navigation.ts
|
|
286
|
+
│ ├── apis/ # (auth)
|
|
287
|
+
│ │ ├── client/index.ts
|
|
288
|
+
│ │ └── server/index.ts
|
|
147
289
|
│ ├── components/
|
|
148
|
-
│ │ ├── adapters/
|
|
149
|
-
│ │ │ └── nuqs.adapter.tsx
|
|
290
|
+
│ │ ├── adapters/nuqs.adapter.tsx
|
|
150
291
|
│ │ ├── atoms/
|
|
151
292
|
│ │ ├── molecules/
|
|
152
293
|
│ │ ├── organisms/
|
|
153
294
|
│ │ ├── primitives/
|
|
154
295
|
│ │ ├── providers/
|
|
155
|
-
│ │ │ ├── index.
|
|
156
|
-
│ │ │ ├── theme.provider.tsx
|
|
157
|
-
│ │ │ ├── locale.provider.tsx
|
|
158
|
-
│ │ │ ├── tz.provider.tsx
|
|
296
|
+
│ │ │ ├── index.tsx # dynamic — opted-in providers only
|
|
297
|
+
│ │ │ ├── theme.provider.tsx # (theming)
|
|
298
|
+
│ │ │ ├── locale.provider.tsx # (i18n)
|
|
299
|
+
│ │ │ ├── tz.provider.tsx # (i18n)
|
|
159
300
|
│ │ │ └── query-client.provider.tsx # (query)
|
|
160
301
|
│ │ └── ui/
|
|
161
302
|
│ │ └── sileo.tsx
|
|
162
|
-
│ ├── constants/
|
|
163
|
-
│ │ └── index.ts
|
|
303
|
+
│ ├── constants/index.ts
|
|
164
304
|
│ ├── hooks/
|
|
165
305
|
│ ├── lib/
|
|
166
|
-
│ │ ├── axios/
|
|
306
|
+
│ │ ├── axios/ # (axios)
|
|
167
307
|
│ │ │ ├── axios.client.ts
|
|
168
308
|
│ │ │ └── axios.server.ts
|
|
169
309
|
│ │ ├── fonts.ts
|
|
170
|
-
│ │ ├── proxy/
|
|
310
|
+
│ │ ├── proxy/ # (auth or i18n)
|
|
171
311
|
│ │ │ ├── index.ts
|
|
172
|
-
│ │ │ └── auth.ts
|
|
173
|
-
│ │ ├── seo/
|
|
174
|
-
│ │ │ └── index.ts
|
|
312
|
+
│ │ │ └── auth.ts # (auth)
|
|
313
|
+
│ │ ├── seo/index.ts
|
|
175
314
|
│ │ ├── utils/
|
|
176
315
|
│ │ │ ├── index.ts
|
|
177
|
-
│ │ │ └── locale-date-formats.ts
|
|
178
|
-
│ │ └── validators/
|
|
316
|
+
│ │ │ └── locale-date-formats.ts # (i18n)
|
|
317
|
+
│ │ └── validators/ # (auth)
|
|
179
318
|
│ │ ├── index.ts
|
|
180
319
|
│ │ └── auth.ts
|
|
181
|
-
│
|
|
182
|
-
│ │ ├── client/
|
|
183
|
-
│ │ └── server/
|
|
184
|
-
│ └── proxy.ts # (i18n or auth)
|
|
320
|
+
│ └── proxy.ts # (i18n or auth)
|
|
185
321
|
├── .env.local
|
|
186
322
|
├── .prettierrc
|
|
187
|
-
├── .husky/pre-commit
|
|
323
|
+
├── .husky/pre-commit # (husky)
|
|
188
324
|
├── components.json
|
|
189
325
|
├── eslint.config.mjs
|
|
190
|
-
├── next-intl.ts
|
|
326
|
+
├── next-intl.ts # (i18n)
|
|
191
327
|
└── next.config.ts
|
|
192
328
|
```
|
|
193
329
|
|
|
@@ -195,7 +331,13 @@ my-app/
|
|
|
195
331
|
|
|
196
332
|
## Error recovery
|
|
197
333
|
|
|
198
|
-
|
|
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.
|
|
199
341
|
|
|
200
342
|
---
|
|
201
343
|
|
|
@@ -203,15 +345,16 @@ If any step fails during setup, the CLI **pauses and shows you the exact error**
|
|
|
203
345
|
|
|
204
346
|
- **Node.js** 18+
|
|
205
347
|
- **Package manager** — npm · pnpm · bun · yarn
|
|
348
|
+
- **GitHub Classic PAT** — only if using the GitHub feature (`repo` scope, not fine-grained)
|
|
206
349
|
|
|
207
350
|
---
|
|
208
351
|
|
|
209
352
|
## Part of the Nexora family
|
|
210
353
|
|
|
211
|
-
| Package | Stack |
|
|
212
|
-
|
|
213
|
-
| `create-nexora-next` | Next.js |
|
|
214
|
-
| `create-nexora-go` |
|
|
354
|
+
| Package | Stack | Status |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| `create-nexora-next` | Next.js 16+ | ✅ Available |
|
|
357
|
+
| `create-nexora-go` | Go | 🔜 Coming soon |
|
|
215
358
|
|
|
216
359
|
---
|
|
217
360
|
|
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 16 + TypeScript + Tailwind + ESLint', true))
|
|
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
|
}
|
|
@@ -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
|
@@ -593,9 +593,9 @@ export const proxyAuthTs = `import { NextFetchEvent, NextRequest, NextResponse }
|
|
|
593
593
|
import type { ContextualProxy, ProxyContext } from '.'
|
|
594
594
|
|
|
595
595
|
export const authProxy: ContextualProxy = async (
|
|
596
|
-
|
|
596
|
+
_req: NextRequest,
|
|
597
597
|
_event: NextFetchEvent,
|
|
598
|
-
|
|
598
|
+
_ctx: ProxyContext,
|
|
599
599
|
) => {
|
|
600
600
|
// TODO: implement auth checks — verify session/token, set ctx.role, redirect if needed
|
|
601
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
|