create-newt-app 0.35.0 → 0.35.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.js +180 -69
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { existsSync, promises } from "fs";
|
|
|
11
11
|
import path from "path";
|
|
12
12
|
import https from "node:https";
|
|
13
13
|
//#region package.json
|
|
14
|
-
var version = "0.35.
|
|
14
|
+
var version = "0.35.2";
|
|
15
15
|
var engines = { "node": ">=24.0.0" };
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/templates/root/index.ts
|
|
@@ -177,6 +177,21 @@ pnpm dev
|
|
|
177
177
|
\`\`\`
|
|
178
178
|
|
|
179
179
|
Open [http://localhost:3000](http://localhost:3000).
|
|
180
|
+
<% if (deployment === 'spa') { -%>
|
|
181
|
+
|
|
182
|
+
## Production build
|
|
183
|
+
|
|
184
|
+
NestJS serves the static export and the API from one origin, so point \`BETTER_AUTH_URL\` at it:
|
|
185
|
+
|
|
186
|
+
\`\`\`sh
|
|
187
|
+
pnpm db:migrate
|
|
188
|
+
pnpm build
|
|
189
|
+
cd apps/api
|
|
190
|
+
BETTER_AUTH_URL=http://localhost:3001 pnpm start:prod
|
|
191
|
+
\`\`\`
|
|
192
|
+
|
|
193
|
+
Open [http://localhost:3001](http://localhost:3001). In a real deployment, set \`BETTER_AUTH_URL\` to the public URL.
|
|
194
|
+
<% } -%>
|
|
180
195
|
|
|
181
196
|
## Apps
|
|
182
197
|
|
|
@@ -224,7 +239,18 @@ BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32
|
|
|
224
239
|
# This is NOT the Next.js you know
|
|
225
240
|
|
|
226
241
|
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` before writing any code. Heed deprecation notices.
|
|
227
|
-
<!-- END:nextjs-agent-rules
|
|
242
|
+
<!-- END:nextjs-agent-rules -->
|
|
243
|
+
|
|
244
|
+
<!-- BEGIN:turborepo-agent-rules -->
|
|
245
|
+
|
|
246
|
+
# This is NOT the Turborepo you know
|
|
247
|
+
|
|
248
|
+
Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the \`turbo\` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run \`node -p "require.resolve('turbo/package.json')"\` from a workspace that depends on \`turbo\`.
|
|
249
|
+
|
|
250
|
+
Read \`docs/README.md\` inside that installed package first, then read the relevant pages from its \`docs/\` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
|
|
251
|
+
|
|
252
|
+
This block is written and re-added by \`turbo\` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in \`crates/turborepo-cli/src/cli/agent_guidance.rs\`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set \`"agentGuidance": false\` in the root \`turbo.json\` or \`turbo.jsonc\` to opt out; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation.
|
|
253
|
+
<!-- END:turborepo-agent-rules --><% if (shadcn) { %>
|
|
228
254
|
|
|
229
255
|
After making changes, run \`pnpm lint:check\` and fix all errors.<% } %>`
|
|
230
256
|
},
|
|
@@ -910,6 +936,7 @@ const auth = { templates: [
|
|
|
910
936
|
"name": "@<%= projectName %>/auth",
|
|
911
937
|
"version": "0.0.0",
|
|
912
938
|
"private": true,
|
|
939
|
+
"type": "module",
|
|
913
940
|
"exports": {
|
|
914
941
|
".": "./src/index.ts"
|
|
915
942
|
},
|
|
@@ -1011,7 +1038,6 @@ export interface SessionTable {
|
|
|
1011
1038
|
|
|
1012
1039
|
export interface AccountTable {
|
|
1013
1040
|
id: string;
|
|
1014
|
-
issuer: string;
|
|
1015
1041
|
accountId: string;
|
|
1016
1042
|
providerId: string;
|
|
1017
1043
|
userId: string;
|
|
@@ -1228,7 +1254,9 @@ export const db = new Kysely<DB>({
|
|
|
1228
1254
|
dialect: new SqliteDialect({ database: driver }),
|
|
1229
1255
|
});
|
|
1230
1256
|
|
|
1231
|
-
export type Database = Kysely<DB
|
|
1257
|
+
export type Database = Kysely<DB>;
|
|
1258
|
+
|
|
1259
|
+
export type * from "./schema.js";`
|
|
1232
1260
|
};
|
|
1233
1261
|
//#endregion
|
|
1234
1262
|
//#region src/templates/db/templates/src-index-postgres.ts
|
|
@@ -1244,7 +1272,9 @@ export const db = new Kysely<DB>({
|
|
|
1244
1272
|
dialect: new PostgresDialect({ pool: driver }),
|
|
1245
1273
|
});
|
|
1246
1274
|
|
|
1247
|
-
export type Database = Kysely<DB
|
|
1275
|
+
export type Database = Kysely<DB>;
|
|
1276
|
+
|
|
1277
|
+
export type * from "./schema.js";`
|
|
1248
1278
|
};
|
|
1249
1279
|
//#endregion
|
|
1250
1280
|
//#region src/templates/db/index.ts
|
|
@@ -1569,7 +1599,8 @@ const versions = {
|
|
|
1569
1599
|
typescript: "6.0.2",
|
|
1570
1600
|
"typescript-eslint": "^8.50.0",
|
|
1571
1601
|
"unplugin-swc": "^1.5.9",
|
|
1572
|
-
vitest: "^4.1.10"
|
|
1602
|
+
vitest: "^4.1.10",
|
|
1603
|
+
zod: "^4.6.5"
|
|
1573
1604
|
};
|
|
1574
1605
|
//#endregion
|
|
1575
1606
|
//#region src/templates/shadcn-ui/index.ts
|
|
@@ -12273,12 +12304,14 @@ import { resolve } from 'path';
|
|
|
12273
12304
|
dotenv.config({ path: resolve(process.cwd(), '../../.env') });
|
|
12274
12305
|
dotenv.config({ path: resolve(process.cwd(), '.env') });
|
|
12275
12306
|
|
|
12307
|
+
import { StandardSchemaValidationPipe } from '@nestjs/common';
|
|
12276
12308
|
import { NestFactory } from '@nestjs/core';
|
|
12277
12309
|
import { AppModule } from './app.module';
|
|
12278
12310
|
|
|
12279
12311
|
async function bootstrap() {
|
|
12280
12312
|
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
|
12281
12313
|
app.setGlobalPrefix('api');
|
|
12314
|
+
app.useGlobalPipes(new StandardSchemaValidationPipe());
|
|
12282
12315
|
app.enableCors({
|
|
12283
12316
|
origin: process.env.CORS_ORIGIN ?? 'http://localhost:3000',
|
|
12284
12317
|
credentials: true,
|
|
@@ -12308,14 +12341,7 @@ var todos_service_default = {
|
|
|
12308
12341
|
filename: "apps/api/src/todos/todos.service.ts",
|
|
12309
12342
|
template: `import { Injectable, NotFoundException } from '@nestjs/common';
|
|
12310
12343
|
import { randomUUID } from 'node:crypto';
|
|
12311
|
-
import { db } from '@<%= projectName %>/db';
|
|
12312
|
-
|
|
12313
|
-
export interface Todo {
|
|
12314
|
-
id: string;
|
|
12315
|
-
title: string;
|
|
12316
|
-
done: boolean;
|
|
12317
|
-
createdAt: string;
|
|
12318
|
-
}
|
|
12344
|
+
import { db, type Todo } from '@<%= projectName %>/db';
|
|
12319
12345
|
|
|
12320
12346
|
const columns = ['id', 'title', 'done', 'createdAt'] as const;
|
|
12321
12347
|
|
|
@@ -12414,6 +12440,15 @@ export interface TodoTable {
|
|
|
12414
12440
|
createdAt: Generated<string>;
|
|
12415
12441
|
}
|
|
12416
12442
|
|
|
12443
|
+
// Wire shape of a todo: what the api serves and the web app consumes.
|
|
12444
|
+
// The service converts table rows (raw sqlite values) into this.
|
|
12445
|
+
export interface Todo {
|
|
12446
|
+
id: string;
|
|
12447
|
+
title: string;
|
|
12448
|
+
done: boolean;
|
|
12449
|
+
createdAt: string;
|
|
12450
|
+
}
|
|
12451
|
+
|
|
12417
12452
|
export interface UserTable {
|
|
12418
12453
|
id: string;
|
|
12419
12454
|
name: string;
|
|
@@ -12437,7 +12472,6 @@ export interface SessionTable {
|
|
|
12437
12472
|
|
|
12438
12473
|
export interface AccountTable {
|
|
12439
12474
|
id: string;
|
|
12440
|
-
issuer: string;
|
|
12441
12475
|
accountId: string;
|
|
12442
12476
|
providerId: string;
|
|
12443
12477
|
userId: string;
|
|
@@ -12507,6 +12541,7 @@ var todos_controller_default = {
|
|
|
12507
12541
|
} from '@nestjs/common';
|
|
12508
12542
|
import { Session } from '@thallesp/nestjs-better-auth';
|
|
12509
12543
|
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
|
12544
|
+
import { z } from 'zod';
|
|
12510
12545
|
import { TodosService } from './todos.service';
|
|
12511
12546
|
|
|
12512
12547
|
@Controller('todos')
|
|
@@ -12519,7 +12554,10 @@ export class TodosController {
|
|
|
12519
12554
|
}
|
|
12520
12555
|
|
|
12521
12556
|
@Post()
|
|
12522
|
-
create(
|
|
12557
|
+
create(
|
|
12558
|
+
@Session() session: UserSession,
|
|
12559
|
+
@Body('title', { schema: z.string().trim().min(1) }) title: string,
|
|
12560
|
+
) {
|
|
12523
12561
|
return this.todosService.create(session.user.id, title);
|
|
12524
12562
|
}
|
|
12525
12563
|
|
|
@@ -12601,7 +12639,7 @@ var api_index_default = {
|
|
|
12601
12639
|
template: `export { AppModule } from './app.module';
|
|
12602
12640
|
export { AppService } from './app.service';
|
|
12603
12641
|
export { TodosService } from './todos/todos.service';
|
|
12604
|
-
export type { Todo } from '
|
|
12642
|
+
export type { Todo } from '@<%= projectName %>/db';`
|
|
12605
12643
|
};
|
|
12606
12644
|
//#endregion
|
|
12607
12645
|
//#region src/templates/todo-example/templates/web-todos-route.ts
|
|
@@ -12635,6 +12673,7 @@ export async function POST(req: Request) {
|
|
|
12635
12673
|
var web_todos_id_route_default = {
|
|
12636
12674
|
filename: "apps/web/app/api/todos/[id]/route.ts",
|
|
12637
12675
|
template: `import { NextResponse } from 'next/server';
|
|
12676
|
+
import { HttpException } from '@nestjs/common';
|
|
12638
12677
|
import { headers } from 'next/headers';
|
|
12639
12678
|
import { inject } from '@/lib/nest';
|
|
12640
12679
|
import { auth } from '@<%= projectName %>/auth';
|
|
@@ -12645,8 +12684,15 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
12645
12684
|
if (!session) return new NextResponse('Unauthorized', { status: 401 });
|
|
12646
12685
|
const { id } = await params;
|
|
12647
12686
|
const todos = await inject(TodosService);
|
|
12648
|
-
|
|
12649
|
-
|
|
12687
|
+
try {
|
|
12688
|
+
await todos.remove(session.user.id, id);
|
|
12689
|
+
return new NextResponse(null, { status: 204 });
|
|
12690
|
+
} catch (err) {
|
|
12691
|
+
if (err instanceof HttpException) {
|
|
12692
|
+
return NextResponse.json(err.getResponse(), { status: err.getStatus() });
|
|
12693
|
+
}
|
|
12694
|
+
throw err;
|
|
12695
|
+
}
|
|
12650
12696
|
}`
|
|
12651
12697
|
};
|
|
12652
12698
|
//#endregion
|
|
@@ -12654,6 +12700,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
12654
12700
|
var web_todos_toggle_route_default = {
|
|
12655
12701
|
filename: "apps/web/app/api/todos/[id]/toggle/route.ts",
|
|
12656
12702
|
template: `import { NextResponse } from 'next/server';
|
|
12703
|
+
import { HttpException } from '@nestjs/common';
|
|
12657
12704
|
import { headers } from 'next/headers';
|
|
12658
12705
|
import { inject } from '@/lib/nest';
|
|
12659
12706
|
import { auth } from '@<%= projectName %>/auth';
|
|
@@ -12664,7 +12711,14 @@ export async function PATCH(_req: Request, { params }: { params: Promise<{ id: s
|
|
|
12664
12711
|
if (!session) return new NextResponse('Unauthorized', { status: 401 });
|
|
12665
12712
|
const { id } = await params;
|
|
12666
12713
|
const todos = await inject(TodosService);
|
|
12667
|
-
|
|
12714
|
+
try {
|
|
12715
|
+
return NextResponse.json(await todos.toggle(session.user.id, id));
|
|
12716
|
+
} catch (err) {
|
|
12717
|
+
if (err instanceof HttpException) {
|
|
12718
|
+
return NextResponse.json(err.getResponse(), { status: err.getStatus() });
|
|
12719
|
+
}
|
|
12720
|
+
throw err;
|
|
12721
|
+
}
|
|
12668
12722
|
}`
|
|
12669
12723
|
};
|
|
12670
12724
|
//#endregion
|
|
@@ -12677,25 +12731,28 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
12677
12731
|
import { useForm } from '@tanstack/react-form';
|
|
12678
12732
|
import { authClient } from '@/lib/auth-client';
|
|
12679
12733
|
import { Button } from '@<%= projectName %>/ui/button';
|
|
12734
|
+
import type { Todo } from '@<%= projectName %>/db';
|
|
12680
12735
|
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12736
|
+
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
|
12737
|
+
const res = await fetch(url, init);
|
|
12738
|
+
if (!res.ok) throw new Error('Request failed with status ' + res.status);
|
|
12739
|
+
const text = await res.text();
|
|
12740
|
+
// SAFETY: T is the response shape the api wrapper promises for each
|
|
12741
|
+
// endpoint, and every success path returns a JSON body.
|
|
12742
|
+
return (text ? JSON.parse(text) : undefined) as T;
|
|
12685
12743
|
}
|
|
12686
12744
|
|
|
12687
12745
|
const api = {
|
|
12688
|
-
getTodos: ()
|
|
12689
|
-
createTodo: (title: string)
|
|
12690
|
-
|
|
12746
|
+
getTodos: () => request<Todo[]>('/api/todos'),
|
|
12747
|
+
createTodo: (title: string) =>
|
|
12748
|
+
request<Todo>('/api/todos', {
|
|
12691
12749
|
method: 'POST',
|
|
12692
12750
|
headers: { 'Content-Type': 'application/json' },
|
|
12693
12751
|
body: JSON.stringify({ title }),
|
|
12694
|
-
})
|
|
12695
|
-
toggleTodo: (id: string)
|
|
12696
|
-
|
|
12697
|
-
deleteTodo: (id: string)
|
|
12698
|
-
fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
|
|
12752
|
+
}),
|
|
12753
|
+
toggleTodo: (id: string) =>
|
|
12754
|
+
request<Todo>(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }),
|
|
12755
|
+
deleteTodo: (id: string) => request<void>(\`/api/todos/\${id}\`, { method: 'DELETE' }),
|
|
12699
12756
|
};
|
|
12700
12757
|
|
|
12701
12758
|
export function TodoList({
|
|
@@ -12705,7 +12762,7 @@ export function TodoList({
|
|
|
12705
12762
|
}) {
|
|
12706
12763
|
const queryClient = useQueryClient();
|
|
12707
12764
|
|
|
12708
|
-
const { data: todos = [], isPending } = useQuery({
|
|
12765
|
+
const { data: todos = [], isPending, isError } = useQuery({
|
|
12709
12766
|
queryKey: ['todos'],
|
|
12710
12767
|
queryFn: api.getTodos,
|
|
12711
12768
|
});
|
|
@@ -12776,8 +12833,14 @@ export function TodoList({
|
|
|
12776
12833
|
</form.Subscribe>
|
|
12777
12834
|
</form>
|
|
12778
12835
|
|
|
12836
|
+
{(createMutation.isError || toggleMutation.isError || deleteMutation.isError) && (
|
|
12837
|
+
<p className="text-sm text-red-400 mb-4">Something went wrong. Try again.</p>
|
|
12838
|
+
)}
|
|
12839
|
+
|
|
12779
12840
|
{isPending ? (
|
|
12780
12841
|
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
12842
|
+
) : isError ? (
|
|
12843
|
+
<p className="text-sm text-red-400">Couldn't load todos.</p>
|
|
12781
12844
|
) : (
|
|
12782
12845
|
<ul className="divide-y divide-neutral-700">
|
|
12783
12846
|
{todos.map((todo) => (
|
|
@@ -12907,25 +12970,29 @@ import { authClient } from '@/lib/auth-client';
|
|
|
12907
12970
|
import { Button } from '@<%= projectName %>/ui/button';
|
|
12908
12971
|
import { Input } from '@<%= projectName %>/ui/input';
|
|
12909
12972
|
import { Checkbox } from '@<%= projectName %>/ui/checkbox';
|
|
12973
|
+
import { toast } from 'sonner';
|
|
12974
|
+
import type { Todo } from '@<%= projectName %>/db';
|
|
12910
12975
|
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12976
|
+
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
|
12977
|
+
const res = await fetch(url, init);
|
|
12978
|
+
if (!res.ok) throw new Error('Request failed with status ' + res.status);
|
|
12979
|
+
const text = await res.text();
|
|
12980
|
+
// SAFETY: T is the response shape the api wrapper promises for each
|
|
12981
|
+
// endpoint, and every success path returns a JSON body.
|
|
12982
|
+
return (text ? JSON.parse(text) : undefined) as T;
|
|
12915
12983
|
}
|
|
12916
12984
|
|
|
12917
12985
|
const api = {
|
|
12918
|
-
getTodos: ()
|
|
12919
|
-
createTodo: (title: string)
|
|
12920
|
-
|
|
12986
|
+
getTodos: () => request<Todo[]>('/api/todos'),
|
|
12987
|
+
createTodo: (title: string) =>
|
|
12988
|
+
request<Todo>('/api/todos', {
|
|
12921
12989
|
method: 'POST',
|
|
12922
12990
|
headers: { 'Content-Type': 'application/json' },
|
|
12923
12991
|
body: JSON.stringify({ title }),
|
|
12924
|
-
})
|
|
12925
|
-
toggleTodo: (id: string)
|
|
12926
|
-
|
|
12927
|
-
deleteTodo: (id: string)
|
|
12928
|
-
fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
|
|
12992
|
+
}),
|
|
12993
|
+
toggleTodo: (id: string) =>
|
|
12994
|
+
request<Todo>(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }),
|
|
12995
|
+
deleteTodo: (id: string) => request<void>(\`/api/todos/\${id}\`, { method: 'DELETE' }),
|
|
12929
12996
|
};
|
|
12930
12997
|
|
|
12931
12998
|
export function TodoList({
|
|
@@ -12935,7 +13002,7 @@ export function TodoList({
|
|
|
12935
13002
|
}) {
|
|
12936
13003
|
const queryClient = useQueryClient();
|
|
12937
13004
|
|
|
12938
|
-
const { data: todos = [], isPending } = useQuery({
|
|
13005
|
+
const { data: todos = [], isPending, isError } = useQuery({
|
|
12939
13006
|
queryKey: ['todos'],
|
|
12940
13007
|
queryFn: api.getTodos,
|
|
12941
13008
|
});
|
|
@@ -12943,16 +13010,19 @@ export function TodoList({
|
|
|
12943
13010
|
const createMutation = useMutation({
|
|
12944
13011
|
mutationFn: api.createTodo,
|
|
12945
13012
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
13013
|
+
onError: () => toast.error("Couldn't add todo"),
|
|
12946
13014
|
});
|
|
12947
13015
|
|
|
12948
13016
|
const toggleMutation = useMutation({
|
|
12949
13017
|
mutationFn: api.toggleTodo,
|
|
12950
13018
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
13019
|
+
onError: () => toast.error("Couldn't update todo"),
|
|
12951
13020
|
});
|
|
12952
13021
|
|
|
12953
13022
|
const deleteMutation = useMutation({
|
|
12954
13023
|
mutationFn: api.deleteTodo,
|
|
12955
13024
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
13025
|
+
onError: () => toast.error("Couldn't delete todo"),
|
|
12956
13026
|
});
|
|
12957
13027
|
|
|
12958
13028
|
const form = useForm({
|
|
@@ -13009,6 +13079,8 @@ export function TodoList({
|
|
|
13009
13079
|
|
|
13010
13080
|
{isPending ? (
|
|
13011
13081
|
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
13082
|
+
) : isError ? (
|
|
13083
|
+
<p className="text-sm text-destructive">Couldn't load todos.</p>
|
|
13012
13084
|
) : (
|
|
13013
13085
|
<ul className="divide-y divide-border">
|
|
13014
13086
|
{todos.map((todo) => (
|
|
@@ -13161,25 +13233,28 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
13161
13233
|
import { authClient } from '@/lib/auth-client';
|
|
13162
13234
|
import { Button } from '@<%= projectName %>/ui/button';
|
|
13163
13235
|
import { colors, radii } from '@<%= projectName %>/ui/tokens.stylex';
|
|
13236
|
+
import type { Todo } from '@<%= projectName %>/db';
|
|
13164
13237
|
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13238
|
+
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
|
13239
|
+
const res = await fetch(url, init);
|
|
13240
|
+
if (!res.ok) throw new Error('Request failed with status ' + res.status);
|
|
13241
|
+
const text = await res.text();
|
|
13242
|
+
// SAFETY: T is the response shape the api wrapper promises for each
|
|
13243
|
+
// endpoint, and every success path returns a JSON body.
|
|
13244
|
+
return (text ? JSON.parse(text) : undefined) as T;
|
|
13169
13245
|
}
|
|
13170
13246
|
|
|
13171
13247
|
const api = {
|
|
13172
|
-
getTodos: ()
|
|
13173
|
-
createTodo: (title: string)
|
|
13174
|
-
|
|
13248
|
+
getTodos: () => request<Todo[]>('/api/todos'),
|
|
13249
|
+
createTodo: (title: string) =>
|
|
13250
|
+
request<Todo>('/api/todos', {
|
|
13175
13251
|
method: 'POST',
|
|
13176
13252
|
headers: { 'Content-Type': 'application/json' },
|
|
13177
13253
|
body: JSON.stringify({ title }),
|
|
13178
|
-
})
|
|
13179
|
-
toggleTodo: (id: string)
|
|
13180
|
-
|
|
13181
|
-
deleteTodo: (id: string)
|
|
13182
|
-
fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
|
|
13254
|
+
}),
|
|
13255
|
+
toggleTodo: (id: string) =>
|
|
13256
|
+
request<Todo>(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }),
|
|
13257
|
+
deleteTodo: (id: string) => request<void>(\`/api/todos/\${id}\`, { method: 'DELETE' }),
|
|
13183
13258
|
};
|
|
13184
13259
|
|
|
13185
13260
|
export function TodoList({
|
|
@@ -13189,7 +13264,7 @@ export function TodoList({
|
|
|
13189
13264
|
}) {
|
|
13190
13265
|
const queryClient = useQueryClient();
|
|
13191
13266
|
|
|
13192
|
-
const { data: todos = [], isPending } = useQuery({
|
|
13267
|
+
const { data: todos = [], isPending, isError } = useQuery({
|
|
13193
13268
|
queryKey: ['todos'],
|
|
13194
13269
|
queryFn: api.getTodos,
|
|
13195
13270
|
});
|
|
@@ -13260,8 +13335,14 @@ export function TodoList({
|
|
|
13260
13335
|
</form.Subscribe>
|
|
13261
13336
|
</form>
|
|
13262
13337
|
|
|
13338
|
+
{(createMutation.isError || toggleMutation.isError || deleteMutation.isError) && (
|
|
13339
|
+
<p {...stylex.props(styles.error)}>Something went wrong. Try again.</p>
|
|
13340
|
+
)}
|
|
13341
|
+
|
|
13263
13342
|
{isPending ? (
|
|
13264
13343
|
<p {...stylex.props(styles.muted)}>Loading…</p>
|
|
13344
|
+
) : isError ? (
|
|
13345
|
+
<p {...stylex.props(styles.error)}>Couldn't load todos.</p>
|
|
13265
13346
|
) : (
|
|
13266
13347
|
<ul {...stylex.props(styles.list)}>
|
|
13267
13348
|
{todos.map((todo) => (
|
|
@@ -13386,6 +13467,11 @@ const styles = stylex.create({
|
|
|
13386
13467
|
fontSize: '0.875rem',
|
|
13387
13468
|
color: colors.mutedForeground,
|
|
13388
13469
|
},
|
|
13470
|
+
error: {
|
|
13471
|
+
fontSize: '0.875rem',
|
|
13472
|
+
color: colors.danger,
|
|
13473
|
+
marginBottom: '1rem',
|
|
13474
|
+
},
|
|
13389
13475
|
});
|
|
13390
13476
|
`
|
|
13391
13477
|
};
|
|
@@ -13576,11 +13662,18 @@ const todoExampleApi = { templates: [
|
|
|
13576
13662
|
db_schema_default,
|
|
13577
13663
|
db_migration_todos_default
|
|
13578
13664
|
] };
|
|
13579
|
-
const todoExampleControllers = {
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13665
|
+
const todoExampleControllers = {
|
|
13666
|
+
templates: [
|
|
13667
|
+
todos_controller_default,
|
|
13668
|
+
todos_module_controllers_default,
|
|
13669
|
+
app_module_controllers_default
|
|
13670
|
+
],
|
|
13671
|
+
packages: [{
|
|
13672
|
+
package: "zod",
|
|
13673
|
+
module: "apps/api",
|
|
13674
|
+
version: versions.zod
|
|
13675
|
+
}]
|
|
13676
|
+
};
|
|
13584
13677
|
const todoExampleDi = { templates: [
|
|
13585
13678
|
web_todos_route_default,
|
|
13586
13679
|
web_todos_id_route_default,
|
|
@@ -13588,9 +13681,23 @@ const todoExampleDi = { templates: [
|
|
|
13588
13681
|
app_module_di_default,
|
|
13589
13682
|
api_index_default
|
|
13590
13683
|
] };
|
|
13591
|
-
const
|
|
13592
|
-
|
|
13593
|
-
|
|
13684
|
+
const webDbPackage = {
|
|
13685
|
+
package: "@<%= projectName %>/db",
|
|
13686
|
+
module: "apps/web",
|
|
13687
|
+
version: "workspace:*"
|
|
13688
|
+
};
|
|
13689
|
+
const todoExampleWeb = {
|
|
13690
|
+
templates: [web_todo_list_default, web_page_default],
|
|
13691
|
+
packages: [webDbPackage]
|
|
13692
|
+
};
|
|
13693
|
+
const todoExampleShadcn = {
|
|
13694
|
+
templates: [shadcn_todo_list_default, shadcn_page_default],
|
|
13695
|
+
packages: [webDbPackage]
|
|
13696
|
+
};
|
|
13697
|
+
const todoExampleStylex = {
|
|
13698
|
+
templates: [stylex_todo_list_default, stylex_page_default],
|
|
13699
|
+
packages: [webDbPackage]
|
|
13700
|
+
};
|
|
13594
13701
|
//#endregion
|
|
13595
13702
|
//#region src/templates/index.ts
|
|
13596
13703
|
const staticDir = new URL("./static/", import.meta.url);
|
|
@@ -13790,7 +13897,7 @@ function validateNodeVersion(current, requirement) {
|
|
|
13790
13897
|
};
|
|
13791
13898
|
}
|
|
13792
13899
|
function normalizeProjectName(projectName) {
|
|
13793
|
-
return projectName.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9\-._~]/g, "-").replace(/-+/g, "-").replace(/^[-._]+/, "").replace(/-+$/, "").slice(0, 214);
|
|
13900
|
+
return projectName.trim().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9\-._~]/g, "-").replace(/-+/g, "-").replace(/^[-._]+/, "").replace(/-+$/, "").slice(0, 214);
|
|
13794
13901
|
}
|
|
13795
13902
|
function validateProjectName(projectName) {
|
|
13796
13903
|
if (!projectName) return {
|
|
@@ -14165,12 +14272,16 @@ async function doInit(options) {
|
|
|
14165
14272
|
});
|
|
14166
14273
|
await p.tasks(taskBuilder.tasks);
|
|
14167
14274
|
p.outro(`Done!`);
|
|
14275
|
+
if (name !== rawName) {
|
|
14276
|
+
console.log(`Note: the project directory is "${name}", not "${rawName}".`);
|
|
14277
|
+
console.log();
|
|
14278
|
+
}
|
|
14168
14279
|
console.log("Next steps:");
|
|
14169
14280
|
console.log();
|
|
14170
14281
|
console.log(chalk.blue(` cd ${name}`));
|
|
14171
14282
|
if (!options.install) {
|
|
14172
14283
|
console.log(chalk.blue(` pnpm install`));
|
|
14173
|
-
console.log(chalk.blue(` pnpm format`));
|
|
14284
|
+
console.log(chalk.blue(` pnpm format:fix`));
|
|
14174
14285
|
}
|
|
14175
14286
|
if (!committed) console.log(chalk.blue(` git commit -m "Initial commit"`));
|
|
14176
14287
|
console.log(chalk.blue(` pnpm dev`));
|