Merge commit '5e1fcb296211538c3724632d82718660593227ad'
This commit is contained in:
37
.eslintrc.json
Normal file
37
.eslintrc.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es2021": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:import/typescript",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 12,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"alphabetize": { "order": "asc", "caseInsensitive": true },
|
||||||
|
"newlines-between": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/no-named-as-default": "off",
|
||||||
|
"import/no-named-as-default-member": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -9,6 +9,7 @@ on:
|
|||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
branches: ["*"]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -50,3 +51,18 @@ jobs:
|
|||||||
run: go test ./...
|
run: go test ./...
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build ./cmd/beacon
|
run: go build ./cmd/beacon
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js 20
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'npm'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
- name: Test
|
||||||
|
run: npm test
|
||||||
|
- name: Build
|
||||||
|
run: npm run compile
|
||||||
|
|||||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -3,3 +3,19 @@ beacon
|
|||||||
|
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
*.db
|
*.db
|
||||||
|
node_modules
|
||||||
|
.dist
|
||||||
|
dist
|
||||||
|
.turbo
|
||||||
|
sandbox
|
||||||
|
scripts/*.js
|
||||||
|
scripts/*.js.map
|
||||||
|
tests/*.js
|
||||||
|
tests/*.js.map
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
pnpm-lock.yaml
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
public/sig.beacon.json
|
||||||
|
|||||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
31
README.md
31
README.md
@@ -1,6 +1,9 @@
|
|||||||
# blackroad-os-beacon
|
# blackroad-os-beacon
|
||||||
|
|
||||||
Lightweight status-ping collector built with Go 1.22 and Fiber v3. Beacon captures health pings, stores them in BoltDB, and streams them to the Core UI via SSE.
|
Lightweight status-ping collector built with Go 1.22 and Fiber v3. Beacon captures health pings, stores them in BoltDB, and streams them to the Core UI via SSE.
|
||||||
|
# Blackroad OS · API Gateway
|
||||||
|
|
||||||
|
Gateway-Gen-0 scaffold for a single entry-point that fronts Blackroad OS services via REST and GraphQL.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
@@ -32,3 +35,31 @@ Environment variables:
|
|||||||
- `make build` — compile the service.
|
- `make build` — compile the service.
|
||||||
- `make sig` — refresh `public/sig_beacon.json`.
|
- `make sig` — refresh `public/sig_beacon.json`.
|
||||||
|
|
||||||
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit `http://localhost:4000/health` to verify the gateway is running.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t blackroad/gateway:0.0.1 .
|
||||||
|
docker run -e PORT=4000 -p 4000:4000 blackroad/gateway:0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Copy `.env.example` and fill in service URLs and JWT keys. No secrets are committed.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- `pnpm dev` – start the gateway with watch mode using tsx.
|
||||||
|
- `pnpm build` – lint, test, compile TypeScript, and emit beacon metadata.
|
||||||
|
- `pnpm start` – run the compiled server from `dist`.
|
||||||
|
|
||||||
|
## TODO(gateway-next)
|
||||||
|
|
||||||
|
- Wire real JWT validation rules and authorization.
|
||||||
|
- Compose remote schemas with Federation v2 and enable caching.
|
||||||
|
- Add persistent rate-limit and request tracing.
|
||||||
|
|||||||
11
gateway.env.example
Normal file
11
gateway.env.example
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
PORT=4000
|
||||||
|
HOST=0.0.0.0
|
||||||
|
JWT_SECRET=replace-me
|
||||||
|
JWT_ISSUER=blackroad-gateway
|
||||||
|
SERVICE_API_URL=http://localhost:4100
|
||||||
|
SERVICE_OPERATOR_URL=http://localhost:4200
|
||||||
|
SERVICE_CORE_URL=http://localhost:4300
|
||||||
|
SERVICE_PRISM_URL=http://localhost:4400
|
||||||
|
RATE_LIMIT_MAX=100
|
||||||
|
RATE_LIMIT_WINDOW=1 minute
|
||||||
|
RATE_LIMIT_ALLOWLIST=127.0.0.1
|
||||||
31
infra/Dockerfile
Normal file
31
infra/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
FROM node:20-alpine AS base
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json* pnpm-lock.yaml* .npmrc* ./
|
||||||
|
|
||||||
|
RUN if [ -f pnpm-lock.yaml ]; then \
|
||||||
|
npm install -g pnpm && pnpm install --frozen-lockfile; \
|
||||||
|
else \
|
||||||
|
npm install --production=false; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
COPY tsconfig.json .eslintrc.json .prettierrc ./
|
||||||
|
COPY src ./src
|
||||||
|
COPY scripts ./scripts
|
||||||
|
|
||||||
|
RUN npm run compile && node ./scripts/postbuild.ts
|
||||||
|
|
||||||
|
FROM node:20-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=base /app/dist ./dist
|
||||||
|
COPY --from=base /app/public ./public
|
||||||
|
COPY package.json package-lock.json* pnpm-lock.yaml* .npmrc* ./
|
||||||
|
|
||||||
|
RUN if [ -f pnpm-lock.yaml ]; then \
|
||||||
|
npm install -g pnpm && pnpm install --prod --frozen-lockfile; \
|
||||||
|
else \
|
||||||
|
npm install --production; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
EXPOSE 4000
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
9
infra/railway.toml
Normal file
9
infra/railway.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[build]
|
||||||
|
builder = "NIXPACKS"
|
||||||
|
|
||||||
|
[deploy]
|
||||||
|
numReplicas = 1
|
||||||
|
startCommand = "npm start"
|
||||||
|
healthcheckPath = "/health"
|
||||||
|
port = 4000
|
||||||
|
restartPolicyType = "ON_FAILURE"
|
||||||
8262
package-lock.json
generated
Normal file
8262
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
package.json
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "blackroad-os-api-gateway",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "commonjs",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/index.ts",
|
||||||
|
"build": "npm run lint && npm run test && npm run compile && tsx scripts/postbuild.ts",
|
||||||
|
"compile": "tsc -p tsconfig.json",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"lint": "eslint . --ext .ts",
|
||||||
|
"test": "vitest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/http-proxy": "^9.0.0",
|
||||||
|
"@fastify/jwt": "^8.0.0",
|
||||||
|
"@fastify/rate-limit": "^8.0.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"fastify": "^4.28.1",
|
||||||
|
"fastify-plugin": "^4.5.1",
|
||||||
|
"mercurius": "^13.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
|
"@types/node": "^20.12.12",
|
||||||
|
"@types/supertest": "^2.0.16",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||||
|
"@typescript-eslint/parser": "^7.11.0",
|
||||||
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"supertest": "^6.3.4",
|
||||||
|
"tsx": "^4.7.3",
|
||||||
|
"typescript": "5.5.4",
|
||||||
|
"vitest": "^1.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
scripts/postbuild.ts
Normal file
21
scripts/postbuild.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { mkdirSync, writeFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
const outputDir = join(process.cwd(), 'public');
|
||||||
|
const beaconPath = join(outputDir, 'sig.beacon.json');
|
||||||
|
|
||||||
|
mkdirSync(outputDir, { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
beaconPath,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
agent: 'Gateway-Gen-0',
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Wrote beacon to ${beaconPath}`);
|
||||||
48
src/index.ts
Normal file
48
src/index.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
import fastify from 'fastify';
|
||||||
|
|
||||||
|
import authPlugin from './plugins/auth';
|
||||||
|
import graphqlPlugin from './plugins/graphql';
|
||||||
|
import proxyPlugin from './plugins/proxy';
|
||||||
|
import rateLimitPlugin from './plugins/rateLimit';
|
||||||
|
import healthRoute from './routes/health';
|
||||||
|
import versionRoute from './routes/version';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export function buildServer() {
|
||||||
|
const app = fastify({
|
||||||
|
logger: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.register(rateLimitPlugin);
|
||||||
|
app.register(authPlugin);
|
||||||
|
app.register(proxyPlugin);
|
||||||
|
app.register(graphqlPlugin);
|
||||||
|
app.register(healthRoute);
|
||||||
|
app.register(versionRoute);
|
||||||
|
|
||||||
|
app.addHook('onReady', async () => {
|
||||||
|
app.log.info({ services: app.serviceMap }, 'service map loaded');
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
const app = buildServer();
|
||||||
|
const port = Number(process.env.PORT || 4000);
|
||||||
|
const host = process.env.HOST || '0.0.0.0';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await app.listen({ port, host });
|
||||||
|
app.log.info(`Gateway listening on http://${host}:${port}`);
|
||||||
|
} catch (error) {
|
||||||
|
app.log.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
void start();
|
||||||
|
}
|
||||||
34
src/plugins/auth.ts
Normal file
34
src/plugins/auth.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import fastifyJwt, { JWT } from '@fastify/jwt';
|
||||||
|
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
import fp from 'fastify-plugin';
|
||||||
|
|
||||||
|
export type AuthenticatedRequest = FastifyRequest & { jwt: JWT };
|
||||||
|
|
||||||
|
async function authPlugin(fastify: FastifyInstance) {
|
||||||
|
fastify.register(fastifyJwt, {
|
||||||
|
secret: process.env.JWT_SECRET || 'development-secret',
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.decorate(
|
||||||
|
'verifyJWT',
|
||||||
|
async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
|
||||||
|
// TODO(gateway-next): Integrate real authz and audience checks.
|
||||||
|
try {
|
||||||
|
await request.jwtVerify();
|
||||||
|
} catch (error) {
|
||||||
|
request.log.warn({ err: error }, 'JWT verification failed');
|
||||||
|
reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'fastify' {
|
||||||
|
interface FastifyInstance {
|
||||||
|
verifyJWT(request: FastifyRequest, reply: FastifyReply): Promise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fp(authPlugin, {
|
||||||
|
name: 'auth-plugin',
|
||||||
|
});
|
||||||
32
src/plugins/graphql.ts
Normal file
32
src/plugins/graphql.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
import fp from 'fastify-plugin';
|
||||||
|
import mercurius from 'mercurius';
|
||||||
|
|
||||||
|
const typeDefs = /* GraphQL */ `
|
||||||
|
type Query {
|
||||||
|
_ping: String
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO(gateway-next): Extend schema with federation v2 directives and compose remote schemas.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Query: {
|
||||||
|
_ping: async () => 'pong',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function graphqlPlugin(fastify: FastifyInstance) {
|
||||||
|
fastify.register(mercurius, {
|
||||||
|
schema: typeDefs,
|
||||||
|
resolvers,
|
||||||
|
graphiql: process.env.NODE_ENV !== 'production',
|
||||||
|
context: () => ({
|
||||||
|
// TODO(gateway-next): Inject authenticated user + stitched service clients.
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fp(graphqlPlugin, {
|
||||||
|
name: 'graphql-plugin',
|
||||||
|
});
|
||||||
50
src/plugins/proxy.ts
Normal file
50
src/plugins/proxy.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import fastifyHttpProxy from '@fastify/http-proxy';
|
||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
import fp from 'fastify-plugin';
|
||||||
|
|
||||||
|
import { ServiceMap } from '../types';
|
||||||
|
|
||||||
|
function registerProxy(
|
||||||
|
fastify: FastifyInstance,
|
||||||
|
path: string,
|
||||||
|
target: string,
|
||||||
|
prefixRewrite = ''
|
||||||
|
) {
|
||||||
|
fastify.register(fastifyHttpProxy, {
|
||||||
|
upstream: target,
|
||||||
|
prefix: path,
|
||||||
|
rewritePrefix: prefixRewrite,
|
||||||
|
replyOptions: {
|
||||||
|
rewriteRequestHeaders: (_req, headers) => ({
|
||||||
|
...headers,
|
||||||
|
'x-gateway-proxied': 'true',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function proxyPlugin(fastify: FastifyInstance) {
|
||||||
|
const services: ServiceMap = {
|
||||||
|
api: process.env.SERVICE_API_URL || 'http://localhost:4100',
|
||||||
|
operator: process.env.SERVICE_OPERATOR_URL || 'http://localhost:4200',
|
||||||
|
core: process.env.SERVICE_CORE_URL || 'http://localhost:4300',
|
||||||
|
prism: process.env.SERVICE_PRISM_URL || 'http://localhost:4400',
|
||||||
|
};
|
||||||
|
|
||||||
|
registerProxy(fastify, '/api', services.api, '/');
|
||||||
|
registerProxy(fastify, '/operator', services.operator, '/');
|
||||||
|
registerProxy(fastify, '/core', services.core, '/');
|
||||||
|
registerProxy(fastify, '/prism', services.prism, '/');
|
||||||
|
|
||||||
|
fastify.decorate('serviceMap', services);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'fastify' {
|
||||||
|
interface FastifyInstance {
|
||||||
|
serviceMap: ServiceMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fp(proxyPlugin, {
|
||||||
|
name: 'proxy-plugin',
|
||||||
|
});
|
||||||
18
src/plugins/rateLimit.ts
Normal file
18
src/plugins/rateLimit.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import fastifyRateLimit from '@fastify/rate-limit';
|
||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
import fp from 'fastify-plugin';
|
||||||
|
|
||||||
|
async function rateLimitPlugin(fastify: FastifyInstance) {
|
||||||
|
fastify.register(fastifyRateLimit, {
|
||||||
|
max: Number(process.env.RATE_LIMIT_MAX || 100),
|
||||||
|
timeWindow: process.env.RATE_LIMIT_WINDOW || '1 minute',
|
||||||
|
allowList: (process.env.RATE_LIMIT_ALLOWLIST || '')
|
||||||
|
.split(',')
|
||||||
|
.map((entry) => entry.trim())
|
||||||
|
.filter(Boolean),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fp(rateLimitPlugin, {
|
||||||
|
name: 'rate-limit-plugin',
|
||||||
|
});
|
||||||
8
src/routes/health.ts
Normal file
8
src/routes/health.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
|
export default async function healthRoute(fastify: FastifyInstance) {
|
||||||
|
fastify.get('/health', async () => ({
|
||||||
|
status: 'ok',
|
||||||
|
uptime: process.uptime(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
8
src/routes/version.ts
Normal file
8
src/routes/version.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
|
export default async function versionRoute(fastify: FastifyInstance) {
|
||||||
|
fastify.get('/version', async () => ({
|
||||||
|
version: '0.0.1',
|
||||||
|
commit: process.env.COMMIT_SHA || 'dev',
|
||||||
|
}));
|
||||||
|
}
|
||||||
10
src/types/index.ts
Normal file
10
src/types/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export type ServiceMap = {
|
||||||
|
api: string;
|
||||||
|
operator: string;
|
||||||
|
core: string;
|
||||||
|
prism: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GatewayContext = {
|
||||||
|
services: ServiceMap;
|
||||||
|
};
|
||||||
23
tests/health.test.ts
Normal file
23
tests/health.test.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import request from 'supertest';
|
||||||
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { buildServer } from '../src/index';
|
||||||
|
|
||||||
|
describe('health route', () => {
|
||||||
|
const app = buildServer();
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await app.ready();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns ok', async () => {
|
||||||
|
const response = await request(app.server).get('/health');
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body.status).toBe('ok');
|
||||||
|
expect(response.body).toHaveProperty('uptime');
|
||||||
|
});
|
||||||
|
});
|
||||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": ".",
|
||||||
|
"sourceMap": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src", "scripts", "tests"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user