create-nexora-next 0.4.8 → 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 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
  [![npm version](https://img.shields.io/npm/v/create-nexora-next?color=cyan&style=flat-square)](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 will guide you through a short prompt sequence — pick your package manager, choose your features, and watch your project build itself.
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 scaffolded with `create-nexora-next` comes with this baseline — no questions asked:
87
+ Every project gets this baseline — no questions asked:
47
88
 
48
89
  | What | Details |
49
90
  |---|---|
50
- | **Next.js 16** | TypeScript · Tailwind CSS v4 · ESLint · App Router · `src/` dir · `~/` path alias |
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 (if i18n opted in) |
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 with Next.js core web vitals + TypeScript rules |
60
- | **Providers pattern** | Dynamic `src/components/providers/index.ts` — only includes what you opted into |
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
- | **Scripts** | `type-check` added to `package.json` → `tsc --pretty --noEmit` |
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
- - `src/i18n/{routing,request,navigation}.ts`
77
- - `locales/en.json` with base translation keys
78
- - `src/app/[locale]/page.tsx` + `src/app/[locale]/layout.tsx`
79
- - Root `layout.tsx` replaced with passthrough shell
80
- - Default `src/app/page.tsx` removed
81
- - `next-intl.ts` config with typed `AppConfig`
82
- - `src/lib/utils/locale-date-formats.ts`
83
- - `LocaleProvider` + `TzProvider` (auto-detects and persists device timezone)
84
- - `next.config.ts` updated with `createNextIntlPlugin`
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
- - `@tanstack/react-query` installed first (peer dep order guaranteed)
88
- - `@tanstack/react-query-persist-client` + `@tanstack/query-async-storage-persister`
89
- - Pre-configured `QueryClientProvider` with:
90
- - 5-minute stale time
91
- - Local storage persistence (24h)
92
- - Mutation cache auto-invalidation via `meta.invalidateQueries`
93
- - `@tanstack/eslint-plugin-query` as devDependency
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
- - `ThemeProvider` with system default, class attribute, smooth transitions
161
+
162
+ `ThemeProvider` with system default, class attribute, and `disableTransitionOnChange`.
163
+
164
+ ---
97
165
 
98
166
  ### 🔐 Auth
99
- - Proxy chain middleware (`src/proxy.ts`)
100
- - `withProxyChain` + `authProxy` stub
101
- - `src/lib/validators/{index,auth}.ts` `LoginSchema`, `RegisterSchema`
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
- - `motion` (formerly Framer Motion)
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` — pre-configured client instance
112
- - `src/lib/axios/axios.server.ts` — pre-configured server instance
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
- Runs three checks on every commit:
116
- ```
117
- 📝 Lint → bun lint
118
- 🔍 Type check → bun type-check
119
- 🏗️ Build → bun run build
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
- Fails fast with a clear error message. Shows timestamp on success.
211
+
212
+ ---
122
213
 
123
214
  ### ⚛️ React Compiler
124
- - `babel-plugin-react-compiler` (always installed as devDep)
125
- - `reactCompiler: true` added to `next.config.ts` if opted in
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 # (i18n)
273
+ │ └── en.json # (i18n)
135
274
  ├── src/
136
275
  │ ├── app/
137
276
  │ │ ├── [locale]/
138
- │ │ │ ├── layout.tsx # (i18n) full root layout
139
- │ │ │ └── page.tsx # (i18n) localized home
140
- │ │ ├── layout.tsx # passthrough shell (i18n) or full layout
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/ # (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.ts # dynamic — opted-in providers only
156
- │ │ │ ├── theme.provider.tsx # (theming)
157
- │ │ │ ├── locale.provider.tsx # (i18n)
158
- │ │ │ ├── tz.provider.tsx # (i18n)
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/ # (axios)
306
+ │ │ ├── axios/ # (axios)
167
307
  │ │ │ ├── axios.client.ts
168
308
  │ │ │ └── axios.server.ts
169
309
  │ │ ├── fonts.ts
170
- │ │ ├── proxy/ # (auth or i18n)
310
+ │ │ ├── proxy/ # (auth or i18n)
171
311
  │ │ │ ├── index.ts
172
- │ │ │ └── auth.ts # (auth)
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 # (i18n)
178
- │ │ └── validators/ # (auth)
316
+ │ │ │ └── locale-date-formats.ts # (i18n)
317
+ │ │ └── validators/ # (auth)
179
318
  │ │ ├── index.ts
180
319
  │ │ └── auth.ts
181
- ├── apis/ # (auth)
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 # (husky)
323
+ ├── .husky/pre-commit # (husky)
188
324
  ├── components.json
189
325
  ├── eslint.config.mjs
190
- ├── next-intl.ts # (i18n)
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
- If any step fails during setup, the CLI **pauses and shows you the exact error**. Fix the issue then press **Enter** to retry that step no need to restart from scratch.
334
+ Every step in the CLI is wrapped with automatic retry-on-failure. If a step throws network issue, permission error, missing binarythe 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` | *(coming soon)* |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nexora-next",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "The official Next.js scaffolding CLI by Rayan — batteries included.",
5
5
  "type": "module",
6
6
  "bin": {
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
+ }
@@ -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
- req: NextRequest,
596
+ _req: NextRequest,
597
597
  _event: NextFetchEvent,
598
- ctx: ProxyContext,
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