@newt-app/templates 0.0.2
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/dist/index.d.ts +43 -0
- package/dist/index.js +1781 -0
- package/dist/static/web/static/favicon.ico +0 -0
- package/dist/static/web/static/fonts/GeistMonoVF.woff +0 -0
- package/dist/static/web/static/fonts/GeistVF.woff +0 -0
- package/dist/static/web/static/public/file-text.svg +3 -0
- package/dist/static/web/static/public/globe.svg +10 -0
- package/dist/static/web/static/public/next.svg +1 -0
- package/dist/static/web/static/public/turborepo-dark.svg +19 -0
- package/dist/static/web/static/public/turborepo-light.svg +19 -0
- package/dist/static/web/static/public/vercel.svg +10 -0
- package/dist/static/web/static/public/window.svg +3 -0
- package/package.json +23 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1781 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
|
|
4
|
+
// src/root/templates/package-json.ts
|
|
5
|
+
var package_json_default = {
|
|
6
|
+
filename: "package.json",
|
|
7
|
+
template: `{
|
|
8
|
+
"name": "<%= projectName %>",
|
|
9
|
+
"private": true,
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "turbo run build",
|
|
12
|
+
"dev": "turbo run dev",
|
|
13
|
+
"lint": "turbo run lint",
|
|
14
|
+
"format": "prettier --write \\"**/*.{ts,tsx,js,jsx,json,md,yaml,yml}\\"",
|
|
15
|
+
"format:check": "prettier --check \\"**/*.{ts,tsx,js,jsx,json,md,yaml,yml}\\"",
|
|
16
|
+
"check-types": "turbo run check-types",
|
|
17
|
+
"db:migrate": "turbo run migrate",
|
|
18
|
+
"db:generate": "turbo run generate"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"prettier": "^3.7.4",
|
|
22
|
+
"turbo": "^2.8.16",
|
|
23
|
+
"typescript": "5.9.2"
|
|
24
|
+
},
|
|
25
|
+
"packageManager": "pnpm@9.0.0",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
}
|
|
29
|
+
}`
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/root/templates/gitignore.ts
|
|
33
|
+
var gitignore_default = {
|
|
34
|
+
filename: ".gitignore",
|
|
35
|
+
template: `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
36
|
+
|
|
37
|
+
# Dependencies
|
|
38
|
+
node_modules
|
|
39
|
+
.pnp
|
|
40
|
+
.pnp.js
|
|
41
|
+
|
|
42
|
+
# Local env files
|
|
43
|
+
.env
|
|
44
|
+
.env.local
|
|
45
|
+
.env.development.local
|
|
46
|
+
.env.test.local
|
|
47
|
+
.env.production.local
|
|
48
|
+
|
|
49
|
+
# Testing
|
|
50
|
+
coverage
|
|
51
|
+
|
|
52
|
+
# Turbo
|
|
53
|
+
.turbo
|
|
54
|
+
|
|
55
|
+
# Vercel
|
|
56
|
+
.vercel
|
|
57
|
+
|
|
58
|
+
# Build Outputs
|
|
59
|
+
.next/
|
|
60
|
+
out/
|
|
61
|
+
build
|
|
62
|
+
dist
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Debug
|
|
66
|
+
npm-debug.log*
|
|
67
|
+
yarn-debug.log*
|
|
68
|
+
yarn-error.log*
|
|
69
|
+
|
|
70
|
+
# Misc
|
|
71
|
+
.DS_Store
|
|
72
|
+
*.pem`
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/root/templates/prettierrc.ts
|
|
76
|
+
var prettierrc_default = {
|
|
77
|
+
filename: ".prettierrc",
|
|
78
|
+
template: `{
|
|
79
|
+
"singleQuote": true,
|
|
80
|
+
"trailingComma": "all"
|
|
81
|
+
}`
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/root/templates/dockerignore.ts
|
|
85
|
+
var dockerignore_default = {
|
|
86
|
+
filename: ".dockerignore",
|
|
87
|
+
template: `node_modules
|
|
88
|
+
.next
|
|
89
|
+
dist
|
|
90
|
+
.turbo
|
|
91
|
+
*.log
|
|
92
|
+
.env
|
|
93
|
+
.env.*
|
|
94
|
+
!.env.example`
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/root/templates/npmrc.ts
|
|
98
|
+
var npmrc_default = {
|
|
99
|
+
filename: ".npmrc",
|
|
100
|
+
template: ``
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/root/templates/turbo-json.ts
|
|
104
|
+
var turbo_json_default = {
|
|
105
|
+
filename: "turbo.json",
|
|
106
|
+
template: `{
|
|
107
|
+
"$schema": "https://turborepo.dev/schema.json",
|
|
108
|
+
"ui": "tui",
|
|
109
|
+
"tasks": {
|
|
110
|
+
"build": {
|
|
111
|
+
"dependsOn": ["^build"],
|
|
112
|
+
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
|
113
|
+
"outputs": [".next/**", "!.next/cache/**"]
|
|
114
|
+
},
|
|
115
|
+
"lint": {
|
|
116
|
+
"dependsOn": ["^lint"]
|
|
117
|
+
},
|
|
118
|
+
"check-types": {
|
|
119
|
+
"dependsOn": ["^check-types"]
|
|
120
|
+
},
|
|
121
|
+
"dev": {
|
|
122
|
+
"cache": false,
|
|
123
|
+
"persistent": true
|
|
124
|
+
},
|
|
125
|
+
"migrate": {
|
|
126
|
+
"cache": false,
|
|
127
|
+
"interactive": true
|
|
128
|
+
},
|
|
129
|
+
"generate": {
|
|
130
|
+
"cache": false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}`
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/root/templates/pnpm-workspace.ts
|
|
137
|
+
var pnpm_workspace_default = {
|
|
138
|
+
filename: "pnpm-workspace.yaml",
|
|
139
|
+
template: `packages:
|
|
140
|
+
- "apps/*"
|
|
141
|
+
- "packages/*"`
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/root/templates/readme.ts
|
|
145
|
+
var readme_default = {
|
|
146
|
+
filename: "README.md",
|
|
147
|
+
template: `# <%= projectName %>
|
|
148
|
+
|
|
149
|
+
Full-stack monorepo: Next.js 16 + NestJS 11 + better-auth + PostgreSQL.
|
|
150
|
+
|
|
151
|
+
## Quick start
|
|
152
|
+
|
|
153
|
+
\`\`\`sh
|
|
154
|
+
cp .env.example .env # fill in DATABASE_URL
|
|
155
|
+
pnpm install
|
|
156
|
+
pnpm db:migrate
|
|
157
|
+
pnpm dev
|
|
158
|
+
\`\`\`
|
|
159
|
+
|
|
160
|
+
Open [http://localhost:3000](http://localhost:3000).
|
|
161
|
+
|
|
162
|
+
## Apps
|
|
163
|
+
|
|
164
|
+
- **web** \u2014 Next.js frontend (port 3000)
|
|
165
|
+
- **api** \u2014 NestJS backend (port 3001)
|
|
166
|
+
|
|
167
|
+
## Packages
|
|
168
|
+
|
|
169
|
+
- **\`@repo/auth\`** \u2014 better-auth config
|
|
170
|
+
- **\`@repo/ui\`** \u2014 shared React components
|
|
171
|
+
- **\`@repo/eslint-config\`** \u2014 shared ESLint config
|
|
172
|
+
- **\`@repo/typescript-config\`** \u2014 shared tsconfig
|
|
173
|
+
`
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/root/templates/env-example.ts
|
|
177
|
+
var env_example_default = {
|
|
178
|
+
filename: ".env.example",
|
|
179
|
+
template: `DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
|
180
|
+
BETTER_AUTH_URL=http://localhost:3000
|
|
181
|
+
BETTER_AUTH_SECRET=your-secret-here`
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// src/root/index.ts
|
|
185
|
+
var root = {
|
|
186
|
+
templates: [
|
|
187
|
+
package_json_default,
|
|
188
|
+
gitignore_default,
|
|
189
|
+
prettierrc_default,
|
|
190
|
+
dockerignore_default,
|
|
191
|
+
npmrc_default,
|
|
192
|
+
turbo_json_default,
|
|
193
|
+
pnpm_workspace_default,
|
|
194
|
+
readme_default,
|
|
195
|
+
env_example_default
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
var root_default = root;
|
|
199
|
+
|
|
200
|
+
// src/web/templates/package-json.ts
|
|
201
|
+
var package_json_default2 = {
|
|
202
|
+
filename: "apps/web/package.json",
|
|
203
|
+
template: `{
|
|
204
|
+
"name": "web",
|
|
205
|
+
"version": "0.1.0",
|
|
206
|
+
"type": "module",
|
|
207
|
+
"private": true,
|
|
208
|
+
"scripts": {
|
|
209
|
+
"dev": "next dev --port 3000",
|
|
210
|
+
"build": "next build",
|
|
211
|
+
"start": "next start",
|
|
212
|
+
"lint": "eslint --max-warnings 0",
|
|
213
|
+
"check-types": "next typegen && tsc --noEmit",
|
|
214
|
+
"db:migrate": "better-auth migrate"
|
|
215
|
+
},
|
|
216
|
+
"dependencies": {
|
|
217
|
+
"@repo/auth": "workspace:*",
|
|
218
|
+
"@repo/ui": "workspace:*",
|
|
219
|
+
"@tailwindcss/postcss": "^4.2.1",
|
|
220
|
+
"@tanstack/react-form": "^1.28.5",
|
|
221
|
+
"@tanstack/react-query": "^5.90.21",
|
|
222
|
+
"better-auth": "^1.2.8",
|
|
223
|
+
"next": "16.1.5",
|
|
224
|
+
"react": "^19.2.0",
|
|
225
|
+
"react-dom": "^19.2.0",
|
|
226
|
+
"tailwindcss": "^4.2.1"
|
|
227
|
+
},
|
|
228
|
+
"devDependencies": {
|
|
229
|
+
"@repo/eslint-config": "workspace:*",
|
|
230
|
+
"@repo/typescript-config": "workspace:*",
|
|
231
|
+
"@types/node": "^22.15.3",
|
|
232
|
+
"@types/react": "19.2.2",
|
|
233
|
+
"@types/react-dom": "19.2.2",
|
|
234
|
+
"eslint": "^9.39.1",
|
|
235
|
+
"typescript": "5.9.2"
|
|
236
|
+
}
|
|
237
|
+
}`
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/web/templates/gitignore.ts
|
|
241
|
+
var gitignore_default2 = {
|
|
242
|
+
filename: "apps/web/.gitignore",
|
|
243
|
+
template: `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
244
|
+
|
|
245
|
+
# dependencies
|
|
246
|
+
/node_modules
|
|
247
|
+
/.pnp
|
|
248
|
+
.pnp.js
|
|
249
|
+
.yarn/install-state.gz
|
|
250
|
+
|
|
251
|
+
# testing
|
|
252
|
+
/coverage
|
|
253
|
+
|
|
254
|
+
# next.js
|
|
255
|
+
/.next/
|
|
256
|
+
/out/
|
|
257
|
+
|
|
258
|
+
# production
|
|
259
|
+
/build
|
|
260
|
+
|
|
261
|
+
# misc
|
|
262
|
+
.DS_Store
|
|
263
|
+
*.pem
|
|
264
|
+
|
|
265
|
+
# debug
|
|
266
|
+
npm-debug.log*
|
|
267
|
+
yarn-debug.log*
|
|
268
|
+
yarn-error.log*
|
|
269
|
+
|
|
270
|
+
# env files (can opt-in for commiting if needed)
|
|
271
|
+
.env*
|
|
272
|
+
|
|
273
|
+
# vercel
|
|
274
|
+
.vercel
|
|
275
|
+
|
|
276
|
+
# typescript
|
|
277
|
+
*.tsbuildinfo
|
|
278
|
+
next-env.d.ts`
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// src/web/templates/readme.ts
|
|
282
|
+
var readme_default2 = {
|
|
283
|
+
filename: "apps/web/README.md",
|
|
284
|
+
template: `This is a [Next.js](https://nextjs.org) project bootstrapped with [\`create-next-app\`](https://nextjs.org/docs/app/api-reference/create-next-app).
|
|
285
|
+
|
|
286
|
+
## Getting Started
|
|
287
|
+
|
|
288
|
+
First, run the development server:
|
|
289
|
+
|
|
290
|
+
\`\`\`bash
|
|
291
|
+
npm run dev
|
|
292
|
+
# or
|
|
293
|
+
yarn dev
|
|
294
|
+
# or
|
|
295
|
+
pnpm dev
|
|
296
|
+
# or
|
|
297
|
+
bun dev
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
301
|
+
|
|
302
|
+
You can start editing the page by modifying \`app/page.tsx\`. The page auto-updates as you edit the file.
|
|
303
|
+
|
|
304
|
+
This project uses [\`next/font\`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font.
|
|
305
|
+
|
|
306
|
+
## Learn More
|
|
307
|
+
|
|
308
|
+
To learn more about Next.js, take a look at the following resources:
|
|
309
|
+
|
|
310
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
311
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
312
|
+
|
|
313
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
314
|
+
|
|
315
|
+
## Deploy on Vercel
|
|
316
|
+
|
|
317
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
318
|
+
|
|
319
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.`
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// src/web/templates/eslint-config.ts
|
|
323
|
+
var eslint_config_default = {
|
|
324
|
+
filename: "apps/web/eslint.config.js",
|
|
325
|
+
template: `import { nextJsConfig } from "@repo/eslint-config/next-js";
|
|
326
|
+
|
|
327
|
+
/** @type {import("eslint").Linter.Config[]} */
|
|
328
|
+
export default nextJsConfig;`
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/web/templates/next-config.ts
|
|
332
|
+
var next_config_default = {
|
|
333
|
+
filename: "apps/web/next.config.js",
|
|
334
|
+
template: `/** @type {import('next').NextConfig} */
|
|
335
|
+
const nextConfig = {
|
|
336
|
+
output: "standalone",
|
|
337
|
+
async rewrites() {
|
|
338
|
+
return [
|
|
339
|
+
{
|
|
340
|
+
source: '/api/:path*',
|
|
341
|
+
destination: 'http://localhost:3001/api/:path*',
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export default nextConfig;`
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// src/web/templates/postcss-config.ts
|
|
351
|
+
var postcss_config_default = {
|
|
352
|
+
filename: "apps/web/postcss.config.mjs",
|
|
353
|
+
template: `const config = {
|
|
354
|
+
plugins: {
|
|
355
|
+
"@tailwindcss/postcss": {},
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export default config;`
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// src/web/templates/tsconfig.ts
|
|
363
|
+
var tsconfig_default = {
|
|
364
|
+
filename: "apps/web/tsconfig.json",
|
|
365
|
+
template: `{
|
|
366
|
+
"extends": "@repo/typescript-config/nextjs.json",
|
|
367
|
+
"compilerOptions": {
|
|
368
|
+
"plugins": [
|
|
369
|
+
{
|
|
370
|
+
"name": "next"
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
"strictNullChecks": true
|
|
374
|
+
},
|
|
375
|
+
"include": [
|
|
376
|
+
"**/*.ts",
|
|
377
|
+
"**/*.tsx",
|
|
378
|
+
"next-env.d.ts",
|
|
379
|
+
"next.config.js",
|
|
380
|
+
".next/types/**/*.ts"
|
|
381
|
+
],
|
|
382
|
+
"exclude": ["node_modules"]
|
|
383
|
+
}`
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// src/web/templates/globals-css.ts
|
|
387
|
+
var globals_css_default = {
|
|
388
|
+
filename: "apps/web/app/globals.css",
|
|
389
|
+
template: `@import 'tailwindcss';
|
|
390
|
+
|
|
391
|
+
html,
|
|
392
|
+
body {
|
|
393
|
+
height: 100%;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
body {
|
|
397
|
+
color: #374151;
|
|
398
|
+
background-color: #ffffff;
|
|
399
|
+
-webkit-font-smoothing: antialiased;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
* {
|
|
403
|
+
@apply border-gray-200;
|
|
404
|
+
}`
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/web/templates/layout.ts
|
|
408
|
+
var layout_default = {
|
|
409
|
+
filename: "apps/web/app/layout.tsx",
|
|
410
|
+
template: `import type { Metadata } from "next";
|
|
411
|
+
import localFont from "next/font/local";
|
|
412
|
+
import Providers from "./providers";
|
|
413
|
+
import "./globals.css";
|
|
414
|
+
|
|
415
|
+
const geistSans = localFont({
|
|
416
|
+
src: "./fonts/GeistVF.woff",
|
|
417
|
+
variable: "--font-geist-sans",
|
|
418
|
+
});
|
|
419
|
+
const geistMono = localFont({
|
|
420
|
+
src: "./fonts/GeistMonoVF.woff",
|
|
421
|
+
variable: "--font-geist-mono",
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
export const metadata: Metadata = {
|
|
425
|
+
title: "<%= projectName %>",
|
|
426
|
+
description: "Next + Nest = Newt",
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
export default function RootLayout({
|
|
430
|
+
children,
|
|
431
|
+
}: Readonly<{
|
|
432
|
+
children: React.ReactNode;
|
|
433
|
+
}>) {
|
|
434
|
+
return (
|
|
435
|
+
<html lang="en">
|
|
436
|
+
<body className={\`\${geistSans.variable} \${geistMono.variable}\`}>
|
|
437
|
+
<Providers>{children}</Providers>
|
|
438
|
+
</body>
|
|
439
|
+
</html>
|
|
440
|
+
);
|
|
441
|
+
}`
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/web/templates/page.ts
|
|
445
|
+
var page_default = {
|
|
446
|
+
filename: "apps/web/app/page.tsx",
|
|
447
|
+
template: `'use client';
|
|
448
|
+
|
|
449
|
+
import { useQuery } from '@tanstack/react-query';
|
|
450
|
+
import { authClient } from '../lib/auth-client';
|
|
451
|
+
import { AuthForm } from './auth-form';
|
|
452
|
+
import { Link } from '@repo/ui/link';
|
|
453
|
+
import { TodoList } from './todo-list';
|
|
454
|
+
|
|
455
|
+
export default function Home() {
|
|
456
|
+
const { data: session, isPending } = authClient.useSession();
|
|
457
|
+
|
|
458
|
+
const { data: hello } = useQuery({
|
|
459
|
+
queryKey: ['hello'],
|
|
460
|
+
queryFn: () => fetch('/api/hello').then((r) => r.json()),
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<main className="max-w-lg mx-auto border-r border-l h-full">
|
|
465
|
+
<div className="border-b p-4">
|
|
466
|
+
<p className="font-mono">apps/web/page.tsx</p>
|
|
467
|
+
<p className="text-gray-500">Delete me to get started!</p>
|
|
468
|
+
</div>
|
|
469
|
+
<div className="border-b p-4">
|
|
470
|
+
<h1 className="text-5xl font-black tracking-tight bg-linear-to-r from-indigo-500 via-purple-500 to-pink-500 bg-clip-text text-transparent">
|
|
471
|
+
<%= projectName %>
|
|
472
|
+
</h1>
|
|
473
|
+
<p className="mt-2 text-sm text-gray-500 tracking-widest uppercase">
|
|
474
|
+
Next + Nest = Newt \u{1F49C}
|
|
475
|
+
</p>
|
|
476
|
+
<p className="text-gray-500">The perfect TypeScript monorepo setup</p>
|
|
477
|
+
</div>
|
|
478
|
+
<div className="border-b p-4 text-gray-500">
|
|
479
|
+
<div className="flex justify-end text-gray-500">
|
|
480
|
+
<h2 className="uppercase">Next.js</h2>
|
|
481
|
+
</div>
|
|
482
|
+
<p className="font-mono">apps/web/layout.tsx</p>
|
|
483
|
+
<p>Next.js rendering</p>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<div className="p-4 border-b">
|
|
487
|
+
<div className="flex justify-end text-gray-500">
|
|
488
|
+
<h2 className="uppercase">Nest</h2>
|
|
489
|
+
</div>
|
|
490
|
+
<span className="font-mono text-gray-500 text-xs">
|
|
491
|
+
HTTP GET /api/hello
|
|
492
|
+
</span>
|
|
493
|
+
<pre className="border p-2 rounded bg-gray-50">
|
|
494
|
+
<code>{JSON.stringify(hello, null, 2)}</code>
|
|
495
|
+
</pre>
|
|
496
|
+
</div>
|
|
497
|
+
<div className="p-4 border-b">
|
|
498
|
+
<div className="flex justify-end text-gray-500">
|
|
499
|
+
<h2>better-auth</h2>
|
|
500
|
+
</div>
|
|
501
|
+
{isPending ? (
|
|
502
|
+
<p className="text-sm text-gray-500">Loading\u2026</p>
|
|
503
|
+
) : session ? (
|
|
504
|
+
<TodoList session={session} />
|
|
505
|
+
) : (
|
|
506
|
+
<AuthForm />
|
|
507
|
+
)}
|
|
508
|
+
</div>
|
|
509
|
+
<div className="p-4 text-sm">
|
|
510
|
+
<p className="mb-2">Learn more</p>
|
|
511
|
+
<ul className="list-disc list-inside space-y-2 mt-2">
|
|
512
|
+
<li>
|
|
513
|
+
<Link href="https://github.com">GitHub</Link>
|
|
514
|
+
</li>
|
|
515
|
+
<li>
|
|
516
|
+
<Link href="https://nextjs.org">Next.js</Link>
|
|
517
|
+
</li>
|
|
518
|
+
<li>
|
|
519
|
+
<Link href="https://nestjs.com">NestJS</Link>
|
|
520
|
+
</li>
|
|
521
|
+
</ul>
|
|
522
|
+
</div>
|
|
523
|
+
</main>
|
|
524
|
+
);
|
|
525
|
+
}`
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// src/web/templates/providers.ts
|
|
529
|
+
var providers_default = {
|
|
530
|
+
filename: "apps/web/app/providers.tsx",
|
|
531
|
+
template: `'use client';
|
|
532
|
+
|
|
533
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
534
|
+
import { useState } from 'react';
|
|
535
|
+
|
|
536
|
+
export default function Providers({ children }: { children: React.ReactNode }) {
|
|
537
|
+
const [client] = useState(() => new QueryClient());
|
|
538
|
+
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
|
|
539
|
+
}`
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// src/web/templates/auth-form.ts
|
|
543
|
+
var auth_form_default = {
|
|
544
|
+
filename: "apps/web/app/auth-form.tsx",
|
|
545
|
+
template: `'use client';
|
|
546
|
+
|
|
547
|
+
import { useForm } from '@tanstack/react-form';
|
|
548
|
+
import { useState } from 'react';
|
|
549
|
+
import { authClient } from '../lib/auth-client';
|
|
550
|
+
|
|
551
|
+
export function AuthForm() {
|
|
552
|
+
const [tab, setTab] = useState<'signin' | 'signup'>('signin');
|
|
553
|
+
const [error, setError] = useState('');
|
|
554
|
+
|
|
555
|
+
const form = useForm({
|
|
556
|
+
defaultValues: { name: '', email: '', password: '' },
|
|
557
|
+
onSubmit: async ({ value }) => {
|
|
558
|
+
setError('');
|
|
559
|
+
if (tab === 'signin') {
|
|
560
|
+
const { error } = await authClient.signIn.email(value);
|
|
561
|
+
if (error) setError(error.message ?? 'Sign in failed');
|
|
562
|
+
} else {
|
|
563
|
+
const { error } = await authClient.signUp.email(value);
|
|
564
|
+
if (error) setError(error.message ?? 'Sign up failed');
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
return (
|
|
570
|
+
<>
|
|
571
|
+
<div className="flex gap-2 mb-6 text-sm">
|
|
572
|
+
<button
|
|
573
|
+
onClick={() => setTab('signin')}
|
|
574
|
+
className={
|
|
575
|
+
tab === 'signin'
|
|
576
|
+
? 'font-semibold'
|
|
577
|
+
: 'text-gray-500 hover:text-gray-700'
|
|
578
|
+
}
|
|
579
|
+
>
|
|
580
|
+
Sign in
|
|
581
|
+
</button>
|
|
582
|
+
<span className="text-gray-200">|</span>
|
|
583
|
+
<button
|
|
584
|
+
onClick={() => setTab('signup')}
|
|
585
|
+
className={
|
|
586
|
+
tab === 'signup'
|
|
587
|
+
? 'font-semibold'
|
|
588
|
+
: 'text-gray-500 hover:text-gray-700'
|
|
589
|
+
}
|
|
590
|
+
>
|
|
591
|
+
Sign up
|
|
592
|
+
</button>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<form
|
|
596
|
+
onSubmit={(e) => {
|
|
597
|
+
e.preventDefault();
|
|
598
|
+
form.handleSubmit();
|
|
599
|
+
}}
|
|
600
|
+
className="space-y-4"
|
|
601
|
+
>
|
|
602
|
+
{tab === 'signup' && (
|
|
603
|
+
<form.Field name="name">
|
|
604
|
+
{(field) => (
|
|
605
|
+
<div className="space-y-1">
|
|
606
|
+
<label className="text-sm font-medium">Name</label>
|
|
607
|
+
<input
|
|
608
|
+
type="text"
|
|
609
|
+
value={field.state.value}
|
|
610
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
611
|
+
required
|
|
612
|
+
className="w-full border border-gray-200 rounded px-3 py-2 text-sm"
|
|
613
|
+
/>
|
|
614
|
+
</div>
|
|
615
|
+
)}
|
|
616
|
+
</form.Field>
|
|
617
|
+
)}
|
|
618
|
+
|
|
619
|
+
<form.Field name="email">
|
|
620
|
+
{(field) => (
|
|
621
|
+
<div className="space-y-1">
|
|
622
|
+
<label className="text-sm font-medium">Email</label>
|
|
623
|
+
<input
|
|
624
|
+
type="email"
|
|
625
|
+
value={field.state.value}
|
|
626
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
627
|
+
required
|
|
628
|
+
className="w-full border border-gray-200 rounded px-3 py-2 text-sm"
|
|
629
|
+
/>
|
|
630
|
+
</div>
|
|
631
|
+
)}
|
|
632
|
+
</form.Field>
|
|
633
|
+
|
|
634
|
+
<form.Field name="password">
|
|
635
|
+
{(field) => (
|
|
636
|
+
<div className="space-y-1">
|
|
637
|
+
<label className="text-sm font-medium">Password</label>
|
|
638
|
+
<input
|
|
639
|
+
type="password"
|
|
640
|
+
value={field.state.value}
|
|
641
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
642
|
+
required
|
|
643
|
+
minLength={8}
|
|
644
|
+
className="w-full border border-gray-200 rounded px-3 py-2 text-sm"
|
|
645
|
+
/>
|
|
646
|
+
</div>
|
|
647
|
+
)}
|
|
648
|
+
</form.Field>
|
|
649
|
+
|
|
650
|
+
{error && <p className="text-sm text-red-500">{error}</p>}
|
|
651
|
+
|
|
652
|
+
<form.Subscribe selector={(s) => s.isSubmitting}>
|
|
653
|
+
{(isSubmitting) => (
|
|
654
|
+
<button
|
|
655
|
+
type="submit"
|
|
656
|
+
disabled={isSubmitting}
|
|
657
|
+
className="w-full bg-gray-900 text-white rounded py-2 text-sm font-medium hover:bg-gray-700 disabled:opacity-40"
|
|
658
|
+
>
|
|
659
|
+
{isSubmitting
|
|
660
|
+
? 'Loading\u2026'
|
|
661
|
+
: tab === 'signin'
|
|
662
|
+
? 'Sign in'
|
|
663
|
+
: 'Create account'}
|
|
664
|
+
</button>
|
|
665
|
+
)}
|
|
666
|
+
</form.Subscribe>
|
|
667
|
+
</form>
|
|
668
|
+
</>
|
|
669
|
+
);
|
|
670
|
+
}`
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// src/web/templates/todo-list.ts
|
|
674
|
+
var todo_list_default = {
|
|
675
|
+
filename: "apps/web/app/todo-list.tsx",
|
|
676
|
+
template: `'use client';
|
|
677
|
+
|
|
678
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
679
|
+
import { useForm } from '@tanstack/react-form';
|
|
680
|
+
import { authClient } from '../lib/auth-client';
|
|
681
|
+
|
|
682
|
+
interface Todo {
|
|
683
|
+
id: number;
|
|
684
|
+
title: string;
|
|
685
|
+
done: boolean;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const api = {
|
|
689
|
+
getTodos: (): Promise<Todo[]> => fetch('/api/todos').then((r) => r.json()),
|
|
690
|
+
createTodo: (title: string): Promise<Todo> =>
|
|
691
|
+
fetch('/api/todos', {
|
|
692
|
+
method: 'POST',
|
|
693
|
+
headers: { 'Content-Type': 'application/json' },
|
|
694
|
+
body: JSON.stringify({ title }),
|
|
695
|
+
}).then((r) => r.json()),
|
|
696
|
+
toggleTodo: (id: number): Promise<Todo> =>
|
|
697
|
+
fetch(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }).then((r) => r.json()),
|
|
698
|
+
deleteTodo: (id: number): Promise<void> =>
|
|
699
|
+
fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
export function TodoList({
|
|
703
|
+
session,
|
|
704
|
+
}: {
|
|
705
|
+
session: { user: { email: string } };
|
|
706
|
+
}) {
|
|
707
|
+
const queryClient = useQueryClient();
|
|
708
|
+
|
|
709
|
+
const { data: todos = [], isPending } = useQuery({
|
|
710
|
+
queryKey: ['todos'],
|
|
711
|
+
queryFn: api.getTodos,
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const createMutation = useMutation({
|
|
715
|
+
mutationFn: api.createTodo,
|
|
716
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
const toggleMutation = useMutation({
|
|
720
|
+
mutationFn: api.toggleTodo,
|
|
721
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
const deleteMutation = useMutation({
|
|
725
|
+
mutationFn: api.deleteTodo,
|
|
726
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
const form = useForm({
|
|
730
|
+
defaultValues: { title: '' },
|
|
731
|
+
onSubmit: async ({ value }) => {
|
|
732
|
+
if (!value.title.trim()) return;
|
|
733
|
+
createMutation.mutate(value.title.trim());
|
|
734
|
+
form.reset();
|
|
735
|
+
},
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
return (
|
|
739
|
+
<>
|
|
740
|
+
<div className="flex items-center justify-between mb-8">
|
|
741
|
+
<h1 className="text-2xl font-semibold">Todos</h1>
|
|
742
|
+
<div className="flex items-center gap-3 text-sm text-gray-500">
|
|
743
|
+
<span>{session.user.email}</span>
|
|
744
|
+
<button
|
|
745
|
+
className="hover:text-gray-900"
|
|
746
|
+
onClick={() => authClient.signOut()}
|
|
747
|
+
>
|
|
748
|
+
Sign out
|
|
749
|
+
</button>
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
|
|
753
|
+
<form
|
|
754
|
+
onSubmit={(e) => {
|
|
755
|
+
e.preventDefault();
|
|
756
|
+
form.handleSubmit();
|
|
757
|
+
}}
|
|
758
|
+
className="flex gap-2 mb-6"
|
|
759
|
+
>
|
|
760
|
+
<form.Field name="title">
|
|
761
|
+
{(field) => (
|
|
762
|
+
<input
|
|
763
|
+
value={field.state.value}
|
|
764
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
765
|
+
placeholder="New todo\u2026"
|
|
766
|
+
className="flex-1 border border-gray-200 rounded px-3 py-2 text-sm"
|
|
767
|
+
/>
|
|
768
|
+
)}
|
|
769
|
+
</form.Field>
|
|
770
|
+
|
|
771
|
+
<form.Subscribe selector={(s) => s.isSubmitting}>
|
|
772
|
+
{(isSubmitting) => (
|
|
773
|
+
<button
|
|
774
|
+
type="submit"
|
|
775
|
+
disabled={isSubmitting || createMutation.isPending}
|
|
776
|
+
className="bg-gray-900 text-white rounded px-4 py-2 text-sm hover:bg-gray-700 disabled:opacity-40"
|
|
777
|
+
>
|
|
778
|
+
Add
|
|
779
|
+
</button>
|
|
780
|
+
)}
|
|
781
|
+
</form.Subscribe>
|
|
782
|
+
</form>
|
|
783
|
+
|
|
784
|
+
{isPending ? (
|
|
785
|
+
<p className="text-sm text-gray-500">Loading\u2026</p>
|
|
786
|
+
) : (
|
|
787
|
+
<ul className="divide-y divide-gray-100">
|
|
788
|
+
{todos.map((todo) => (
|
|
789
|
+
<li key={todo.id} className="flex items-center gap-3 py-3">
|
|
790
|
+
<input
|
|
791
|
+
type="checkbox"
|
|
792
|
+
checked={todo.done}
|
|
793
|
+
onChange={() => toggleMutation.mutate(todo.id)}
|
|
794
|
+
/>
|
|
795
|
+
<span
|
|
796
|
+
className={\`flex-1 text-sm \${todo.done ? 'line-through text-gray-500' : ''}\`}
|
|
797
|
+
>
|
|
798
|
+
{todo.title}
|
|
799
|
+
</span>
|
|
800
|
+
<button
|
|
801
|
+
onClick={() => deleteMutation.mutate(todo.id)}
|
|
802
|
+
className="text-gray-300 hover:text-red-400 text-lg leading-none"
|
|
803
|
+
>
|
|
804
|
+
\xD7
|
|
805
|
+
</button>
|
|
806
|
+
</li>
|
|
807
|
+
))}
|
|
808
|
+
</ul>
|
|
809
|
+
)}
|
|
810
|
+
|
|
811
|
+
{!isPending && todos.length === 0 && (
|
|
812
|
+
<p className="text-sm text-gray-500">No todos yet.</p>
|
|
813
|
+
)}
|
|
814
|
+
</>
|
|
815
|
+
);
|
|
816
|
+
}`
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
// src/web/templates/auth-route.ts
|
|
820
|
+
var auth_route_default = {
|
|
821
|
+
filename: "apps/web/app/api/auth/[...all]/route.ts",
|
|
822
|
+
template: `import { auth } from "@repo/auth";
|
|
823
|
+
import { toNextJsHandler } from "better-auth/next-js";
|
|
824
|
+
|
|
825
|
+
export const { GET, POST } = toNextJsHandler(auth);`
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// src/web/templates/auth-client.ts
|
|
829
|
+
var auth_client_default = {
|
|
830
|
+
filename: "apps/web/lib/auth-client.ts",
|
|
831
|
+
template: `import { createAuthClient } from "better-auth/react";
|
|
832
|
+
|
|
833
|
+
export const authClient: ReturnType<typeof createAuthClient> = createAuthClient();`
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// src/web/index.ts
|
|
837
|
+
var web = {
|
|
838
|
+
templates: [
|
|
839
|
+
package_json_default2,
|
|
840
|
+
gitignore_default2,
|
|
841
|
+
readme_default2,
|
|
842
|
+
eslint_config_default,
|
|
843
|
+
next_config_default,
|
|
844
|
+
postcss_config_default,
|
|
845
|
+
tsconfig_default,
|
|
846
|
+
globals_css_default,
|
|
847
|
+
layout_default,
|
|
848
|
+
page_default,
|
|
849
|
+
providers_default,
|
|
850
|
+
auth_form_default,
|
|
851
|
+
todo_list_default,
|
|
852
|
+
auth_route_default,
|
|
853
|
+
auth_client_default
|
|
854
|
+
],
|
|
855
|
+
staticFiles: [
|
|
856
|
+
{ src: "web/static/fonts/GeistVF.woff", filename: "apps/web/app/fonts/GeistVF.woff" },
|
|
857
|
+
{ src: "web/static/fonts/GeistMonoVF.woff", filename: "apps/web/app/fonts/GeistMonoVF.woff" },
|
|
858
|
+
{ src: "web/static/favicon.ico", filename: "apps/web/app/favicon.ico" },
|
|
859
|
+
{ src: "web/static/public/file-text.svg", filename: "apps/web/public/file-text.svg" },
|
|
860
|
+
{ src: "web/static/public/globe.svg", filename: "apps/web/public/globe.svg" },
|
|
861
|
+
{ src: "web/static/public/next.svg", filename: "apps/web/public/next.svg" },
|
|
862
|
+
{ src: "web/static/public/turborepo-dark.svg", filename: "apps/web/public/turborepo-dark.svg" },
|
|
863
|
+
{ src: "web/static/public/turborepo-light.svg", filename: "apps/web/public/turborepo-light.svg" },
|
|
864
|
+
{ src: "web/static/public/vercel.svg", filename: "apps/web/public/vercel.svg" },
|
|
865
|
+
{ src: "web/static/public/window.svg", filename: "apps/web/public/window.svg" }
|
|
866
|
+
]
|
|
867
|
+
};
|
|
868
|
+
var web_default = web;
|
|
869
|
+
|
|
870
|
+
// src/api/templates/package-json.ts
|
|
871
|
+
var package_json_default3 = {
|
|
872
|
+
filename: "apps/api/package.json",
|
|
873
|
+
template: `{
|
|
874
|
+
"name": "api",
|
|
875
|
+
"version": "0.0.1",
|
|
876
|
+
"description": "",
|
|
877
|
+
"author": "",
|
|
878
|
+
"private": true,
|
|
879
|
+
"license": "UNLICENSED",
|
|
880
|
+
"scripts": {
|
|
881
|
+
"build": "nest build",
|
|
882
|
+
"start": "nest start",
|
|
883
|
+
"dev": "nest start --watch",
|
|
884
|
+
"start:dev": "nest start --watch",
|
|
885
|
+
"start:debug": "nest start --debug --watch",
|
|
886
|
+
"start:prod": "node dist/main",
|
|
887
|
+
"lint": "eslint \\"{src,apps,libs,test}/**/*.ts\\" --fix",
|
|
888
|
+
"test": "jest",
|
|
889
|
+
"test:watch": "jest --watch",
|
|
890
|
+
"test:cov": "jest --coverage",
|
|
891
|
+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
892
|
+
"test:e2e": "jest --config ./test/jest-e2e.json"
|
|
893
|
+
},
|
|
894
|
+
"dependencies": {
|
|
895
|
+
"@nestjs/common": "^11.0.1",
|
|
896
|
+
"@nestjs/core": "^11.0.1",
|
|
897
|
+
"@nestjs/platform-express": "^11.0.1",
|
|
898
|
+
"@repo/auth": "workspace:*",
|
|
899
|
+
"@thallesp/nestjs-better-auth": "^2.5.1",
|
|
900
|
+
"dotenv": "^17.3.1",
|
|
901
|
+
"reflect-metadata": "^0.2.2",
|
|
902
|
+
"rxjs": "^7.8.1"
|
|
903
|
+
},
|
|
904
|
+
"devDependencies": {
|
|
905
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
906
|
+
"@eslint/js": "^9.18.0",
|
|
907
|
+
"@nestjs/cli": "^11.0.0",
|
|
908
|
+
"@nestjs/schematics": "^11.0.0",
|
|
909
|
+
"@nestjs/testing": "^11.0.1",
|
|
910
|
+
"@types/express": "^5.0.0",
|
|
911
|
+
"@types/jest": "^30.0.0",
|
|
912
|
+
"@types/node": "^22.10.7",
|
|
913
|
+
"@types/supertest": "^6.0.2",
|
|
914
|
+
"eslint": "^9.18.0",
|
|
915
|
+
"eslint-config-prettier": "^10.0.1",
|
|
916
|
+
"globals": "^16.0.0",
|
|
917
|
+
"jest": "^30.0.0",
|
|
918
|
+
"source-map-support": "^0.5.21",
|
|
919
|
+
"supertest": "^7.0.0",
|
|
920
|
+
"ts-jest": "^29.2.5",
|
|
921
|
+
"ts-loader": "^9.5.2",
|
|
922
|
+
"ts-node": "^10.9.2",
|
|
923
|
+
"tsconfig-paths": "^4.2.0",
|
|
924
|
+
"typescript": "^5.7.3",
|
|
925
|
+
"typescript-eslint": "^8.20.0"
|
|
926
|
+
},
|
|
927
|
+
"jest": {
|
|
928
|
+
"moduleFileExtensions": ["js", "json", "ts"],
|
|
929
|
+
"rootDir": "src",
|
|
930
|
+
"testRegex": ".*\\\\.spec\\\\.ts$",
|
|
931
|
+
"transform": {
|
|
932
|
+
"^.+\\\\.(t|j)s$": "ts-jest"
|
|
933
|
+
},
|
|
934
|
+
"collectCoverageFrom": ["**/*.(t|j)s"],
|
|
935
|
+
"coverageDirectory": "../coverage",
|
|
936
|
+
"testEnvironment": "node"
|
|
937
|
+
}
|
|
938
|
+
}`
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// src/api/templates/eslint-config.ts
|
|
942
|
+
var eslint_config_default2 = {
|
|
943
|
+
filename: "apps/api/eslint.config.mjs",
|
|
944
|
+
template: `// @ts-check
|
|
945
|
+
import eslint from '@eslint/js';
|
|
946
|
+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
947
|
+
import globals from 'globals';
|
|
948
|
+
import tseslint from 'typescript-eslint';
|
|
949
|
+
|
|
950
|
+
export default tseslint.config(
|
|
951
|
+
{
|
|
952
|
+
ignores: ['eslint.config.mjs'],
|
|
953
|
+
},
|
|
954
|
+
eslint.configs.recommended,
|
|
955
|
+
...tseslint.configs.recommendedTypeChecked,
|
|
956
|
+
eslintPluginPrettierRecommended,
|
|
957
|
+
{
|
|
958
|
+
languageOptions: {
|
|
959
|
+
globals: {
|
|
960
|
+
...globals.node,
|
|
961
|
+
...globals.jest,
|
|
962
|
+
},
|
|
963
|
+
sourceType: 'commonjs',
|
|
964
|
+
parserOptions: {
|
|
965
|
+
projectService: true,
|
|
966
|
+
tsconfigRootDir: import.meta.dirname,
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
rules: {
|
|
972
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
973
|
+
'@typescript-eslint/no-floating-promises': 'warn',
|
|
974
|
+
'@typescript-eslint/no-unsafe-argument': 'warn',
|
|
975
|
+
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
|
976
|
+
},
|
|
977
|
+
},
|
|
978
|
+
);`
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
// src/api/templates/nest-cli.ts
|
|
982
|
+
var nest_cli_default = {
|
|
983
|
+
filename: "apps/api/nest-cli.json",
|
|
984
|
+
template: `{
|
|
985
|
+
"$schema": "https://json.schemastore.org/nest-cli",
|
|
986
|
+
"collection": "@nestjs/schematics",
|
|
987
|
+
"sourceRoot": "src",
|
|
988
|
+
"compilerOptions": {
|
|
989
|
+
"deleteOutDir": true
|
|
990
|
+
}
|
|
991
|
+
}`
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
// src/api/templates/tsconfig.ts
|
|
995
|
+
var tsconfig_default2 = {
|
|
996
|
+
filename: "apps/api/tsconfig.json",
|
|
997
|
+
template: `{
|
|
998
|
+
"compilerOptions": {
|
|
999
|
+
"module": "nodenext",
|
|
1000
|
+
"moduleResolution": "nodenext",
|
|
1001
|
+
"resolvePackageJsonExports": true,
|
|
1002
|
+
"esModuleInterop": true,
|
|
1003
|
+
"isolatedModules": true,
|
|
1004
|
+
"declaration": true,
|
|
1005
|
+
"removeComments": true,
|
|
1006
|
+
"emitDecoratorMetadata": true,
|
|
1007
|
+
"experimentalDecorators": true,
|
|
1008
|
+
"allowSyntheticDefaultImports": true,
|
|
1009
|
+
"target": "ES2023",
|
|
1010
|
+
"sourceMap": true,
|
|
1011
|
+
"outDir": "./dist",
|
|
1012
|
+
"baseUrl": "./",
|
|
1013
|
+
"incremental": true,
|
|
1014
|
+
"skipLibCheck": true,
|
|
1015
|
+
"strictNullChecks": true,
|
|
1016
|
+
"forceConsistentCasingInFileNames": true,
|
|
1017
|
+
"noImplicitAny": false,
|
|
1018
|
+
"strictBindCallApply": false,
|
|
1019
|
+
"noFallthroughCasesInSwitch": false
|
|
1020
|
+
}
|
|
1021
|
+
}`
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// src/api/templates/tsconfig-build.ts
|
|
1025
|
+
var tsconfig_build_default = {
|
|
1026
|
+
filename: "apps/api/tsconfig.build.json",
|
|
1027
|
+
template: `{
|
|
1028
|
+
"extends": "./tsconfig.json",
|
|
1029
|
+
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
|
1030
|
+
}`
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
// src/api/templates/app-controller.ts
|
|
1034
|
+
var app_controller_default = {
|
|
1035
|
+
filename: "apps/api/src/app.controller.ts",
|
|
1036
|
+
template: `import { Controller, Get } from '@nestjs/common';
|
|
1037
|
+
import {
|
|
1038
|
+
AllowAnonymous,
|
|
1039
|
+
Session,
|
|
1040
|
+
UserSession,
|
|
1041
|
+
} from '@thallesp/nestjs-better-auth';
|
|
1042
|
+
|
|
1043
|
+
@Controller()
|
|
1044
|
+
export class AppController {
|
|
1045
|
+
@AllowAnonymous()
|
|
1046
|
+
@Get('hello')
|
|
1047
|
+
getHello(@Session() session: UserSession | null) {
|
|
1048
|
+
if (session?.user.name) {
|
|
1049
|
+
return { message: \`Hello \${session.user.name}\` };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return { message: 'Hello from Nest' };
|
|
1053
|
+
}
|
|
1054
|
+
}`
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
// src/api/templates/app-controller-spec.ts
|
|
1058
|
+
var app_controller_spec_default = {
|
|
1059
|
+
filename: "apps/api/src/app.controller.spec.ts",
|
|
1060
|
+
template: `import { Test, TestingModule } from '@nestjs/testing';
|
|
1061
|
+
import { AppController } from './app.controller';
|
|
1062
|
+
|
|
1063
|
+
describe('AppController', () => {
|
|
1064
|
+
let appController: AppController;
|
|
1065
|
+
|
|
1066
|
+
beforeEach(async () => {
|
|
1067
|
+
const app: TestingModule = await Test.createTestingModule({
|
|
1068
|
+
controllers: [AppController],
|
|
1069
|
+
}).compile();
|
|
1070
|
+
|
|
1071
|
+
appController = app.get<AppController>(AppController);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it('should be defined', () => {
|
|
1075
|
+
expect(appController).toBeDefined();
|
|
1076
|
+
});
|
|
1077
|
+
});`
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
// src/api/templates/app-module.ts
|
|
1081
|
+
var app_module_default = {
|
|
1082
|
+
filename: "apps/api/src/app.module.ts",
|
|
1083
|
+
template: `import { Module } from '@nestjs/common';
|
|
1084
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
1085
|
+
import { AuthGuard, AuthModule } from '@thallesp/nestjs-better-auth';
|
|
1086
|
+
import { auth } from '@repo/auth';
|
|
1087
|
+
import { AppController } from './app.controller';
|
|
1088
|
+
import { TodosModule } from './todos/todos.module';
|
|
1089
|
+
|
|
1090
|
+
@Module({
|
|
1091
|
+
imports: [AuthModule.forRoot({ auth }), TodosModule],
|
|
1092
|
+
controllers: [AppController],
|
|
1093
|
+
providers: [{ provide: APP_GUARD, useClass: AuthGuard }],
|
|
1094
|
+
})
|
|
1095
|
+
export class AppModule {}`
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
// src/api/templates/main.ts
|
|
1099
|
+
var main_default = {
|
|
1100
|
+
filename: "apps/api/src/main.ts",
|
|
1101
|
+
template: `import 'dotenv/config';
|
|
1102
|
+
import { NestFactory } from '@nestjs/core';
|
|
1103
|
+
import { AppModule } from './app.module';
|
|
1104
|
+
|
|
1105
|
+
async function bootstrap() {
|
|
1106
|
+
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
|
1107
|
+
app.setGlobalPrefix('api');
|
|
1108
|
+
app.enableCors({ origin: 'http://localhost:3000', credentials: true });
|
|
1109
|
+
await app.listen(process.env.PORT ?? 3001);
|
|
1110
|
+
}
|
|
1111
|
+
bootstrap();`
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
// src/api/templates/todos-controller.ts
|
|
1115
|
+
var todos_controller_default = {
|
|
1116
|
+
filename: "apps/api/src/todos/todos.controller.ts",
|
|
1117
|
+
template: `import {
|
|
1118
|
+
Body,
|
|
1119
|
+
Controller,
|
|
1120
|
+
Delete,
|
|
1121
|
+
Get,
|
|
1122
|
+
Param,
|
|
1123
|
+
Patch,
|
|
1124
|
+
Post,
|
|
1125
|
+
} from '@nestjs/common';
|
|
1126
|
+
import { TodosService } from './todos.service';
|
|
1127
|
+
|
|
1128
|
+
@Controller('todos')
|
|
1129
|
+
export class TodosController {
|
|
1130
|
+
constructor(private readonly todosService: TodosService) {}
|
|
1131
|
+
|
|
1132
|
+
@Get()
|
|
1133
|
+
findAll() {
|
|
1134
|
+
return this.todosService.findAll();
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
@Post()
|
|
1138
|
+
create(@Body('title') title: string) {
|
|
1139
|
+
return this.todosService.create(title);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
@Patch(':id/toggle')
|
|
1143
|
+
toggle(@Param('id') id: string) {
|
|
1144
|
+
return this.todosService.toggle(Number(id));
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
@Delete(':id')
|
|
1148
|
+
remove(@Param('id') id: string) {
|
|
1149
|
+
return this.todosService.remove(Number(id));
|
|
1150
|
+
}
|
|
1151
|
+
}`
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// src/api/templates/todos-module.ts
|
|
1155
|
+
var todos_module_default = {
|
|
1156
|
+
filename: "apps/api/src/todos/todos.module.ts",
|
|
1157
|
+
template: `import { Module } from '@nestjs/common';
|
|
1158
|
+
import { TodosController } from './todos.controller';
|
|
1159
|
+
import { TodosService } from './todos.service';
|
|
1160
|
+
|
|
1161
|
+
@Module({
|
|
1162
|
+
controllers: [TodosController],
|
|
1163
|
+
providers: [TodosService],
|
|
1164
|
+
})
|
|
1165
|
+
export class TodosModule {}`
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
// src/api/templates/todos-service.ts
|
|
1169
|
+
var todos_service_default = {
|
|
1170
|
+
filename: "apps/api/src/todos/todos.service.ts",
|
|
1171
|
+
template: `import { Injectable, NotFoundException } from '@nestjs/common';
|
|
1172
|
+
|
|
1173
|
+
export interface Todo {
|
|
1174
|
+
id: number;
|
|
1175
|
+
title: string;
|
|
1176
|
+
done: boolean;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
@Injectable()
|
|
1180
|
+
export class TodosService {
|
|
1181
|
+
private todos: Todo[] = [];
|
|
1182
|
+
private nextId = 1;
|
|
1183
|
+
|
|
1184
|
+
findAll(): Todo[] {
|
|
1185
|
+
return this.todos;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
create(title: string): Todo {
|
|
1189
|
+
const todo: Todo = { id: this.nextId++, title, done: false };
|
|
1190
|
+
this.todos.push(todo);
|
|
1191
|
+
return todo;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
toggle(id: number): Todo {
|
|
1195
|
+
const todo = this.todos.find((t) => t.id === id);
|
|
1196
|
+
if (!todo) throw new NotFoundException(\`Todo \${id} not found\`);
|
|
1197
|
+
todo.done = !todo.done;
|
|
1198
|
+
return todo;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
remove(id: number): void {
|
|
1202
|
+
const index = this.todos.findIndex((t) => t.id === id);
|
|
1203
|
+
if (index === -1) throw new NotFoundException(\`Todo \${id} not found\`);
|
|
1204
|
+
this.todos.splice(index, 1);
|
|
1205
|
+
}
|
|
1206
|
+
}`
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// src/api/templates/e2e-spec.ts
|
|
1210
|
+
var e2e_spec_default = {
|
|
1211
|
+
filename: "apps/api/test/app.e2e-spec.ts",
|
|
1212
|
+
template: `import { Test, TestingModule } from '@nestjs/testing';
|
|
1213
|
+
import { INestApplication } from '@nestjs/common';
|
|
1214
|
+
import request from 'supertest';
|
|
1215
|
+
import { App } from 'supertest/types';
|
|
1216
|
+
import { AppModule } from './../src/app.module';
|
|
1217
|
+
|
|
1218
|
+
describe('AppController (e2e)', () => {
|
|
1219
|
+
let app: INestApplication<App>;
|
|
1220
|
+
|
|
1221
|
+
beforeEach(async () => {
|
|
1222
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
1223
|
+
imports: [AppModule],
|
|
1224
|
+
}).compile();
|
|
1225
|
+
|
|
1226
|
+
app = moduleFixture.createNestApplication();
|
|
1227
|
+
await app.init();
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
it('/ (GET)', () => {
|
|
1231
|
+
return request(app.getHttpServer())
|
|
1232
|
+
.get('/')
|
|
1233
|
+
.expect(200)
|
|
1234
|
+
.expect('Hello World!');
|
|
1235
|
+
});
|
|
1236
|
+
});`
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
// src/api/templates/jest-e2e.ts
|
|
1240
|
+
var jest_e2e_default = {
|
|
1241
|
+
filename: "apps/api/test/jest-e2e.json",
|
|
1242
|
+
template: `{
|
|
1243
|
+
"moduleFileExtensions": ["js", "json", "ts"],
|
|
1244
|
+
"rootDir": ".",
|
|
1245
|
+
"testEnvironment": "node",
|
|
1246
|
+
"testRegex": ".e2e-spec.ts$",
|
|
1247
|
+
"transform": {
|
|
1248
|
+
"^.+\\\\.(t|j)s$": "ts-jest"
|
|
1249
|
+
}
|
|
1250
|
+
}`
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
// src/api/templates/readme.ts
|
|
1254
|
+
var readme_default3 = {
|
|
1255
|
+
filename: "apps/api/README.md",
|
|
1256
|
+
template: `# API
|
|
1257
|
+
|
|
1258
|
+
NestJS backend for the monorepo.
|
|
1259
|
+
|
|
1260
|
+
## Scripts
|
|
1261
|
+
|
|
1262
|
+
\`\`\`sh
|
|
1263
|
+
pnpm dev # watch mode
|
|
1264
|
+
pnpm build # production build
|
|
1265
|
+
pnpm test # unit tests
|
|
1266
|
+
pnpm test:e2e # e2e tests
|
|
1267
|
+
\`\`\``
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// src/api/index.ts
|
|
1271
|
+
var api = {
|
|
1272
|
+
templates: [
|
|
1273
|
+
package_json_default3,
|
|
1274
|
+
eslint_config_default2,
|
|
1275
|
+
nest_cli_default,
|
|
1276
|
+
tsconfig_default2,
|
|
1277
|
+
tsconfig_build_default,
|
|
1278
|
+
app_controller_default,
|
|
1279
|
+
app_controller_spec_default,
|
|
1280
|
+
app_module_default,
|
|
1281
|
+
main_default,
|
|
1282
|
+
todos_controller_default,
|
|
1283
|
+
todos_module_default,
|
|
1284
|
+
todos_service_default,
|
|
1285
|
+
e2e_spec_default,
|
|
1286
|
+
jest_e2e_default,
|
|
1287
|
+
readme_default3
|
|
1288
|
+
]
|
|
1289
|
+
};
|
|
1290
|
+
var api_default = api;
|
|
1291
|
+
|
|
1292
|
+
// src/auth/templates/package-json.ts
|
|
1293
|
+
var package_json_default4 = {
|
|
1294
|
+
filename: "packages/auth/package.json",
|
|
1295
|
+
template: `{
|
|
1296
|
+
"name": "@repo/auth",
|
|
1297
|
+
"version": "0.0.0",
|
|
1298
|
+
"private": true,
|
|
1299
|
+
"exports": {
|
|
1300
|
+
".": "./src/index.ts"
|
|
1301
|
+
},
|
|
1302
|
+
"scripts": {
|
|
1303
|
+
"migrate": "dotenv -e .env -- auth migrate --config src/index.ts",
|
|
1304
|
+
"generate": "dotenv -e .env -- auth generate --config src/index.ts"
|
|
1305
|
+
},
|
|
1306
|
+
"dependencies": {
|
|
1307
|
+
"better-auth": "^1.2.8",
|
|
1308
|
+
"pg": "^8.14.1"
|
|
1309
|
+
},
|
|
1310
|
+
"devDependencies": {
|
|
1311
|
+
"@repo/typescript-config": "workspace:*",
|
|
1312
|
+
"@types/pg": "^8.11.13",
|
|
1313
|
+
"auth": "^1.5.5",
|
|
1314
|
+
"dotenv-cli": "^11.0.0",
|
|
1315
|
+
"typescript": "5.9.2"
|
|
1316
|
+
}
|
|
1317
|
+
}`
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
// src/auth/templates/src-index.ts
|
|
1321
|
+
var src_index_default = {
|
|
1322
|
+
filename: "packages/auth/src/index.ts",
|
|
1323
|
+
template: `import { betterAuth } from "better-auth";
|
|
1324
|
+
import { Pool } from "pg";
|
|
1325
|
+
|
|
1326
|
+
export const auth = betterAuth({
|
|
1327
|
+
database: new Pool({
|
|
1328
|
+
connectionString: process.env.DATABASE_URL,
|
|
1329
|
+
}),
|
|
1330
|
+
emailAndPassword: { enabled: true },
|
|
1331
|
+
trustedOrigins: [process.env.BETTER_AUTH_URL ?? "http://localhost:3000"],
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
export type Auth = typeof auth;`
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
// src/auth/templates/tsconfig.ts
|
|
1338
|
+
var tsconfig_default3 = {
|
|
1339
|
+
filename: "packages/auth/tsconfig.json",
|
|
1340
|
+
template: `{
|
|
1341
|
+
"extends": "@repo/typescript-config/base.json",
|
|
1342
|
+
"compilerOptions": {
|
|
1343
|
+
"outDir": "dist",
|
|
1344
|
+
"strictNullChecks": true
|
|
1345
|
+
},
|
|
1346
|
+
"include": ["src"],
|
|
1347
|
+
"exclude": ["node_modules", "dist"]
|
|
1348
|
+
}`
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
// src/auth/index.ts
|
|
1352
|
+
var auth = {
|
|
1353
|
+
templates: [package_json_default4, src_index_default, tsconfig_default3]
|
|
1354
|
+
};
|
|
1355
|
+
var auth_default = auth;
|
|
1356
|
+
|
|
1357
|
+
// src/ui/templates/package-json.ts
|
|
1358
|
+
var package_json_default5 = {
|
|
1359
|
+
filename: "packages/ui/package.json",
|
|
1360
|
+
template: `{
|
|
1361
|
+
"name": "@repo/ui",
|
|
1362
|
+
"version": "0.0.0",
|
|
1363
|
+
"private": true,
|
|
1364
|
+
"exports": {
|
|
1365
|
+
"./*": "./src/*.tsx"
|
|
1366
|
+
},
|
|
1367
|
+
"scripts": {
|
|
1368
|
+
"lint": "eslint . --max-warnings 0",
|
|
1369
|
+
"check-types": "tsc --noEmit"
|
|
1370
|
+
},
|
|
1371
|
+
"devDependencies": {
|
|
1372
|
+
"@repo/eslint-config": "workspace:*",
|
|
1373
|
+
"@repo/typescript-config": "workspace:*",
|
|
1374
|
+
"@types/node": "^22.15.3",
|
|
1375
|
+
"@types/react": "19.2.2",
|
|
1376
|
+
"@types/react-dom": "19.2.2",
|
|
1377
|
+
"eslint": "^9.39.1",
|
|
1378
|
+
"typescript": "5.9.2"
|
|
1379
|
+
},
|
|
1380
|
+
"dependencies": {
|
|
1381
|
+
"react": "^19.2.0",
|
|
1382
|
+
"react-dom": "^19.2.0"
|
|
1383
|
+
}
|
|
1384
|
+
}`
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
// src/ui/templates/button.ts
|
|
1388
|
+
var button_default = {
|
|
1389
|
+
filename: "packages/ui/src/button.tsx",
|
|
1390
|
+
template: `"use client";
|
|
1391
|
+
|
|
1392
|
+
import { ReactNode } from "react";
|
|
1393
|
+
|
|
1394
|
+
interface ButtonProps {
|
|
1395
|
+
children: ReactNode;
|
|
1396
|
+
className?: string;
|
|
1397
|
+
appName: string;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
export const Button = ({ children, className, appName }: ButtonProps) => {
|
|
1401
|
+
return (
|
|
1402
|
+
<button
|
|
1403
|
+
className={className}
|
|
1404
|
+
onClick={() => alert(\`Hello from your \${appName} app!\`)}
|
|
1405
|
+
>
|
|
1406
|
+
{children}
|
|
1407
|
+
</button>
|
|
1408
|
+
);
|
|
1409
|
+
};`
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
// src/ui/templates/card.ts
|
|
1413
|
+
var card_default = {
|
|
1414
|
+
filename: "packages/ui/src/card.tsx",
|
|
1415
|
+
template: `import { type JSX } from "react";
|
|
1416
|
+
|
|
1417
|
+
export function Card({
|
|
1418
|
+
className,
|
|
1419
|
+
title,
|
|
1420
|
+
children,
|
|
1421
|
+
href,
|
|
1422
|
+
}: {
|
|
1423
|
+
className?: string;
|
|
1424
|
+
title: string;
|
|
1425
|
+
children: React.ReactNode;
|
|
1426
|
+
href: string;
|
|
1427
|
+
}): JSX.Element {
|
|
1428
|
+
return (
|
|
1429
|
+
<a
|
|
1430
|
+
className={className}
|
|
1431
|
+
href={\`\${href}?utm_source=create-turbo&utm_medium=basic&utm_campaign=create-turbo"\`}
|
|
1432
|
+
rel="noopener noreferrer"
|
|
1433
|
+
target="_blank"
|
|
1434
|
+
>
|
|
1435
|
+
<h2>
|
|
1436
|
+
{title} <span>-></span>
|
|
1437
|
+
</h2>
|
|
1438
|
+
<p>{children}</p>
|
|
1439
|
+
</a>
|
|
1440
|
+
);
|
|
1441
|
+
}`
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
// src/ui/templates/code.ts
|
|
1445
|
+
var code_default = {
|
|
1446
|
+
filename: "packages/ui/src/code.tsx",
|
|
1447
|
+
template: `import { type JSX } from "react";
|
|
1448
|
+
|
|
1449
|
+
export function Code({
|
|
1450
|
+
children,
|
|
1451
|
+
className,
|
|
1452
|
+
}: {
|
|
1453
|
+
children: React.ReactNode;
|
|
1454
|
+
className?: string;
|
|
1455
|
+
}): JSX.Element {
|
|
1456
|
+
return <code className={className}>{children}</code>;
|
|
1457
|
+
}`
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1460
|
+
// src/ui/templates/link.ts
|
|
1461
|
+
var link_default = {
|
|
1462
|
+
filename: "packages/ui/src/link.tsx",
|
|
1463
|
+
template: `"use client";
|
|
1464
|
+
|
|
1465
|
+
import { AnchorHTMLAttributes } from "react";
|
|
1466
|
+
|
|
1467
|
+
export function Link({ children, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) {
|
|
1468
|
+
return (
|
|
1469
|
+
<a
|
|
1470
|
+
target="_blank"
|
|
1471
|
+
rel="noopener noreferrer"
|
|
1472
|
+
className="hover:underline underline-offset-2 hover:text-gray-900 transition-colors"
|
|
1473
|
+
{...props}
|
|
1474
|
+
>
|
|
1475
|
+
{children}
|
|
1476
|
+
</a>
|
|
1477
|
+
);
|
|
1478
|
+
}`
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
// src/ui/templates/tsconfig.ts
|
|
1482
|
+
var tsconfig_default4 = {
|
|
1483
|
+
filename: "packages/ui/tsconfig.json",
|
|
1484
|
+
template: `{
|
|
1485
|
+
"extends": "@repo/typescript-config/react-library.json",
|
|
1486
|
+
"compilerOptions": {
|
|
1487
|
+
"outDir": "dist",
|
|
1488
|
+
"strictNullChecks": true
|
|
1489
|
+
},
|
|
1490
|
+
"include": ["src"],
|
|
1491
|
+
"exclude": ["node_modules", "dist"]
|
|
1492
|
+
}`
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
// src/ui/templates/eslint-config.ts
|
|
1496
|
+
var eslint_config_default3 = {
|
|
1497
|
+
filename: "packages/ui/eslint.config.mjs",
|
|
1498
|
+
template: `import { config } from "@repo/eslint-config/react-internal";
|
|
1499
|
+
|
|
1500
|
+
/** @type {import("eslint").Linter.Config} */
|
|
1501
|
+
export default config;`
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
// src/ui/index.ts
|
|
1505
|
+
var ui = {
|
|
1506
|
+
templates: [package_json_default5, button_default, card_default, code_default, link_default, tsconfig_default4, eslint_config_default3]
|
|
1507
|
+
};
|
|
1508
|
+
var ui_default = ui;
|
|
1509
|
+
|
|
1510
|
+
// src/eslint-config/templates/package-json.ts
|
|
1511
|
+
var package_json_default6 = {
|
|
1512
|
+
filename: "packages/eslint-config/package.json",
|
|
1513
|
+
template: `{
|
|
1514
|
+
"name": "@repo/eslint-config",
|
|
1515
|
+
"version": "0.0.0",
|
|
1516
|
+
"type": "module",
|
|
1517
|
+
"private": true,
|
|
1518
|
+
"exports": {
|
|
1519
|
+
"./base": "./base.js",
|
|
1520
|
+
"./next-js": "./next.js",
|
|
1521
|
+
"./react-internal": "./react-internal.js"
|
|
1522
|
+
},
|
|
1523
|
+
"devDependencies": {
|
|
1524
|
+
"@eslint/js": "^9.39.1",
|
|
1525
|
+
"@next/eslint-plugin-next": "^15.5.0",
|
|
1526
|
+
"eslint": "^9.39.1",
|
|
1527
|
+
"eslint-config-prettier": "^10.1.1",
|
|
1528
|
+
"eslint-plugin-only-warn": "^1.1.0",
|
|
1529
|
+
"eslint-plugin-react": "^7.37.5",
|
|
1530
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
1531
|
+
"eslint-plugin-turbo": "^2.7.1",
|
|
1532
|
+
"globals": "^16.5.0",
|
|
1533
|
+
"typescript": "^5.9.2",
|
|
1534
|
+
"typescript-eslint": "^8.50.0"
|
|
1535
|
+
}
|
|
1536
|
+
}`
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
// src/eslint-config/templates/base.ts
|
|
1540
|
+
var base_default = {
|
|
1541
|
+
filename: "packages/eslint-config/base.js",
|
|
1542
|
+
template: `import js from "@eslint/js";
|
|
1543
|
+
import eslintConfigPrettier from "eslint-config-prettier";
|
|
1544
|
+
import turboPlugin from "eslint-plugin-turbo";
|
|
1545
|
+
import tseslint from "typescript-eslint";
|
|
1546
|
+
import onlyWarn from "eslint-plugin-only-warn";
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* A shared ESLint configuration for the repository.
|
|
1550
|
+
*
|
|
1551
|
+
* @type {import("eslint").Linter.Config[]}
|
|
1552
|
+
* */
|
|
1553
|
+
export const config = [
|
|
1554
|
+
js.configs.recommended,
|
|
1555
|
+
eslintConfigPrettier,
|
|
1556
|
+
...tseslint.configs.recommended,
|
|
1557
|
+
{
|
|
1558
|
+
plugins: {
|
|
1559
|
+
turbo: turboPlugin,
|
|
1560
|
+
},
|
|
1561
|
+
rules: {
|
|
1562
|
+
"turbo/no-undeclared-env-vars": "warn",
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
plugins: {
|
|
1567
|
+
onlyWarn,
|
|
1568
|
+
},
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
ignores: ["dist/**"],
|
|
1572
|
+
},
|
|
1573
|
+
];`
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
// src/eslint-config/templates/next.ts
|
|
1577
|
+
var next_default = {
|
|
1578
|
+
filename: "packages/eslint-config/next.js",
|
|
1579
|
+
template: `import js from "@eslint/js";
|
|
1580
|
+
import { globalIgnores } from "eslint/config";
|
|
1581
|
+
import eslintConfigPrettier from "eslint-config-prettier";
|
|
1582
|
+
import tseslint from "typescript-eslint";
|
|
1583
|
+
import pluginReactHooks from "eslint-plugin-react-hooks";
|
|
1584
|
+
import pluginReact from "eslint-plugin-react";
|
|
1585
|
+
import globals from "globals";
|
|
1586
|
+
import pluginNext from "@next/eslint-plugin-next";
|
|
1587
|
+
import { config as baseConfig } from "./base.js";
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* A custom ESLint configuration for libraries that use Next.js.
|
|
1591
|
+
*
|
|
1592
|
+
* @type {import("eslint").Linter.Config[]}
|
|
1593
|
+
* */
|
|
1594
|
+
export const nextJsConfig = [
|
|
1595
|
+
...baseConfig,
|
|
1596
|
+
js.configs.recommended,
|
|
1597
|
+
eslintConfigPrettier,
|
|
1598
|
+
...tseslint.configs.recommended,
|
|
1599
|
+
globalIgnores([
|
|
1600
|
+
// Default ignores of eslint-config-next:
|
|
1601
|
+
".next/**",
|
|
1602
|
+
"out/**",
|
|
1603
|
+
"build/**",
|
|
1604
|
+
"next-env.d.ts",
|
|
1605
|
+
]),
|
|
1606
|
+
{
|
|
1607
|
+
...pluginReact.configs.flat.recommended,
|
|
1608
|
+
languageOptions: {
|
|
1609
|
+
...pluginReact.configs.flat.recommended.languageOptions,
|
|
1610
|
+
globals: {
|
|
1611
|
+
...globals.serviceworker,
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1614
|
+
},
|
|
1615
|
+
{
|
|
1616
|
+
plugins: {
|
|
1617
|
+
"@next/next": pluginNext,
|
|
1618
|
+
},
|
|
1619
|
+
rules: {
|
|
1620
|
+
...pluginNext.configs.recommended.rules,
|
|
1621
|
+
...pluginNext.configs["core-web-vitals"].rules,
|
|
1622
|
+
},
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
plugins: {
|
|
1626
|
+
"react-hooks": pluginReactHooks,
|
|
1627
|
+
},
|
|
1628
|
+
settings: { react: { version: "detect" } },
|
|
1629
|
+
rules: {
|
|
1630
|
+
...pluginReactHooks.configs.recommended.rules,
|
|
1631
|
+
// React scope no longer necessary with new JSX transform.
|
|
1632
|
+
"react/react-in-jsx-scope": "off",
|
|
1633
|
+
},
|
|
1634
|
+
},
|
|
1635
|
+
];`
|
|
1636
|
+
};
|
|
1637
|
+
|
|
1638
|
+
// src/eslint-config/templates/react-internal.ts
|
|
1639
|
+
var react_internal_default = {
|
|
1640
|
+
filename: "packages/eslint-config/react-internal.js",
|
|
1641
|
+
template: `import js from "@eslint/js";
|
|
1642
|
+
import eslintConfigPrettier from "eslint-config-prettier";
|
|
1643
|
+
import tseslint from "typescript-eslint";
|
|
1644
|
+
import pluginReactHooks from "eslint-plugin-react-hooks";
|
|
1645
|
+
import pluginReact from "eslint-plugin-react";
|
|
1646
|
+
import globals from "globals";
|
|
1647
|
+
import { config as baseConfig } from "./base.js";
|
|
1648
|
+
|
|
1649
|
+
/**
|
|
1650
|
+
* A custom ESLint configuration for libraries that use React.
|
|
1651
|
+
*
|
|
1652
|
+
* @type {import("eslint").Linter.Config[]} */
|
|
1653
|
+
export const config = [
|
|
1654
|
+
...baseConfig,
|
|
1655
|
+
js.configs.recommended,
|
|
1656
|
+
eslintConfigPrettier,
|
|
1657
|
+
...tseslint.configs.recommended,
|
|
1658
|
+
pluginReact.configs.flat.recommended,
|
|
1659
|
+
{
|
|
1660
|
+
languageOptions: {
|
|
1661
|
+
...pluginReact.configs.flat.recommended.languageOptions,
|
|
1662
|
+
globals: {
|
|
1663
|
+
...globals.serviceworker,
|
|
1664
|
+
...globals.browser,
|
|
1665
|
+
},
|
|
1666
|
+
},
|
|
1667
|
+
},
|
|
1668
|
+
{
|
|
1669
|
+
plugins: {
|
|
1670
|
+
"react-hooks": pluginReactHooks,
|
|
1671
|
+
},
|
|
1672
|
+
settings: { react: { version: "detect" } },
|
|
1673
|
+
rules: {
|
|
1674
|
+
...pluginReactHooks.configs.recommended.rules,
|
|
1675
|
+
// React scope no longer necessary with new JSX transform.
|
|
1676
|
+
"react/react-in-jsx-scope": "off",
|
|
1677
|
+
},
|
|
1678
|
+
},
|
|
1679
|
+
];`
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
// src/eslint-config/index.ts
|
|
1683
|
+
var eslintConfig = {
|
|
1684
|
+
templates: [package_json_default6, base_default, next_default, react_internal_default]
|
|
1685
|
+
};
|
|
1686
|
+
var eslint_config_default4 = eslintConfig;
|
|
1687
|
+
|
|
1688
|
+
// src/typescript-config/templates/package-json.ts
|
|
1689
|
+
var package_json_default7 = {
|
|
1690
|
+
filename: "packages/typescript-config/package.json",
|
|
1691
|
+
template: `{
|
|
1692
|
+
"name": "@repo/typescript-config",
|
|
1693
|
+
"version": "0.0.0",
|
|
1694
|
+
"private": true,
|
|
1695
|
+
"license": "MIT",
|
|
1696
|
+
"publishConfig": {
|
|
1697
|
+
"access": "public"
|
|
1698
|
+
}
|
|
1699
|
+
}`
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
// src/typescript-config/templates/base.ts
|
|
1703
|
+
var base_default2 = {
|
|
1704
|
+
filename: "packages/typescript-config/base.json",
|
|
1705
|
+
template: `{
|
|
1706
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
1707
|
+
"compilerOptions": {
|
|
1708
|
+
"declaration": true,
|
|
1709
|
+
"declarationMap": true,
|
|
1710
|
+
"esModuleInterop": true,
|
|
1711
|
+
"incremental": false,
|
|
1712
|
+
"isolatedModules": true,
|
|
1713
|
+
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
|
1714
|
+
"module": "NodeNext",
|
|
1715
|
+
"moduleDetection": "force",
|
|
1716
|
+
"moduleResolution": "NodeNext",
|
|
1717
|
+
"noUncheckedIndexedAccess": true,
|
|
1718
|
+
"resolveJsonModule": true,
|
|
1719
|
+
"skipLibCheck": true,
|
|
1720
|
+
"strict": true,
|
|
1721
|
+
"target": "ES2022"
|
|
1722
|
+
}
|
|
1723
|
+
}`
|
|
1724
|
+
};
|
|
1725
|
+
|
|
1726
|
+
// src/typescript-config/templates/nextjs.ts
|
|
1727
|
+
var nextjs_default = {
|
|
1728
|
+
filename: "packages/typescript-config/nextjs.json",
|
|
1729
|
+
template: `{
|
|
1730
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
1731
|
+
"extends": "./base.json",
|
|
1732
|
+
"compilerOptions": {
|
|
1733
|
+
"plugins": [{ "name": "next" }],
|
|
1734
|
+
"module": "ESNext",
|
|
1735
|
+
"moduleResolution": "Bundler",
|
|
1736
|
+
"allowJs": true,
|
|
1737
|
+
"jsx": "preserve",
|
|
1738
|
+
"noEmit": true
|
|
1739
|
+
}
|
|
1740
|
+
}`
|
|
1741
|
+
};
|
|
1742
|
+
|
|
1743
|
+
// src/typescript-config/templates/react-library.ts
|
|
1744
|
+
var react_library_default = {
|
|
1745
|
+
filename: "packages/typescript-config/react-library.json",
|
|
1746
|
+
template: `{
|
|
1747
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
1748
|
+
"extends": "./base.json",
|
|
1749
|
+
"compilerOptions": {
|
|
1750
|
+
"jsx": "react-jsx"
|
|
1751
|
+
}
|
|
1752
|
+
}`
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
// src/typescript-config/index.ts
|
|
1756
|
+
var typescriptConfig = {
|
|
1757
|
+
templates: [package_json_default7, base_default2, nextjs_default, react_library_default]
|
|
1758
|
+
};
|
|
1759
|
+
var typescript_config_default = typescriptConfig;
|
|
1760
|
+
|
|
1761
|
+
// src/index.ts
|
|
1762
|
+
var staticDir = new URL("./static/", import.meta.url);
|
|
1763
|
+
var staticDirPath = fileURLToPath(staticDir);
|
|
1764
|
+
function getStaticFilePath(name) {
|
|
1765
|
+
return fileURLToPath(new URL(`./static/${name}`, import.meta.url));
|
|
1766
|
+
}
|
|
1767
|
+
var templates = {
|
|
1768
|
+
root: root_default,
|
|
1769
|
+
web: web_default,
|
|
1770
|
+
api: api_default,
|
|
1771
|
+
auth: auth_default,
|
|
1772
|
+
ui: ui_default,
|
|
1773
|
+
eslintConfig: eslint_config_default4,
|
|
1774
|
+
typescriptConfig: typescript_config_default
|
|
1775
|
+
};
|
|
1776
|
+
export {
|
|
1777
|
+
getStaticFilePath,
|
|
1778
|
+
staticDir,
|
|
1779
|
+
staticDirPath,
|
|
1780
|
+
templates
|
|
1781
|
+
};
|