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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/dist/cli.mjs +272 -0
  4. package/dist/config/dependencies.json +156 -0
  5. package/dist/generators/project.mjs +354 -0
  6. package/dist/index.d.mts +4 -0
  7. package/dist/index.mjs +32 -0
  8. package/dist/templates/base/files/.github/workflows/node.js.yml +20 -0
  9. package/dist/templates/base/files/.prettierrc.json +6 -0
  10. package/dist/templates/base/files/AGENTS.md +5 -0
  11. package/dist/templates/base/files/CONTRIBUTING.md +31 -0
  12. package/dist/templates/base/files/README.md +23 -0
  13. package/dist/templates/base/files/_oxlint.config.ts +74 -0
  14. package/dist/templates/base/files/commitlint.config.js +1 -0
  15. package/dist/templates/base/files/package.json +32 -0
  16. package/dist/templates/base/files/tsconfig.json +42 -0
  17. package/dist/templates/base/files/vitest.config.ts +3 -0
  18. package/dist/templates/base/index.mjs +16 -0
  19. package/dist/templates/cli/files/package.json +14 -0
  20. package/dist/templates/cli/files/src/index.test.ts +5 -0
  21. package/dist/templates/cli/files/src/index.ts +13 -0
  22. package/dist/templates/cli/files/tsdown.config.ts +3 -0
  23. package/dist/templates/cli/index.mjs +16 -0
  24. package/dist/templates/fullstack/files/client/index.html +8 -0
  25. package/dist/templates/fullstack/files/client/package.json +10 -0
  26. package/dist/templates/fullstack/files/client/src/App.test.tsx +8 -0
  27. package/dist/templates/fullstack/files/client/src/App.tsx +50 -0
  28. package/dist/templates/fullstack/files/client/src/components/ProtectedRoute.tsx +21 -0
  29. package/dist/templates/fullstack/files/client/src/contexts/AuthContext.tsx +63 -0
  30. package/dist/templates/fullstack/files/client/src/main.tsx +9 -0
  31. package/dist/templates/fullstack/files/client/src/pages/Dashboard.tsx +39 -0
  32. package/dist/templates/fullstack/files/client/src/pages/Login.tsx +81 -0
  33. package/dist/templates/fullstack/files/client/src/trpc.ts +4 -0
  34. package/dist/templates/fullstack/files/client/tsdown.config.ts +3 -0
  35. package/dist/templates/fullstack/files/package.json +35 -0
  36. package/dist/templates/fullstack/files/server/package.json +10 -0
  37. package/dist/templates/fullstack/files/server/src/context.ts +24 -0
  38. package/dist/templates/fullstack/files/server/src/index.test.ts +7 -0
  39. package/dist/templates/fullstack/files/server/src/index.ts +32 -0
  40. package/dist/templates/fullstack/files/server/src/routers/_app.ts +8 -0
  41. package/dist/templates/fullstack/files/server/src/routers/auth.ts +29 -0
  42. package/dist/templates/fullstack/files/server/src/trpc.ts +18 -0
  43. package/dist/templates/fullstack/files/server/tsdown.config.ts +3 -0
  44. package/dist/templates/fullstack/index.mjs +42 -0
  45. package/dist/templates/webapp/files/backend/src/index.ts +17 -0
  46. package/dist/templates/webapp/files/frontend/index.html +9 -0
  47. package/dist/templates/webapp/files/frontend/src/index.ts +4 -0
  48. package/dist/templates/webapp/files/package.json +13 -0
  49. package/dist/templates/webapp/files/src/index.test.ts +5 -0
  50. package/dist/templates/webapp/files/tsdown.config.ts +10 -0
  51. package/dist/templates/webapp/index.mjs +16 -0
  52. package/dist/templates/webpage/files/index.html +8 -0
  53. package/dist/templates/webpage/files/package.json +8 -0
  54. package/dist/templates/webpage/files/src/index.test.ts +5 -0
  55. package/dist/templates/webpage/files/src/index.ts +1 -0
  56. package/dist/templates/webpage/index.mjs +16 -0
  57. package/dist/types.mjs +30 -0
  58. package/dist/utils/file.mjs +101 -0
  59. package/package.json +79 -0
