init repo
This commit is contained in:
commit
f8f94217ee
|
@ -0,0 +1,6 @@
|
|||
APP_DEBUG=true
|
||||
|
||||
DB_PASS=db
|
||||
DB_NAME=db
|
||||
DB_USER=db
|
||||
DB_HOST=db
|
|
@ -0,0 +1,5 @@
|
|||
/docker-compose.yml
|
||||
/.env
|
||||
|
||||
node_modules
|
||||
dist
|
|
@ -0,0 +1,7 @@
|
|||
# digital.solutions.test
|
||||
to start up
|
||||
|
||||
```
|
||||
cp docker-compose.yml.dev docker-compose.yml
|
||||
docker-compose up -d
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to docker",
|
||||
"restart": true,
|
||||
"localRoot": ".",
|
||||
"remoteRoot": "/app",
|
||||
"address": "localhost",
|
||||
"port": 9229
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "back",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"license": "GPL-3.0-only",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/node": "^22.9.0",
|
||||
"esbuild": "^0.24.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node scripts/build.js",
|
||||
"start": "yarn build && node dist/index.js",
|
||||
"typeorm": "yarn build && typeorm -d dist/typeorm/data-source.repo.js",
|
||||
"docker": "docker-compose exec back yarn"
|
||||
},
|
||||
"dependencies": {
|
||||
"connect-typeorm": "^2.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"pg": "^8.13.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"typeorm": "^0.3.20"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import esbuild from 'esbuild';
|
||||
import fsp from 'node:fs/promises';
|
||||
|
||||
fsp.rm('dist', { recursive: true, force: true });
|
||||
fsp.mkdir('dist', { recursive: true });
|
||||
const src = await fsp.readdir('src', { recursive: true });
|
||||
const filtered = src.filter(x => x.endsWith('.ts')).map(x => 'src/' + x);
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: filtered,
|
||||
outdir: 'dist',
|
||||
tsconfig: 'tsconfig.json'
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config({
|
||||
path: [
|
||||
'/.env.global',
|
||||
'/app/.env'
|
||||
]
|
||||
})
|
|
@ -0,0 +1,36 @@
|
|||
import express from 'express';
|
||||
import session from 'express-session';
|
||||
import 'reflect-metadata';
|
||||
|
||||
import routes from './routes.js';
|
||||
import dataSourceRepo from './typeorm/data-source.repo.js';
|
||||
|
||||
import './env.js';
|
||||
import { TypeORMError } from 'typeorm';
|
||||
import { TypeormStore } from 'connect-typeorm';
|
||||
|
||||
const listen_host = process.env.LISTEN_HOST ?? '0.0.0.0';
|
||||
const listen_port = parseInt(process.env.LISTEN_PORT ?? '80');
|
||||
|
||||
await dataSourceRepo.initialize();
|
||||
await dataSourceRepo.runMigrations();
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
app.use(session({
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: new TypeormStore({
|
||||
cleanupLimit: 2,
|
||||
limitSubquery: false,
|
||||
ttl: 3600
|
||||
}),
|
||||
secret: 'keyboard cat'
|
||||
}));
|
||||
|
||||
app.use('/api', routes());
|
||||
|
||||
app.listen(listen_port, listen_host, () => {
|
||||
console.log(`Listening on ${listen_host}:${listen_port}`)
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { Router } from 'express';
|
||||
|
||||
import data from './routes/data.js';
|
||||
import selection from './routes/selection.js';
|
||||
|
||||
export default function(): Router {
|
||||
const app = Router();
|
||||
app.use('/data', data());
|
||||
app.use('/selection', selection())
|
||||
|
||||
return app;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { IRouter, Router } from 'express';
|
||||
import dataSourceRepo from '../typeorm/data-source.repo.js';
|
||||
import { DataRow } from '../typeorm/entity/DataRow.entity.js';
|
||||
|
||||
export default function(): IRouter {
|
||||
const app = Router();
|
||||
app.get('/list', async (req, res) => {
|
||||
const [page, pageSz, search] = [
|
||||
parseInt(req.query.page as string | undefined),
|
||||
parseInt((req.query.page_sz ?? '20').toString()),
|
||||
req.query.search as string | undefined
|
||||
];
|
||||
if (pageSz <= 0) {
|
||||
res.status(400);
|
||||
res.send('invalid page_sz');
|
||||
return;
|
||||
}
|
||||
|
||||
const query = dataSourceRepo.getRepository(DataRow).createQueryBuilder('data');
|
||||
if (!Number.isNaN(page)) {
|
||||
query.take(pageSz);
|
||||
query.skip(page * pageSz);
|
||||
}
|
||||
if (search !== undefined) {
|
||||
query.where('data like :search', { search });
|
||||
}
|
||||
|
||||
const data = await query.getMany();
|
||||
res.send(data);
|
||||
})
|
||||
|
||||
app.put('/', async (req, res) => {
|
||||
const data = req.body.data;
|
||||
if (!data) {
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const row = new DataRow();
|
||||
row.data = data;
|
||||
await dataSourceRepo.getRepository(DataRow).insert(row);
|
||||
|
||||
res.status(200);
|
||||
res.send('');
|
||||
return;
|
||||
})
|
||||
|
||||
return app;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { IRouter, Router } from "express";
|
||||
|
||||
export default function(): IRouter {
|
||||
const app = Router();
|
||||
return app;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { DataSource } from "typeorm";
|
||||
import '../env.js';
|
||||
|
||||
export default new DataSource({
|
||||
type: 'postgres',
|
||||
host: process.env.DB_HOST,
|
||||
port: 5432,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME,
|
||||
entities: [ import.meta.dirname + "/entity/*.js" ],
|
||||
migrations: [ import.meta.dirname + "/migration/*.js" ],
|
||||
logging: true,
|
||||
synchronize: true
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import 'reflect-metadata';
|
||||
|
||||
@Entity()
|
||||
export class DataRow {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column('varchar')
|
||||
data: string
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { Column, DeleteDateColumn, Entity, Index, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class Session {
|
||||
@Index()
|
||||
@Column('bigint')
|
||||
public expiredAt = Date.now();
|
||||
|
||||
@PrimaryColumn('varchar', { length: 255 })
|
||||
public id = '';
|
||||
|
||||
@Column('text')
|
||||
public json = '';
|
||||
|
||||
@DeleteDateColumn()
|
||||
public destroyedAt?: Date;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableColumn } from "typeorm";
|
||||
|
||||
export class Datarow1731074164993 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'DataRow',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'bigint',
|
||||
isGenerated: true,
|
||||
isPrimary: true,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
type: 'varchar',
|
||||
isNullable: false
|
||||
}
|
||||
]
|
||||
}),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
queryRunner.dropTable('DataRow', true)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableColumn } from "typeorm";
|
||||
|
||||
export class Session1731074164994 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'Session',
|
||||
columns: [
|
||||
{
|
||||
name: 'expiredAt',
|
||||
type: 'bigint',
|
||||
isGenerated: true,
|
||||
isPrimary: true,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
type: 'varchar',
|
||||
length: '255',
|
||||
isNullable: false,
|
||||
isUnique: true
|
||||
},
|
||||
{
|
||||
name: 'json',
|
||||
type: 'varchar',
|
||||
isNullable: false
|
||||
},
|
||||
{
|
||||
name: 'destroyedAt',
|
||||
type: 'date',
|
||||
isNullable: true
|
||||
}
|
||||
]
|
||||
}),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
queryRunner.dropTable('Session', true)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
yarn
|
||||
yarn build
|
||||
node dist/index.js --inspect=0.0.0.0:9229
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es5", "es6", "dom"],
|
||||
"target": "es2017",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
:80 {
|
||||
route /api/* {
|
||||
reverse_proxy http://back
|
||||
}
|
||||
reverse_proxy http://front
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
services:
|
||||
back:
|
||||
image: node:22-alpine3.20
|
||||
# restart: always
|
||||
ports:
|
||||
- 9229:9229
|
||||
networks:
|
||||
internal:
|
||||
aliases:
|
||||
- back
|
||||
entrypoint: '/app/start.sh'
|
||||
working_dir: '/app'
|
||||
volumes:
|
||||
- './back:/app'
|
||||
- 'back-node-modules:/app/node_modules'
|
||||
- './.env:/.env.global:ro'
|
||||
front:
|
||||
build:
|
||||
context: front
|
||||
dockerfile: Dockerfile.dev
|
||||
networks:
|
||||
internal:
|
||||
aliases:
|
||||
- front
|
||||
volumes:
|
||||
- './front:/app'
|
||||
- 'front-node-modules:/app/node_modules'
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
volumes:
|
||||
- 'db-data:/var/lib/postgresql'
|
||||
environment:
|
||||
POSTGRES_PASSWORD: '${DB_PASS}'
|
||||
POSTGRES_USER: '${DB_USER}'
|
||||
POSTGRES_DB: '${DB_NAME}'
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
internal:
|
||||
aliases:
|
||||
- '${DB_HOST}'
|
||||
server:
|
||||
image: caddy:2.8.4-alpine
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- './config/caddy:/etc/caddy:ro'
|
||||
- './volatile/caddy/log:/var/log/caddy'
|
||||
networks:
|
||||
internal:
|
||||
|
||||
networks:
|
||||
internal:
|
||||
volumes:
|
||||
'back-node-modules':
|
||||
'front-node-modules':
|
||||
'db-data':
|
|
@ -0,0 +1,3 @@
|
|||
Dockerfile
|
||||
Dockerfile.dev
|
||||
.dockerignore
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,8 @@
|
|||
FROM node:22-alpine3.20
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
RUN yarn
|
||||
|
||||
CMD [ "yarn", "dev", "--host=front", "--port=80" ]
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Preact + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "front",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ky": "^1.7.2",
|
||||
"preact": "^10.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.9.1",
|
||||
"sass": "^1.80.6",
|
||||
"sass-embedded": "^1.80.6",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import ky from "ky";
|
||||
|
||||
export type DataRowSearchOptions = {
|
||||
page?: number,
|
||||
page_sz?: number,
|
||||
search?: string
|
||||
}
|
||||
|
||||
export class DataRow {
|
||||
constructor(
|
||||
public id: number,
|
||||
public data: string
|
||||
) { }
|
||||
|
||||
static reviveJSON(data: { id: number, data: string }): DataRow {
|
||||
return new DataRow(data.id, data.data);
|
||||
}
|
||||
|
||||
static async find(options?: DataRowSearchOptions): Promise<DataRow[]> {
|
||||
const req = await ky('/api/data/list', {
|
||||
searchParams: options
|
||||
});
|
||||
|
||||
const raw = await req.json<any[]>();
|
||||
return raw.map(this.reviveJSON);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { Index } from "./pages";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<>
|
||||
<h1>4chan ripoff</h1>
|
||||
<Index />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.posts {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { DataRow } from "../api/DataRow";
|
||||
import style from './Posts.module.scss';
|
||||
import { Post } from "./display/Post";
|
||||
|
||||
export type PostsProps = { rows: DataRow[] };
|
||||
|
||||
export function Posts({ rows }: PostsProps) {
|
||||
return (
|
||||
<div className={style.posts}>
|
||||
{ rows.map(row => <Post row={row} />) }
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { TargetedEvent } from 'preact/compat';
|
||||
|
||||
export type PaginatorProps = {
|
||||
page: number,
|
||||
onChange: (page: number) => void,
|
||||
className?: string
|
||||
};
|
||||
export function Paginator({page, onChange, className}: PaginatorProps) {
|
||||
function middleware(e: TargetedEvent<HTMLInputElement, Event>) {
|
||||
onChange(parseInt(e.currentTarget.value));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<label for='page'>Page: </label>
|
||||
<input type='number' id='page' value={page} onChange={middleware}/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.post {
|
||||
border: 1px solid gray;
|
||||
padding: 0.5rem;
|
||||
margin: 0.5rem 0;
|
||||
pre {
|
||||
padding: 0; margin: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { DataRow } from "../../api/DataRow";
|
||||
import style from './Post.module.scss';
|
||||
|
||||
export type PostProps = { row: DataRow };
|
||||
export function Post({ row }: PostProps) {
|
||||
return (
|
||||
<div id={`post-${row.id}`} className={style.post}>
|
||||
<pre>{row.data}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import { render } from 'preact'
|
||||
import { App } from './app.tsx'
|
||||
|
||||
render(<App />, document.getElementById('app')!)
|
|
@ -0,0 +1,6 @@
|
|||
.index {
|
||||
.paginator {
|
||||
border-bottom: 1px solid black;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import { useEffect, useState } from "preact/hooks";
|
||||
import { DataRow } from "../api/DataRow";
|
||||
import style from './index.module.scss';
|
||||
import { Posts } from "../components/Posts";
|
||||
import { Paginator } from "../components/display/Paginator";
|
||||
|
||||
const cachedPages: {
|
||||
[ key: number ]: DataRow[]
|
||||
} = {};
|
||||
|
||||
let firstRender = true;
|
||||
|
||||
export function Index() {
|
||||
const [ data, setData ] = useState(null as null | DataRow[]);
|
||||
const [ page, setPage ] = useState(NaN);
|
||||
const [ pageBlocked, setPageBlocked ] = useState(false);
|
||||
|
||||
async function goToPage(newPage: number) {
|
||||
if (newPage < 0) newPage = 0;
|
||||
if (pageBlocked) return;
|
||||
|
||||
let newData = null as DataRow[] | null;
|
||||
if (cachedPages[newPage]) {
|
||||
newData = cachedPages[newPage];
|
||||
} else {
|
||||
setData(null);
|
||||
}
|
||||
|
||||
setPage(newPage);
|
||||
setPageBlocked(true);
|
||||
try {
|
||||
newData = await DataRow.find({
|
||||
page: newPage
|
||||
});
|
||||
} catch (_) {
|
||||
return setPageBlocked(false)
|
||||
}
|
||||
|
||||
if (newData === undefined) {
|
||||
newData = [] as DataRow[];
|
||||
}
|
||||
|
||||
cachedPages[newPage] = newData;
|
||||
setPageBlocked(false);
|
||||
setData(newData);
|
||||
}
|
||||
|
||||
if (firstRender) {
|
||||
goToPage(0);
|
||||
firstRender = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={style.index}>
|
||||
<Paginator page={page} onChange={goToPage} className={style.paginator} />
|
||||
{
|
||||
data
|
||||
? <Posts rows={data} />
|
||||
: <pre>loading . . .</pre>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"react": ["./node_modules/preact/compat/"],
|
||||
"react-dom": ["./node_modules/preact/compat/"]
|
||||
},
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import preact from '@preact/preset-vite'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [preact()],
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
Loading…
Reference in New Issue