restarted with less bloat
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# big data
|
||||||
|
/assets
|
||||||
|
/static/tiles
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
26
README.md
Normal file
26
README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Ŋəmap
|
||||||
|
|
||||||
|
To setup dependencies:
|
||||||
|
```bash
|
||||||
|
# Grab the latest jxl base map, it’s path is hereafter `themap`
|
||||||
|
bun install
|
||||||
|
bun devscripts/generate_tiles.ts themap threadcount
|
||||||
|
# This will take a while and a lot ram of per thread. If threadcount is not set, default is 8 (uses ~20GB of ram in my tests)
|
||||||
|
```
|
||||||
|
|
||||||
|
To start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
To run for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun devscripts/optimise_assets.ts threadcount
|
||||||
|
# Will take a bit once again, but not as much ram, set threadcount to around the number of CPU threads you have, if not set default is 16 threads
|
||||||
|
bun build
|
||||||
|
bun start
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.2.21. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
17
bun-env.d.ts
vendored
Normal file
17
bun-env.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Generated by `bun init`
|
||||||
|
|
||||||
|
declare module "*.svg" {
|
||||||
|
/**
|
||||||
|
* A path to the SVG file
|
||||||
|
*/
|
||||||
|
const path: `${string}.svg`;
|
||||||
|
export = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.module.css" {
|
||||||
|
/**
|
||||||
|
* A record of class names to their corresponding CSS module classes
|
||||||
|
*/
|
||||||
|
const classes: { readonly [key: string]: string };
|
||||||
|
export = classes;
|
||||||
|
}
|
54
bun.lock
Normal file
54
bun.lock
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "bun-react-template",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/leaflet": "^1.9.20",
|
||||||
|
"@types/react-leaflet": "^3.0.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"react": "^19",
|
||||||
|
"react-dom": "^19",
|
||||||
|
"react-leaflet": "^5.0.0",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@react-leaflet/core": ["@react-leaflet/core@3.0.0", "", { "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||||
|
|
||||||
|
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
||||||
|
|
||||||
|
"@types/leaflet": ["@types/leaflet@1.9.20", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.4.0", "", { "dependencies": { "undici-types": "~7.11.0" } }, "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="],
|
||||||
|
|
||||||
|
"@types/react-leaflet": ["@types/react-leaflet@3.0.0", "", { "dependencies": { "react-leaflet": "*" } }, "sha512-p8R9mVKbCDDqOdW+M6GyJJuFn6q+IgDFYavFiOIvaWHuOe5kIHZEtCy1pfM43JIA6JiB3D/aDoby7C51eO+XSg=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"leaflet": ["leaflet@1.9.4", "", {}, "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="],
|
||||||
|
|
||||||
|
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
|
||||||
|
|
||||||
|
"react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="],
|
||||||
|
|
||||||
|
"react-leaflet": ["react-leaflet@5.0.0", "", { "dependencies": { "@react-leaflet/core": "^3.0.0" }, "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw=="],
|
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="],
|
||||||
|
}
|
||||||
|
}
|
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[serve.static]
|
||||||
|
env = "BUN_PUBLIC_*"
|
29
devscripts/generate_tiles.ts
Normal file
29
devscripts/generate_tiles.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { $, sleep, spawn } from "bun";
|
||||||
|
|
||||||
|
let path = process.argv[2];
|
||||||
|
let cur_procs : number = 0;
|
||||||
|
const max_procs = (process.argv.length >= 4 && process.argv[3] != undefined) ? +process.argv[3] : 8
|
||||||
|
|
||||||
|
await $`mkdir -p /tmp/nguhmap`
|
||||||
|
for (let zoomlevel = 0; zoomlevel <= 4; ++zoomlevel) {
|
||||||
|
let size = 1024 << zoomlevel
|
||||||
|
console.log(`Resizing image for zoomlevel ${zoomlevel}: ${size}x${size}`)
|
||||||
|
await $`magick ${path} -resize ${size}x${size} /tmp/nguhmap/${zoomlevel - 4}.png`
|
||||||
|
let file = `/tmp/nguhmap/${zoomlevel - 4}`
|
||||||
|
console.log(`Cutting tiles for zoomlevel ${zoomlevel - 4}`)
|
||||||
|
await $`mkdir -p src/tiles/${zoomlevel - 4}`
|
||||||
|
for (let rx = 0; rx < 1 << (zoomlevel + 1); rx++) {
|
||||||
|
for (let ry = 0; ry < 1 << (zoomlevel + 1) ; ry++) {
|
||||||
|
while (cur_procs >= max_procs) {await sleep(200)}
|
||||||
|
cur_procs++
|
||||||
|
console.log(`region: ${rx - (1 << zoomlevel)}/${ry - (1 << zoomlevel)}`)
|
||||||
|
spawn(
|
||||||
|
[`magick`, `${file}.png`, `-crop`, `512x512+${rx*512}+${ry*512}`, `src/tiles/${zoomlevel - 4}/${rx - (1 << zoomlevel)}_${ry - (1 << zoomlevel)}.png`],
|
||||||
|
{
|
||||||
|
onExit: () => {cur_procs--}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (cur_procs > 0) {await sleep(1000)}
|
||||||
|
await $`rm -rf /tmp/nguhmap`
|
25
devscripts/optimise_assets.ts
Normal file
25
devscripts/optimise_assets.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { sleep, spawn } from "bun";
|
||||||
|
import { readdir } from "node:fs/promises"
|
||||||
|
let cur_subprocs = 0;
|
||||||
|
const max_subprocs = (process.argv.length >= 3 && process.argv[2] != undefined) ? +process.argv[2] : 8
|
||||||
|
const assets_dir = "./static/"
|
||||||
|
let assets_files = (await readdir(assets_dir, {recursive: true})).map(it => assets_dir + it)
|
||||||
|
console.log(assets_files)
|
||||||
|
for (const index in assets_files)
|
||||||
|
{
|
||||||
|
const file = assets_files[index]
|
||||||
|
if (file == undefined) continue;
|
||||||
|
if (file.match(/\.png$/)) {
|
||||||
|
while (cur_subprocs >= max_subprocs) {await sleep(200)}
|
||||||
|
cur_subprocs++
|
||||||
|
console.log(`optimizing ${file} (${index} / ${assets_files.length}): ${cur_subprocs} processes running`)
|
||||||
|
spawn(["optipng", "-o7", file], {
|
||||||
|
onExit: () => {cur_subprocs--},
|
||||||
|
stdout: "ignore",
|
||||||
|
stderr: "ignore"
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log(`Ignoring file ${file}: not an optimisable filetype`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (cur_subprocs > 0) {await sleep(1000)}
|
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "bun-react-template",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bun --hot src/index.tsx",
|
||||||
|
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
|
||||||
|
"start": "NODE_ENV=production bun src/index.tsx"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/leaflet": "^1.9.20",
|
||||||
|
"@types/react-leaflet": "^3.0.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"react": "^19",
|
||||||
|
"react-dom": "^19",
|
||||||
|
"react-leaflet": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"@types/bun": "latest"
|
||||||
|
}
|
||||||
|
}
|
46
src/App.tsx
Normal file
46
src/App.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { MapContainer, Marker, TileLayer, Tooltip } from "react-leaflet";
|
||||||
|
import L, { marker } from "leaflet";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
const [coords, set_coords] = useState({x:0, y:0})
|
||||||
|
const [next_label, set_next_label] = useState("")
|
||||||
|
const [markers, set_markers] = useState([
|
||||||
|
{label: "Spawn", x: 0, y: 0}
|
||||||
|
])
|
||||||
|
|
||||||
|
let map = useCallback((it: L.Map|null) => {
|
||||||
|
if (it == null) return;
|
||||||
|
it.on('mousemove', (e) => {set_coords({x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat)})})
|
||||||
|
it.on('click', (e) => set_markers([...markers, {label: next_label, x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat)}]))
|
||||||
|
}, [coords, markers, next_label])
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
<MapContainer id="map"
|
||||||
|
center={[0,0]}
|
||||||
|
maxBounds={[[-8192,-8192], [8192,8192]]}
|
||||||
|
crs={L.CRS.Simple}
|
||||||
|
zoom={0}
|
||||||
|
ref={map}>
|
||||||
|
<TileLayer
|
||||||
|
url="/tiles/{z}/{x}/{y}"
|
||||||
|
tileSize={512}
|
||||||
|
minZoom={-4}
|
||||||
|
maxNativeZoom={0}/>
|
||||||
|
{
|
||||||
|
markers.map((it, i) => (
|
||||||
|
<Marker key={i} position={[it.y, it.x]} icon={new L.Icon({iconUrl: "/legend/city.svg", iconAnchor: [8,8]})}>
|
||||||
|
<Tooltip>{it.label}</Tooltip>
|
||||||
|
</Marker>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</MapContainer>
|
||||||
|
<p>Mouse is at {coords.x}, {-coords.y}</p>
|
||||||
|
<label htmlFor="nextmarker">Next Marker Label</label>
|
||||||
|
<input id="nextmarker" type="text" onChange={(e) => set_next_label(e.target.value)} value={next_label} />
|
||||||
|
</>)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
26
src/frontend.tsx
Normal file
26
src/frontend.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* This file is the entry point for the React app, it sets up the root
|
||||||
|
* element and renders the App component to the DOM.
|
||||||
|
*
|
||||||
|
* It is included in `src/index.html`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { App } from "./App";
|
||||||
|
|
||||||
|
const elem = document.getElementById("root")!;
|
||||||
|
const app = (
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
// With hot module reloading, `import.meta.hot.data` is persisted.
|
||||||
|
const root = (import.meta.hot.data.root ??= createRoot(elem));
|
||||||
|
root.render(app);
|
||||||
|
} else {
|
||||||
|
// The hot module reloading API is not available in production.
|
||||||
|
createRoot(elem).render(app);
|
||||||
|
}
|
5
src/index.css
Normal file
5
src/index.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
* {padding: 0; margin: 0;}
|
||||||
|
#map {
|
||||||
|
width: 100vw;
|
||||||
|
height: 90vh;
|
||||||
|
}
|
15
src/index.html
Normal file
15
src/index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||||
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||||
|
crossorigin=""/>
|
||||||
|
<title>Nguhmap</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./frontend.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
31
src/index.tsx
Normal file
31
src/index.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { serve, file, type BunRequest } from "bun";
|
||||||
|
import index from "./index.html";
|
||||||
|
|
||||||
|
const server = serve({
|
||||||
|
routes: {
|
||||||
|
// Serve index.html for all unmatched routes.
|
||||||
|
"/": index,
|
||||||
|
"/tiles/:z/:x/:y": (req : BunRequest<"/tiles/:z/:x/:y">) => {
|
||||||
|
const { z, x, y} = req.params;
|
||||||
|
return new Response(file(`./static/tiles/${z}/${x}_${y}.png`), {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "image/png"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
"/legend/:item": (req: BunRequest<"/legend/:item">) => {
|
||||||
|
const {item} = req.params
|
||||||
|
return new Response(file(`./static/legend/${item}`))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
development: process.env.NODE_ENV !== "production" && {
|
||||||
|
// Enable browser hot reloading in development
|
||||||
|
hmr: true,
|
||||||
|
|
||||||
|
// Echo console logs from the browser to the server
|
||||||
|
console: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🚀 Server running at ${server.url}`);
|
11
static/legend/city.svg
Normal file
11
static/legend/city.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16px"
|
||||||
|
height="16px"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
>
|
||||||
|
<circle style="fill: #000" r="8" transform="translate(8,8)" />
|
||||||
|
<circle style="fill: #fff" r="6" transform="translate(8,8)" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 311 B |
36
tsconfig.json
Normal file
36
tsconfig.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"exclude": ["dist", "node_modules"]
|
||||||
|
}
|
Reference in New Issue
Block a user