create-newt-app 0.35.1 → 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 +127 -57
- 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
|
|
|
@@ -921,6 +936,7 @@ const auth = { templates: [
|
|
|
921
936
|
"name": "@<%= projectName %>/auth",
|
|
922
937
|
"version": "0.0.0",
|
|
923
938
|
"private": true,
|
|
939
|
+
"type": "module",
|
|
924
940
|
"exports": {
|
|
925
941
|
".": "./src/index.ts"
|
|
926
942
|
},
|
|
@@ -1022,7 +1038,6 @@ export interface SessionTable {
|
|
|
1022
1038
|
|
|
1023
1039
|
export interface AccountTable {
|
|
1024
1040
|
id: string;
|
|
1025
|
-
issuer: string;
|
|
1026
1041
|
accountId: string;
|
|
1027
1042
|
providerId: string;
|
|
1028
1043
|
userId: string;
|
|
@@ -1239,7 +1254,9 @@ export const db = new Kysely<DB>({
|
|
|
1239
1254
|
dialect: new SqliteDialect({ database: driver }),
|
|
1240
1255
|
});
|
|
1241
1256
|
|
|
1242
|
-
export type Database = Kysely<DB
|
|
1257
|
+
export type Database = Kysely<DB>;
|
|
1258
|
+
|
|
1259
|
+
export type * from "./schema.js";`
|
|
1243
1260
|
};
|
|
1244
1261
|
//#endregion
|
|
1245
1262
|
//#region src/templates/db/templates/src-index-postgres.ts
|
|
@@ -1255,7 +1272,9 @@ export const db = new Kysely<DB>({
|
|
|
1255
1272
|
dialect: new PostgresDialect({ pool: driver }),
|
|
1256
1273
|
});
|
|
1257
1274
|
|
|
1258
|
-
export type Database = Kysely<DB
|
|
1275
|
+
export type Database = Kysely<DB>;
|
|
1276
|
+
|
|
1277
|
+
export type * from "./schema.js";`
|
|
1259
1278
|
};
|
|
1260
1279
|
//#endregion
|
|
1261
1280
|
//#region src/templates/db/index.ts
|
|
@@ -12322,14 +12341,7 @@ var todos_service_default = {
|
|
|
12322
12341
|
filename: "apps/api/src/todos/todos.service.ts",
|
|
12323
12342
|
template: `import { Injectable, NotFoundException } from '@nestjs/common';
|
|
12324
12343
|
import { randomUUID } from 'node:crypto';
|
|
12325
|
-
import { db } from '@<%= projectName %>/db';
|
|
12326
|
-
|
|
12327
|
-
export interface Todo {
|
|
12328
|
-
id: string;
|
|
12329
|
-
title: string;
|
|
12330
|
-
done: boolean;
|
|
12331
|
-
createdAt: string;
|
|
12332
|
-
}
|
|
12344
|
+
import { db, type Todo } from '@<%= projectName %>/db';
|
|
12333
12345
|
|
|
12334
12346
|
const columns = ['id', 'title', 'done', 'createdAt'] as const;
|
|
12335
12347
|
|
|
@@ -12428,6 +12440,15 @@ export interface TodoTable {
|
|
|
12428
12440
|
createdAt: Generated<string>;
|
|
12429
12441
|
}
|
|
12430
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
|
+
|
|
12431
12452
|
export interface UserTable {
|
|
12432
12453
|
id: string;
|
|
12433
12454
|
name: string;
|
|
@@ -12451,7 +12472,6 @@ export interface SessionTable {
|
|
|
12451
12472
|
|
|
12452
12473
|
export interface AccountTable {
|
|
12453
12474
|
id: string;
|
|
12454
|
-
issuer: string;
|
|
12455
12475
|
accountId: string;
|
|
12456
12476
|
providerId: string;
|
|
12457
12477
|
userId: string;
|
|
@@ -12619,7 +12639,7 @@ var api_index_default = {
|
|
|
12619
12639
|
template: `export { AppModule } from './app.module';
|
|
12620
12640
|
export { AppService } from './app.service';
|
|
12621
12641
|
export { TodosService } from './todos/todos.service';
|
|
12622
|
-
export type { Todo } from '
|
|
12642
|
+
export type { Todo } from '@<%= projectName %>/db';`
|
|
12623
12643
|
};
|
|
12624
12644
|
//#endregion
|
|
12625
12645
|
//#region src/templates/todo-example/templates/web-todos-route.ts
|
|
@@ -12711,25 +12731,28 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
12711
12731
|
import { useForm } from '@tanstack/react-form';
|
|
12712
12732
|
import { authClient } from '@/lib/auth-client';
|
|
12713
12733
|
import { Button } from '@<%= projectName %>/ui/button';
|
|
12734
|
+
import type { Todo } from '@<%= projectName %>/db';
|
|
12714
12735
|
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
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;
|
|
12719
12743
|
}
|
|
12720
12744
|
|
|
12721
12745
|
const api = {
|
|
12722
|
-
getTodos: ()
|
|
12723
|
-
createTodo: (title: string)
|
|
12724
|
-
|
|
12746
|
+
getTodos: () => request<Todo[]>('/api/todos'),
|
|
12747
|
+
createTodo: (title: string) =>
|
|
12748
|
+
request<Todo>('/api/todos', {
|
|
12725
12749
|
method: 'POST',
|
|
12726
12750
|
headers: { 'Content-Type': 'application/json' },
|
|
12727
12751
|
body: JSON.stringify({ title }),
|
|
12728
|
-
})
|
|
12729
|
-
toggleTodo: (id: string)
|
|
12730
|
-
|
|
12731
|
-
deleteTodo: (id: string)
|
|
12732
|
-
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' }),
|
|
12733
12756
|
};
|
|
12734
12757
|
|
|
12735
12758
|
export function TodoList({
|
|
@@ -12739,7 +12762,7 @@ export function TodoList({
|
|
|
12739
12762
|
}) {
|
|
12740
12763
|
const queryClient = useQueryClient();
|
|
12741
12764
|
|
|
12742
|
-
const { data: todos = [], isPending } = useQuery({
|
|
12765
|
+
const { data: todos = [], isPending, isError } = useQuery({
|
|
12743
12766
|
queryKey: ['todos'],
|
|
12744
12767
|
queryFn: api.getTodos,
|
|
12745
12768
|
});
|
|
@@ -12810,8 +12833,14 @@ export function TodoList({
|
|
|
12810
12833
|
</form.Subscribe>
|
|
12811
12834
|
</form>
|
|
12812
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
|
+
|
|
12813
12840
|
{isPending ? (
|
|
12814
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>
|
|
12815
12844
|
) : (
|
|
12816
12845
|
<ul className="divide-y divide-neutral-700">
|
|
12817
12846
|
{todos.map((todo) => (
|
|
@@ -12941,25 +12970,29 @@ import { authClient } from '@/lib/auth-client';
|
|
|
12941
12970
|
import { Button } from '@<%= projectName %>/ui/button';
|
|
12942
12971
|
import { Input } from '@<%= projectName %>/ui/input';
|
|
12943
12972
|
import { Checkbox } from '@<%= projectName %>/ui/checkbox';
|
|
12973
|
+
import { toast } from 'sonner';
|
|
12974
|
+
import type { Todo } from '@<%= projectName %>/db';
|
|
12944
12975
|
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
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;
|
|
12949
12983
|
}
|
|
12950
12984
|
|
|
12951
12985
|
const api = {
|
|
12952
|
-
getTodos: ()
|
|
12953
|
-
createTodo: (title: string)
|
|
12954
|
-
|
|
12986
|
+
getTodos: () => request<Todo[]>('/api/todos'),
|
|
12987
|
+
createTodo: (title: string) =>
|
|
12988
|
+
request<Todo>('/api/todos', {
|
|
12955
12989
|
method: 'POST',
|
|
12956
12990
|
headers: { 'Content-Type': 'application/json' },
|
|
12957
12991
|
body: JSON.stringify({ title }),
|
|
12958
|
-
})
|
|
12959
|
-
toggleTodo: (id: string)
|
|
12960
|
-
|
|
12961
|
-
deleteTodo: (id: string)
|
|
12962
|
-
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' }),
|
|
12963
12996
|
};
|
|
12964
12997
|
|
|
12965
12998
|
export function TodoList({
|
|
@@ -12969,7 +13002,7 @@ export function TodoList({
|
|
|
12969
13002
|
}) {
|
|
12970
13003
|
const queryClient = useQueryClient();
|
|
12971
13004
|
|
|
12972
|
-
const { data: todos = [], isPending } = useQuery({
|
|
13005
|
+
const { data: todos = [], isPending, isError } = useQuery({
|
|
12973
13006
|
queryKey: ['todos'],
|
|
12974
13007
|
queryFn: api.getTodos,
|
|
12975
13008
|
});
|
|
@@ -12977,16 +13010,19 @@ export function TodoList({
|
|
|
12977
13010
|
const createMutation = useMutation({
|
|
12978
13011
|
mutationFn: api.createTodo,
|
|
12979
13012
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
13013
|
+
onError: () => toast.error("Couldn't add todo"),
|
|
12980
13014
|
});
|
|
12981
13015
|
|
|
12982
13016
|
const toggleMutation = useMutation({
|
|
12983
13017
|
mutationFn: api.toggleTodo,
|
|
12984
13018
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
13019
|
+
onError: () => toast.error("Couldn't update todo"),
|
|
12985
13020
|
});
|
|
12986
13021
|
|
|
12987
13022
|
const deleteMutation = useMutation({
|
|
12988
13023
|
mutationFn: api.deleteTodo,
|
|
12989
13024
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
13025
|
+
onError: () => toast.error("Couldn't delete todo"),
|
|
12990
13026
|
});
|
|
12991
13027
|
|
|
12992
13028
|
const form = useForm({
|
|
@@ -13043,6 +13079,8 @@ export function TodoList({
|
|
|
13043
13079
|
|
|
13044
13080
|
{isPending ? (
|
|
13045
13081
|
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
13082
|
+
) : isError ? (
|
|
13083
|
+
<p className="text-sm text-destructive">Couldn't load todos.</p>
|
|
13046
13084
|
) : (
|
|
13047
13085
|
<ul className="divide-y divide-border">
|
|
13048
13086
|
{todos.map((todo) => (
|
|
@@ -13195,25 +13233,28 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
13195
13233
|
import { authClient } from '@/lib/auth-client';
|
|
13196
13234
|
import { Button } from '@<%= projectName %>/ui/button';
|
|
13197
13235
|
import { colors, radii } from '@<%= projectName %>/ui/tokens.stylex';
|
|
13236
|
+
import type { Todo } from '@<%= projectName %>/db';
|
|
13198
13237
|
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
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;
|
|
13203
13245
|
}
|
|
13204
13246
|
|
|
13205
13247
|
const api = {
|
|
13206
|
-
getTodos: ()
|
|
13207
|
-
createTodo: (title: string)
|
|
13208
|
-
|
|
13248
|
+
getTodos: () => request<Todo[]>('/api/todos'),
|
|
13249
|
+
createTodo: (title: string) =>
|
|
13250
|
+
request<Todo>('/api/todos', {
|
|
13209
13251
|
method: 'POST',
|
|
13210
13252
|
headers: { 'Content-Type': 'application/json' },
|
|
13211
13253
|
body: JSON.stringify({ title }),
|
|
13212
|
-
})
|
|
13213
|
-
toggleTodo: (id: string)
|
|
13214
|
-
|
|
13215
|
-
deleteTodo: (id: string)
|
|
13216
|
-
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' }),
|
|
13217
13258
|
};
|
|
13218
13259
|
|
|
13219
13260
|
export function TodoList({
|
|
@@ -13223,7 +13264,7 @@ export function TodoList({
|
|
|
13223
13264
|
}) {
|
|
13224
13265
|
const queryClient = useQueryClient();
|
|
13225
13266
|
|
|
13226
|
-
const { data: todos = [], isPending } = useQuery({
|
|
13267
|
+
const { data: todos = [], isPending, isError } = useQuery({
|
|
13227
13268
|
queryKey: ['todos'],
|
|
13228
13269
|
queryFn: api.getTodos,
|
|
13229
13270
|
});
|
|
@@ -13294,8 +13335,14 @@ export function TodoList({
|
|
|
13294
13335
|
</form.Subscribe>
|
|
13295
13336
|
</form>
|
|
13296
13337
|
|
|
13338
|
+
{(createMutation.isError || toggleMutation.isError || deleteMutation.isError) && (
|
|
13339
|
+
<p {...stylex.props(styles.error)}>Something went wrong. Try again.</p>
|
|
13340
|
+
)}
|
|
13341
|
+
|
|
13297
13342
|
{isPending ? (
|
|
13298
13343
|
<p {...stylex.props(styles.muted)}>Loading…</p>
|
|
13344
|
+
) : isError ? (
|
|
13345
|
+
<p {...stylex.props(styles.error)}>Couldn't load todos.</p>
|
|
13299
13346
|
) : (
|
|
13300
13347
|
<ul {...stylex.props(styles.list)}>
|
|
13301
13348
|
{todos.map((todo) => (
|
|
@@ -13420,6 +13467,11 @@ const styles = stylex.create({
|
|
|
13420
13467
|
fontSize: '0.875rem',
|
|
13421
13468
|
color: colors.mutedForeground,
|
|
13422
13469
|
},
|
|
13470
|
+
error: {
|
|
13471
|
+
fontSize: '0.875rem',
|
|
13472
|
+
color: colors.danger,
|
|
13473
|
+
marginBottom: '1rem',
|
|
13474
|
+
},
|
|
13423
13475
|
});
|
|
13424
13476
|
`
|
|
13425
13477
|
};
|
|
@@ -13629,9 +13681,23 @@ const todoExampleDi = { templates: [
|
|
|
13629
13681
|
app_module_di_default,
|
|
13630
13682
|
api_index_default
|
|
13631
13683
|
] };
|
|
13632
|
-
const
|
|
13633
|
-
|
|
13634
|
-
|
|
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
|
+
};
|
|
13635
13701
|
//#endregion
|
|
13636
13702
|
//#region src/templates/index.ts
|
|
13637
13703
|
const staticDir = new URL("./static/", import.meta.url);
|
|
@@ -13831,7 +13897,7 @@ function validateNodeVersion(current, requirement) {
|
|
|
13831
13897
|
};
|
|
13832
13898
|
}
|
|
13833
13899
|
function normalizeProjectName(projectName) {
|
|
13834
|
-
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);
|
|
13835
13901
|
}
|
|
13836
13902
|
function validateProjectName(projectName) {
|
|
13837
13903
|
if (!projectName) return {
|
|
@@ -14206,6 +14272,10 @@ async function doInit(options) {
|
|
|
14206
14272
|
});
|
|
14207
14273
|
await p.tasks(taskBuilder.tasks);
|
|
14208
14274
|
p.outro(`Done!`);
|
|
14275
|
+
if (name !== rawName) {
|
|
14276
|
+
console.log(`Note: the project directory is "${name}", not "${rawName}".`);
|
|
14277
|
+
console.log();
|
|
14278
|
+
}
|
|
14209
14279
|
console.log("Next steps:");
|
|
14210
14280
|
console.log();
|
|
14211
14281
|
console.log(chalk.blue(` cd ${name}`));
|