create-nexora-next 0.3.6 → 0.4.7
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/package.json +1 -1
- package/src/index.js +105 -130
- package/src/utils/args.js +119 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -4,24 +4,23 @@ import pc from 'picocolors'
|
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { existsSync } from 'fs'
|
|
6
6
|
import { createRequire } from 'module'
|
|
7
|
+
import { parseArgs, hasAnyFlag, printHelp } from './utils/args.js'
|
|
7
8
|
|
|
8
9
|
const require = createRequire(import.meta.url)
|
|
9
10
|
const { version } = require('../package.json')
|
|
10
11
|
|
|
11
|
-
import { stepCreateNextApp }
|
|
12
|
-
import { stepSetupShadcn }
|
|
13
|
-
import { stepWriteBaseFiles }
|
|
14
|
-
import { stepWriteProviders }
|
|
15
|
-
import { stepSetupI18n }
|
|
16
|
-
import { stepSetupAuth }
|
|
17
|
-
import { stepWriteProxy }
|
|
18
|
-
import { stepInstallDeps }
|
|
19
|
-
import { stepSetupHusky }
|
|
20
|
-
import { stepSetupAxios }
|
|
12
|
+
import { stepCreateNextApp } from './steps/01-create-next-app.js'
|
|
13
|
+
import { stepSetupShadcn } from './steps/02-shadcn.js'
|
|
14
|
+
import { stepWriteBaseFiles } from './steps/03-base-files.js'
|
|
15
|
+
import { stepWriteProviders } from './steps/04-providers.js'
|
|
16
|
+
import { stepSetupI18n } from './steps/05-i18n.js'
|
|
17
|
+
import { stepSetupAuth } from './steps/06-auth.js'
|
|
18
|
+
import { stepWriteProxy } from './steps/07-proxy.js'
|
|
19
|
+
import { stepInstallDeps } from './steps/08-install-deps.js'
|
|
20
|
+
import { stepSetupHusky } from './steps/09-husky.js'
|
|
21
|
+
import { stepSetupAxios } from './steps/10-axios.js'
|
|
21
22
|
import { stepPatchPackageJson } from './steps/11-patch-pkg.js'
|
|
22
23
|
|
|
23
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
24
|
function bail(msg) {
|
|
26
25
|
p.cancel(pc.red(msg))
|
|
27
26
|
process.exit(1)
|
|
@@ -35,8 +34,6 @@ function section(title) {
|
|
|
35
34
|
p.log.step(pc.bold(pc.cyan(`\n ${title}`)))
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
37
|
function printBanner() {
|
|
41
38
|
const art = [
|
|
42
39
|
' ███╗ ██╗███████╗██╗ ██╗ ██████╗ ██████╗ █████╗ ',
|
|
@@ -46,9 +43,7 @@ function printBanner() {
|
|
|
46
43
|
' ██║ ╚████║███████╗██╔╝ ██╗╚██████╔╝██║ ██║██║ ██║',
|
|
47
44
|
' ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝',
|
|
48
45
|
]
|
|
49
|
-
// Fade from cyan → blue top to bottom
|
|
50
46
|
const gradient = [pc.cyan, pc.cyan, pc.cyan, pc.blue, pc.blue, pc.blue]
|
|
51
|
-
|
|
52
47
|
console.log()
|
|
53
48
|
art.forEach((line, i) => console.log(pc.bold(gradient[i](line))))
|
|
54
49
|
console.log()
|
|
@@ -56,8 +51,7 @@ function printBanner() {
|
|
|
56
51
|
const centre = (str) => {
|
|
57
52
|
const pad = W - str.length
|
|
58
53
|
const l = Math.floor(pad / 2)
|
|
59
|
-
|
|
60
|
-
return ' '.repeat(l) + str + ' '.repeat(r)
|
|
54
|
+
return ' '.repeat(l) + str + ' '.repeat(pad - l)
|
|
61
55
|
}
|
|
62
56
|
const bar = '═'.repeat(W)
|
|
63
57
|
console.log(pc.bold(pc.cyan(
|
|
@@ -69,8 +63,6 @@ function printBanner() {
|
|
|
69
63
|
console.log()
|
|
70
64
|
}
|
|
71
65
|
|
|
72
|
-
// ─── Summary ──────────────────────────────────────────────────────────────────
|
|
73
|
-
|
|
74
66
|
function printSummary(opts, projectName, pm) {
|
|
75
67
|
const yes = pc.green('✓')
|
|
76
68
|
const skip = pc.dim('–')
|
|
@@ -103,103 +95,97 @@ function printSummary(opts, projectName, pm) {
|
|
|
103
95
|
console.log()
|
|
104
96
|
}
|
|
105
97
|
|
|
106
|
-
|
|
98
|
+
async function ask(condition, prompt) {
|
|
99
|
+
if (condition !== null) return condition
|
|
100
|
+
const val = await prompt()
|
|
101
|
+
if (p.isCancel(val)) onCancel()
|
|
102
|
+
return Boolean(val)
|
|
103
|
+
}
|
|
107
104
|
|
|
108
105
|
async function main() {
|
|
106
|
+
const parsed = parseArgs(process.argv)
|
|
107
|
+
|
|
109
108
|
printBanner()
|
|
110
|
-
p.intro(pc.bgCyan(pc.black(" create-nexora-next ")))
|
|
111
109
|
|
|
112
|
-
|
|
110
|
+
if (parsed.version) {
|
|
111
|
+
console.log(` v${version}`)
|
|
112
|
+
process.exit(0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (parsed.help) {
|
|
116
|
+
printHelp(version)
|
|
117
|
+
process.exit(0)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
p.intro(pc.bgCyan(pc.black(' create-nexora-next ')))
|
|
121
|
+
|
|
122
|
+
const flagMode = hasAnyFlag(parsed)
|
|
123
|
+
|
|
124
|
+
if (flagMode) {
|
|
125
|
+
p.log.info(pc.dim(' Running in flag mode — skipping answered prompts'))
|
|
126
|
+
console.log()
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
section('Phase 1 — Project identity')
|
|
114
130
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
let name = parsed.name
|
|
132
|
+
if (!name) {
|
|
133
|
+
const res = await p.text({
|
|
134
|
+
message: 'What is your project name?',
|
|
135
|
+
placeholder: 'my-app',
|
|
136
|
+
validate: (v) => {
|
|
137
|
+
if (!v || !v.trim()) return 'Project name is required.'
|
|
138
|
+
if (!/^[a-z0-9-_]+$/.test(v.trim()))
|
|
139
|
+
return 'Use lowercase letters, numbers, hyphens or underscores only.'
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
if (p.isCancel(res)) onCancel()
|
|
143
|
+
name = res.trim()
|
|
144
|
+
} else {
|
|
145
|
+
p.log.step(`Project name: ${pc.cyan(name)}`)
|
|
146
|
+
}
|
|
126
147
|
|
|
127
148
|
const targetDir = path.resolve(process.cwd(), name)
|
|
128
|
-
if (existsSync(targetDir)) {
|
|
129
|
-
|
|
149
|
+
if (existsSync(targetDir)) bail(`Directory "${name}" already exists.`)
|
|
150
|
+
|
|
151
|
+
let pm = parsed.pm
|
|
152
|
+
if (!pm) {
|
|
153
|
+
const res = await p.select({
|
|
154
|
+
message: 'Which package manager?',
|
|
155
|
+
options: [
|
|
156
|
+
{ value: 'pnpm', label: 'pnpm', hint: 'recommended' },
|
|
157
|
+
{ value: 'bun', label: 'bun', hint: 'fastest' },
|
|
158
|
+
{ value: 'npm', label: 'npm', hint: 'classic' },
|
|
159
|
+
{ value: 'yarn', label: 'yarn', hint: 'classic alt' },
|
|
160
|
+
],
|
|
161
|
+
})
|
|
162
|
+
if (p.isCancel(res)) onCancel()
|
|
163
|
+
pm = res
|
|
164
|
+
} else {
|
|
165
|
+
p.log.step(`Package manager: ${pc.cyan(pm)}`)
|
|
130
166
|
}
|
|
131
167
|
|
|
132
|
-
const pm = await p.select({
|
|
133
|
-
message: 'Which package manager?',
|
|
134
|
-
options: [
|
|
135
|
-
{ value: 'pnpm', label: 'pnpm', hint: 'recommended' },
|
|
136
|
-
{ value: 'bun', label: 'bun', hint: 'fastest' },
|
|
137
|
-
{ value: 'npm', label: 'npm', hint: 'classic' },
|
|
138
|
-
{ value: 'yarn', label: 'yarn', hint: 'classic alt' },
|
|
139
|
-
],
|
|
140
|
-
})
|
|
141
|
-
if (p.isCancel(pm)) onCancel()
|
|
142
|
-
|
|
143
|
-
// ── Phase 2: Feature flags ─────────────────────────────────────────────────
|
|
144
168
|
section('Phase 2 — Features')
|
|
145
169
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const theming = await p.confirm({
|
|
159
|
-
message: 'Set up theming? (next-themes dark/light)',
|
|
160
|
-
initialValue: true,
|
|
161
|
-
})
|
|
162
|
-
if (p.isCancel(theming)) onCancel()
|
|
163
|
-
|
|
164
|
-
const animations = await p.confirm({
|
|
165
|
-
message: 'Install animation libraries? (motion + gsap)',
|
|
166
|
-
initialValue: false,
|
|
167
|
-
})
|
|
168
|
-
if (p.isCancel(animations)) onCancel()
|
|
169
|
-
|
|
170
|
-
const auth = await p.confirm({
|
|
171
|
-
message: 'Set up auth proxy + zod validators?',
|
|
172
|
-
initialValue: false,
|
|
173
|
-
})
|
|
174
|
-
if (p.isCancel(auth)) onCancel()
|
|
175
|
-
|
|
176
|
-
const axios = await p.confirm({
|
|
177
|
-
message: 'Set up axios client/server instances?',
|
|
178
|
-
initialValue: false,
|
|
179
|
-
})
|
|
180
|
-
if (p.isCancel(axios)) onCancel()
|
|
181
|
-
|
|
182
|
-
const husky = await p.confirm({
|
|
183
|
-
message: 'Configure Husky pre-commit hooks?',
|
|
184
|
-
initialValue: true,
|
|
185
|
-
})
|
|
186
|
-
if (p.isCancel(husky)) onCancel()
|
|
187
|
-
|
|
188
|
-
const reactCompiler = await p.confirm({
|
|
189
|
-
message: 'Enable React Compiler? (babel-plugin-react-compiler)',
|
|
190
|
-
initialValue: true,
|
|
191
|
-
})
|
|
192
|
-
if (p.isCancel(reactCompiler)) onCancel()
|
|
193
|
-
|
|
194
|
-
// ── Confirm ────────────────────────────────────────────────────────────────
|
|
170
|
+
const defaults = { theming: true, husky: true, reactCompiler: true }
|
|
171
|
+
|
|
172
|
+
const i18n = await ask(parsed.i18n, () => p.confirm({ message: 'Set up localization? (next-intl)', initialValue: false }))
|
|
173
|
+
const query = await ask(parsed.query, () => p.confirm({ message: 'Set up TanStack Query + local persistence?', initialValue: false }))
|
|
174
|
+
const theming = await ask(parsed.theming, () => p.confirm({ message: 'Set up theming? (next-themes dark/light)', initialValue: true }))
|
|
175
|
+
const animations = await ask(parsed.animations, () => p.confirm({ message: 'Install animation libraries? (motion + gsap)', initialValue: false }))
|
|
176
|
+
const auth = await ask(parsed.auth, () => p.confirm({ message: 'Set up auth proxy + zod validators?', initialValue: false }))
|
|
177
|
+
const axios = await ask(parsed.axios, () => p.confirm({ message: 'Set up axios client/server instances?', initialValue: false }))
|
|
178
|
+
const husky = await ask(parsed.husky, () => p.confirm({ message: 'Configure Husky pre-commit hooks?', initialValue: true }))
|
|
179
|
+
const reactCompiler = await ask(parsed.reactCompiler, () => p.confirm({ message: 'Enable React Compiler? (babel-plugin-react-compiler)', initialValue: true }))
|
|
180
|
+
|
|
195
181
|
const features = [
|
|
196
|
-
i18n
|
|
197
|
-
query
|
|
198
|
-
theming
|
|
199
|
-
animations
|
|
200
|
-
auth
|
|
201
|
-
axios
|
|
202
|
-
husky
|
|
182
|
+
i18n && 'i18n',
|
|
183
|
+
query && 'query',
|
|
184
|
+
theming && 'theming',
|
|
185
|
+
animations && 'animations',
|
|
186
|
+
auth && 'auth',
|
|
187
|
+
axios && 'axios',
|
|
188
|
+
husky && 'husky',
|
|
203
189
|
reactCompiler && 'react-compiler',
|
|
204
190
|
].filter(Boolean)
|
|
205
191
|
|
|
@@ -211,23 +197,21 @@ async function main() {
|
|
|
211
197
|
)
|
|
212
198
|
console.log()
|
|
213
199
|
|
|
214
|
-
|
|
215
|
-
message: 'Ready to scaffold?',
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
if (p.isCancel(go) || !go) onCancel()
|
|
200
|
+
if (!parsed.yes && !flagMode) {
|
|
201
|
+
const go = await p.confirm({ message: 'Ready to scaffold?', initialValue: true })
|
|
202
|
+
if (p.isCancel(go) || !go) onCancel()
|
|
203
|
+
}
|
|
219
204
|
|
|
220
|
-
// ── Phase 3: Execution ─────────────────────────────────────────────────────
|
|
221
205
|
section('Phase 3 — Building your project')
|
|
222
206
|
|
|
223
207
|
const opts = {
|
|
224
|
-
i18n:
|
|
225
|
-
query:
|
|
226
|
-
theming:
|
|
227
|
-
animations:
|
|
228
|
-
auth:
|
|
229
|
-
axios:
|
|
230
|
-
husky:
|
|
208
|
+
i18n: Boolean(i18n),
|
|
209
|
+
query: Boolean(query),
|
|
210
|
+
theming: Boolean(theming),
|
|
211
|
+
animations: Boolean(animations),
|
|
212
|
+
auth: Boolean(auth),
|
|
213
|
+
axios: Boolean(axios),
|
|
214
|
+
husky: Boolean(husky),
|
|
231
215
|
reactCompiler: Boolean(reactCompiler),
|
|
232
216
|
}
|
|
233
217
|
|
|
@@ -236,26 +220,17 @@ async function main() {
|
|
|
236
220
|
await stepWriteBaseFiles(targetDir, opts)
|
|
237
221
|
await stepWriteProviders(targetDir, opts)
|
|
238
222
|
|
|
239
|
-
if (opts.i18n)
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (opts.auth || opts.i18n) {
|
|
244
|
-
await stepSetupAuth(targetDir, opts)
|
|
245
|
-
}
|
|
223
|
+
if (opts.i18n) await stepSetupI18n(targetDir, pm, opts)
|
|
224
|
+
if (opts.auth || opts.i18n) await stepSetupAuth(targetDir, opts)
|
|
246
225
|
|
|
247
226
|
await stepWriteProxy(targetDir, opts)
|
|
248
227
|
|
|
249
|
-
if (opts.axios)
|
|
250
|
-
await stepSetupAxios(targetDir)
|
|
251
|
-
}
|
|
228
|
+
if (opts.axios) await stepSetupAxios(targetDir)
|
|
252
229
|
|
|
253
230
|
await stepInstallDeps(targetDir, pm, opts)
|
|
254
231
|
await stepPatchPackageJson(targetDir)
|
|
255
232
|
|
|
256
|
-
if (opts.husky)
|
|
257
|
-
await stepSetupHusky(targetDir, pm)
|
|
258
|
-
}
|
|
233
|
+
if (opts.husky) await stepSetupHusky(targetDir, pm)
|
|
259
234
|
|
|
260
235
|
printSummary(opts, name, pm)
|
|
261
236
|
p.outro(pc.bgGreen(pc.black(' Done! ')))
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const VALID_PMS = ['npm', 'pnpm', 'bun', 'yarn']
|
|
2
|
+
|
|
3
|
+
const FLAGS = {
|
|
4
|
+
'--all': { key: 'all' },
|
|
5
|
+
'--i18n': { key: 'i18n' },
|
|
6
|
+
'--query': { key: 'query' },
|
|
7
|
+
'--theming': { key: 'theming' },
|
|
8
|
+
'--animations': { key: 'animations' },
|
|
9
|
+
'--auth': { key: 'auth' },
|
|
10
|
+
'--axios': { key: 'axios' },
|
|
11
|
+
'--husky': { key: 'husky' },
|
|
12
|
+
'--rc': { key: 'reactCompiler' },
|
|
13
|
+
'--react-compiler': { key: 'reactCompiler' },
|
|
14
|
+
'--no-theming': { key: 'theming', value: false },
|
|
15
|
+
'--no-husky': { key: 'husky', value: false },
|
|
16
|
+
'--no-rc': { key: 'reactCompiler', value: false },
|
|
17
|
+
'--pm': { key: 'pm', takesValue: true },
|
|
18
|
+
'-y': { key: 'yes' },
|
|
19
|
+
'--yes': { key: 'yes' },
|
|
20
|
+
'--help': { key: 'help' },
|
|
21
|
+
'-h': { key: 'help' },
|
|
22
|
+
'--version': { key: 'version' },
|
|
23
|
+
'-v': { key: 'version' },
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseArgs(argv) {
|
|
27
|
+
const args = argv.slice(2)
|
|
28
|
+
const result = {
|
|
29
|
+
name: null,
|
|
30
|
+
pm: null,
|
|
31
|
+
yes: false,
|
|
32
|
+
help: false,
|
|
33
|
+
version: false,
|
|
34
|
+
all: false,
|
|
35
|
+
i18n: null,
|
|
36
|
+
query: null,
|
|
37
|
+
theming: null,
|
|
38
|
+
animations: null,
|
|
39
|
+
auth: null,
|
|
40
|
+
axios: null,
|
|
41
|
+
husky: null,
|
|
42
|
+
reactCompiler: null,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let i = 0
|
|
46
|
+
while (i < args.length) {
|
|
47
|
+
const arg = args[i]
|
|
48
|
+
const def = FLAGS[arg]
|
|
49
|
+
|
|
50
|
+
if (def) {
|
|
51
|
+
if (def.takesValue) {
|
|
52
|
+
result[def.key] = args[++i] ?? null
|
|
53
|
+
} else if (def.value === false) {
|
|
54
|
+
result[def.key] = false
|
|
55
|
+
} else {
|
|
56
|
+
result[def.key] = true
|
|
57
|
+
}
|
|
58
|
+
} else if (!arg.startsWith('-') && !result.name) {
|
|
59
|
+
result.name = arg
|
|
60
|
+
}
|
|
61
|
+
i++
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (result.all) {
|
|
65
|
+
for (const key of ['i18n','query','theming','animations','auth','axios','husky','reactCompiler']) {
|
|
66
|
+
if (result[key] === null) result[key] = true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (result.pm && !VALID_PMS.includes(result.pm)) {
|
|
71
|
+
result.pm = null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function hasAnyFlag(parsed) {
|
|
78
|
+
return (
|
|
79
|
+
parsed.yes ||
|
|
80
|
+
parsed.all ||
|
|
81
|
+
parsed.name !== null ||
|
|
82
|
+
parsed.pm !== null ||
|
|
83
|
+
['i18n','query','theming','animations','auth','axios','husky','reactCompiler']
|
|
84
|
+
.some(k => parsed[k] !== null)
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function printHelp(version) {
|
|
89
|
+
console.log(`
|
|
90
|
+
create-nexora-next v${version}
|
|
91
|
+
|
|
92
|
+
Usage:
|
|
93
|
+
npx create-nexora-next [name] [flags]
|
|
94
|
+
|
|
95
|
+
Flags:
|
|
96
|
+
--pm <npm|pnpm|bun|yarn> Package manager (default: prompt)
|
|
97
|
+
--all Enable every feature
|
|
98
|
+
--i18n Localization via next-intl
|
|
99
|
+
--query TanStack Query + persistence
|
|
100
|
+
--theming Theming via next-themes
|
|
101
|
+
--animations Motion + GSAP
|
|
102
|
+
--auth Auth proxy + validators
|
|
103
|
+
--axios Axios client/server instances
|
|
104
|
+
--husky Husky pre-commit hooks
|
|
105
|
+
--rc / --react-compiler React Compiler
|
|
106
|
+
--no-theming Skip theming
|
|
107
|
+
--no-husky Skip Husky
|
|
108
|
+
--no-rc Skip React Compiler
|
|
109
|
+
-y / --yes Accept all defaults (no prompts)
|
|
110
|
+
-v / --version Print version
|
|
111
|
+
-h / --help Show this help
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
npx create-nexora-next my-app --pm pnpm --all
|
|
115
|
+
npx create-nexora-next my-app --pm bun --i18n --query --theming
|
|
116
|
+
npx create-nexora-next my-app --pm bun --all --no-animations --no-auth
|
|
117
|
+
npx create-nexora-next my-app -y
|
|
118
|
+
`)
|
|
119
|
+
}
|