create-nexora-next 0.3.7 → 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/src/index.js CHANGED
@@ -1,267 +1,242 @@
1
- #!/usr/bin/env node
2
- import * as p from '@clack/prompts'
3
- import pc from 'picocolors'
4
- import path from 'path'
5
- import { existsSync } from 'fs'
6
- import { createRequire } from 'module'
7
-
8
- const require = createRequire(import.meta.url)
9
- const { version } = require('../package.json')
10
-
11
- import { stepCreateNextApp } from './steps/01-create-next-app.js'
12
- import { stepSetupShadcn } from './steps/02-shadcn.js'
13
- import { stepWriteBaseFiles } from './steps/03-base-files.js'
14
- import { stepWriteProviders } from './steps/04-providers.js'
15
- import { stepSetupI18n } from './steps/05-i18n.js'
16
- import { stepSetupAuth } from './steps/06-auth.js'
17
- import { stepWriteProxy } from './steps/07-proxy.js'
18
- import { stepInstallDeps } from './steps/08-install-deps.js'
19
- import { stepSetupHusky } from './steps/09-husky.js'
20
- import { stepSetupAxios } from './steps/10-axios.js'
21
- import { stepPatchPackageJson } from './steps/11-patch-pkg.js'
22
-
23
- // ─── Helpers ──────────────────────────────────────────────────────────────────
24
-
25
- function bail(msg) {
26
- p.cancel(pc.red(msg))
27
- process.exit(1)
28
- }
29
-
30
- function onCancel() {
31
- bail('Setup cancelled.')
32
- }
33
-
34
- function section(title) {
35
- p.log.step(pc.bold(pc.cyan(`\n ${title}`)))
36
- }
37
-
38
- // ─── Banner ───────────────────────────────────────────────────────────────────
39
-
40
- function printBanner() {
41
- const art = [
42
- ' ███╗ ██╗███████╗██╗ ██╗ ██████╗ ██████╗ █████╗ ',
43
- ' ████╗ ██║██╔════╝╚██╗██╔╝██╔═══██╗██╔══██╗██╔══██╗',
44
- ' ██╔██╗ ██║█████╗ ╚███╔╝ ██║ ██║██████╔╝███████║',
45
- ' ██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║██╔══██╗██╔══██║',
46
- ' ██║ ╚████║███████╗██╔╝ ██╗╚██████╔╝██║ ██║██║ ██║',
47
- ' ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝',
48
- ]
49
- // Fade from cyan → blue top to bottom
50
- const gradient = [pc.cyan, pc.cyan, pc.cyan, pc.blue, pc.blue, pc.blue]
51
-
52
- console.log()
53
- art.forEach((line, i) => console.log(pc.bold(gradient[i](line))))
54
- console.log()
55
- const W = 44
56
- const centre = (str) => {
57
- const pad = W - str.length
58
- const l = Math.floor(pad / 2)
59
- const r = pad - l
60
- return ' '.repeat(l) + str + ' '.repeat(r)
61
- }
62
- const bar = '═'.repeat(W)
63
- console.log(pc.bold(pc.cyan(
64
- ` ╔${bar}╗\n` +
65
- ` ║${centre(`create-nexora-next v${version}`)}║\n` +
66
- ` ║${centre('Next.js scaffold · by Rayan')}║\n` +
67
- ` ╚${bar}╝`
68
- )))
69
- console.log()
70
- }
71
-
72
- // ─── Summary ──────────────────────────────────────────────────────────────────
73
-
74
- function printSummary(opts, projectName, pm) {
75
- const yes = pc.green('')
76
- const skip = pc.dim('–')
77
- const row = (label, on) =>
78
- ` ${on ? yes : skip} ${on ? pc.white(label) : pc.dim(label)}`
79
-
80
- console.log()
81
- p.log.success(pc.bold(pc.green(` 🚀 ${projectName} is ready!`)))
82
- console.log()
83
- console.log(pc.bold(' What was set up:'))
84
- console.log(row('Next.js 15 + TypeScript + Tailwind + ESLint', true))
85
- console.log(row('shadcn/ui + custom globals.css utilities', true))
86
- console.log(row('Prettier + tailwind class sorting', true))
87
- console.log(row('Poppins font + path alias ~/', true))
88
- console.log(row('Localization via next-intl', opts.i18n))
89
- console.log(row('TanStack Query + local persistence', opts.query))
90
- console.log(row('Theming with next-themes', opts.theming))
91
- console.log(row('Animations motion + gsap', opts.animations))
92
- console.log(row('Auth proxy + zod validators', opts.auth))
93
- console.log(row('Axios client + server instances', opts.axios))
94
- console.log(row('Husky pre-commit hooks', opts.husky))
95
- console.log(row('React Compiler', opts.reactCompiler))
96
- console.log()
97
- console.log(pc.bold(' Next steps:'))
98
- console.log(` ${pc.cyan('cd')} ${projectName}`)
99
- const devCmd = pm === 'npm' ? 'npm run dev' : `${pm} dev`
100
- console.log(` ${pc.cyan(devCmd)}`)
101
- console.log()
102
- console.log(pc.dim(' Happy shipping, Rayan. 🔥'))
103
- console.log()
104
- }
105
-
106
- // ─── Main ─────────────────────────────────────────────────────────────────────
107
-
108
- async function main() {
109
- printBanner()
110
- p.intro(pc.bgCyan(pc.black(" create-nexora-next ")))
111
-
112
- // ── Phase 1: Identity ──────────────────────────────────────────────────────
113
- section('Phase 1 — Project identity')
114
-
115
- const projectName = await p.text({
116
- message: 'What is your project name?',
117
- placeholder: 'my-app',
118
- validate: (v) => {
119
- if (!v || !v.trim()) return 'Project name is required.'
120
- if (!/^[a-z0-9-_]+$/.test(v.trim()))
121
- return 'Use lowercase letters, numbers, hyphens or underscores only.'
122
- },
123
- })
124
- if (p.isCancel(projectName)) onCancel()
125
- const name = projectName.trim()
126
-
127
- const targetDir = path.resolve(process.cwd(), name)
128
- if (existsSync(targetDir)) {
129
- bail(`Directory "${name}" already exists. Choose a different name.`)
130
- }
131
-
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
- section('Phase 2 — Features')
145
-
146
- const i18n = await p.confirm({
147
- message: 'Set up localization? (next-intl)',
148
- initialValue: false,
149
- })
150
- if (p.isCancel(i18n)) onCancel()
151
-
152
- const query = await p.confirm({
153
- message: 'Set up TanStack Query + local persistence?',
154
- initialValue: false,
155
- })
156
- if (p.isCancel(query)) onCancel()
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 ────────────────────────────────────────────────────────────────
195
- const features = [
196
- i18n && 'i18n',
197
- query && 'query',
198
- theming && 'theming',
199
- animations && 'animations',
200
- auth && 'auth',
201
- axios && 'axios',
202
- husky && 'husky',
203
- reactCompiler && 'react-compiler',
204
- ].filter(Boolean)
205
-
206
- console.log()
207
- p.log.info(
208
- pc.bold(` ${pc.cyan(name)}`) +
209
- pc.dim(` · ${pm}`) +
210
- (features.length ? pc.dim(' · ' + features.join(' · ')) : '')
211
- )
212
- console.log()
213
-
214
- const go = await p.confirm({
215
- message: 'Ready to scaffold?',
216
- initialValue: true,
217
- })
218
- if (p.isCancel(go) || !go) onCancel()
219
-
220
- // ── Phase 3: Execution ─────────────────────────────────────────────────────
221
- section('Phase 3 — Building your project')
222
-
223
- const opts = {
224
- i18n: Boolean(i18n),
225
- query: Boolean(query),
226
- theming: Boolean(theming),
227
- animations: Boolean(animations),
228
- auth: Boolean(auth),
229
- axios: Boolean(axios),
230
- husky: Boolean(husky),
231
- reactCompiler: Boolean(reactCompiler),
232
- }
233
-
234
- await stepCreateNextApp(name, pm, targetDir)
235
- await stepSetupShadcn(targetDir, pm)
236
- await stepWriteBaseFiles(targetDir, opts)
237
- await stepWriteProviders(targetDir, opts)
238
-
239
- if (opts.i18n) {
240
- await stepSetupI18n(targetDir, pm, opts)
241
- }
242
-
243
- if (opts.auth || opts.i18n) {
244
- await stepSetupAuth(targetDir, opts)
245
- }
246
-
247
- await stepWriteProxy(targetDir, opts)
248
-
249
- if (opts.axios) {
250
- await stepSetupAxios(targetDir)
251
- }
252
-
253
- await stepInstallDeps(targetDir, pm, opts)
254
- await stepPatchPackageJson(targetDir)
255
-
256
- if (opts.husky) {
257
- await stepSetupHusky(targetDir, pm)
258
- }
259
-
260
- printSummary(opts, name, pm)
261
- p.outro(pc.bgGreen(pc.black(' Done! ')))
262
- }
263
-
264
- main().catch((err) => {
265
- console.error(pc.red('\n Unexpected error:'), err.message || err)
266
- process.exit(1)
1
+ #!/usr/bin/env node
2
+ import * as p from '@clack/prompts'
3
+ import pc from 'picocolors'
4
+ import path from 'path'
5
+ import { existsSync } from 'fs'
6
+ import { createRequire } from 'module'
7
+ import { parseArgs, hasAnyFlag, printHelp } from './utils/args.js'
8
+
9
+ const require = createRequire(import.meta.url)
10
+ const { version } = require('../package.json')
11
+
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'
22
+ import { stepPatchPackageJson } from './steps/11-patch-pkg.js'
23
+
24
+ function bail(msg) {
25
+ p.cancel(pc.red(msg))
26
+ process.exit(1)
27
+ }
28
+
29
+ function onCancel() {
30
+ bail('Setup cancelled.')
31
+ }
32
+
33
+ function section(title) {
34
+ p.log.step(pc.bold(pc.cyan(`\n ${title}`)))
35
+ }
36
+
37
+ function printBanner() {
38
+ const art = [
39
+ ' ███╗ ██╗███████╗██╗ ██╗ ██████╗ ██████╗ █████╗ ',
40
+ ' ████╗ ██║██╔════╝╚██╗██╔╝██╔═══██╗██╔══██╗██╔══██╗',
41
+ ' ██╔██╗ ██║█████╗ ╚███╔╝ ██║ ██║██████╔╝███████║',
42
+ ' ██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║██╔══██╗██╔══██║',
43
+ ' ██║ ╚████║███████╗██╔╝ ██╗╚██████╔╝██║ ██║██║ ██║',
44
+ ' ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝',
45
+ ]
46
+ const gradient = [pc.cyan, pc.cyan, pc.cyan, pc.blue, pc.blue, pc.blue]
47
+ console.log()
48
+ art.forEach((line, i) => console.log(pc.bold(gradient[i](line))))
49
+ console.log()
50
+ const W = 44
51
+ const centre = (str) => {
52
+ const pad = W - str.length
53
+ const l = Math.floor(pad / 2)
54
+ return ' '.repeat(l) + str + ' '.repeat(pad - l)
55
+ }
56
+ const bar = '═'.repeat(W)
57
+ console.log(pc.bold(pc.cyan(
58
+ ` ╔${bar}╗\n` +
59
+ ` ║${centre(`create-nexora-next v${version}`)}║\n` +
60
+ ` ║${centre('Next.js scaffold · by Rayan')}║\n` +
61
+ ` ╚${bar}╝`
62
+ )))
63
+ console.log()
64
+ }
65
+
66
+ function printSummary(opts, projectName, pm) {
67
+ const yes = pc.green('✓')
68
+ const skip = pc.dim('–')
69
+ const row = (label, on) =>
70
+ ` ${on ? yes : skip} ${on ? pc.white(label) : pc.dim(label)}`
71
+
72
+ console.log()
73
+ p.log.success(pc.bold(pc.green(` 🚀 ${projectName} is ready!`)))
74
+ console.log()
75
+ console.log(pc.bold(' What was set up:'))
76
+ console.log(row('Next.js 15 + TypeScript + Tailwind + ESLint', true))
77
+ console.log(row('shadcn/ui + custom globals.css utilities', true))
78
+ console.log(row('Prettier + tailwind class sorting', true))
79
+ console.log(row('Poppins font + path alias ~/', true))
80
+ console.log(row('Localization via next-intl', opts.i18n))
81
+ console.log(row('TanStack Query + local persistence', opts.query))
82
+ console.log(row('Theming with next-themes', opts.theming))
83
+ console.log(row('Animations motion + gsap', opts.animations))
84
+ console.log(row('Auth proxy + zod validators', opts.auth))
85
+ console.log(row('Axios client + server instances', opts.axios))
86
+ console.log(row('Husky pre-commit hooks', opts.husky))
87
+ console.log(row('React Compiler', opts.reactCompiler))
88
+ console.log()
89
+ console.log(pc.bold(' Next steps:'))
90
+ console.log(` ${pc.cyan('cd')} ${projectName}`)
91
+ const devCmd = pm === 'npm' ? 'npm run dev' : `${pm} dev`
92
+ console.log(` ${pc.cyan(devCmd)}`)
93
+ console.log()
94
+ console.log(pc.dim(' Happy shipping, Rayan. 🔥'))
95
+ console.log()
96
+ }
97
+
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
+ }
104
+
105
+ async function main() {
106
+ const parsed = parseArgs(process.argv)
107
+
108
+ printBanner()
109
+
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
+
129
+ section('Phase 1 Project identity')
130
+
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
+ }
147
+
148
+ const targetDir = path.resolve(process.cwd(), name)
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)}`)
166
+ }
167
+
168
+ section('Phase 2 — Features')
169
+
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
+
181
+ const features = [
182
+ i18n && 'i18n',
183
+ query && 'query',
184
+ theming && 'theming',
185
+ animations && 'animations',
186
+ auth && 'auth',
187
+ axios && 'axios',
188
+ husky && 'husky',
189
+ reactCompiler && 'react-compiler',
190
+ ].filter(Boolean)
191
+
192
+ console.log()
193
+ p.log.info(
194
+ pc.bold(` ${pc.cyan(name)}`) +
195
+ pc.dim(` · ${pm}`) +
196
+ (features.length ? pc.dim(' · ' + features.join(' · ')) : '')
197
+ )
198
+ console.log()
199
+
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
+ }
204
+
205
+ section('Phase 3 — Building your project')
206
+
207
+ const opts = {
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),
215
+ reactCompiler: Boolean(reactCompiler),
216
+ }
217
+
218
+ await stepCreateNextApp(name, pm, targetDir)
219
+ await stepSetupShadcn(targetDir, pm)
220
+ await stepWriteBaseFiles(targetDir, opts)
221
+ await stepWriteProviders(targetDir, opts)
222
+
223
+ if (opts.i18n) await stepSetupI18n(targetDir, pm, opts)
224
+ if (opts.auth || opts.i18n) await stepSetupAuth(targetDir, opts)
225
+
226
+ await stepWriteProxy(targetDir, opts)
227
+
228
+ if (opts.axios) await stepSetupAxios(targetDir)
229
+
230
+ await stepInstallDeps(targetDir, pm, opts)
231
+ await stepPatchPackageJson(targetDir)
232
+
233
+ if (opts.husky) await stepSetupHusky(targetDir, pm)
234
+
235
+ printSummary(opts, name, pm)
236
+ p.outro(pc.bgGreen(pc.black(' Done! ')))
237
+ }
238
+
239
+ main().catch((err) => {
240
+ console.error(pc.red('\n Unexpected error:'), err.message || err)
241
+ process.exit(1)
267
242
  })
@@ -1,19 +1,19 @@
1
- import path from 'path'
2
- import { run, execCmd } from '../utils/runner.js'
3
- import { safeStep } from '../utils/safe-step.js'
4
-
5
- /**
6
- * @param {string} projectName
7
- * @param {'npm'|'pnpm'|'bun'|'yarn'} pm
8
- * @param {string} targetDir Absolute path where the project will live
9
- */
10
- export async function stepCreateNextApp(projectName, pm, targetDir) {
11
- await safeStep('Scaffolding Next.js app', () => {
12
- const x = execCmd(pm)
13
- // create-next-app flags: TypeScript, Tailwind, ESLint, App Router, src dir, no Turbopack prompt, import alias ~
14
- run(
15
- `${x} create-next-app@latest ${projectName} --typescript --tailwind --eslint --app --src-dir --no-turbopack --import-alias "~/*" --use-${pm === 'npm' ? 'npm' : pm}`,
16
- path.dirname(targetDir),
17
- )
18
- })
1
+ import path from 'path'
2
+ import { run, execCmd } from '../utils/runner.js'
3
+ import { safeStep } from '../utils/safe-step.js'
4
+
5
+ /**
6
+ * @param {string} projectName
7
+ * @param {'npm'|'pnpm'|'bun'|'yarn'} pm
8
+ * @param {string} targetDir Absolute path where the project will live
9
+ */
10
+ export async function stepCreateNextApp(projectName, pm, targetDir) {
11
+ await safeStep('Scaffolding Next.js app', () => {
12
+ const x = execCmd(pm)
13
+ // create-next-app flags: TypeScript, Tailwind, ESLint, App Router, src dir, no Turbopack prompt, import alias ~
14
+ run(
15
+ `${x} create-next-app@latest ${projectName} --typescript --tailwind --eslint --app --src-dir --no-turbopack --import-alias "~/*" --use-${pm === 'npm' ? 'npm' : pm}`,
16
+ path.dirname(targetDir),
17
+ )
18
+ })
19
19
  }
@@ -1,39 +1,39 @@
1
- import path from 'path'
2
- import fs from 'fs'
3
- import { run, execCmd } from '../utils/runner.js'
4
- import { appendFile } from '../utils/writer.js'
5
- import { safeStep } from '../utils/safe-step.js'
6
- import { globalsCssAppend } from '../templates/files.js'
7
-
8
- /**
9
- * @param {string} targetDir
10
- * @param {'npm'|'pnpm'|'bun'|'yarn'} pm
11
- */
12
- export async function stepSetupShadcn(targetDir, pm) {
13
- await safeStep('Initialising shadcn/ui', () => {
14
- const x = execCmd(pm)
15
- run(`${x} shadcn@latest init -y -d`, targetDir)
16
- })
17
-
18
- await safeStep('Patching shadcn config + cleaning up', () => {
19
- // 1. Delete src/lib/utils.ts if shadcn created it (we use src/lib/utils/index.ts)
20
- const shadcnUtils = path.join(targetDir, 'src', 'lib', 'utils.ts')
21
- if (fs.existsSync(shadcnUtils)) fs.unlinkSync(shadcnUtils)
22
-
23
- // 2. Update components.json utils path to point at our index.ts
24
- const componentsJson = path.join(targetDir, 'components.json')
25
- if (fs.existsSync(componentsJson)) {
26
- const raw = fs.readFileSync(componentsJson, 'utf8')
27
- const patched = raw.replace(
28
- '"utils": "~/lib/utils"',
29
- '"utils": "~/lib/utils/index.ts"',
30
- )
31
- fs.writeFileSync(componentsJson, patched, 'utf8')
32
- }
33
- })
34
-
35
- await safeStep('Appending custom styles to globals.css', () => {
36
- const globalsPath = path.join(targetDir, 'src', 'app', 'globals.css')
37
- appendFile(globalsPath, globalsCssAppend)
38
- })
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+ import { run, execCmd } from '../utils/runner.js'
4
+ import { appendFile } from '../utils/writer.js'
5
+ import { safeStep } from '../utils/safe-step.js'
6
+ import { globalsCssAppend } from '../templates/files.js'
7
+
8
+ /**
9
+ * @param {string} targetDir
10
+ * @param {'npm'|'pnpm'|'bun'|'yarn'} pm
11
+ */
12
+ export async function stepSetupShadcn(targetDir, pm) {
13
+ await safeStep('Initialising shadcn/ui', () => {
14
+ const x = execCmd(pm)
15
+ run(`${x} shadcn@latest init -y -d`, targetDir)
16
+ })
17
+
18
+ await safeStep('Patching shadcn config + cleaning up', () => {
19
+ // 1. Delete src/lib/utils.ts if shadcn created it (we use src/lib/utils/index.ts)
20
+ const shadcnUtils = path.join(targetDir, 'src', 'lib', 'utils.ts')
21
+ if (fs.existsSync(shadcnUtils)) fs.unlinkSync(shadcnUtils)
22
+
23
+ // 2. Update components.json utils path to point at our index.ts
24
+ const componentsJson = path.join(targetDir, 'components.json')
25
+ if (fs.existsSync(componentsJson)) {
26
+ const raw = fs.readFileSync(componentsJson, 'utf8')
27
+ const patched = raw.replace(
28
+ '"utils": "~/lib/utils"',
29
+ '"utils": "~/lib/utils/index.ts"',
30
+ )
31
+ fs.writeFileSync(componentsJson, patched, 'utf8')
32
+ }
33
+ })
34
+
35
+ await safeStep('Appending custom styles to globals.css', () => {
36
+ const globalsPath = path.join(targetDir, 'src', 'app', 'globals.css')
37
+ appendFile(globalsPath, globalsCssAppend)
38
+ })
39
39
  }