@@ -0,0 +1,5 @@
1
+ import {expect, test} from 'vitest';
2
+
3
+ test('math works', () => {
4
+ expect(1 + 1).toBe(2);
5
+ });
@@ -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,3 @@
1
+ import {defineConfig} from 'tsdown';
2
+
3
+ export default defineConfig({entry: ['./src/index.ts'], format: ['esm'], clean: true, dts: true});
@@ -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,8 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Server Template - Client</title></head>
4
+ <body>
5
+ <div id="root"></div>
6
+ <script src="./dist/index.js"></script>
7
+ </body>
8
+ </html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "client",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsdown",
7
+ "dev": "tsdown --watch",
8
+ "test": "vitest run"
9
+ }
10
+ }
@@ -0,0 +1,8 @@
1
+ import {describe, it, expect} from 'vitest';
2
+ import {App} from './App.js';
3
+
4
+ describe('App', () => {
5
+ it('should be a function', () => {
6
+ expect(typeof App).toBe('function');
7
+ });
8
+ });
@@ -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,9 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import {App} from './App.js';
4
+
5
+ ReactDOM.createRoot(document.getElementById('root')!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>,
9
+ );
@@ -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,4 @@
1
+ import {createTRPCReact} from '@trpc/react-query';
2
+ import type {AppRouter} from '../../server/src/routers/_app.js';
3
+
4
+ export const trpc = createTRPCReact<AppRouter>();
@@ -0,0 +1,3 @@
1
+ import {defineConfig} from 'tsdown';
2
+
3
+ export default defineConfig({entry: ['./src/main.tsx'], format: ['esm'], clean: true});
@@ -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,10 @@
1
+ {
2
+ "name": "server",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsdown",
7
+ "dev": "tsdown --watch",
8
+ "test": "vitest run"
9
+ }
10
+ }
@@ -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,7 @@
1
+ import {describe, it, expect} from 'vitest';
2
+
3
+ describe('server', () => {
4
+ it('should have a truthy test', () => {
5
+ expect(true).toBe(true);
6
+ });
7
+ });
@@ -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,8 @@
1
+ import {router} from '../trpc.js';
2
+ import {authRouter} from './auth.js';
3
+
4
+ export const appRouter = router({
5
+ auth: authRouter,
6
+ });
7
+
8
+ export type AppRouter = typeof appRouter;
@@ -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,3 @@
1
+ import {defineConfig} from 'tsdown';
2
+
3
+ export default defineConfig({entry: ['./src/index.ts'], format: ['esm'], clean: true});
@@ -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,9 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Vanilla JS Template</title></head>
4
+ <body>
5
+ <h1>Vanilla JS Template</h1>
6
+ <div id="app"></div>
7
+ <script src="./dist/index.js"></script>
8
+ </body>
9
+ </html>
@@ -0,0 +1,4 @@
1
+ console.log('Hello from Vanilla JS Frontend!');
2
+ void fetch('/api/hello')
3
+ .then((res) => res.json())
4
+ .then((data) => console.log(data.message));
@@ -0,0 +1,13 @@
1
+ {
2
+ "dependencies": {
3
+ "express": ""
4
+ },
5
+ "devDependencies": {
6
+ "@types/express": "",
7
+ "tsdown": ""
8
+ },
9
+ "scripts": {
10
+ "build": "tsdown",
11
+ "start": "node dist/server/index.js"
12
+ }
13
+ }
@@ -0,0 +1,5 @@
1
+ import {expect, test} from 'vitest';
2
+
3
+ test('math works', () => {
4
+ expect(1 + 1).toBe(2);
5
+ });
@@ -0,0 +1,10 @@
1
+ import {defineConfig} from 'tsdown';
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ server: 'backend/src/index.ts',
6
+ client: 'frontend/src/index.ts',
7
+ },
8
+ format: ['esm'],
9
+ clean: true,
10
+ });
@@ -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 };
@@ -0,0 +1,8 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Browser Template</title></head>
4
+ <body>
5
+ <h1>Browser Template</h1>
6
+ <script src="{{scriptSrc}}"></script>
7
+ </body>
8
+ </html>