create-nexora-next 0.1.0

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 ADDED
@@ -0,0 +1,46 @@
1
+ # create-nexora-next
2
+
3
+ > The official Next.js scaffolding CLI by Rayan — batteries included.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-nexora-next
9
+ pnpm dlx create-nexora-next
10
+ bunx create-nexora-next
11
+ ```
12
+
13
+ ## What it sets up
14
+
15
+ ### Always included
16
+ - Next.js (TypeScript, Tailwind CSS v4, ESLint, App Router, `~/` alias)
17
+ - shadcn/ui initialized with defaults
18
+ - Custom `globals.css` (squircle utilities, container, skeleton animation)
19
+ - `.prettierrc` with `prettier-plugin-tailwindcss`
20
+ - `.env.local` pre-populated
21
+ - Poppins font via `next/font/google`
22
+ - `cn()`, `toSlug()`, `trycatch()`, and other utils
23
+ - Typed constants (`TZ_COOKIE`, `SITE_URL`)
24
+ - SEO metadata scaffold
25
+ - `SileoToaster` component
26
+ - Providers pattern (`src/components/providers/`)
27
+ - nuqs adapter (URL state management)
28
+ - Core deps: `zustand`, `zod`, `react-hook-form`, `date-fns`, `nuqs`, `cookies-next`, `nextjs-toploader`
29
+ - Dev deps: `prettier`, `prettier-plugin-tailwindcss`, `babel-plugin-react-compiler`
30
+
31
+ ### Optional features (prompted)
32
+
33
+ | Feature | What gets installed / created |
34
+ |---|---|
35
+ | Husky | `husky`, `lint-staged`, pre-commit hook |
36
+ | Localization | `next-intl`, `src/i18n/`, `locales/en.json`, `[locale]/page.tsx` |
37
+ | TanStack Query | `@tanstack/react-query`, persist client, query-client provider |
38
+ | Auth | proxy chain, auth validators, `src/apis/` |
39
+ | Theming | `next-themes`, theme provider |
40
+ | Animations | `motion`, `gsap` |
41
+ | Axios | `axios`, `axios.client.ts`, `axios.server.ts` |
42
+
43
+ ## Requirements
44
+
45
+ - Node.js 18+
46
+ - npm, pnpm, bun, or yarn
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "create-nexora-next",
3
+ "version": "0.1.0",
4
+ "description": "The official Next.js scaffolding CLI by Rayan — batteries included.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-nexora-next": "src/index.js"
8
+ },
9
+ "files": [
10
+ "src"
11
+ ],
12
+ "scripts": {
13
+ "start": "node src/index.js",
14
+ "dev": "node src/index.js"
15
+ },
16
+ "dependencies": {
17
+ "@clack/prompts": "^0.9.0",
18
+ "picocolors": "^1.1.1"
19
+ },
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "keywords": [
24
+ "nextjs",
25
+ "scaffold",
26
+ "cli",
27
+ "create-app",
28
+ "rayan",
29
+ "next-intl",
30
+ "shadcn",
31
+ "tanstack"
32
+ ],
33
+ "author": "Rayan",
34
+ "license": "MIT"
35
+ }
package/src/index.js ADDED
@@ -0,0 +1,267 @@
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)
267
+ })
@@ -0,0 +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
+ })
19
+ }
@@ -0,0 +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
+ })
39
+ }
@@ -0,0 +1,110 @@
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+ import { writeFile, mkdir } from '../utils/writer.js'
4
+ import { safeStep } from '../utils/safe-step.js'
5
+ import {
6
+ envLocal,
7
+ prettierRc,
8
+ fontsTs,
9
+ utilsTs,
10
+ utilsWithAxiosTs,
11
+ constantsTs,
12
+ seoTs,
13
+ seoWithLocaleTs,
14
+ sileoUiTsx,
15
+ sileoProviderTsx,
16
+ nuqsAdapterTsx,
17
+ rootLayoutI18nTs,
18
+ rootLayoutTs,
19
+ localeLayoutTs,
20
+ eslintConfigMjs,
21
+ sitemapTs,
22
+ } from '../templates/files.js'
23
+
24
+ /**
25
+ * @param {string} targetDir
26
+ * @param {{ i18n: boolean, axios: boolean, query: boolean }} opts
27
+ */
28
+ export async function stepWriteBaseFiles(targetDir, opts) {
29
+ await safeStep('Writing base config files', () => {
30
+ const p = (...parts) => path.join(targetDir, ...parts)
31
+
32
+ // .env.local
33
+ writeFile(p('.env.local'), envLocal)
34
+
35
+ // .prettierrc
36
+ writeFile(p('.prettierrc'), prettierRc)
37
+
38
+ // src/lib/fonts.ts
39
+ writeFile(p('src', 'lib', 'fonts.ts'), fontsTs)
40
+
41
+ // src/lib/utils/index.ts
42
+ writeFile(
43
+ p('src', 'lib', 'utils', 'index.ts'),
44
+ opts.axios ? utilsWithAxiosTs : utilsTs,
45
+ )
46
+
47
+ // src/constants/index.ts
48
+ writeFile(p('src', 'constants', 'index.ts'), constantsTs(opts))
49
+
50
+ // src/lib/seo/index.ts
51
+ writeFile(
52
+ p('src', 'lib', 'seo', 'index.ts'),
53
+ opts.i18n ? seoWithLocaleTs : seoTs,
54
+ )
55
+
56
+ // src/components/ui/sileo.tsx
57
+ writeFile(p('src', 'components', 'ui', 'sileo.tsx'), sileoUiTsx)
58
+
59
+ // src/components/providers/sileo.provider.tsx
60
+ writeFile(p('src', 'components', 'providers', 'sileo.provider.tsx'), sileoProviderTsx)
61
+
62
+ // src/components/adapters/nuqs.adapter.tsx
63
+ writeFile(p('src', 'components', 'adapters', 'nuqs.adapter.tsx'), nuqsAdapterTsx)
64
+
65
+ // Empty structural folders
66
+ const emptyDirs = [
67
+ ['src', 'hooks'],
68
+ ['src', 'components', 'atoms'],
69
+ ['src', 'components', 'molecules'],
70
+ ['src', 'components', 'organisms'],
71
+ ['src', 'components', 'primitives'],
72
+ ]
73
+ for (const parts of emptyDirs) {
74
+ mkdir(p(...parts))
75
+ writeFile(p(...parts, 'index.ts'), `// ${parts[parts.length - 1]}\n`)
76
+ }
77
+ })
78
+
79
+ // ── Layouts ────────────────────────────────────────────────────────────────
80
+ await safeStep('Setting up app layouts', () => {
81
+ const p = (...parts) => path.join(targetDir, ...parts)
82
+
83
+ if (opts.i18n) {
84
+ // Replace root layout with passthrough shell
85
+ writeFile(p('src', 'app', 'layout.tsx'), rootLayoutI18nTs)
86
+
87
+ // Remove the default src/app/page.tsx (locale takes over routing)
88
+ const defaultPage = p('src', 'app', 'page.tsx')
89
+ if (fs.existsSync(defaultPage)) fs.unlinkSync(defaultPage)
90
+
91
+ // Create src/app/[locale]/layout.tsx
92
+ writeFile(p('src', 'app', '[locale]', 'layout.tsx'), localeLayoutTs)
93
+ } else {
94
+ // Standard root layout wiring up Providers
95
+ writeFile(p('src', 'app', 'layout.tsx'), rootLayoutTs)
96
+ }
97
+ })
98
+
99
+ // ── ESLint config ──────────────────────────────────────────────────────────
100
+ await safeStep('Replacing ESLint config', () => {
101
+ const p = (...parts) => path.join(targetDir, ...parts)
102
+ writeFile(p('eslint.config.mjs'), eslintConfigMjs(opts))
103
+ })
104
+
105
+ // ── Sitemap ────────────────────────────────────────────────────────────────
106
+ await safeStep('Writing sitemap', () => {
107
+ const p = (...parts) => path.join(targetDir, ...parts)
108
+ writeFile(p('src', 'app', 'sitemap.ts'), sitemapTs)
109
+ })
110
+ }
@@ -0,0 +1,51 @@
1
+ import path from "path";
2
+ import {
3
+ localeProviderTsx,
4
+ providersIndexTsx,
5
+ queryClientProviderTsx,
6
+ themeProviderTsx,
7
+ tzProviderTsx,
8
+ } from "../templates/files.js";
9
+ import { safeStep } from "../utils/safe-step.js";
10
+ import { writeFile } from "../utils/writer.js";
11
+
12
+ /**
13
+ * @param {string} targetDir
14
+ * @param {{ i18n: boolean, theming: boolean, query: boolean }} opts
15
+ */
16
+ export async function stepWriteProviders(targetDir, opts) {
17
+ await safeStep("Generating providers", () => {
18
+ const p = (...parts) => path.join(targetDir, ...parts);
19
+
20
+ // Dynamic providers/index.ts
21
+ writeFile(
22
+ p("src", "components", "providers", "index.tsx"),
23
+ providersIndexTsx(opts),
24
+ );
25
+
26
+ if (opts.theming) {
27
+ writeFile(
28
+ p("src", "components", "providers", "theme.provider.tsx"),
29
+ themeProviderTsx,
30
+ );
31
+ }
32
+
33
+ if (opts.i18n) {
34
+ writeFile(
35
+ p("src", "components", "providers", "locale.provider.tsx"),
36
+ localeProviderTsx,
37
+ );
38
+ writeFile(
39
+ p("src", "components", "providers", "tz.provider.tsx"),
40
+ tzProviderTsx,
41
+ );
42
+ }
43
+
44
+ if (opts.query) {
45
+ writeFile(
46
+ p("src", "components", "providers", "query-client.provider.tsx"),
47
+ queryClientProviderTsx,
48
+ );
49
+ }
50
+ });
51
+ }
@@ -0,0 +1,42 @@
1
+ import path from 'path'
2
+ import { writeFile } from '../utils/writer.js'
3
+ import { run, installCmd } from '../utils/runner.js'
4
+ import { safeStep } from '../utils/safe-step.js'
5
+ import {
6
+ i18nRoutingTs,
7
+ i18nRequestTs,
8
+ i18nNavigationTs,
9
+ nextIntlTs,
10
+ localesEnJson,
11
+ localPageTsx,
12
+ localeDateFormatsTs,
13
+ nextConfigI18nTs,
14
+ } from '../templates/files.js'
15
+
16
+ /**
17
+ * @param {string} targetDir
18
+ * @param {'npm'|'pnpm'|'bun'|'yarn'} pm
19
+ * @param {{ reactCompiler: boolean }} opts
20
+ */
21
+ export async function stepSetupI18n(targetDir, pm, opts) {
22
+ await safeStep('Installing next-intl', () => {
23
+ run(installCmd(pm, ['next-intl']), targetDir)
24
+ })
25
+
26
+ await safeStep('Writing i18n files', () => {
27
+ const p = (...parts) => path.join(targetDir, ...parts)
28
+
29
+ writeFile(p('locales', 'en.json'), localesEnJson)
30
+ writeFile(p('src', 'i18n', 'routing.ts'), i18nRoutingTs)
31
+ writeFile(p('src', 'i18n', 'request.ts'), i18nRequestTs)
32
+ writeFile(p('src', 'i18n', 'navigation.ts'), i18nNavigationTs)
33
+ writeFile(p('next-intl.ts'), nextIntlTs)
34
+ writeFile(p('src', 'app', '[locale]', 'page.tsx'), localPageTsx)
35
+ writeFile(p('src', 'lib', 'utils', 'locale-date-formats.ts'), localeDateFormatsTs)
36
+ })
37
+
38
+ await safeStep('Updating next.config.ts for next-intl', () => {
39
+ const p = (...parts) => path.join(targetDir, ...parts)
40
+ writeFile(p('next.config.ts'), nextConfigI18nTs(opts))
41
+ })
42
+ }
@@ -0,0 +1,31 @@
1
+ import path from 'path'
2
+ import { writeFile } from '../utils/writer.js'
3
+ import { safeStep } from '../utils/safe-step.js'
4
+ import {
5
+ proxyLibTs,
6
+ proxyAuthTs,
7
+ validatorsAuthTs,
8
+ validatorsTs,
9
+ apisClientTs,
10
+ apisServerTs,
11
+ } from '../templates/files.js'
12
+
13
+ /**
14
+ * @param {string} targetDir
15
+ * @param {{ i18n: boolean, auth: boolean, axios: boolean }} opts
16
+ */
17
+ export async function stepSetupAuth(targetDir, opts) {
18
+ await safeStep('Writing auth & proxy files', () => {
19
+ const p = (...parts) => path.join(targetDir, ...parts)
20
+
21
+ writeFile(p('src', 'lib', 'proxy', 'index.ts'), proxyLibTs)
22
+
23
+ if (opts.auth) {
24
+ writeFile(p('src', 'lib', 'proxy', 'auth.ts'), proxyAuthTs)
25
+ writeFile(p('src', 'lib', 'validators', 'index.ts'), validatorsTs)
26
+ writeFile(p('src', 'lib', 'validators', 'auth.ts'), validatorsAuthTs)
27
+ writeFile(p('src', 'apis', 'client', 'index.ts'), apisClientTs)
28
+ writeFile(p('src', 'apis', 'server', 'index.ts'), apisServerTs)
29
+ }
30
+ })
31
+ }
@@ -0,0 +1,17 @@
1
+ import path from 'path'
2
+ import { writeFile } from '../utils/writer.js'
3
+ import { safeStep } from '../utils/safe-step.js'
4
+ import { proxyTs } from '../templates/files.js'
5
+
6
+ /**
7
+ * @param {string} targetDir
8
+ * @param {{ i18n: boolean, auth: boolean }} opts
9
+ */
10
+ export async function stepWriteProxy(targetDir, opts) {
11
+ if (!opts.i18n && !opts.auth) return
12
+
13
+ await safeStep('Writing Next.js middleware (proxy)', () => {
14
+ const p = (...parts) => path.join(targetDir, ...parts)
15
+ writeFile(p('src', 'proxy.ts'), proxyTs(opts))
16
+ })
17
+ }