create-template-project 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/cli.mjs +272 -0
- package/dist/config/dependencies.json +156 -0
- package/dist/generators/project.mjs +354 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +32 -0
- package/dist/templates/base/files/.github/workflows/node.js.yml +20 -0
- package/dist/templates/base/files/.prettierrc.json +6 -0
- package/dist/templates/base/files/AGENTS.md +5 -0
- package/dist/templates/base/files/CONTRIBUTING.md +31 -0
- package/dist/templates/base/files/README.md +23 -0
- package/dist/templates/base/files/_oxlint.config.ts +74 -0
- package/dist/templates/base/files/commitlint.config.js +1 -0
- package/dist/templates/base/files/package.json +32 -0
- package/dist/templates/base/files/tsconfig.json +42 -0
- package/dist/templates/base/files/vitest.config.ts +3 -0
- package/dist/templates/base/index.mjs +16 -0
- package/dist/templates/cli/files/package.json +14 -0
- package/dist/templates/cli/files/src/index.test.ts +5 -0
- package/dist/templates/cli/files/src/index.ts +13 -0
- package/dist/templates/cli/files/tsdown.config.ts +3 -0
- package/dist/templates/cli/index.mjs +16 -0
- package/dist/templates/fullstack/files/client/index.html +8 -0
- package/dist/templates/fullstack/files/client/package.json +10 -0
- package/dist/templates/fullstack/files/client/src/App.test.tsx +8 -0
- package/dist/templates/fullstack/files/client/src/App.tsx +50 -0
- package/dist/templates/fullstack/files/client/src/components/ProtectedRoute.tsx +21 -0
- package/dist/templates/fullstack/files/client/src/contexts/AuthContext.tsx +63 -0
- package/dist/templates/fullstack/files/client/src/main.tsx +9 -0
- package/dist/templates/fullstack/files/client/src/pages/Dashboard.tsx +39 -0
- package/dist/templates/fullstack/files/client/src/pages/Login.tsx +81 -0
- package/dist/templates/fullstack/files/client/src/trpc.ts +4 -0
- package/dist/templates/fullstack/files/client/tsdown.config.ts +3 -0
- package/dist/templates/fullstack/files/package.json +35 -0
- package/dist/templates/fullstack/files/server/package.json +10 -0
- package/dist/templates/fullstack/files/server/src/context.ts +24 -0
- package/dist/templates/fullstack/files/server/src/index.test.ts +7 -0
- package/dist/templates/fullstack/files/server/src/index.ts +32 -0
- package/dist/templates/fullstack/files/server/src/routers/_app.ts +8 -0
- package/dist/templates/fullstack/files/server/src/routers/auth.ts +29 -0
- package/dist/templates/fullstack/files/server/src/trpc.ts +18 -0
- package/dist/templates/fullstack/files/server/tsdown.config.ts +3 -0
- package/dist/templates/fullstack/index.mjs +42 -0
- package/dist/templates/webapp/files/backend/src/index.ts +17 -0
- package/dist/templates/webapp/files/frontend/index.html +9 -0
- package/dist/templates/webapp/files/frontend/src/index.ts +4 -0
- package/dist/templates/webapp/files/package.json +13 -0
- package/dist/templates/webapp/files/src/index.test.ts +5 -0
- package/dist/templates/webapp/files/tsdown.config.ts +10 -0
- package/dist/templates/webapp/index.mjs +16 -0
- package/dist/templates/webpage/files/index.html +8 -0
- package/dist/templates/webpage/files/package.json +8 -0
- package/dist/templates/webpage/files/src/index.test.ts +5 -0
- package/dist/templates/webpage/files/src/index.ts +1 -0
- package/dist/templates/webpage/index.mjs +16 -0
- package/dist/types.mjs +30 -0
- package/dist/utils/file.mjs +101 -0
- package/package.json +79 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {Command} from 'commander';
|
|
2
|
+
import {SingleBar, Presets} from 'cli-progress';
|
|
3
|
+
|
|
4
|
+
const program = new Command();
|
|
5
|
+
program.name('my-cli').description('A sample CLI');
|
|
6
|
+
program.parse();
|
|
7
|
+
|
|
8
|
+
const bar = new SingleBar({}, Presets.shades_classic);
|
|
9
|
+
bar.start(100, 0);
|
|
10
|
+
bar.update(50);
|
|
11
|
+
bar.stop();
|
|
12
|
+
|
|
13
|
+
console.log('Hello from CLI template!');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/templates/cli/index.ts
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const getCliTemplate = (_opts) => {
|
|
6
|
+
return {
|
|
7
|
+
name: "cli",
|
|
8
|
+
dependencies: {},
|
|
9
|
+
devDependencies: {},
|
|
10
|
+
scripts: {},
|
|
11
|
+
files: [],
|
|
12
|
+
templateDir: path.resolve(__dirname, "files")
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { getCliTemplate };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {useState} from 'react';
|
|
2
|
+
import {BrowserRouter, Routes, Route, Navigate} from 'react-router-dom';
|
|
3
|
+
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
4
|
+
import {httpBatchLink} from '@trpc/client';
|
|
5
|
+
import {trpc} from './trpc.js';
|
|
6
|
+
import {AuthProvider} from './contexts/AuthContext.js';
|
|
7
|
+
import {ProtectedRoute} from './components/ProtectedRoute.js';
|
|
8
|
+
import {Login} from './pages/Login.js';
|
|
9
|
+
import {Dashboard} from './pages/Dashboard.js';
|
|
10
|
+
import {CssBaseline, ThemeProvider, createTheme} from '@mui/material';
|
|
11
|
+
|
|
12
|
+
const theme = createTheme();
|
|
13
|
+
|
|
14
|
+
export const App = () => {
|
|
15
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
16
|
+
const [trpcClient] = useState(() =>
|
|
17
|
+
trpc.createClient({
|
|
18
|
+
links: [
|
|
19
|
+
httpBatchLink({
|
|
20
|
+
url: 'http://localhost:3001/trpc',
|
|
21
|
+
headers: () => {
|
|
22
|
+
const token = localStorage.getItem('token');
|
|
23
|
+
return token ? {Authorization: `Bearer ${token}`} : {};
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
|
32
|
+
<QueryClientProvider client={queryClient}>
|
|
33
|
+
<ThemeProvider theme={theme}>
|
|
34
|
+
<CssBaseline />
|
|
35
|
+
<AuthProvider>
|
|
36
|
+
<BrowserRouter>
|
|
37
|
+
<Routes>
|
|
38
|
+
<Route path="/login" element={<Login />} />
|
|
39
|
+
<Route element={<ProtectedRoute />}>
|
|
40
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
41
|
+
</Route>
|
|
42
|
+
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
|
43
|
+
</Routes>
|
|
44
|
+
</BrowserRouter>
|
|
45
|
+
</AuthProvider>
|
|
46
|
+
</ThemeProvider>
|
|
47
|
+
</QueryClientProvider>
|
|
48
|
+
</trpc.Provider>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {Navigate, Outlet} from 'react-router-dom';
|
|
2
|
+
import {useAuth} from '../contexts/AuthContext.js';
|
|
3
|
+
import {CircularProgress, Box} from '@mui/material';
|
|
4
|
+
|
|
5
|
+
export const ProtectedRoute = () => {
|
|
6
|
+
const {token, isLoading} = useAuth();
|
|
7
|
+
|
|
8
|
+
if (isLoading) {
|
|
9
|
+
return (
|
|
10
|
+
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
|
|
11
|
+
<CircularProgress />
|
|
12
|
+
</Box>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!token) {
|
|
17
|
+
return <Navigate to="/login" replace />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <Outlet />;
|
|
21
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {createContext, useContext, useState, useEffect, ReactNode} from 'react';
|
|
2
|
+
import {trpc} from '../trpc.js';
|
|
3
|
+
|
|
4
|
+
interface User {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
email: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface AuthContextType {
|
|
11
|
+
user: User | null;
|
|
12
|
+
token: string | null;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
login: (token: string, user: User) => void;
|
|
15
|
+
logout: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
19
|
+
|
|
20
|
+
export const AuthProvider = ({children}: {children: ReactNode}) => {
|
|
21
|
+
const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
|
|
22
|
+
const [user, setUser] = useState<User | null>(null);
|
|
23
|
+
|
|
24
|
+
const {data: me, isLoading} = trpc.auth.me.useQuery(undefined, {
|
|
25
|
+
enabled: !!token,
|
|
26
|
+
retry: false,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (me) {
|
|
31
|
+
setUser(me);
|
|
32
|
+
} else if (!isLoading && token) {
|
|
33
|
+
// Token might be invalid
|
|
34
|
+
logout();
|
|
35
|
+
}
|
|
36
|
+
}, [me, isLoading, token]);
|
|
37
|
+
|
|
38
|
+
const login = (newToken: string, newUser: User) => {
|
|
39
|
+
localStorage.setItem('token', newToken);
|
|
40
|
+
setToken(newToken);
|
|
41
|
+
setUser(newUser);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const logout = () => {
|
|
45
|
+
localStorage.removeItem('token');
|
|
46
|
+
setToken(null);
|
|
47
|
+
setUser(null);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<AuthContext.Provider value={{user, token, isLoading, login, logout}}>
|
|
52
|
+
{children}
|
|
53
|
+
</AuthContext.Provider>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const useAuth = () => {
|
|
58
|
+
const context = useContext(AuthContext);
|
|
59
|
+
if (!context) {
|
|
60
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
61
|
+
}
|
|
62
|
+
return context;
|
|
63
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {Container, Typography, Button, Paper, Box} from '@mui/material';
|
|
2
|
+
import {useAuth} from '../contexts/AuthContext.js';
|
|
3
|
+
import {useNavigate} from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
export const Dashboard = () => {
|
|
6
|
+
const {user, logout} = useAuth();
|
|
7
|
+
const navigate = useNavigate();
|
|
8
|
+
|
|
9
|
+
const handleLogout = () => {
|
|
10
|
+
logout();
|
|
11
|
+
void navigate('/login');
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Container maxWidth="md">
|
|
16
|
+
<Box mt={4}>
|
|
17
|
+
<Paper elevation={3}>
|
|
18
|
+
<Box p={4}>
|
|
19
|
+
<Typography variant="h4" gutterBottom>
|
|
20
|
+
Dashboard
|
|
21
|
+
</Typography>
|
|
22
|
+
<Typography variant="h6">Welcome, {user?.name}!</Typography>
|
|
23
|
+
<Typography variant="body1" color="textSecondary" gutterBottom>
|
|
24
|
+
Email: {user?.email}
|
|
25
|
+
</Typography>
|
|
26
|
+
<Box mt={4}>
|
|
27
|
+
<Typography variant="body2" gutterBottom>
|
|
28
|
+
This is a protected page. You can only see this because you are logged in.
|
|
29
|
+
</Typography>
|
|
30
|
+
<Button variant="outlined" color="primary" onClick={handleLogout}>
|
|
31
|
+
Logout
|
|
32
|
+
</Button>
|
|
33
|
+
</Box>
|
|
34
|
+
</Box>
|
|
35
|
+
</Paper>
|
|
36
|
+
</Box>
|
|
37
|
+
</Container>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {useState, FormEvent} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
TextField,
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogActions,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
Typography,
|
|
10
|
+
Alert,
|
|
11
|
+
Box,
|
|
12
|
+
} from '@mui/material';
|
|
13
|
+
import {useAuth} from '../contexts/AuthContext.js';
|
|
14
|
+
import {trpc} from '../trpc.js';
|
|
15
|
+
import {useNavigate} from 'react-router-dom';
|
|
16
|
+
|
|
17
|
+
export const Login = () => {
|
|
18
|
+
const [email, setEmail] = useState('demo@example.com');
|
|
19
|
+
const [password, setPassword] = useState('password');
|
|
20
|
+
const [error, setError] = useState<string | null>(null);
|
|
21
|
+
const {login} = useAuth();
|
|
22
|
+
const navigate = useNavigate();
|
|
23
|
+
|
|
24
|
+
const loginMutation = trpc.auth.login.useMutation({
|
|
25
|
+
onSuccess: (data) => {
|
|
26
|
+
login(data.token, data.user);
|
|
27
|
+
void navigate('/dashboard');
|
|
28
|
+
},
|
|
29
|
+
onError: (err) => {
|
|
30
|
+
setError(err.message);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const handleSubmit = (e: FormEvent) => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
setError(null);
|
|
37
|
+
loginMutation.mutate({email, password});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Dialog open fullWidth maxWidth="xs">
|
|
42
|
+
<form onSubmit={handleSubmit}>
|
|
43
|
+
<DialogTitle>Login</DialogTitle>
|
|
44
|
+
<DialogContent>
|
|
45
|
+
<Box display="flex" flexDirection="column" gap={2} pt={1}>
|
|
46
|
+
{error && <Alert severity="error">{error}</Alert>}
|
|
47
|
+
<Typography variant="body2" color="textSecondary">
|
|
48
|
+
Use <b>demo@example.com</b> / <b>password</b> to login.
|
|
49
|
+
</Typography>
|
|
50
|
+
<TextField
|
|
51
|
+
label="Email"
|
|
52
|
+
type="email"
|
|
53
|
+
fullWidth
|
|
54
|
+
required
|
|
55
|
+
value={email}
|
|
56
|
+
onChange={(e) => setEmail(e.currentTarget.value)}
|
|
57
|
+
/>
|
|
58
|
+
<TextField
|
|
59
|
+
label="Password"
|
|
60
|
+
type="password"
|
|
61
|
+
fullWidth
|
|
62
|
+
required
|
|
63
|
+
value={password}
|
|
64
|
+
onChange={(e) => setPassword(e.currentTarget.value)}
|
|
65
|
+
/>
|
|
66
|
+
</Box>
|
|
67
|
+
</DialogContent>
|
|
68
|
+
<DialogActions>
|
|
69
|
+
<Button
|
|
70
|
+
type="submit"
|
|
71
|
+
variant="contained"
|
|
72
|
+
fullWidth
|
|
73
|
+
disabled={loginMutation.isPending}
|
|
74
|
+
>
|
|
75
|
+
{loginMutation.isPending ? 'Logging in...' : 'Login'}
|
|
76
|
+
</Button>
|
|
77
|
+
</DialogActions>
|
|
78
|
+
</form>
|
|
79
|
+
</Dialog>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workspaces": [
|
|
3
|
+
"client",
|
|
4
|
+
"server"
|
|
5
|
+
],
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"react": "",
|
|
8
|
+
"react-dom": "",
|
|
9
|
+
"@mui/material": "",
|
|
10
|
+
"@mui/icons-material": "",
|
|
11
|
+
"@emotion/react": "",
|
|
12
|
+
"@emotion/styled": "",
|
|
13
|
+
"express": "",
|
|
14
|
+
"@trpc/server": "",
|
|
15
|
+
"@trpc/client": "",
|
|
16
|
+
"@trpc/react-query": "",
|
|
17
|
+
"@tanstack/react-query": "",
|
|
18
|
+
"zod": "",
|
|
19
|
+
"react-router-dom": "",
|
|
20
|
+
"cors": ""
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/react": "",
|
|
24
|
+
"@types/react-dom": "",
|
|
25
|
+
"@types/express": "",
|
|
26
|
+
"@types/cors": "",
|
|
27
|
+
"@playwright/test": "",
|
|
28
|
+
"tsdown": ""
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "npm run build --workspaces",
|
|
32
|
+
"dev": "npm run dev --workspaces",
|
|
33
|
+
"test:e2e": "playwright test"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {CreateExpressContextOptions} from '@trpc/server/adapters/express';
|
|
2
|
+
|
|
3
|
+
interface User {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
email: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const createContext = ({req}: CreateExpressContextOptions) => {
|
|
10
|
+
const authHeader = req.headers.authorization;
|
|
11
|
+
let user: User | null = null;
|
|
12
|
+
|
|
13
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
14
|
+
const token = authHeader.split(' ')[1];
|
|
15
|
+
// Mock token verification
|
|
16
|
+
if (token === 'mock-token') {
|
|
17
|
+
user = {id: '1', name: 'Demo User', email: 'demo@example.com'};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {user};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type Context = Awaited<ReturnType<typeof createContext>>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
4
|
+
import {appRouter} from './routers/_app.js';
|
|
5
|
+
import {createContext} from './context.js';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const app = express();
|
|
11
|
+
const port = process.env['PORT'] || 3001;
|
|
12
|
+
|
|
13
|
+
app.use(cors());
|
|
14
|
+
app.use(express.json());
|
|
15
|
+
|
|
16
|
+
app.use(
|
|
17
|
+
'/trpc',
|
|
18
|
+
trpcExpress.createExpressMiddleware({
|
|
19
|
+
router: appRouter,
|
|
20
|
+
createContext,
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
app.use(express.static(path.join(__dirname, '../../client/dist')));
|
|
25
|
+
|
|
26
|
+
app.get('*', (_req, res) => {
|
|
27
|
+
res.sendFile(path.join(__dirname, '../../client/dist/index.html'));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
app.listen(port, () => {
|
|
31
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
32
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {z} from 'zod';
|
|
2
|
+
import {publicProcedure, protectedProcedure, router} from '../trpc.js';
|
|
3
|
+
import {TRPCError} from '@trpc/server';
|
|
4
|
+
|
|
5
|
+
export const authRouter = router({
|
|
6
|
+
login: publicProcedure
|
|
7
|
+
.input(
|
|
8
|
+
z.object({
|
|
9
|
+
email: z.string().email(),
|
|
10
|
+
password: z.string().min(6),
|
|
11
|
+
}),
|
|
12
|
+
)
|
|
13
|
+
.mutation(async ({input}) => {
|
|
14
|
+
// Mock authentication logic
|
|
15
|
+
if (input.email === 'demo@example.com' && input.password === 'password') {
|
|
16
|
+
return {
|
|
17
|
+
token: 'mock-token',
|
|
18
|
+
user: {id: '1', name: 'Demo User', email: 'demo@example.com'},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
throw new TRPCError({
|
|
22
|
+
code: 'UNAUTHORIZED',
|
|
23
|
+
message: 'Invalid email or password',
|
|
24
|
+
});
|
|
25
|
+
}),
|
|
26
|
+
me: protectedProcedure.query(({ctx}) => {
|
|
27
|
+
return ctx.user;
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {initTRPC, TRPCError} from '@trpc/server';
|
|
2
|
+
import {Context} from './context.js';
|
|
3
|
+
|
|
4
|
+
const t = initTRPC.context<Context>().create();
|
|
5
|
+
|
|
6
|
+
export const router = t.router;
|
|
7
|
+
export const publicProcedure = t.procedure;
|
|
8
|
+
|
|
9
|
+
export const protectedProcedure = t.procedure.use(({ctx, next}) => {
|
|
10
|
+
if (!ctx.user) {
|
|
11
|
+
throw new TRPCError({code: 'UNAUTHORIZED'});
|
|
12
|
+
}
|
|
13
|
+
return next({
|
|
14
|
+
ctx: {
|
|
15
|
+
user: ctx.user,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/templates/fullstack/index.ts
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const getFullstackTemplate = (_opts) => {
|
|
6
|
+
return {
|
|
7
|
+
name: "fullstack",
|
|
8
|
+
dependencies: {
|
|
9
|
+
react: "react",
|
|
10
|
+
"react-dom": "react-dom",
|
|
11
|
+
"@mui/material": "@mui/material",
|
|
12
|
+
"@mui/icons-material": "@mui/icons-material",
|
|
13
|
+
"@emotion/react": "@emotion/react",
|
|
14
|
+
"@emotion/styled": "@emotion/styled",
|
|
15
|
+
express: "express",
|
|
16
|
+
"@trpc/server": "@trpc/server",
|
|
17
|
+
"@trpc/client": "@trpc/client",
|
|
18
|
+
"@trpc/react-query": "@trpc/react-query",
|
|
19
|
+
"@tanstack/react-query": "@tanstack/react-query",
|
|
20
|
+
zod: "zod",
|
|
21
|
+
"react-router-dom": "react-router-dom",
|
|
22
|
+
cors: "cors"
|
|
23
|
+
},
|
|
24
|
+
devDependencies: {
|
|
25
|
+
"@types/react": "@types/react",
|
|
26
|
+
"@types/react-dom": "@types/react-dom",
|
|
27
|
+
"@types/express": "@types/express",
|
|
28
|
+
"@types/cors": "@types/cors",
|
|
29
|
+
"@playwright/test": "@playwright/test",
|
|
30
|
+
tsdown: "tsdown"
|
|
31
|
+
},
|
|
32
|
+
scripts: {
|
|
33
|
+
build: "npm run build --workspaces",
|
|
34
|
+
dev: "npm run dev --workspaces",
|
|
35
|
+
"test:e2e": "playwright test"
|
|
36
|
+
},
|
|
37
|
+
files: [],
|
|
38
|
+
templateDir: path.resolve(__dirname, "files")
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { getFullstackTemplate };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const app = express();
|
|
7
|
+
const port = process.env.PORT || 3000;
|
|
8
|
+
|
|
9
|
+
app.use(express.static(path.join(__dirname, '../../frontend/dist')));
|
|
10
|
+
|
|
11
|
+
app.get('/api/hello', (req, res) => {
|
|
12
|
+
res.json({message: 'Hello from Express!'});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
app.listen(port, () => {
|
|
16
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
17
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/templates/webapp/index.ts
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const getWebappTemplate = (_opts) => {
|
|
6
|
+
return {
|
|
7
|
+
name: "webapp",
|
|
8
|
+
dependencies: {},
|
|
9
|
+
devDependencies: {},
|
|
10
|
+
scripts: {},
|
|
11
|
+
files: [],
|
|
12
|
+
templateDir: path.resolve(__dirname, "files")
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { getWebappTemplate };
|