@leo-h/create-nodejs-app 1.0.52 → 1.0.53
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/package.json +1 -1
- package/templates/fastify/.lintstagedrc.json +1 -1
- package/templates/fastify/src/core/fastify/middleware.ts +20 -0
- package/templates/fastify/src/core/schema.ts +5 -0
- package/templates/fastify/src/http/controllers/hello-multipart.controller.ts +1 -1
- package/templates/fastify/src/http/controllers/hello.controller.ts +1 -1
- package/templates/react-vite/.lintstagedrc.json +3 -1
- package/templates/react-vite/.vscode/settings.json +9 -0
- package/templates/react-vite/api/errors.ts +3 -3
- package/templates/react-vite/api/swr-fetcher.ts +37 -10
- package/templates/react-vite/gitignore +0 -1
- package/templates/react-vite/src/app.tsx +1 -1
- package/templates/react-vite/src/hooks/use-current-route.ts +24 -0
- package/templates/react-vite/src/lib/react-router-config.tsx +32 -0
- package/templates/react-vite/src/pages/_layouts/app.tsx +4 -7
- package/templates/react-vite/src/pages/_layouts/auth.tsx +4 -7
- package/templates/react-vite/src/pages/app/dashboard/{dashboard.tsx → index.tsx} +1 -1
- package/templates/react-vite/src/pages/auth/sign-in/index.tsx +1 -1
- package/templates/react-vite/src/routes.ts +27 -0
- package/templates/react-vite/src/@types/routes.ts +0 -24
- package/templates/react-vite/src/routes.tsx +0 -49
- /package/templates/fastify/src/core/{create-controller-response-schema.ts → fastify/create-controller-response-schema.ts} +0 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@leo-h/create-nodejs-app",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.53",
|
4
4
|
"packageManager": "pnpm@9.15.9",
|
5
5
|
"author": "Leonardo Henrique <leonardo0507.business@gmail.com>",
|
6
6
|
"description": "Create a modern Node.js app with TypeScript using one command.",
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { BaseError } from "@/http/errors";
|
2
|
+
import { Constructor } from "type-fest";
|
3
|
+
|
4
|
+
export type MiddlewareHandlerOutput<
|
5
|
+
Errors extends ReadonlyArray<Constructor<BaseError>>,
|
6
|
+
> = Promise<MiddlewareLeft<InstanceType<Errors[number]>> | void>;
|
7
|
+
|
8
|
+
export const middleware = Object.freeze({
|
9
|
+
left,
|
10
|
+
} as const);
|
11
|
+
|
12
|
+
class MiddlewareLeft<Error extends BaseError> {
|
13
|
+
public constructor(public readonly error: Error) {
|
14
|
+
throw error;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
function left<Error extends BaseError>(error: Error) {
|
19
|
+
return new MiddlewareLeft<Error>(error);
|
20
|
+
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { FastifyZodInstance } from "@/@types/fastify";
|
2
|
-
import { createControllerResponseSchema } from "@/core/create-controller-response-schema";
|
2
|
+
import { createControllerResponseSchema } from "@/core/fastify/create-controller-response-schema";
|
3
3
|
import { z } from "zod";
|
4
4
|
import { InternalServerError, ValidationError } from "../errors";
|
5
5
|
import {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { FastifyZodInstance } from "@/@types/fastify";
|
2
|
-
import { createControllerResponseSchema } from "@/core/create-controller-response-schema";
|
2
|
+
import { createControllerResponseSchema } from "@/core/fastify/create-controller-response-schema";
|
3
3
|
import { z } from "zod";
|
4
4
|
import { InternalServerError, ValidationError } from "../errors";
|
5
5
|
|
@@ -6,9 +6,9 @@ export class ApiError {
|
|
6
6
|
) {}
|
7
7
|
}
|
8
8
|
|
9
|
-
export class
|
9
|
+
export class ApiUnknownError {
|
10
10
|
static readonly message =
|
11
|
-
"Houve uma falha inesperada ao se comunicar com nosso servidor.
|
11
|
+
"Houve uma falha inesperada ao se comunicar com nosso servidor. Tente novamente em alguns segundos ou nos contate se o problema persistir.";
|
12
12
|
|
13
|
-
constructor(public message =
|
13
|
+
constructor(public message = ApiUnknownError.message) {}
|
14
14
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { publicEnv } from "@/public-env";
|
2
|
-
import { ApiError,
|
2
|
+
import { ApiError, ApiUnknownError } from "./errors";
|
3
3
|
|
4
|
-
type SwrFetcherOutput = {
|
4
|
+
export type SwrFetcherOutput = {
|
5
5
|
body: unknown;
|
6
6
|
status: number;
|
7
7
|
headers: Headers;
|
@@ -11,16 +11,19 @@ export async function swrFetcher<Output extends SwrFetcherOutput>(
|
|
11
11
|
endpointUrl: RequestInfo | URL,
|
12
12
|
options: RequestInit,
|
13
13
|
): Promise<Output> {
|
14
|
-
const url =
|
14
|
+
const url = getFullUrlFromApiByEndpoint(endpointUrl);
|
15
|
+
let response: Response;
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
try {
|
18
|
+
response = await fetch(url, {
|
19
|
+
credentials: "include",
|
20
|
+
...options,
|
21
|
+
});
|
22
|
+
} catch {
|
23
|
+
throw new ApiUnknownError();
|
20
24
|
}
|
21
25
|
|
22
|
-
|
23
|
-
let body: unknown;
|
26
|
+
let body: unknown = null;
|
24
27
|
|
25
28
|
if (
|
26
29
|
response.headers.get("Content-Type")?.includes("application/json") &&
|
@@ -38,7 +41,7 @@ export async function swrFetcher<Output extends SwrFetcherOutput>(
|
|
38
41
|
|
39
42
|
if (error) throw new ApiError(error.statusCode, error.error, error.message);
|
40
43
|
|
41
|
-
throw new
|
44
|
+
throw new ApiUnknownError();
|
42
45
|
}
|
43
46
|
|
44
47
|
return {
|
@@ -47,3 +50,27 @@ export async function swrFetcher<Output extends SwrFetcherOutput>(
|
|
47
50
|
headers: response.headers,
|
48
51
|
} as Awaited<Output>;
|
49
52
|
}
|
53
|
+
|
54
|
+
function getFullUrlFromApiByEndpoint(endpointUrl: RequestInfo | URL) {
|
55
|
+
const url = new URL(publicEnv.API_BASE_URL);
|
56
|
+
const [pathname, stringifiedSearchParams] = endpointUrl.toString().split("?");
|
57
|
+
|
58
|
+
if (url.pathname.endsWith("/")) {
|
59
|
+
url.pathname = pathname;
|
60
|
+
} else {
|
61
|
+
url.pathname += pathname;
|
62
|
+
}
|
63
|
+
|
64
|
+
if (stringifiedSearchParams) {
|
65
|
+
const searchParams = new URLSearchParams(stringifiedSearchParams);
|
66
|
+
|
67
|
+
for (const searchParamKey of Array.from(searchParams.keys())) {
|
68
|
+
const searchParamValue = searchParams.get(searchParamKey);
|
69
|
+
|
70
|
+
if (searchParamValue)
|
71
|
+
url.searchParams.set(searchParamKey, searchParamValue);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
return url;
|
76
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { routeGroups } from "@/routes";
|
2
|
+
import { useLocation } from "react-router";
|
3
|
+
|
4
|
+
type RouteGroup = (typeof routeGroups)[number];
|
5
|
+
|
6
|
+
type Route = RouteGroup extends Record<string, infer Route> ? Route : never;
|
7
|
+
|
8
|
+
export function useCurrentRoute() {
|
9
|
+
const { pathname } = useLocation();
|
10
|
+
let currentRoute: Route | null = null;
|
11
|
+
|
12
|
+
for (const routeGroup of routeGroups) {
|
13
|
+
const matchedRoute = Object.values(routeGroup).find(route => {
|
14
|
+
return route.path === pathname;
|
15
|
+
});
|
16
|
+
|
17
|
+
if (matchedRoute) {
|
18
|
+
currentRoute = matchedRoute;
|
19
|
+
break;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
return currentRoute;
|
24
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { AppLayout } from "@/pages/_layouts/app";
|
2
|
+
import { AuthLayout } from "@/pages/_layouts/auth";
|
3
|
+
import { privateRoutes, publicRoutes } from "@/routes";
|
4
|
+
import { createBrowserRouter, RouteObject } from "react-router";
|
5
|
+
|
6
|
+
export const router = createBrowserRouter([
|
7
|
+
{
|
8
|
+
element: <AuthLayout />,
|
9
|
+
children: [
|
10
|
+
...Object.values(publicRoutes).map(
|
11
|
+
publicRoute =>
|
12
|
+
({
|
13
|
+
path: publicRoute.path,
|
14
|
+
element: <publicRoute.element />,
|
15
|
+
index: true,
|
16
|
+
}) satisfies RouteObject,
|
17
|
+
),
|
18
|
+
],
|
19
|
+
},
|
20
|
+
{
|
21
|
+
element: <AppLayout />,
|
22
|
+
children: [
|
23
|
+
...Object.values(privateRoutes).map(
|
24
|
+
publicRoute =>
|
25
|
+
({
|
26
|
+
path: publicRoute.path,
|
27
|
+
element: <publicRoute.element />,
|
28
|
+
}) satisfies RouteObject,
|
29
|
+
),
|
30
|
+
],
|
31
|
+
},
|
32
|
+
]);
|
@@ -1,15 +1,12 @@
|
|
1
|
-
import {
|
2
|
-
import { Outlet
|
1
|
+
import { useCurrentRoute } from "@/hooks/use-current-route";
|
2
|
+
import { Outlet } from "react-router";
|
3
3
|
|
4
4
|
export function AppLayout() {
|
5
|
-
const
|
6
|
-
const currentPublicRoute = Object.values(privateRoutes).find(privateRoute => {
|
7
|
-
return privateRoute.path === pathname;
|
8
|
-
});
|
5
|
+
const currentRoute = useCurrentRoute();
|
9
6
|
|
10
7
|
return (
|
11
8
|
<>
|
12
|
-
{
|
9
|
+
{currentRoute && <title>{currentRoute.title}</title>}
|
13
10
|
|
14
11
|
<div>
|
15
12
|
<Outlet />
|
@@ -1,15 +1,12 @@
|
|
1
|
-
import {
|
2
|
-
import { Outlet
|
1
|
+
import { useCurrentRoute } from "@/hooks/use-current-route";
|
2
|
+
import { Outlet } from "react-router";
|
3
3
|
|
4
4
|
export function AuthLayout() {
|
5
|
-
const
|
6
|
-
const currentPublicRoute = Object.values(publicRoutes).find(publicRoute => {
|
7
|
-
return publicRoute.path === pathname;
|
8
|
-
});
|
5
|
+
const currentRoute = useCurrentRoute();
|
9
6
|
|
10
7
|
return (
|
11
8
|
<>
|
12
|
-
{
|
9
|
+
{currentRoute && <title>{currentRoute.title}</title>}
|
13
10
|
|
14
11
|
<div>
|
15
12
|
<Outlet />
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { DashboardPage } from "./pages/app/dashboard";
|
2
|
+
import { SignInPage } from "./pages/auth/sign-in";
|
3
|
+
import { publicEnv } from "./public-env";
|
4
|
+
|
5
|
+
export const publicRoutes = Object.freeze({
|
6
|
+
signIn: {
|
7
|
+
path: "/",
|
8
|
+
element: SignInPage,
|
9
|
+
title: createPageTitleTemplate("Sign In"),
|
10
|
+
label: "Sign In",
|
11
|
+
},
|
12
|
+
} as const);
|
13
|
+
|
14
|
+
export const privateRoutes = Object.freeze({
|
15
|
+
dashboard: {
|
16
|
+
path: "/dashboard",
|
17
|
+
element: DashboardPage,
|
18
|
+
title: createPageTitleTemplate("Dashboard"),
|
19
|
+
label: "Dashboard",
|
20
|
+
},
|
21
|
+
} as const);
|
22
|
+
|
23
|
+
export const routeGroups = [publicRoutes, privateRoutes] as const;
|
24
|
+
|
25
|
+
function createPageTitleTemplate(title: string) {
|
26
|
+
return `${title} | ${publicEnv.APP_NAME}`;
|
27
|
+
}
|
@@ -1,24 +0,0 @@
|
|
1
|
-
import { RouteObject } from "react-router";
|
2
|
-
import { OverrideProperties } from "type-fest";
|
3
|
-
|
4
|
-
export type CustomRouteDefinition = {
|
5
|
-
path: string;
|
6
|
-
label: string;
|
7
|
-
};
|
8
|
-
|
9
|
-
export type ReactRouterRouteDefinition<Routes extends CustomRouteDefinition[]> =
|
10
|
-
OverrideProperties<
|
11
|
-
RouteObject,
|
12
|
-
{
|
13
|
-
path: Routes[number]["path"] | "*";
|
14
|
-
handle: ReactRouterRouteHandleDefinition;
|
15
|
-
}
|
16
|
-
>;
|
17
|
-
|
18
|
-
export type ReactRouterRouteHandleDefinition = {
|
19
|
-
metadata: {
|
20
|
-
title: string;
|
21
|
-
description?: string;
|
22
|
-
};
|
23
|
-
[K: string]: unknown;
|
24
|
-
};
|
@@ -1,49 +0,0 @@
|
|
1
|
-
import { createBrowserRouter, RouteObject } from "react-router";
|
2
|
-
import { AppLayout } from "./pages/_layouts/app";
|
3
|
-
import { AuthLayout } from "./pages/_layouts/auth";
|
4
|
-
import { Dashboard } from "./pages/app/dashboard/dashboard";
|
5
|
-
import { SignIn } from "./pages/auth/sign-in";
|
6
|
-
import { publicEnv } from "./public-env";
|
7
|
-
|
8
|
-
export const publicRoutes = Object.freeze({
|
9
|
-
signIn: {
|
10
|
-
path: "/",
|
11
|
-
element: <SignIn />,
|
12
|
-
title: createTitleTemplate("Sign In"),
|
13
|
-
},
|
14
|
-
} as const);
|
15
|
-
|
16
|
-
export const privateRoutes = Object.freeze({
|
17
|
-
dashboard: {
|
18
|
-
path: "/dashboard",
|
19
|
-
element: <Dashboard />,
|
20
|
-
title: createTitleTemplate("Dashboard"),
|
21
|
-
},
|
22
|
-
} as const);
|
23
|
-
|
24
|
-
export const router = createBrowserRouter([
|
25
|
-
{
|
26
|
-
element: <AuthLayout />,
|
27
|
-
children: [
|
28
|
-
...Object.values(publicRoutes).map<RouteObject>(publicRoute => ({
|
29
|
-
path: publicRoute.path,
|
30
|
-
element: publicRoute.element,
|
31
|
-
index: true,
|
32
|
-
})),
|
33
|
-
],
|
34
|
-
},
|
35
|
-
{
|
36
|
-
element: <AppLayout />,
|
37
|
-
children: [
|
38
|
-
...Object.values(privateRoutes).map<RouteObject>(publicRoute => ({
|
39
|
-
path: publicRoute.path,
|
40
|
-
element: publicRoute.element,
|
41
|
-
index: true,
|
42
|
-
})),
|
43
|
-
],
|
44
|
-
},
|
45
|
-
]);
|
46
|
-
|
47
|
-
function createTitleTemplate(title: string) {
|
48
|
-
return `${title} | ${publicEnv.APP_NAME}`;
|
49
|
-
}
|